blob: 9a5ac725ef4e92f60c35bfc0ac37668175eb8ae9 [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.html;
import static org.apache.juneau.internal.StringUtils.*;
import static org.apache.juneau.internal.ObjectUtils.*;
import static org.apache.juneau.xml.XmlSerializerSession.ContentResult.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
import org.apache.juneau.*;
import org.apache.juneau.html.annotation.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.transform.*;
import org.apache.juneau.xml.*;
import org.apache.juneau.xml.annotation.*;
/**
* Session object that lives for the duration of a single use of {@link HtmlSerializer}.
*
* <p>
* This class is NOT thread safe.
* It is typically discarded after one-time use although it can be reused within the same thread.
*/
public class HtmlSerializerSession extends XmlSerializerSession {
private final HtmlSerializer ctx;
private final Pattern urlPattern = Pattern.compile("http[s]?\\:\\/\\/.*");
private final Pattern labelPattern;
/**
* Create a new session using properties specified in the context.
*
* @param ctx
* The context creating this session object.
* The context contains all the configuration settings for this object.
* @param args
* Runtime arguments.
* These specify session-level information such as locale and URI context.
* It also include session-level properties that override the properties defined on the bean and
* serializer contexts.
*/
protected HtmlSerializerSession(HtmlSerializer ctx, SerializerSessionArgs args) {
super(ctx, args);
this.ctx = ctx;
labelPattern = Pattern.compile("[\\?\\&]" + Pattern.quote(ctx.getLabelParameter()) + "=([^\\&]*)");
}
/**
* Converts the specified output target object to an {@link HtmlWriter}.
*
* @param out The output target object.
* @return The output target object wrapped in an {@link HtmlWriter}.
* @throws IOException Thrown by underlying stream.
*/
protected final HtmlWriter getHtmlWriter(SerializerPipe out) throws IOException {
Object output = out.getRawOutput();
if (output instanceof HtmlWriter)
return (HtmlWriter)output;
HtmlWriter w = new HtmlWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isTrimStrings(), getQuoteChar(),
getUriResolver());
out.setWriter(w);
return w;
}
/**
* Returns <jk>true</jk> if the specified object is a URL.
*
* @param cm The ClassMeta of the object being serialized.
* @param pMeta
* The property metadata of the bean property of the object.
* Can be <jk>null</jk> if the object isn't from a bean property.
* @param o The object.
* @return <jk>true</jk> if the specified object is a URL.
*/
public boolean isUri(ClassMeta<?> cm, BeanPropertyMeta pMeta, Object o) {
if (cm.isUri())
return true;
if (pMeta != null && pMeta.isUri())
return true;
if (isDetectLinksInStrings() && o instanceof CharSequence && urlPattern.matcher(o.toString()).matches())
return true;
return false;
}
/**
* Returns the anchor text to use for the specified URL object.
*
* @param pMeta
* The property metadata of the bean property of the object.
* Can be <jk>null</jk> if the object isn't from a bean property.
* @param o The URL object.
* @return The anchor text to use for the specified URL object.
*/
public String getAnchorText(BeanPropertyMeta pMeta, Object o) {
String s = o.toString();
if (isDetectLabelParameters()) {
Matcher m = labelPattern.matcher(s);
if (m.find())
return urlDecode(m.group(1));
}
switch (getUriAnchorText()) {
case LAST_TOKEN:
s = resolveUri(s);
if (s.indexOf('/') != -1)
s = s.substring(s.lastIndexOf('/')+1);
if (s.indexOf('?') != -1)
s = s.substring(0, s.indexOf('?'));
if (s.indexOf('#') != -1)
s = s.substring(0, s.indexOf('#'));
if (s.isEmpty())
s = "/";
return urlDecode(s);
case URI_ANCHOR:
if (s.indexOf('#') != -1)
s = s.substring(s.lastIndexOf('#')+1);
return urlDecode(s);
case PROPERTY_NAME:
return pMeta == null ? s : pMeta.getName();
case URI:
return resolveUri(s);
case CONTEXT_RELATIVE:
return relativizeUri("context:/", s);
case SERVLET_RELATIVE:
return relativizeUri("servlet:/", s);
case PATH_RELATIVE:
return relativizeUri("request:/", s);
default /* TO_STRING */:
return s;
}
}
@Override /* XmlSerializer */
public boolean isHtmlMode() {
return true;
}
@Override /* Serializer */
protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException {
doSerialize(o, getHtmlWriter(out));
}
/**
* Main serialization routine.
*
* @param session The serialization context object.
* @param o The object being serialized.
* @param w The writer to serialize to.
* @return The same writer passed in.
* @throws IOException If a problem occurred trying to send output to the writer.
*/
private XmlWriter doSerialize(Object o, XmlWriter w) throws IOException, SerializeException {
serializeAnything(w, o, getExpectedRootType(o), null, null, getInitialDepth()-1, true, false);
return w;
}
@SuppressWarnings({ "rawtypes" })
@Override /* XmlSerializerSession */
protected ContentResult serializeAnything(
XmlWriter out,
Object o,
ClassMeta<?> eType,
String elementName,
Namespace elementNamespace,
boolean addNamespaceUris,
XmlFormat format,
boolean isMixed,
boolean preserveWhitespace,
BeanPropertyMeta pMeta) throws IOException, SerializeException {
// If this is a bean, then we want to serialize it as HTML unless it's @Html(format=XML).
ClassMeta<?> type = push2(elementName, o, eType);
pop();
if (type == null)
type = object();
else if (type.isDelegate())
type = ((Delegate)o).getClassMeta();
PojoSwap swap = type.getPojoSwap(this);
if (swap != null) {
o = swap(swap, o);
type = swap.getSwapClassMeta(this);
if (type.isObject())
type = getClassMetaForObject(o);
}
HtmlClassMeta cHtml = getHtmlClassMeta(type);
if (type.isMapOrBean() && ! cHtml.isXml())
return serializeAnything(out, o, eType, elementName, pMeta, 0, false, false);
return super.serializeAnything(out, o, eType, elementName, elementNamespace, addNamespaceUris, format, isMixed, preserveWhitespace, pMeta);
}
/**
* Serialize the specified object to the specified writer.
*
* @param out The writer.
* @param o The object to serialize.
* @param eType The expected type of the object if this is a bean property.
* @param name
* The attribute name of this object if this object was a field in a JSON object (i.e. key of a
* {@link java.util.Map.Entry} or property name of a bean).
* @param pMeta The bean property being serialized, or <jk>null</jk> if we're not serializing a bean property.
* @param xIndent The current indentation value.
* @param isRoot <jk>true</jk> if this is the root element of the document.
* @param nlIfElement <jk>true</jk> if we should add a newline to the output before serializing only if the object is an element and not text.
* @return The type of content encountered. Either simple (no whitespace) or normal (elements with whitespace).
* @throws IOException Thrown by underlying stream.
* @throws SerializeException Generic serialization error occurred.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected ContentResult serializeAnything(XmlWriter out, Object o,
ClassMeta<?> eType, String name, BeanPropertyMeta pMeta, int xIndent, boolean isRoot, boolean nlIfElement) throws IOException, SerializeException {
ClassMeta<?> aType = null; // The actual type
ClassMeta<?> wType = null; // The wrapped type (delegate)
ClassMeta<?> sType = object(); // The serialized type
if (eType == null)
eType = object();
aType = push2(name, o, eType);
// Handle recursion
if (aType == null) {
o = null;
aType = object();
}
// Handle Optional<X>
if (isOptional(aType)) {
o = getOptionalValue(o);
eType = getOptionalType(eType);
aType = getClassMetaForObject(o, object());
}
indent += xIndent;
ContentResult cr = CR_ELEMENTS;
// Determine the type.
if (o == null || (aType.isChar() && ((Character)o).charValue() == 0)) {
out.tag("null");
cr = ContentResult.CR_MIXED;
} else {
if (aType.isDelegate()) {
wType = aType;
aType = ((Delegate)o).getClassMeta();
}
sType = aType;
String typeName = null;
if (isAddBeanTypes() && ! eType.equals(aType))
typeName = aType.getDictionaryName();
// Swap if necessary
PojoSwap swap = aType.getPojoSwap(this);
if (swap != null) {
o = swap(swap, o);
sType = swap.getSwapClassMeta(this);
// If the getSwapClass() method returns Object, we need to figure out
// the actual type now.
if (sType.isObject())
sType = getClassMetaForObject(o);
}
// Handle the case where we're serializing a raw stream.
if (sType.isReader() || sType.isInputStream()) {
pop();
indent -= xIndent;
IOUtils.pipe(o, out);
return ContentResult.CR_MIXED;
}
HtmlClassMeta cHtml = getHtmlClassMeta(sType);
HtmlBeanPropertyMeta bpHtml = getHtmlBeanPropertyMeta(pMeta);
HtmlRender render = firstNonNull(bpHtml.getRender(), cHtml.getRender());
if (render != null) {
Object o2 = render.getContent(this, o);
if (o2 != o) {
indent -= xIndent;
pop();
out.nl(indent);
return serializeAnything(out, o2, null, typeName, null, xIndent, false, false);
}
}
if (cHtml.isXml() || bpHtml.isXml()) {
pop();
indent++;
if (nlIfElement)
out.nl(0);
super.serializeAnything(out, o, null, null, null, false, XmlFormat.MIXED, false, false, null);
indent -= xIndent+1;
return cr;
} else if (cHtml.isPlainText() || bpHtml.isPlainText()) {
out.write(o == null ? "null" : o.toString());
cr = CR_MIXED;
} else if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) {
out.tag("null");
cr = CR_MIXED;
} else if (sType.isNumber()) {
if (eType.isNumber() && ! isRoot)
out.append(o);
else
out.sTag("number").append(o).eTag("number");
cr = CR_MIXED;
} else if (sType.isBoolean()) {
if (eType.isBoolean() && ! isRoot)
out.append(o);
else
out.sTag("boolean").append(o).eTag("boolean");
cr = CR_MIXED;
} else if (sType.isMap() || (wType != null && wType.isMap())) {
out.nlIf(! isRoot, xIndent+1);
if (o instanceof BeanMap)
serializeBeanMap(out, (BeanMap)o, eType, pMeta);
else
serializeMap(out, (Map)o, sType, eType.getKeyType(), eType.getValueType(), typeName, pMeta);
} else if (sType.isBean()) {
BeanMap m = toBeanMap(o);
if (aType.hasAnnotation(HtmlLink.class)) {
HtmlLink h = aType.getAnnotation(HtmlLink.class);
Object urlProp = m.get(h.uriProperty());
Object nameProp = m.get(h.nameProperty());
out.oTag("a").attrUri("href", urlProp).append('>').text(nameProp).eTag("a");
cr = CR_MIXED;
} else {
out.nlIf(! isRoot, xIndent+2);
serializeBeanMap(out, m, eType, pMeta);
}
} else if (sType.isCollection() || sType.isArray() || (wType != null && wType.isCollection())) {
out.nlIf(! isRoot, xIndent+1);
serializeCollection(out, o, sType, eType, name, pMeta);
} else if (isUri(sType, pMeta, o)) {
String label = getAnchorText(pMeta, o);
out.oTag("a").attrUri("href", o).append('>');
out.text(label);
out.eTag("a");
cr = CR_MIXED;
} else {
if (isRoot)
out.sTag("string").text(toString(o)).eTag("string");
else
out.text(toString(o));
cr = CR_MIXED;
}
}
pop();
indent -= xIndent;
return cr;
}
@SuppressWarnings({ "rawtypes" })
private void serializeMap(XmlWriter out, Map m, ClassMeta<?> sType,
ClassMeta<?> eKeyType, ClassMeta<?> eValueType, String typeName, BeanPropertyMeta ppMeta) throws IOException, SerializeException {
ClassMeta<?> keyType = eKeyType == null ? string() : eKeyType;
ClassMeta<?> valueType = eValueType == null ? object() : eValueType;
ClassMeta<?> aType = getClassMetaForObject(m); // The actual type
HtmlClassMeta cHtml = getHtmlClassMeta(aType);
HtmlBeanPropertyMeta bpHtml = getHtmlBeanPropertyMeta(ppMeta);
int i = indent;
out.oTag(i, "table");
if (typeName != null && ppMeta != null && ppMeta.getClassMeta() != aType)
out.attr(getBeanTypePropertyName(sType), typeName);
out.append(">").nl(i+1);
if (isAddKeyValueTableHeaders() && ! (cHtml.isNoTableHeaders() || bpHtml.isNoTableHeaders())) {
out.sTag(i+1, "tr").nl(i+2);
out.sTag(i+2, "th").append("key").eTag("th").nl(i+3);
out.sTag(i+2, "th").append("value").eTag("th").nl(i+3);
out.ie(i+1).eTag("tr").nl(i+2);
}
for (Map.Entry e : (Set<Map.Entry>)m.entrySet()) {
Object key = generalize(e.getKey(), keyType);
Object value = null;
try {
value = e.getValue();
} catch (StackOverflowError t) {
throw t;
} catch (Throwable t) {
onError(t, "Could not call getValue() on property ''{0}'', {1}", e.getKey(), t.getLocalizedMessage());
}
String link = getLink(ppMeta);
String style = getStyle(this, ppMeta, value);
out.sTag(i+1, "tr").nl(i+2);
out.oTag(i+2, "td");
if (style != null)
out.attr("style", style);
out.cTag();
if (link != null)
out.oTag(i+3, "a").attrUri("href", link.replace("{#}", stringify(value))).cTag();
ContentResult cr = serializeAnything(out, key, keyType, null, null, 2, false, false);
if (link != null)
out.eTag("a");
if (cr == CR_ELEMENTS)
out.i(i+2);
out.eTag("td").nl(i+2);
out.sTag(i+2, "td");
cr = serializeAnything(out, value, valueType, (key == null ? "_x0000_" : toString(key)), null, 2, false, false);
if (cr == CR_ELEMENTS)
out.ie(i+2);
out.eTag("td").nl(i+2);
out.ie(i+1).eTag("tr").nl(i+1);
}
out.ie(i).eTag("table").nl(i);
}
private void serializeBeanMap(XmlWriter out, BeanMap<?> m, ClassMeta<?> eType, BeanPropertyMeta ppMeta) throws IOException, SerializeException {
HtmlClassMeta cHtml = getHtmlClassMeta(m.getClassMeta());
HtmlBeanPropertyMeta bpHtml = getHtmlBeanPropertyMeta(ppMeta);
int i = indent;
out.oTag(i, "table");
String typeName = m.getMeta().getDictionaryName();
if (typeName != null && eType != m.getClassMeta())
out.attr(getBeanTypePropertyName(m.getClassMeta()), typeName);
out.append('>').nl(i);
if (isAddKeyValueTableHeaders() && ! (cHtml.isNoTableHeaders() || bpHtml.isNoTableHeaders())) {
out.sTag(i+1, "tr").nl(i+1);
out.sTag(i+2, "th").append("key").eTag("th").nl(i+2);
out.sTag(i+2, "th").append("value").eTag("th").nl(i+2);
out.ie(i+1).eTag("tr").nl(i+1);
}
for (BeanPropertyValue p : m.getValues(isTrimNullProperties())) {
BeanPropertyMeta pMeta = p.getMeta();
ClassMeta<?> cMeta = p.getClassMeta();
String key = p.getName();
Object value = p.getValue();
Throwable t = p.getThrown();
if (t != null)
onBeanGetterException(pMeta, t);
if (canIgnoreValue(cMeta, key, value))
continue;
String link = null, anchorText = null;
if (! cMeta.isCollectionOrArray()) {
link = m.resolveVars(getLink(pMeta));
anchorText = m.resolveVars(getAnchorText(pMeta));
}
if (anchorText != null)
value = anchorText;
out.sTag(i+1, "tr").nl(i+1);
out.sTag(i+2, "td").text(key).eTag("td").nl(i+2);
out.oTag(i+2, "td");
String style = getStyle(this, pMeta, value);
if (style != null)
out.attr("style", style);
out.cTag();
try {
if (link != null)
out.oTag(i+3, "a").attrUri("href", link).cTag();
ContentResult cr = serializeAnything(out, value, cMeta, key, pMeta, 2, false, true);
if (cr == CR_ELEMENTS)
out.i(i+2);
if (link != null)
out.eTag("a");
} catch (SerializeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Throwable e) {
e.printStackTrace();
onBeanGetterException(pMeta, e);
}
out.eTag("td").nl(i+2);
out.ie(i+1).eTag("tr").nl(i+1);
}
out.ie(i).eTag("table").nl(i);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void serializeCollection(XmlWriter out, Object in, ClassMeta<?> sType, ClassMeta<?> eType, String name, BeanPropertyMeta ppMeta) throws IOException, SerializeException {
HtmlClassMeta cHtml = getHtmlClassMeta(sType);
HtmlBeanPropertyMeta bpHtml = getHtmlBeanPropertyMeta(ppMeta);
Collection c = (sType.isCollection() ? (Collection)in : toList(sType.getInnerClass(), in));
boolean isCdc = cHtml.isHtmlCdc() || bpHtml.isHtmlCdc();
boolean isSdc = cHtml.isHtmlSdc() || bpHtml.isHtmlSdc();
boolean isDc = isCdc || isSdc;
int i = indent;
if (c.isEmpty()) {
out.appendln(i, "<ul></ul>");
return;
}
String type2 = null;
if (sType != eType)
type2 = sType.getDictionaryName();
if (type2 == null)
type2 = "array";
c = sort(c);
String btpn = getBeanTypePropertyName(eType);
// Look at the objects to see how we're going to handle them. Check the first object to see how we're going to
// handle this.
// If it's a map or bean, then we'll create a table.
// Otherwise, we'll create a list.
Object[] th = getTableHeaders(c, bpHtml);
if (th != null) {
out.oTag(i, "table").attr(btpn, type2).append('>').nl(i+1);
out.sTag(i+1, "tr").nl(i+2);
for (Object key : th) {
out.sTag(i+2, "th");
out.text(convertToType(key, String.class));
out.eTag("th").nl(i+2);
}
out.ie(i+1).eTag("tr").nl(i+1);
for (Object o : c) {
ClassMeta<?> cm = getClassMetaForObject(o);
if (cm != null && cm.getPojoSwap(this) != null) {
PojoSwap swap = cm.getPojoSwap(this);
o = swap(swap, o);
cm = swap.getSwapClassMeta(this);
}
out.oTag(i+1, "tr");
String typeName = (cm == null ? null : cm.getDictionaryName());
String typeProperty = getBeanTypePropertyName(cm);
if (typeName != null && eType.getElementType() != cm)
out.attr(typeProperty, typeName);
out.cTag().nl(i+2);
if (cm == null) {
out.i(i+2);
serializeAnything(out, o, null, null, null, 1, false, false);
out.nl(0);
} else if (cm.isMap() && ! (cm.isBeanMap())) {
Map m2 = sort((Map)o);
for (Object k : th) {
out.sTag(i+2, "td");
ContentResult cr = serializeAnything(out, m2.get(k), eType.getElementType(), toString(k), null, 2, false, true);
if (cr == CR_ELEMENTS)
out.i(i+2);
out.eTag("td").nl(i+2);
}
} else {
BeanMap m2 = null;
if (o instanceof BeanMap)
m2 = (BeanMap)o;
else
m2 = toBeanMap(o);
for (Object k : th) {
BeanMapEntry p = m2.getProperty(toString(k));
BeanPropertyMeta pMeta = p.getMeta();
if (pMeta.canRead()) {
Object value = p.getValue();
String link = null, anchorText = null;
if (! pMeta.getClassMeta().isCollectionOrArray()) {
link = m2.resolveVars(getLink(pMeta));
anchorText = m2.resolveVars(getAnchorText(pMeta));
}
if (anchorText != null)
value = anchorText;
String style = getStyle(this, pMeta, value);
out.oTag(i+2, "td");
if (style != null)
out.attr("style", style);
out.cTag();
if (link != null)
out.oTag("a").attrUri("href", link).cTag();
ContentResult cr = serializeAnything(out, value, pMeta.getClassMeta(), p.getKey().toString(), pMeta, 2, false, true);
if (cr == CR_ELEMENTS)
out.i(i+2);
if (link != null)
out.eTag("a");
out.eTag("td").nl(i+2);
}
}
}
out.ie(i+1).eTag("tr").nl(i+1);
}
out.ie(i).eTag("table").nl(i);
} else {
out.oTag(i, isDc ? "p" : "ul");
if (! type2.equals("array"))
out.attr(btpn, type2);
out.append('>').nl(i+1);
boolean isFirst = true;
for (Object o : c) {
if (isDc && ! isFirst)
out.append(isCdc ? ", " : " ");
if (! isDc)
out.oTag(i+1, "li");
String style = getStyle(this, ppMeta, o);
String link = getLink(ppMeta);
if (style != null && ! isDc)
out.attr("style", style);
if (! isDc)
out.cTag();
if (link != null)
out.oTag(i+2, "a").attrUri("href", link.replace("{#}", stringify(o))).cTag();
ContentResult cr = serializeAnything(out, o, eType.getElementType(), name, null, 1, false, true);
if (link != null)
out.eTag("a");
if (cr == CR_ELEMENTS)
out.ie(i+1);
if (! isDc)
out.eTag("li").nl(i+1);
isFirst = false;
}
out.ie(i).eTag(isDc ? "p" : "ul").nl(i);
}
}
private HtmlRender<?> getRender(HtmlSerializerSession session, BeanPropertyMeta pMeta, Object value) {
if (pMeta == null)
return null;
HtmlRender<?> render = getHtmlBeanPropertyMeta(pMeta).getRender();
if (render != null)
return render;
ClassMeta<?> cMeta = session.getClassMetaForObject(value);
render = cMeta == null ? null : getHtmlClassMeta(cMeta).getRender();
return render;
}
@SuppressWarnings({"rawtypes","unchecked"})
private String getStyle(HtmlSerializerSession session, BeanPropertyMeta pMeta, Object value) {
HtmlRender render = getRender(session, pMeta, value);
return render == null ? null : render.getStyle(session, value);
}
private String getLink(BeanPropertyMeta pMeta) {
return pMeta == null ? null : getHtmlBeanPropertyMeta(pMeta).getLink();
}
private String getAnchorText(BeanPropertyMeta pMeta) {
return pMeta == null ? null : getHtmlBeanPropertyMeta(pMeta).getAnchorText();
}
/*
* Returns the table column headers for the specified collection of objects.
* Returns null if collection should not be serialized as a 2-dimensional table.
* 2-dimensional tables are used for collections of objects that all have the same set of property names.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private Object[] getTableHeaders(Collection c, HtmlBeanPropertyMeta bpHtml) throws SerializeException {
if (c.size() == 0)
return null;
c = sort(c);
Object[] th;
Set<ClassMeta> prevC = new HashSet<>();
Object o1 = null;
for (Object o : c)
if (o != null) {
o1 = o;
break;
}
if (o1 == null)
return null;
ClassMeta<?> cm = getClassMetaForObject(o1);
PojoSwap swap = cm.getPojoSwap(this);
if (swap != null) {
o1 = swap(swap, o1);
cm = swap.getSwapClassMeta(this);
}
if (cm == null || ! cm.isMapOrBean())
return null;
if (cm.getInnerClass().isAnnotationPresent(HtmlLink.class))
return null;
HtmlClassMeta cHtml = getHtmlClassMeta(cm);
if (cHtml.isNoTables() || bpHtml.isNoTables())
return null;
if (cHtml.isNoTableHeaders() || bpHtml.isNoTableHeaders())
return new Object[0];
if (canIgnoreValue(cm, null, o1))
return null;
if (cm.isMap() && ! cm.isBeanMap()) {
Set<Object> set = new LinkedHashSet<>();
for (Object o : c) {
if (! canIgnoreValue(cm, null, o)) {
if (! cm.isInstance(o))
return null;
Map m = sort((Map)o);
for (Map.Entry e : (Set<Map.Entry>)m.entrySet()) {
if (e.getValue() != null)
set.add(e.getKey() == null ? null : e.getKey());
}
}
}
th = set.toArray(new Object[set.size()]);
} else {
Map<String,Boolean> m = new LinkedHashMap<>();
for (Object o : c) {
if (! canIgnoreValue(cm, null, o)) {
if (! cm.isInstance(o))
return null;
BeanMap<?> bm = (o instanceof BeanMap ? (BeanMap)o : toBeanMap(o));
for (Map.Entry<String,Object> e : bm.entrySet()) {
String key = e.getKey();
if (e.getValue() != null)
m.put(key, true);
else if (! m.containsKey(key))
m.put(key, false);
}
}
}
for (Iterator<Boolean> i = m.values().iterator(); i.hasNext();)
if (! i.next())
i.remove();
th = m.keySet().toArray(new Object[m.size()]);
}
prevC.add(cm);
boolean isSortable = true;
for (Object o : th)
isSortable &= (o instanceof Comparable);
Set<Object> s = (isSortable ? new TreeSet<>() : new LinkedHashSet<>());
s.addAll(Arrays.asList(th));
for (Object o : c) {
if (o == null)
continue;
cm = getClassMetaForObject(o);
PojoSwap ps = cm == null ? null : cm.getPojoSwap(this);
if (ps != null) {
o = swap(ps, o);
cm = ps.getSwapClassMeta(this);
}
if (prevC.contains(cm))
continue;
if (cm == null || ! (cm.isMap() || cm.isBean()))
return null;
if (cm.getInnerClass().isAnnotationPresent(HtmlLink.class))
return null;
if (canIgnoreValue(cm, null, o))
return null;
if (cm.isMap() && ! cm.isBeanMap()) {
Map m = (Map)o;
if (th.length != m.keySet().size())
return null;
for (Object k : m.keySet())
if (! s.contains(k.toString()))
return null;
} else {
BeanMap<?> bm = (o instanceof BeanMap ? (BeanMap)o : toBeanMap(o));
int l = 0;
for (String k : bm.keySet()) {
if (! s.contains(k))
return null;
l++;
}
if (s.size() != l)
return null;
}
}
return th;
}
//-----------------------------------------------------------------------------------------------------------------
// Properties
//-----------------------------------------------------------------------------------------------------------------
/**
* Configuration property: Add <js>"_type"</js> properties when needed.
*
* @see HtmlSerializer#HTML_addBeanTypes
* @return
* <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
* through reflection.
*/
@Override
protected final boolean isAddBeanTypes() {
return ctx.isAddBeanTypes();
}
/**
* Configuration property: Add key/value headers on bean/map tables.
*
* @see HtmlSerializer#HTML_addKeyValueTableHeaders
* @return
* <jk>true</jk> if <bc>key</bc> and <bc>value</bc> column headers are added to tables.
*/
protected final boolean isAddKeyValueTableHeaders() {
return ctx.isAddKeyValueTableHeaders();
}
/**
* Configuration property: Look for link labels in URIs.
*
* @see HtmlSerializer#HTML_detectLabelParameters
* @return
* <jk>true</jk> if we should look for URL label parameters (e.g. <js>"?label=foobar"</js>).
*/
protected final boolean isDetectLabelParameters() {
return ctx.isDetectLabelParameters();
}
/**
* Configuration property: Look for URLs in {@link String Strings}.
*
* @see HtmlSerializer#HTML_detectLinksInStrings
* @return
* <jk>true</jk> if we should automatically convert strings to URLs if they look like a URL.
*/
protected final boolean isDetectLinksInStrings() {
return ctx.isDetectLinksInStrings();
}
/**
* Configuration property: Link label parameter name.
*
* @see HtmlSerializer#HTML_labelParameter
* @return
* The parameter name to look for when resolving link labels via {@link HtmlSerializer#HTML_detectLabelParameters}.
*/
protected final String getLabelParameter() {
return ctx.getLabelParameter();
}
/**
* Configuration property: Anchor text source.
*
* @see HtmlSerializer#HTML_uriAnchorText
* @return
* When creating anchor tags (e.g. <code><xt>&lt;a</xt> <xa>href</xa>=<xs>'...'</xs>
* <xt>&gt;</xt>text<xt>&lt;/a&gt;</xt></code>) in HTML, this setting defines what to set the inner text to.
*/
protected final AnchorText getUriAnchorText() {
return ctx.getUriAnchorText();
}
//-----------------------------------------------------------------------------------------------------------------
// Extended metadata
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the language-specific metadata on the specified class.
*
* @param cm The class to return the metadata on.
* @return The metadata.
*/
protected HtmlClassMeta getHtmlClassMeta(ClassMeta<?> cm) {
return ctx.getHtmlClassMeta(cm);
}
/**
* Returns the language-specific metadata on the specified bean property.
*
* @param bpm The bean property to return the metadata on.
* @return The metadata.
*/
protected HtmlBeanPropertyMeta getHtmlBeanPropertyMeta(BeanPropertyMeta bpm) {
return ctx.getHtmlBeanPropertyMeta(bpm);
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
@Override /* Session */
public ObjectMap toMap() {
return super.toMap()
.append("HtmlSerializerSession", new DefaultFilteringObjectMap()
);
}
}