All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.html.HtmlSerializerSession Maven / Gradle / Ivy

There is a newer version: 9.0.1
Show newest version
// ***************************************************************************************************************************
// * 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.html.HtmlSerializerSession.ContentResult.*;
import static org.apache.juneau.html.HtmlSerializer.*;
import static org.apache.juneau.xml.XmlUtils.*;

import java.io.*;
import java.util.*;
import java.util.regex.*;

import org.apache.juneau.*;
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}.
 *
 * 

* 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 AnchorText anchorText; private final boolean detectLinksInStrings, lookForLabelParameters, addKeyValueTableHeaders, addBeanTypeProperties; 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(HtmlSerializerContext ctx, SerializerSessionArgs args) { super(ctx, args); String labelParameter; ObjectMap p = getProperties(); anchorText = p.getWithDefault(HTML_uriAnchorText, ctx.uriAnchorText, AnchorText.class); detectLinksInStrings = p.getBoolean(HTML_detectLinksInStrings, ctx.detectLinksInStrings); lookForLabelParameters = p.getBoolean(HTML_lookForLabelParameters, ctx.lookForLabelParameters); labelParameter = p.getString(HTML_labelParameter, ctx.labelParameter); addKeyValueTableHeaders = p.getBoolean(HTML_addKeyValueTableHeaders, ctx.addKeyValueTableHeaders); addBeanTypeProperties = p.getBoolean(HTML_addBeanTypeProperties, ctx.addBeanTypeProperties); labelPattern = Pattern.compile("[\\?\\&]" + Pattern.quote(labelParameter) + "=([^\\&]*)"); } @Override /* Session */ public ObjectMap asMap() { return super.asMap() .append("HtmlSerializerSession", new ObjectMap() .append("addBeanTypeProperties", addBeanTypeProperties) .append("addKeyValueTableHeaders", addKeyValueTableHeaders) .append("anchorText", anchorText) .append("detectLinksInStrings", detectLinksInStrings) .append("labelPattern", labelPattern) .append("lookForLabelParameters", lookForLabelParameters) ); } /** * 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 Exception */ protected final HtmlWriter getHtmlWriter(SerializerPipe out) throws Exception { 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 true 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 null if the object isn't from a bean property. * @param o The object. * @return true 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 (detectLinksInStrings && 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 null 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 (lookForLabelParameters) { Matcher m = labelPattern.matcher(s); if (m.find()) return urlDecode(m.group(1)); } switch (anchorText) { 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; } } /** * Returns the {@link HtmlSerializer#HTML_addKeyValueTableHeaders} setting value for this session. * * @return The {@link HtmlSerializer#HTML_addKeyValueTableHeaders} setting value for this session. */ public final boolean isAddKeyValueTableHeaders() { return addKeyValueTableHeaders; } /** * Returns the {@link HtmlSerializer#HTML_addBeanTypeProperties} setting value for this session. * * @return The {@link HtmlSerializer#HTML_addBeanTypeProperties} setting value for this session. */ @Override /* SerializerSession */ public final boolean isAddBeanTypeProperties() { return addBeanTypeProperties; } @Override /* XmlSerializer */ public boolean isHtmlMode() { return true; } @Override /* Serializer */ protected void doSerialize(SerializerPipe out, Object o) throws Exception { 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 HtmlWriter doSerialize(Object o, HtmlWriter w) throws Exception { serializeAnything(w, o, getExpectedRootType(o), null, getInitialDepth()-1, null, true); return w; } /** * 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 xIndent The current indentation value. * @param pMeta The bean property being serialized, or null if we're not serializing a bean property. * @param isRoot true if this is the root element of the document. * @return The type of content encountered. Either simple (no whitespace) or normal (elements with whitespace). * @throws Exception If a problem occurred trying to convert the output. */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected ContentResult serializeAnything(HtmlWriter out, Object o, ClassMeta eType, String name, int xIndent, BeanPropertyMeta pMeta, boolean isRoot) throws Exception { 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 = push(name, o, eType); // Handle recursion if (aType == null) { o = null; aType = object(); } indent += xIndent; ContentResult cr = CR_NORMAL; // Determine the type. if (o == null || (aType.isChar() && ((Character)o).charValue() == 0)) { out.tag("null"); cr = ContentResult.CR_SIMPLE; } else { if (aType.isDelegate()) { wType = aType; aType = ((Delegate)o).getClassMeta(); } sType = aType; String typeName = null; if (isAddBeanTypeProperties() && ! eType.equals(aType)) typeName = aType.getDictionaryName(); // Swap if necessary PojoSwap swap = aType.getPojoSwap(this); if (swap != null) { o = swap.swap(this, 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_SIMPLE; } HtmlClassMeta html = sType.getExtendedMeta(HtmlClassMeta.class); HtmlRender render = (pMeta == null ? null : pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).getRender()); if (render == null) render = html.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, xIndent, null, false); } } if (html.isAsXml() || (pMeta != null && pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).isAsXml())) { pop(); indent++; super.serializeAnything(out, o, null, null, null, false, XmlFormat.MIXED, false, false, null); indent -= xIndent+1; return cr; } else if (html.isAsPlainText() || (pMeta != null && pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).isAsPlainText())) { out.write(o == null ? "null" : o.toString()); cr = CR_SIMPLE; } else if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) { out.tag("null"); cr = CR_SIMPLE; } else if (sType.isNumber()) { if (eType.isNumber() && ! isRoot) out.append(o); else out.sTag("number").append(o).eTag("number"); cr = CR_SIMPLE; } else if (sType.isBoolean()) { if (eType.isBoolean() && ! isRoot) out.append(o); else out.sTag("boolean").append(o).eTag("boolean"); cr = CR_SIMPLE; } 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); Class c = o.getClass(); if (c.isAnnotationPresent(HtmlLink.class)) { HtmlLink h = o.getClass().getAnnotation(HtmlLink.class); Object urlProp = m.get(h.hrefProperty()); Object nameProp = m.get(h.nameProperty()); out.oTag("a").attrUri("href", urlProp).append('>').text(nameProp).eTag("a"); cr = CR_SIMPLE; } 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_SIMPLE; } else { if (isRoot) out.sTag("string").text(toString(o)).eTag("string"); else out.text(toString(o)); cr = CR_SIMPLE; } } pop(); indent -= xIndent; return cr; } /** * Identifies what the contents were of a serialized bean. */ static enum ContentResult { CR_SIMPLE, // Simple content. Shouldn't use whitespace. CR_NORMAL // Normal content. Use whitespace. } @SuppressWarnings({ "rawtypes", "unchecked" }) private void serializeMap(HtmlWriter out, Map m, ClassMeta sType, ClassMeta eKeyType, ClassMeta eValueType, String typeName, BeanPropertyMeta ppMeta) throws Exception { ClassMeta keyType = eKeyType == null ? string() : eKeyType; ClassMeta valueType = eValueType == null ? object() : eValueType; ClassMeta aType = getClassMetaForObject(m); // The actual type 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() && ! (aType.getExtendedMeta(HtmlClassMeta.class).isNoTableHeaders() || (ppMeta != null && ppMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).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)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("{#}", StringUtils.toString(value))).cTag(); ContentResult cr = serializeAnything(out, key, keyType, null, 2, null, false); if (link != null) out.eTag("a"); if (cr == CR_NORMAL) 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)), 2, null, false); if (cr == CR_NORMAL) 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(HtmlWriter out, BeanMap m, ClassMeta eType, BeanPropertyMeta ppMeta) throws Exception { 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() && ! (m.getClassMeta().getExtendedMeta(HtmlClassMeta.class).isNoTableHeaders() || (ppMeta != null && ppMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).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(isTrimNulls())) { 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, 2, pMeta, false); if (cr == CR_NORMAL) 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(HtmlWriter out, Object in, ClassMeta sType, ClassMeta eType, String name, BeanPropertyMeta ppMeta) throws Exception { ClassMeta seType = sType.getElementType(); if (seType == null) seType = object(); Collection c = (sType.isCollection() ? (Collection)in : toList(sType.getInnerClass(), in)); int i = indent; if (c.isEmpty()) { out.appendln(i, "

    "); return; } String type2 = null; if (sType != eType) type2 = sType.getDictionaryName(); if (type2 == null) type2 = "array"; c = sort(c); HtmlBeanPropertyMeta hbpMeta = (ppMeta == null ? null : ppMeta.getExtendedMeta(HtmlBeanPropertyMeta.class)); 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, hbpMeta); 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(this, 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, 1, null, 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), 2, null, false); if (cr == CR_NORMAL) 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(); 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(), 2, pMeta, false); if (cr == CR_NORMAL) 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, "ul"); if (! type2.equals("array")) out.attr(btpn, type2); out.append('>').nl(i+1); for (Object o : c) { out.oTag(i+1, "li"); String style = getStyle(this, ppMeta, o); String link = getLink(ppMeta); if (style != null) out.attr("style", style); out.cTag(); if (link != null) out.oTag(i+2, "a").attrUri("href", link.replace("{#}", StringUtils.toString(o))).cTag(); ContentResult cr = serializeAnything(out, o, eType.getElementType(), name, 1, null, false); if (link != null) out.eTag("a"); if (cr == CR_NORMAL) out.ie(i+1); out.eTag("li").nl(i+1); } out.ie(i).eTag("ul").nl(i); } } private static HtmlRender getRender(HtmlSerializerSession session, BeanPropertyMeta pMeta, Object value) { if (pMeta == null) return null; HtmlBeanPropertyMeta hpMeta = pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class); HtmlRender render = hpMeta.getRender(); if (render != null) return render; ClassMeta cMeta = session.getClassMetaForObject(value); render = cMeta == null ? null : cMeta.getExtendedMeta(HtmlClassMeta.class).getRender(); return render; } @SuppressWarnings({"rawtypes","unchecked"}) private static String getStyle(HtmlSerializerSession session, BeanPropertyMeta pMeta, Object value) { HtmlRender render = getRender(session, pMeta, value); return render == null ? null : render.getStyle(session, value); } private static String getLink(BeanPropertyMeta pMeta) { return pMeta == null ? null : pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).getLink(); } private static String getAnchorText(BeanPropertyMeta pMeta) { return pMeta == null ? null : pMeta.getExtendedMeta(HtmlBeanPropertyMeta.class).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 hbpMeta) throws Exception { if (c.size() == 0) return null; c = sort(c); Object[] th; Set 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(this, o1); cm = swap.getSwapClassMeta(this); } if (cm == null || ! cm.isMapOrBean()) return null; if (cm.getInnerClass().isAnnotationPresent(HtmlLink.class)) return null; HtmlClassMeta h = cm.getExtendedMeta(HtmlClassMeta.class); if (h.isNoTables() || (hbpMeta != null && hbpMeta.isNoTables())) return null; if (h.isNoTableHeaders() || (hbpMeta != null && hbpMeta.isNoTableHeaders())) return new Object[0]; if (canIgnoreValue(cm, null, o1)) return null; if (cm.isMap() && ! cm.isBeanMap()) { Set 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)m.entrySet()) { if (e.getValue() != null) set.add(e.getKey() == null ? null : e.getKey()); } } } th = set.toArray(new Object[set.size()]); } else { Map 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 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 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 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 = ps.swap(this, 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; } }