http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java index 6de1afc..7ec11d6 100644 --- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java +++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java @@ -12,24 +12,28 @@ // *************************************************************************************************************************** package org.apache.juneau.html; +import static org.apache.juneau.html.HtmlSerializerSession.ContentResult.*; import static org.apache.juneau.html.HtmlSerializerContext.*; import static org.apache.juneau.msgpack.MsgPackSerializerContext.*; import static org.apache.juneau.xml.XmlUtils.*; -import java.lang.reflect.*; +import java.io.*; import java.util.*; import java.util.regex.*; import org.apache.juneau.*; -import org.apache.juneau.http.*; +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 meant to be discarded after one-time use. + * 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 { @@ -54,26 +58,18 @@ public class HtmlSerializerSession extends XmlSerializerSession { * @param ctx * The context creating this session object. * The context contains all the configuration settings for this object. - * @param op - * The override properties. - * These override any context properties defined in the context. - * @param javaMethod The java method that called this serializer, usually the method in a REST servlet. - * @param locale - * The session locale. - * If <jk>null</jk>, then the locale defined on the context is used. - * @param timeZone - * The session timezone. - * If <jk>null</jk>, then the timezone defined on the context is used. - * @param mediaType The session media type (e.g. <js>"application/json"</js>). - * @param uriContext - * The URI context. - * Identifies the current request URI used for resolution of URIs to absolute or root-relative form. + * @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. + * <br>If <jk>null</jk>, defaults to {@link SerializerSessionArgs#DEFAULT}. */ - protected HtmlSerializerSession(HtmlSerializerContext ctx, ObjectMap op, Method javaMethod, - Locale locale, TimeZone timeZone, MediaType mediaType, UriContext uriContext) { - super(ctx, op, javaMethod, locale, timeZone, mediaType, uriContext); + protected HtmlSerializerSession(HtmlSerializerContext ctx, SerializerSessionArgs args) { + super(ctx, args); String labelParameter; - if (op == null || op.isEmpty()) { + ObjectMap p = getProperties(); + if (p.isEmpty()) { anchorText = Enum.valueOf(AnchorText.class, ctx.uriAnchorText); detectLinksInStrings = ctx.detectLinksInStrings; lookForLabelParameters = ctx.lookForLabelParameters; @@ -81,12 +77,12 @@ public class HtmlSerializerSession extends XmlSerializerSession { addKeyValueTableHeaders = ctx.addKeyValueTableHeaders; addBeanTypeProperties = ctx.addBeanTypeProperties; } else { - anchorText = Enum.valueOf(AnchorText.class, op.getString(HTML_uriAnchorText, ctx.uriAnchorText)); - detectLinksInStrings = op.getBoolean(HTML_detectLinksInStrings, ctx.detectLinksInStrings); - lookForLabelParameters = op.getBoolean(HTML_lookForLabelParameters, ctx.lookForLabelParameters); - labelParameter = op.getString(HTML_labelParameter, ctx.labelParameter); - addKeyValueTableHeaders = op.getBoolean(HTML_addKeyValueTableHeaders, ctx.addKeyValueTableHeaders); - addBeanTypeProperties = op.getBoolean(MSGPACK_addBeanTypeProperties, ctx.addBeanTypeProperties); + anchorText = Enum.valueOf(AnchorText.class, p.getString(HTML_uriAnchorText, ctx.uriAnchorText)); + 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(MSGPACK_addBeanTypeProperties, ctx.addBeanTypeProperties); } labelPattern = Pattern.compile("[\\?\\&]" + Pattern.quote(labelParameter) + "=([^\\&]*)"); } @@ -98,12 +94,14 @@ public class HtmlSerializerSession extends XmlSerializerSession { * @return The output target object wrapped in an {@link HtmlWriter}. * @throws Exception */ - public HtmlWriter getHtmlWriter(SerializerOutput out) throws Exception { + protected final HtmlWriter getHtmlWriter(SerializerPipe out) throws Exception { Object output = out.getRawOutput(); if (output instanceof HtmlWriter) return (HtmlWriter)output; - return new HtmlWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isTrimStrings(), getQuoteChar(), + HtmlWriter w = new HtmlWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isTrimStrings(), getQuoteChar(), getUriResolver()); + out.setWriter(w); + return w; } /** @@ -196,4 +194,581 @@ public class HtmlSerializerSession extends XmlSerializerSession { 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 <jk>null</jk> if we're not serializing a bean property. + * @param isRoot <jk>true</jk> 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.getSerializedClassMeta(); + String typeName = null; + if (isAddBeanTypeProperties() && ! eType.equals(aType)) + typeName = aType.getDictionaryName(); + + // Swap if necessary + PojoSwap swap = aType.getPojoSwap(); + if (swap != null) { + o = swap.swap(this, o); + + // If the getSwapClass() method returns Object, we need to figure out + // the actual type now. + if (sType.isObject()) + sType = getClassMetaForObject(o); + } + + 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<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("{#}", 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 = cMeta.isCollectionOrArray() ? null : getLink(pMeta); + + 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", m.resolveVars(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, "<ul></ul>"); + 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() != null) { + PojoSwap f = cm.getPojoSwap(); + o = f.swap(this, o); + cm = cm.getSerializedClassMeta(); + } + + 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) { + serializeAnything(out, o, null, null, 1, null, false); + + } 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(); + String link = pMeta.getClassMeta().isCollectionOrArray() ? null : getLink(pMeta); + Object value = p.getValue(); + 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", m2.resolveVars(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(); + } + + /* + * 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<ClassMeta> prevC = new HashSet<ClassMeta>(); + Object o1 = null; + for (Object o : c) + if (o != null) { + o1 = o; + break; + } + if (o1 == null) + return null; + ClassMeta<?> cm = getClassMetaForObject(o1); + if (cm.getPojoSwap() != null) { + PojoSwap f = cm.getPojoSwap(); + o1 = f.swap(this, o1); + cm = cm.getSerializedClassMeta(); + } + 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<Object> set = new LinkedHashSet<Object>(); + 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<String,Boolean>(); + 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<Object>() : new LinkedHashSet<Object>()); + s.addAll(Arrays.asList(th)); + + for (Object o : c) { + if (o == null) + continue; + cm = getClassMetaForObject(o); + if (cm != null && cm.getPojoSwap() != null) { + PojoSwap f = cm.getPojoSwap(); + o = f.swap(this, o); + cm = cm.getSerializedClassMeta(); + } + 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; + } }
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializer.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializer.java index 938fecf..2d61aab 100644 --- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializer.java @@ -12,9 +12,6 @@ // *************************************************************************************************************************** package org.apache.juneau.html; -import java.lang.reflect.*; -import java.util.*; - import org.apache.juneau.*; import org.apache.juneau.annotation.*; import org.apache.juneau.serializer.*; @@ -47,19 +44,8 @@ public class HtmlStrippedDocSerializer extends HtmlSerializer { super(propertyStore); } - //--------------------------------------------------------------------------- - // Overridden methods - //--------------------------------------------------------------------------- - @Override /* Serializer */ - protected void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception { - HtmlSerializerSession s = (HtmlSerializerSession)session; - HtmlWriter w = s.getHtmlWriter(out); - if (o == null - || (o instanceof Collection && ((Collection<?>)o).size() == 0) - || (o.getClass().isArray() && Array.getLength(o) == 0)) - w.sTag(1, "p").append("No Results").eTag("p").nl(1); - else - super.doSerialize(s, out, o); + public WriterSerializerSession createSession(SerializerSessionArgs args) { + return new HtmlStrippedDocSerializerSession(ctx, args); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializerSession.java new file mode 100644 index 0000000..7589781 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlStrippedDocSerializerSession.java @@ -0,0 +1,51 @@ +// *************************************************************************************************************************** +// * 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 java.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.serializer.*; + +/** + * Session object that lives for the duration of a single use of {@link HtmlStrippedDocSerializer}. + * + * <p> + * This class is NOT thread safe. It is meant to be discarded after one-time use. + */ +public class HtmlStrippedDocSerializerSession extends HtmlSerializerSession { + + /** + * 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. + */ + protected HtmlStrippedDocSerializerSession(HtmlSerializerContext ctx, SerializerSessionArgs args) { + super(ctx, args); + } + + @Override /* SerializerSession */ + protected void doSerialize(SerializerPipe out, Object o) throws Exception { + HtmlWriter w = getHtmlWriter(out); + if (o == null + || (o instanceof Collection && ((Collection<?>)o).size() == 0) + || (o.getClass().isArray() && Array.getLength(o) == 0)) + w.sTag(1, "p").append("No Results").eTag("p").nl(1); + else + super.doSerialize(out, o); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java b/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java index c93aa5d..e841931 100644 --- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java +++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlTag.java @@ -129,8 +129,6 @@ enum HtmlTag { t = (end ? xP : P); else if (c == 'h') t = (end ? xHTML : HTML); - if (t == null) - throw new XmlParseException(null, "Unknown tag ''{0}'' encountered", tag); return t; } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java b/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java index b2da86a..1c3626e 100644 --- a/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java +++ b/juneau-core/src/main/java/org/apache/juneau/internal/IOUtils.java @@ -261,7 +261,7 @@ public final class IOUtils { * reader. */ public static Reader getBufferedReader(Reader r) { - if (r instanceof BufferedReader || r instanceof StringReader) + if (r == null || r instanceof BufferedReader || r instanceof StringReader) return r; return new BufferedReader(r); } @@ -410,6 +410,59 @@ public final class IOUtils { } /** + * Flushes multiple output streams and writers in a single call. + * + * @param o + * The objects to flush. + * <jk>null</jk> entries are ignored. + * @throws IOException + */ + public static void flush(Object...o) throws IOException { + IOException ex = null; + for (Object o2 : o) { + try { + if (o2 instanceof OutputStream) + ((OutputStream)o2).flush(); + if (o2 instanceof Writer) + ((Writer)o2).flush(); + } catch (IOException e) { + ex = e; + } + } + if (ex != null) + throw ex; + } + + /** + * Close all specified input streams, output streams, readers, and writers. + * + * @param o + * The list of all objects to close. + * <jk>null</jk> entries are ignored. + * @throws IOException + */ + public static void close(Object...o) throws IOException { + IOException ex = null; + for (Object o2 : o) { + try { + if (o2 instanceof InputStream) + ((InputStream)o2).close(); + if (o2 instanceof OutputStream) + ((OutputStream)o2).close(); + if (o2 instanceof Reader) + ((Reader)o2).close(); + if (o2 instanceof Writer) + ((Writer)o2).close(); + } catch (IOException e) { + ex = e; + } + } + if (ex != null) + throw ex; + } + + + /** * Converts an object to an <code>InputStream</code>. * * @param o http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/jso/JsoParser.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/jso/JsoParser.java b/juneau-core/src/main/java/org/apache/juneau/jso/JsoParser.java index 67a302f..599fa17 100644 --- a/juneau-core/src/main/java/org/apache/juneau/jso/JsoParser.java +++ b/juneau-core/src/main/java/org/apache/juneau/jso/JsoParser.java @@ -46,15 +46,8 @@ public final class JsoParser extends InputStreamParser { return new JsoParserBuilder(propertyStore); } - - //-------------------------------------------------------------------------------- - // Overridden methods - //-------------------------------------------------------------------------------- - - @SuppressWarnings("unchecked") - @Override /* InputStreamParser */ - protected <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception { - ObjectInputStream ois = new ObjectInputStream(session.getInputStream()); - return (T)ois.readObject(); + @Override /* Parser */ + public InputStreamParserSession createSession(ParserSessionArgs args) { + return new JsoParserSession(args); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserSession.java b/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserSession.java new file mode 100644 index 0000000..12f9cf2 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserSession.java @@ -0,0 +1,45 @@ +// *************************************************************************************************************************** +// * 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.jso; + +import java.io.*; + +import org.apache.juneau.*; +import org.apache.juneau.parser.*; + +/** + * Session object that lives for the duration of a single use of {@link JsoParser}. + * + * <p> + * This class is NOT thread safe. + * It is typically discarded after one-time use although it can be reused against multiple inputs. + */ +@SuppressWarnings("unchecked") +public class JsoParserSession extends InputStreamParserSession { + + /** + * Create a new session using properties specified in the context. + * + * @param args + * Runtime session arguments. + */ + protected JsoParserSession(ParserSessionArgs args) { + super(args); + } + + @Override /* ParserSession */ + protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception { + ObjectInputStream ois = new ObjectInputStream(pipe.getInputStream()); + return (T)ois.readObject(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializer.java b/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializer.java index 6ea9e43..6e3ceb8 100644 --- a/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializer.java @@ -34,6 +34,7 @@ public class JsoSerializer extends OutputStreamSerializer { /** Default serializer, all default settings.*/ public static final JsoSerializer DEFAULT = new JsoSerializer(PropertyStore.create()); + private final SerializerContext ctx; /** * Constructor. @@ -42,6 +43,7 @@ public class JsoSerializer extends OutputStreamSerializer { */ public JsoSerializer(PropertyStore propertyStore) { super(propertyStore); + this.ctx = createContext(SerializerContext.class); } @Override /* CoreObject */ @@ -49,15 +51,8 @@ public class JsoSerializer extends OutputStreamSerializer { return new JsoSerializerBuilder(propertyStore); } - //-------------------------------------------------------------------------------- - // Overridden methods - //-------------------------------------------------------------------------------- - - @Override /* OutputStreamSerializer */ - protected void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception { - ObjectOutputStream oos = new ObjectOutputStream(out.getOutputStream()); - oos.writeObject(o); - oos.flush(); - oos.close(); + @Override /* Serializer */ + public OutputStreamSerializerSession createSession(SerializerSessionArgs args) { + return new JsoSerializerSession(ctx, args); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerSession.java new file mode 100644 index 0000000..a445ac4 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerSession.java @@ -0,0 +1,52 @@ +// *************************************************************************************************************************** +// * 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.jso; + +import java.io.*; + +import org.apache.juneau.serializer.*; + +/** + * Session object that lives for the duration of a single use of {@link JsoSerializer}. + * + * <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 JsoSerializerSession extends OutputStreamSerializerSession { + + /** + * 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. + * <br>If <jk>null</jk>, defaults to {@link SerializerSessionArgs#DEFAULT}. + */ + protected JsoSerializerSession(SerializerContext ctx, SerializerSessionArgs args) { + super(ctx, args); + } + + @Override /* OutputStreamSerializerSession */ + protected void doSerialize(SerializerPipe out, Object o) throws Exception { + ObjectOutputStream oos = new ObjectOutputStream(out.getOutputStream()); + oos.writeObject(o); + oos.flush(); + oos.close(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java b/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java index 3afd115..460ae5d 100644 --- a/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java +++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java @@ -13,18 +13,10 @@ package org.apache.juneau.json; import static org.apache.juneau.parser.ParserContext.*; -import static org.apache.juneau.internal.StringUtils.*; - -import java.io.*; -import java.lang.reflect.*; -import java.util.*; import org.apache.juneau.*; import org.apache.juneau.annotation.*; -import org.apache.juneau.http.*; -import org.apache.juneau.internal.*; import org.apache.juneau.parser.*; -import org.apache.juneau.transform.*; /** * Parses any valid JSON text into a POJO model. @@ -116,7 +108,6 @@ import org.apache.juneau.transform.*; * <li>{@link JsonParserContext} * </ul> */ -@SuppressWarnings({ "rawtypes", "unchecked" }) @Consumes("application/json,text/json") public class JsonParser extends ReaderParser { @@ -126,9 +117,6 @@ public class JsonParser extends ReaderParser { /** Default parser, all default settings.*/ public static final JsonParser DEFAULT_STRICT = new JsonParser.Strict(PropertyStore.create()); - private static final AsciiSet decChars = new AsciiSet("0123456789"); - - /** Default parser, strict mode. */ public static class Strict extends JsonParser { @@ -138,12 +126,7 @@ public class JsonParser extends ReaderParser { * @param propertyStore The property store containing all the settings for this object. */ public Strict(PropertyStore propertyStore) { - super(propertyStore); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(PARSER_strict, true); + super(propertyStore.copy().append(PARSER_strict, true)); } } @@ -165,680 +148,8 @@ public class JsonParser extends ReaderParser { return new JsonParserBuilder(propertyStore); } - private <T> T parseAnything(JsonParserSession session, ClassMeta<T> eType, ParserReader r, Object outer, - BeanPropertyMeta pMeta) throws Exception { - - if (eType == null) - eType = (ClassMeta<T>)object(); - PojoSwap<T,Object> transform = (PojoSwap<T,Object>)eType.getPojoSwap(); - ClassMeta<?> sType = eType.getSerializedClassMeta(); - session.setCurrentClass(sType); - String wrapperAttr = sType.getExtendedMeta(JsonClassMeta.class).getWrapperAttr(); - - Object o = null; - - skipCommentsAndSpace(session, r); - if (wrapperAttr != null) - skipWrapperAttrStart(session, r, wrapperAttr); - int c = r.peek(); - if (c == -1) { - if (session.isStrict()) - throw new ParseException(session, "Empty input."); - // Let o be null. - } else if ((c == ',' || c == '}' || c == ']')) { - if (session.isStrict()) - throw new ParseException(session, "Missing value detected."); - // Handle bug in Cognos 10.2.1 that can product non-existent values. - // Let o be null; - } else if (c == 'n') { - parseKeyword(session, "null", r); - } else if (sType.isObject()) { - if (c == '{') { - ObjectMap m2 = new ObjectMap(session); - parseIntoMap2(session, r, m2, string(), object(), pMeta); - o = session.cast(m2, pMeta, eType); - } else if (c == '[') { - o = parseIntoCollection2(session, r, new ObjectList(session), object(), pMeta); - } else if (c == '\'' || c == '"') { - o = parseString(session, r); - if (sType.isChar()) - o = o.toString().charAt(0); - } else if (c >= '0' && c <= '9' || c == '-' || c == '.') { - o = parseNumber(session, r, null); - } else if (c == 't') { - parseKeyword(session, "true", r); - o = Boolean.TRUE; - } else { - parseKeyword(session, "false", r); - o = Boolean.FALSE; - } - } else if (sType.isBoolean()) { - o = parseBoolean(session, r); - } else if (sType.isCharSequence()) { - o = parseString(session, r); - } else if (sType.isChar()) { - o = parseString(session, r).charAt(0); - } else if (sType.isNumber()) { - o = parseNumber(session, r, (Class<? extends Number>)sType.getInnerClass()); - } else if (sType.isMap()) { - Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : new ObjectMap(session)); - o = parseIntoMap2(session, r, m, sType.getKeyType(), sType.getValueType(), pMeta); - } else if (sType.isCollection()) { - if (c == '{') { - ObjectMap m = new ObjectMap(session); - parseIntoMap2(session, r, m, string(), object(), pMeta); - o = session.cast(m, pMeta, eType); - } else { - Collection l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance() : new ObjectList(session)); - o = parseIntoCollection2(session, r, l, sType, pMeta); - } - } else if (sType.canCreateNewBean(outer)) { - BeanMap m = session.newBeanMap(outer, sType.getInnerClass()); - o = parseIntoBeanMap2(session, r, m).getBean(); - } else if (sType.canCreateNewInstanceFromString(outer) && (c == '\'' || c == '"')) { - o = sType.newInstanceFromString(outer, parseString(session, r)); - } else if (sType.canCreateNewInstanceFromNumber(outer) && isFirstNumberChar((char)c)) { - o = sType.newInstanceFromNumber(session, outer, parseNumber(session, r, sType.getNewInstanceFromNumberClass())); - } else if (sType.isArray() || sType.isArgs()) { - if (c == '{') { - ObjectMap m = new ObjectMap(session); - parseIntoMap2(session, r, m, string(), object(), pMeta); - o = session.cast(m, pMeta, eType); - } else { - ArrayList l = (ArrayList)parseIntoCollection2(session, r, new ArrayList(), sType, pMeta); - o = session.toArray(sType, l); - } - } else if (c == '{') { - Map m = new ObjectMap(session); - parseIntoMap2(session, r, m, sType.getKeyType(), sType.getValueType(), pMeta); - if (m.containsKey(session.getBeanTypePropertyName(eType))) - o = session.cast((ObjectMap)m, pMeta, eType); - else - throw new ParseException(session, "Class ''{0}'' could not be instantiated. Reason: ''{1}''", - sType.getInnerClass().getName(), sType.getNotABeanReason()); - } else if (sType.canCreateNewInstanceFromString(outer) && ! session.isStrict()) { - o = sType.newInstanceFromString(outer, parseString(session, r)); - } else { - throw new ParseException(session, "Unrecognized syntax for class type ''{0}'', starting character ''{1}''", - sType, (char)c); - } - - if (wrapperAttr != null) - skipWrapperAttrEnd(session, r); - - if (transform != null && o != null) - o = transform.unswap(session, o, eType); - - if (outer != null) - setParent(eType, o, outer); - - return (T)o; - } - - private Number parseNumber(JsonParserSession session, ParserReader r, Class<? extends Number> type) throws Exception { - int c = r.peek(); - if (c == '\'' || c == '"') - return parseNumber(session, parseString(session, r), type); - return parseNumber(session, parseNumberString(r), type); - } - - private static Number parseNumber(JsonParserSession session, String s, Class<? extends Number> type) throws Exception { - - // JSON has slightly different number rules from Java. - // Strict mode enforces these different rules, lax does not. - if (session.isStrict()) { - - // Lax allows blank strings to represent 0. - // Strict does not allow blank strings. - if (s.length() == 0) - throw new ParseException(session, "Invalid JSON number: ''{0}''", s); - - // Need to weed out octal and hexadecimal formats: 0123,-0123,0x123,-0x123. - // Don't weed out 0 or -0. - boolean isNegative = false; - char c = s.charAt(0); - if (c == '-') { - isNegative = true; - c = (s.length() == 1 ? 'x' : s.charAt(1)); - } - - // JSON doesn't allow '.123' and '-.123'. - if (c == '.') - throw new ParseException(session, "Invalid JSON number: ''{0}''", s); - - // '01' is not a valid number, but '0.1', '0e1', '0e+1' are valid. - if (c == '0' && s.length() > (isNegative ? 2 : 1)) { - char c2 = s.charAt((isNegative ? 2 : 1)); - if (c2 != '.' && c2 != 'e' && c2 != 'E') - throw new ParseException(session, "Invalid JSON number: ''{0}''", s); - } - - // JSON doesn't allow '1.' or '0.e1'. - int i = s.indexOf('.'); - if (i != -1 && (s.length() == (i+1) || ! decChars.contains(s.charAt(i+1)))) - throw new ParseException(session, "Invalid JSON number: ''{0}''", s); - - } - return StringUtils.parseNumber(s, type); - } - - private Boolean parseBoolean(JsonParserSession session, ParserReader r) throws Exception { - int c = r.peek(); - if (c == '\'' || c == '"') - return Boolean.valueOf(parseString(session, r)); - if (c == 't') { - parseKeyword(session, "true", r); - return Boolean.TRUE; - } - parseKeyword(session, "false", r); - return Boolean.FALSE; - } - - - private <K,V> Map<K,V> parseIntoMap2(JsonParserSession session, ParserReader r, Map<K,V> m, ClassMeta<K> keyType, - ClassMeta<V> valueType, BeanPropertyMeta pMeta) throws Exception { - - if (keyType == null) - keyType = (ClassMeta<K>)string(); - - int S0=0; // Looking for outer { - int S1=1; // Looking for attrName start. - int S3=3; // Found attrName end, looking for :. - int S4=4; // Found :, looking for valStart: { [ " ' LITERAL. - int S5=5; // Looking for , or } - int S6=6; // Found , looking for attr start. - - int state = S0; - String currAttr = null; - int c = 0; - while (c != -1) { - c = r.read(); - if (state == S0) { - if (c == '{') - state = S1; - } else if (state == S1) { - if (c == '}') { - return m; - } else if (session.isCommentOrWhitespace(c)) { - skipCommentsAndSpace(session, r.unread()); - } else { - currAttr = parseFieldName(session, r.unread()); - state = S3; - } - } else if (state == S3) { - if (c == ':') - state = S4; - } else if (state == S4) { - if (session.isCommentOrWhitespace(c)) { - skipCommentsAndSpace(session, r.unread()); - } else { - K key = convertAttrToType(session, m, currAttr, keyType); - V value = parseAnything(session, valueType, r.unread(), m, pMeta); - setName(valueType, value, key); - m.put(key, value); - state = S5; - } - } else if (state == S5) { - if (c == ',') - state = S6; - else if (session.isCommentOrWhitespace(c)) - skipCommentsAndSpace(session, r.unread()); - else if (c == '}') { - return m; - } else { - break; - } - } else if (state == S6) { - if (c == '}') { - break; - } else if (session.isCommentOrWhitespace(c)) { - skipCommentsAndSpace(session, r.unread()); - } else { - currAttr = parseFieldName(session, r.unread()); - state = S3; - } - } - } - if (state == S0) - throw new ParseException(session, "Expected '{' at beginning of JSON object."); - if (state == S1) - throw new ParseException(session, "Could not find attribute name on JSON object."); - if (state == S3) - throw new ParseException(session, "Could not find ':' following attribute name on JSON object."); - if (state == S4) - throw new ParseException(session, "Expected one of the following characters: {,[,',\",LITERAL."); - if (state == S5) - throw new ParseException(session, "Could not find '}' marking end of JSON object."); - if (state == S6) - throw new ParseException(session, "Unexpected '}' found in JSON object."); - - return null; // Unreachable. - } - - /* - * Parse a JSON attribute from the character array at the specified position, then - * set the position marker to the last character in the field name. - */ - private String parseFieldName(JsonParserSession session, ParserReader r) throws Exception { - int c = r.peek(); - if (c == '\'' || c == '"') - return parseString(session, r); - if (session.isStrict()) - throw new ParseException(session, "Unquoted attribute detected."); - r.mark(); - // Look for whitespace. - while (c != -1) { - c = r.read(); - if (c == ':' || session.isWhitespace(c) || c == '/') { - r.unread(); - String s = r.getMarked().intern(); - return s.equals("null") ? null : s; - } - } - throw new ParseException(session, "Could not find the end of the field name."); - } - - private <E> Collection<E> parseIntoCollection2(JsonParserSession session, ParserReader r, Collection<E> l, - ClassMeta<?> type, BeanPropertyMeta pMeta) throws Exception { - - int S0=0; // Looking for outermost [ - int S1=1; // Looking for starting [ or { or " or ' or LITERAL or ] - int S2=2; // Looking for , or ] - int S3=3; // Looking for starting [ or { or " or ' or LITERAL - - int argIndex = 0; - - int state = S0; - int c = 0; - while (c != -1) { - c = r.read(); - if (state == S0) { - if (c == '[') - state = S1; - } else if (state == S1) { - if (c == ']') { - return l; - } else if (session.isCommentOrWhitespace(c)) { - skipCommentsAndSpace(session, r.unread()); - } else if (c != -1) { - l.add((E)parseAnything(session, type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), r.unread(), l, pMeta)); - state = S2; - } - } else if (state == S2) { - if (c == ',') { - state = S3; - } else if (session.isCommentOrWhitespace(c)) { - skipCommentsAndSpace(session, r.unread()); - } else if (c == ']') { - return l; - } else { - break; // Invalid character found. - } - } else if (state == S3) { - if (session.isCommentOrWhitespace(c)) { - skipCommentsAndSpace(session, r.unread()); - } else if (c == ']') { - break; - } else if (c != -1) { - l.add((E)parseAnything(session, type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), r.unread(), l, pMeta)); - state = S2; - } - } - } - if (state == S0) - throw new ParseException(session, "Expected '[' at beginning of JSON array."); - if (state == S1) - throw new ParseException(session, "Expected one of the following characters: {,[,',\",LITERAL."); - if (state == S2) - throw new ParseException(session, "Expected ',' or ']'."); - if (state == S3) - throw new ParseException(session, "Unexpected trailing comma in array."); - - return null; // Unreachable. - } - - private <T> BeanMap<T> parseIntoBeanMap2(JsonParserSession session, ParserReader r, BeanMap<T> m) throws Exception { - - int S0=0; // Looking for outer { - int S1=1; // Looking for attrName start. - int S3=3; // Found attrName end, looking for :. - int S4=4; // Found :, looking for valStart: { [ " ' LITERAL. - int S5=5; // Looking for , or } - - int state = S0; - String currAttr = ""; - int c = 0; - int currAttrLine = -1, currAttrCol = -1; - while (c != -1) { - c = r.read(); - if (state == S0) { - if (c == '{') - state = S1; - } else if (state == S1) { - if (c == '}') { - return m; - } else if (session.isCommentOrWhitespace(c)) { - skipCommentsAndSpace(session, r.unread()); - } else { - r.unread(); - currAttrLine= r.getLine(); - currAttrCol = r.getColumn(); - currAttr = parseFieldName(session, r); - state = S3; - } - } else if (state == S3) { - if (c == ':') - state = S4; - } else if (state == S4) { - if (session.isCommentOrWhitespace(c)) { - skipCommentsAndSpace(session, r.unread()); - } else { - if (! currAttr.equals(session.getBeanTypePropertyName(m.getClassMeta()))) { - BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr); - session.setCurrentProperty(pMeta); - if (pMeta == null) { - session.onUnknownProperty(currAttr, m, currAttrLine, currAttrCol); - parseAnything(session, object(), r.unread(), m.getBean(false), null); // Read content anyway to ignore it - } else { - ClassMeta<?> cm = pMeta.getClassMeta(); - Object value = parseAnything(session, cm, r.unread(), m.getBean(false), pMeta); - setName(cm, value, currAttr); - pMeta.set(m, currAttr, value); - } - session.setCurrentProperty(null); - } - state = S5; - } - } else if (state == S5) { - if (c == ',') - state = S1; - else if (session.isCommentOrWhitespace(c)) - skipCommentsAndSpace(session, r.unread()); - else if (c == '}') { - return m; - } - } - } - if (state == S0) - throw new ParseException(session, "Expected '{' at beginning of JSON object."); - if (state == S1) - throw new ParseException(session, "Could not find attribute name on JSON object."); - if (state == S3) - throw new ParseException(session, "Could not find ':' following attribute name on JSON object."); - if (state == S4) - throw new ParseException(session, "Expected one of the following characters: {,[,',\",LITERAL."); - if (state == S5) - throw new ParseException(session, "Could not find '}' marking end of JSON object."); - - return null; // Unreachable. - } - - /* - * Starting from the specified position in the character array, returns the - * position of the character " or '. - * If the string consists of a concatenation of strings (e.g. 'AAA' + "BBB"), this method - * will automatically concatenate the strings and return the result. - */ - private String parseString(JsonParserSession session, ParserReader r) throws Exception { - r.mark(); - int qc = r.read(); // The quote character being used (" or ') - if (qc != '"' && session.isStrict()) { - String msg = ( - qc == '\'' - ? "Invalid quote character \"{0}\" being used." - : "Did not find quote character marking beginning of string. Character=\"{0}\"" - ); - throw new ParseException(session, msg, (char)qc); - } - final boolean isQuoted = (qc == '\'' || qc == '"'); - String s = null; - boolean isInEscape = false; - int c = 0; - while (c != -1) { - c = r.read(); - // Strict syntax requires that all control characters be escaped. - if (session.isStrict() && c <= 0x1F) - throw new ParseException("Unescaped control character encountered: ''0x{0}''", String.format("%04X", c)); - if (isInEscape) { - switch (c) { - case 'n': r.replace('\n'); break; - case 'r': r.replace('\r'); break; - case 't': r.replace('\t'); break; - case 'f': r.replace('\f'); break; - case 'b': r.replace('\b'); break; - case '\\': r.replace('\\'); break; - case '/': r.replace('/'); break; - case '\'': r.replace('\''); break; - case '"': r.replace('"'); break; - case 'u': { - String n = r.read(4); - try { - r.replace(Integer.parseInt(n, 16), 6); - } catch (NumberFormatException e) { - throw new ParseException(session, "Invalid Unicode escape sequence in string."); - } - break; - } - default: - throw new ParseException(session, "Invalid escape sequence in string."); - } - isInEscape = false; - } else { - if (c == '\\') { - isInEscape = true; - r.delete(); - } else if (isQuoted) { - if (c == qc) { - s = r.getMarked(1, -1); - break; - } - } else { - if (c == ',' || c == '}' || c == ']' || session.isWhitespace(c)) { - s = r.getMarked(0, -1); - r.unread(); - break; - } else if (c == -1) { - s = r.getMarked(0, 0); - break; - } - } - } - } - if (s == null) - throw new ParseException(session, "Could not find expected end character ''{0}''.", (char)qc); - - // Look for concatenated string (i.e. whitespace followed by +). - skipCommentsAndSpace(session, r); - if (r.peek() == '+') { - if (session.isStrict()) - throw new ParseException(session, "String concatenation detected."); - r.read(); // Skip past '+' - skipCommentsAndSpace(session, r); - s += parseString(session, r); - } - return session.trim(s); // End of input reached. - } - - /* - * Looks for the keywords true, false, or null. - * Throws an exception if any of these keywords are not found at the specified position. - */ - private static void parseKeyword(JsonParserSession session, String keyword, ParserReader r) throws Exception { - try { - String s = r.read(keyword.length()); - if (s.equals(keyword)) - return; - throw new ParseException(session, "Unrecognized syntax."); - } catch (IndexOutOfBoundsException e) { - throw new ParseException(session, "Unrecognized syntax."); - } - } - - /* - * Doesn't actually parse anything, but moves the position beyond any whitespace or comments. - * If positionOnNext is 'true', then the cursor will be set to the point immediately after - * the comments and whitespace. Otherwise, the cursor will be set to the last position of - * the comments and whitespace. - */ - private static void skipCommentsAndSpace(JsonParserSession session, ParserReader r) throws Exception { - int c = 0; - while ((c = r.read()) != -1) { - if (! session.isWhitespace(c)) { - if (c == '/') { - if (session.isStrict()) - throw new ParseException(session, "Javascript comment detected."); - skipComments(session, r); - } else { - r.unread(); - return; - } - } - } - } - - /* - * Doesn't actually parse anything, but moves the position beyond the construct "{wrapperAttr:" when - * the @Json.wrapperAttr() annotation is used on a class. - */ - private void skipWrapperAttrStart(JsonParserSession session, ParserReader r, String wrapperAttr) throws Exception { - - int S0=0; // Looking for outer { - int S1=1; // Looking for attrName start. - int S3=3; // Found attrName end, looking for :. - int S4=4; // Found :, looking for valStart: { [ " ' LITERAL. - - int state = S0; - String currAttr = null; - int c = 0; - while (c != -1) { - c = r.read(); - if (state == S0) { - if (c == '{') - state = S1; - } else if (state == S1) { - if (session.isCommentOrWhitespace(c)) { - skipCommentsAndSpace(session, r.unread()); - } else { - currAttr = parseFieldName(session, r.unread()); - if (! currAttr.equals(wrapperAttr)) - throw new ParseException(session, - "Expected to find wrapper attribute ''{0}'' but found attribute ''{1}''", wrapperAttr, currAttr); - state = S3; - } - } else if (state == S3) { - if (c == ':') - state = S4; - } else if (state == S4) { - if (session.isCommentOrWhitespace(c)) { - skipCommentsAndSpace(session, r.unread()); - } else { - r.unread(); - return; - } - } - } - if (state == S0) - throw new ParseException(session, "Expected '{' at beginning of JSON object."); - if (state == S1) - throw new ParseException(session, "Could not find attribute name on JSON object."); - if (state == S3) - throw new ParseException(session, "Could not find ':' following attribute name on JSON object."); - if (state == S4) - throw new ParseException(session, "Expected one of the following characters: {,[,',\",LITERAL."); - } - - /* - * Doesn't actually parse anything, but moves the position beyond the construct "}" when - * the @Json.wrapperAttr() annotation is used on a class. - */ - private static void skipWrapperAttrEnd(JsonParserSession session, ParserReader r) throws ParseException, IOException { - int c = 0; - while ((c = r.read()) != -1) { - if (! session.isWhitespace(c)) { - if (c == '/') { - if (session.isStrict()) - throw new ParseException(session, "Javascript comment detected."); - skipComments(session, r); - } else if (c == '}') { - return; - } else { - throw new ParseException(session, "Could not find '}' at the end of JSON wrapper object."); - } - } - } - } - - /* - * Doesn't actually parse anything, but when positioned at the beginning of comment, - * it will move the pointer to the last character in the comment. - */ - private static void skipComments(JsonParserSession session, ParserReader r) throws ParseException, IOException { - int c = r.read(); - // "/* */" style comments - if (c == '*') { - while (c != -1) - if ((c = r.read()) == '*') - if ((c = r.read()) == '/') - return; - // "//" style comments - } else if (c == '/') { - while (c != -1) { - c = r.read(); - if (c == -1 || c == '\n') - return; - } - } - throw new ParseException(session, "Open ended comment."); - } - - /* - * Call this method after you've finished a parsing a string to make sure that if there's any - * remainder in the input, that it consists only of whitespace and comments. - */ - private static void validateEnd(JsonParserSession session, ParserReader r) throws Exception { - skipCommentsAndSpace(session, r); - int c = r.read(); - if (c != -1 && c != ';') // var x = {...}; expressions can end with a semicolon. - throw new ParseException(session, "Remainder after parse: ''{0}''.", (char)c); - } - - - //-------------------------------------------------------------------------------- - // Entry point methods - //-------------------------------------------------------------------------------- - - @Override /* Parser */ - public JsonParserSession createSession(Object input, ObjectMap op, Method javaMethod, Object outer, Locale locale, - TimeZone timeZone, MediaType mediaType) { - return new JsonParserSession(ctx, op, input, javaMethod, outer, locale, timeZone, mediaType); - } - @Override /* Parser */ - protected <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception { - JsonParserSession s = (JsonParserSession)session; - ParserReader r = s.getReader(); - if (r == null) - return null; - T o = parseAnything(s, type, r, s.getOuter(), null); - validateEnd(s, r); - return o; - } - - @Override /* ReaderParser */ - protected <K,V> Map<K,V> doParseIntoMap(ParserSession session, Map<K,V> m, Type keyType, Type valueType) throws Exception { - JsonParserSession s = (JsonParserSession)session; - ParserReader r = s.getReader(); - m = parseIntoMap2(s, r, m, (ClassMeta<K>)s.getClassMeta(keyType), (ClassMeta<V>)s.getClassMeta(valueType), null); - validateEnd(s, r); - return m; - } - - @Override /* ReaderParser */ - protected <E> Collection<E> doParseIntoCollection(ParserSession session, Collection<E> c, Type elementType) throws Exception { - JsonParserSession s = (JsonParserSession)session; - ParserReader r = s.getReader(); - c = parseIntoCollection2(s, r, c, s.getClassMeta(elementType), null); - validateEnd(s, r); - return c; + public ReaderParserSession createSession(ParserSessionArgs args) { + return new JsonParserSession(ctx, args); } }
