You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2017/07/28 18:22:24 UTC
[09/12] incubator-juneau git commit: Allow serializer and parser
sessions to be reused.
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);
}
}