http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java index ade91df..4b6c17a 100644 --- a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java +++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSession.java @@ -16,16 +16,18 @@ import static org.apache.juneau.internal.ClassUtils.*; import static org.apache.juneau.internal.StringUtils.*; import static org.apache.juneau.serializer.SerializerContext.*; +import java.io.*; import java.lang.reflect.*; import java.text.*; import java.util.*; import org.apache.juneau.*; -import org.apache.juneau.http.*; +import org.apache.juneau.parser.*; +import org.apache.juneau.soap.*; import org.apache.juneau.transform.*; /** - * Context object that lives for the duration of a single use of {@link Serializer}. + * Serializer session that lives for the duration of a single use of {@link Serializer}. * * <p> * Used by serializers for the following purposes: @@ -41,9 +43,10 @@ import org.apache.juneau.transform.*; * </ul> * * <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 SerializerSession extends BeanSession { +public abstract class SerializerSession extends BeanSession { private final int maxDepth, initialDepth, maxIndent; private final boolean @@ -61,17 +64,19 @@ public class SerializerSession extends BeanSession { private final char quoteChar; private final UriResolver uriResolver; - /** The current indentation depth into the model. */ - public int indent; - private final Map<Object,Object> set; // Contains the current objects in the current branch of the model. private final LinkedList<StackElement> stack = new LinkedList<StackElement>(); // Contains the current objects in the current branch of the model. - private boolean isBottom; // If 'true', then we're at a leaf in the model (i.e. a String, Number, Boolean, or null). private final Method javaMethod; // Java method that invoked this serializer. + + // Writable properties + private boolean isBottom; // If 'true', then we're at a leaf in the model (i.e. a String, Number, Boolean, or null). private BeanPropertyMeta currentProperty; private ClassMeta<?> currentClass; private final SerializerListener listener; + /** The current indentation depth into the model. */ + public int indent; + /** * Create a new session using properties specified in the context. @@ -79,29 +84,26 @@ public class SerializerSession extends BeanSession { * @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. - */ - public SerializerSession(SerializerContext ctx, ObjectMap op, Method javaMethod, Locale locale, - TimeZone timeZone, MediaType mediaType, UriContext uriContext) { - super(ctx, op, locale, timeZone, mediaType); - this.javaMethod = javaMethod; + * <br>If <jk>null</jk>, defaults to {@link SerializerContext#DEFAULT}. + * @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 SerializerSession(SerializerContext ctx, SerializerSessionArgs args) { + super(ctx != null ? ctx : SerializerContext.DEFAULT, args != null ? args : SerializerSessionArgs.DEFAULT); + if (ctx == null) + ctx = SerializerContext.DEFAULT; + if (args == null) + args = SerializerSessionArgs.DEFAULT; + this.javaMethod = args.javaMethod; UriResolution uriResolution; UriRelativity uriRelativity; Class<?> listenerClass; - if (op == null || op.isEmpty()) { + ObjectMap p = getProperties(); + if (p.isEmpty()) { maxDepth = ctx.maxDepth; initialDepth = ctx.initialDepth; detectRecursions = ctx.detectRecursions; @@ -121,27 +123,27 @@ public class SerializerSession extends BeanSession { uriRelativity = ctx.uriRelativity; listenerClass = ctx.listener; } else { - maxDepth = op.getInt(SERIALIZER_maxDepth, ctx.maxDepth); - initialDepth = op.getInt(SERIALIZER_initialDepth, ctx.initialDepth); - detectRecursions = op.getBoolean(SERIALIZER_detectRecursions, ctx.detectRecursions); - ignoreRecursions = op.getBoolean(SERIALIZER_ignoreRecursions, ctx.ignoreRecursions); - useWhitespace = op.getBoolean(SERIALIZER_useWhitespace, ctx.useWhitespace); - maxIndent = op.getInt(SERIALIZER_maxIndent, ctx.maxIndent); - addBeanTypeProperties = op.getBoolean(SERIALIZER_addBeanTypeProperties, ctx.addBeanTypeProperties); - trimNulls = op.getBoolean(SERIALIZER_trimNullProperties, ctx.trimNulls); - trimEmptyCollections = op.getBoolean(SERIALIZER_trimEmptyCollections, ctx.trimEmptyCollections); - trimEmptyMaps = op.getBoolean(SERIALIZER_trimEmptyMaps, ctx.trimEmptyMaps); - trimStrings = op.getBoolean(SERIALIZER_trimStrings, ctx.trimStrings); - quoteChar = op.getString(SERIALIZER_quoteChar, ""+ctx.quoteChar).charAt(0); - sortCollections = op.getBoolean(SERIALIZER_sortCollections, ctx.sortMaps); - sortMaps = op.getBoolean(SERIALIZER_sortMaps, ctx.sortMaps); - abridged = op.getBoolean(SERIALIZER_abridged, ctx.abridged); - uriResolution = op.get(UriResolution.class, SERIALIZER_uriResolution, UriResolution.ROOT_RELATIVE); - uriRelativity = op.get(UriRelativity.class, SERIALIZER_uriRelativity, UriRelativity.RESOURCE); - listenerClass = op.get(Class.class, SERIALIZER_listener, ctx.listener); + maxDepth = p.getInt(SERIALIZER_maxDepth, ctx.maxDepth); + initialDepth = p.getInt(SERIALIZER_initialDepth, ctx.initialDepth); + detectRecursions = p.getBoolean(SERIALIZER_detectRecursions, ctx.detectRecursions); + ignoreRecursions = p.getBoolean(SERIALIZER_ignoreRecursions, ctx.ignoreRecursions); + useWhitespace = p.getBoolean(SERIALIZER_useWhitespace, ctx.useWhitespace); + maxIndent = p.getInt(SERIALIZER_maxIndent, ctx.maxIndent); + addBeanTypeProperties = p.getBoolean(SERIALIZER_addBeanTypeProperties, ctx.addBeanTypeProperties); + trimNulls = p.getBoolean(SERIALIZER_trimNullProperties, ctx.trimNulls); + trimEmptyCollections = p.getBoolean(SERIALIZER_trimEmptyCollections, ctx.trimEmptyCollections); + trimEmptyMaps = p.getBoolean(SERIALIZER_trimEmptyMaps, ctx.trimEmptyMaps); + trimStrings = p.getBoolean(SERIALIZER_trimStrings, ctx.trimStrings); + quoteChar = p.getString(SERIALIZER_quoteChar, ""+ctx.quoteChar).charAt(0); + sortCollections = p.getBoolean(SERIALIZER_sortCollections, ctx.sortMaps); + sortMaps = p.getBoolean(SERIALIZER_sortMaps, ctx.sortMaps); + abridged = p.getBoolean(SERIALIZER_abridged, ctx.abridged); + uriResolution = p.get(UriResolution.class, SERIALIZER_uriResolution, UriResolution.ROOT_RELATIVE); + uriRelativity = p.get(UriRelativity.class, SERIALIZER_uriRelativity, UriRelativity.RESOURCE); + listenerClass = p.get(Class.class, SERIALIZER_listener, ctx.listener); } - uriResolver = new UriResolver(uriResolution, uriRelativity, uriContext == null ? ctx.uriContext : uriContext); + uriResolver = new UriResolver(uriResolution, uriRelativity, args.uriContext == null ? ctx.uriContext : args.uriContext); listener = newInstance(SerializerListener.class, listenerClass); @@ -154,11 +156,102 @@ public class SerializerSession extends BeanSession { } /** + * Wraps the specified input object into a {@link ParserPipe} object so that it can be easily converted into + * a stream or reader. + * + * @param output + * The output location. + * <br>For character-based serializers, this can be any of the following types: + * <ul> + * <li>{@link Writer} + * <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream. + * <li>{@link File} - Output will be written as system-default encoded stream. + * <li>{@link StringBuilder} + * </ul> + * <br>For byte-based serializers, this can be any of the following types: + * <ul> + * <li>{@link OutputStream} + * <li>{@link File} + * </ul> + * @return + * A new {@link ParserPipe} wrapper around the specified input object. + */ + protected SerializerPipe createPipe(Object output) { + return new SerializerPipe(output); + } + + + //-------------------------------------------------------------------------------- + // Abstract methods + //-------------------------------------------------------------------------------- + + /** + * Serializes a POJO to the specified output stream or writer. + * + * <p> + * This method should NOT close the context object. + * + * @param pipe Where to send the output from the serializer. + * @param o The object to serialize. + * @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. + */ + protected abstract void doSerialize(SerializerPipe pipe, Object o) throws Exception; + + /** + * Shortcut method for serializing objects directly to either a <code>String</code> or <code><jk>byte</jk>[]</code> + * depending on the serializer type. + * + * @param o The object to serialize. + * @return + * The serialized object. + * <br>Character-based serializers will return a <code>String</code> + * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> + * @throws SerializeException If a problem occurred trying to convert the output. + */ + public abstract Object serialize(Object o) throws SerializeException; + + /** + * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. + * + * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. + */ + public abstract boolean isWriterSerializer(); + + + //-------------------------------------------------------------------------------- + // Other methods + //-------------------------------------------------------------------------------- + + /** + * Serialize the specified object using the specified session. + * + * @param out Where to send the output from the serializer. + * @param o The object to serialize. + * @throws SerializeException If a problem occurred trying to convert the output. + */ + public final void serialize(Object out, Object o) throws SerializeException { + SerializerPipe pipe = createPipe(out); + try { + doSerialize(pipe, o); + } catch (SerializeException e) { + throw e; + } catch (StackOverflowError e) { + throw new SerializeException(this, + "Stack overflow occurred. This can occur when trying to serialize models containing loops. It's recommended you use the SerializerContext.SERIALIZER_detectRecursions setting to help locate the loop.").initCause(e); + } catch (Exception e) { + throw new SerializeException(this, e); + } finally { + pipe.close(); + close(); + } + } + + /** * Sets the current bean property being serialized for proper error messages. * * @param currentProperty The current property being serialized. */ - public void setCurrentProperty(BeanPropertyMeta currentProperty) { + protected final void setCurrentProperty(BeanPropertyMeta currentProperty) { this.currentProperty = currentProperty; } @@ -167,7 +260,7 @@ public class SerializerSession extends BeanSession { * * @param currentClass The current class being serialized. */ - public void setCurrentClass(ClassMeta<?> currentClass) { + protected final void setCurrentClass(ClassMeta<?> currentClass) { this.currentClass = currentClass; } @@ -180,7 +273,7 @@ public class SerializerSession extends BeanSession { * * @return The Java method that invoked this serializer. */ - public final Method getJavaMethod() { + protected final Method getJavaMethod() { return javaMethod; } @@ -189,7 +282,7 @@ public class SerializerSession extends BeanSession { * * @return The URI resolver. */ - public final UriResolver getUriResolver() { + protected final UriResolver getUriResolver() { return uriResolver; } @@ -198,7 +291,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_maxDepth} setting value for this session. */ - public final int getMaxDepth() { + protected final int getMaxDepth() { return maxDepth; } @@ -207,7 +300,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_initialDepth} setting value for this session. */ - public final int getInitialDepth() { + protected final int getInitialDepth() { return initialDepth; } @@ -216,7 +309,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_detectRecursions} setting value for this session. */ - public final boolean isDetectRecursions() { + protected final boolean isDetectRecursions() { return detectRecursions; } @@ -225,7 +318,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_ignoreRecursions} setting value for this session. */ - public final boolean isIgnoreRecursions() { + protected final boolean isIgnoreRecursions() { return ignoreRecursions; } @@ -234,7 +327,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_useWhitespace} setting value for this session. */ - public final boolean isUseWhitespace() { + protected final boolean isUseWhitespace() { return useWhitespace; } @@ -243,7 +336,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_maxIndent} setting value for this session. */ - public final int getMaxIndent() { + protected final int getMaxIndent() { return maxIndent; } @@ -252,7 +345,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_addBeanTypeProperties} setting value for this session. */ - public boolean isAddBeanTypeProperties() { + protected boolean isAddBeanTypeProperties() { return addBeanTypeProperties; } @@ -261,7 +354,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_quoteChar} setting value for this session. */ - public final char getQuoteChar() { + protected final char getQuoteChar() { return quoteChar; } @@ -270,7 +363,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_trimNullProperties} setting value for this session. */ - public final boolean isTrimNulls() { + protected final boolean isTrimNulls() { return trimNulls; } @@ -279,7 +372,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_trimEmptyCollections} setting value for this session. */ - public final boolean isTrimEmptyCollections() { + protected final boolean isTrimEmptyCollections() { return trimEmptyCollections; } @@ -288,7 +381,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_trimEmptyMaps} setting value for this session. */ - public final boolean isTrimEmptyMaps() { + protected final boolean isTrimEmptyMaps() { return trimEmptyMaps; } @@ -297,7 +390,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_trimStrings} setting value for this session. */ - public final boolean isTrimStrings() { + protected final boolean isTrimStrings() { return trimStrings; } @@ -306,7 +399,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_sortCollections} setting value for this session. */ - public final boolean isSortCollections() { + protected final boolean isSortCollections() { return sortCollections; } @@ -315,7 +408,7 @@ public class SerializerSession extends BeanSession { * * @return The {@link SerializerContext#SERIALIZER_sortMaps} setting value for this session. */ - public final boolean isSortMaps() { + protected final boolean isSortMaps() { return sortMaps; } @@ -330,7 +423,7 @@ public class SerializerSession extends BeanSession { * once (since they can be expensive). * @throws SerializeException If recursion occurred. */ - public ClassMeta<?> push(String attrName, Object o, ClassMeta<?> eType) throws SerializeException { + protected final ClassMeta<?> push(String attrName, Object o, ClassMeta<?> eType) throws SerializeException { indent++; isBottom = true; if (o == null) @@ -363,7 +456,7 @@ public class SerializerSession extends BeanSession { * @return <jk>true</jk> if recursion detected. * @throws SerializeException If recursion occurred. */ - public boolean willRecurse(String attrName, Object o, ClassMeta<?> cm) throws SerializeException { + protected final boolean willRecurse(String attrName, Object o, ClassMeta<?> cm) throws SerializeException { if (! (detectRecursions || isDebug())) return false; if (! set.containsKey(o)) @@ -378,7 +471,7 @@ public class SerializerSession extends BeanSession { /** * Pop an object off the stack. */ - public void pop() { + protected final void pop() { indent--; if ((detectRecursions || isDebug()) && ! isBottom) { Object o = stack.removeLast().o; @@ -391,21 +484,12 @@ public class SerializerSession extends BeanSession { } /** - * The current indentation depth. - * - * @return The current indentation depth. - */ - public int getIndent() { - return indent; - } - - /** * Specialized warning when an exception is thrown while executing a bean getter. * * @param p The bean map entry representing the bean property. * @param t The throwable that the bean getter threw. */ - public void onBeanGetterException(BeanPropertyMeta p, Throwable t) { + protected final void onBeanGetterException(BeanPropertyMeta p, Throwable t) { if (listener != null) listener.onBeanGetterException(this, t, p); String prefix = (isDebug() ? getStack(false) + ": " : ""); @@ -420,7 +504,7 @@ public class SerializerSession extends BeanSession { * @param msg The warning message. * @param args Optional {@link MessageFormat}-style arguments. */ - public final void onError(Throwable t, String msg, Object... args) { + protected final void onError(Throwable t, String msg, Object... args) { if (listener != null) listener.onError(this, t, format(msg, args)); super.addWarning(msg, args); @@ -432,7 +516,7 @@ public class SerializerSession extends BeanSession { * @param o The input string to trim. * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>. */ - public final String trim(Object o) { + protected final String trim(Object o) { if (o == null) return null; String s = o.toString(); @@ -450,7 +534,7 @@ public class SerializerSession extends BeanSession { * @throws SerializeException If a problem occurred trying to convert the output. */ @SuppressWarnings({ "rawtypes", "unchecked" }) - public final Object generalize(Object o, ClassMeta<?> type) throws SerializeException { + protected final Object generalize(Object o, ClassMeta<?> type) throws SerializeException { if (o == null) return null; PojoSwap f = (type == null || type.isObject() ? getClassMeta(o.getClass()).getPojoSwap() : type.getPojoSwap()); @@ -468,7 +552,7 @@ public class SerializerSession extends BeanSession { * @return <jk>true</jk> if the specified value should not be serialized. * @throws SerializeException If recursion occurred. */ - public final boolean canIgnoreValue(ClassMeta<?> cm, String attrName, Object value) throws SerializeException { + protected final boolean canIgnoreValue(ClassMeta<?> cm, String attrName, Object value) throws SerializeException { if (trimNulls && value == null) return true; @@ -509,7 +593,7 @@ public class SerializerSession extends BeanSession { * @param m The map being sorted. * @return A new sorted {@link TreeMap}. */ - public final <K,V> Map<K,V> sort(Map<K,V> m) { + protected final <K,V> Map<K,V> sort(Map<K,V> m) { if (sortMaps && m != null && (! m.isEmpty()) && m.keySet().iterator().next() instanceof Comparable<?>) return new TreeMap<K,V>(m); return m; @@ -521,13 +605,40 @@ public class SerializerSession extends BeanSession { * @param c The collection being sorted. * @return A new sorted {@link TreeSet}. */ - public final <E> Collection<E> sort(Collection<E> c) { + protected final <E> Collection<E> sort(Collection<E> c) { if (sortCollections && c != null && (! c.isEmpty()) && c.iterator().next() instanceof Comparable<?>) return new TreeSet<E>(c); return c; } /** + * Converts the contents of the specified object array to a list. + * + * <p> + * Works on both object and primitive arrays. + * + * <p> + * In the case of multi-dimensional arrays, the outgoing list will contain elements of type n-1 dimension. + * i.e. if {@code type} is <code><jk>int</jk>[][]</code> then {@code list} will have entries of type + * <code><jk>int</jk>[]</code>. + * + * @param type The type of array. + * @param array The array being converted. + * @return The array as a list. + */ + protected static final List<Object> toList(Class<?> type, Object array) { + Class<?> componentType = type.getComponentType(); + if (componentType.isPrimitive()) { + int l = Array.getLength(array); + List<Object> list = new ArrayList<Object>(l); + for (int i = 0; i < l; i++) + list.add(Array.get(array, i)); + return list; + } + return Arrays.asList((Object[])array); + } + + /** * Converts a String to an absolute URI based on the {@link UriContext} on this session. * * @param uri @@ -554,7 +665,7 @@ public class SerializerSession extends BeanSession { * </ul> * @return The resolved URI. */ - public String resolveUri(Object uri) { + public final String resolveUri(Object uri) { return uriResolver.resolve(uri); } @@ -592,7 +703,7 @@ public class SerializerSession extends BeanSession { * @param uri The URI to relativize. * @return The relativized URI. */ - public String relativizeUri(Object relativeTo, Object uri) { + protected final String relativizeUri(Object relativeTo, Object uri) { return uriResolver.relativize(relativeTo, uri); } @@ -602,7 +713,7 @@ public class SerializerSession extends BeanSession { * @param o The object to convert to a <code>String</code>. * @return The */ - public String toString(Object o) { + public final String toString(Object o) { if (o == null) return null; if (o.getClass() == Class.class) @@ -613,11 +724,6 @@ public class SerializerSession extends BeanSession { return s; } - @Override - public boolean close() { - return super.close(); - } - private static class StackElement { private int depth; private String name; @@ -663,7 +769,7 @@ public class SerializerSession extends BeanSession { * * @return A map, typically containing something like <code>{line:123,column:456,currentProperty:"foobar"}</code> */ - public Map<String,Object> getLastLocation() { + protected final Map<String,Object> getLastLocation() { Map<String,Object> m = new LinkedHashMap<String,Object>(); if (currentClass != null) m.put("currentClass", currentClass); @@ -681,7 +787,7 @@ public class SerializerSession extends BeanSession { * @param typeName The type name of the bean. * @return A new bean property value. */ - public BeanPropertyValue createBeanTypeNameProperty(BeanMap<?> m, String typeName) { + protected final static BeanPropertyValue createBeanTypeNameProperty(BeanMap<?> m, String typeName) { BeanMeta<?> bm = m.getMeta(); return new BeanPropertyValue(bm.getTypeProperty(), bm.getTypeProperty().getName(), typeName, null); } @@ -694,7 +800,7 @@ public class SerializerSession extends BeanSession { * @param pMeta The current bean property being serialized. * @return The bean dictionary name, or <jk>null</jk> if a name could not be found. */ - public String getBeanTypeName(ClassMeta<?> eType, ClassMeta<?> aType, BeanPropertyMeta pMeta) { + protected final String getBeanTypeName(ClassMeta<?> eType, ClassMeta<?> aType, BeanPropertyMeta pMeta) { if (eType == aType) return null; @@ -748,7 +854,25 @@ public class SerializerSession extends BeanSession { * @param o The object to get the expected type on. * @return The expected type. */ - public ClassMeta<?> getExpectedRootType(Object o) { + protected final ClassMeta<?> getExpectedRootType(Object o) { return abridged ? getClassMetaForObject(o) : object(); } + + /** + * Optional method that specifies HTTP request headers for this serializer. + * + * <p> + * For example, {@link SoapXmlSerializer} needs to set a <code>SOAPAction</code> header. + * + * <p> + * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server + * or client). + * + * @return + * The HTTP headers to set on HTTP requests. + * Never <jk>null</jk>. + */ + public Map<String,String> getResponseHeaders() { + return Collections.emptyMap(); + } }
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSessionArgs.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSessionArgs.java b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSessionArgs.java new file mode 100644 index 0000000..68077b4 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/serializer/SerializerSessionArgs.java @@ -0,0 +1,65 @@ +// *************************************************************************************************************************** +// * 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.serializer; + +import java.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.http.*; + +/** + * Runtime arguments common to all serializer sessions. + * + * <p> + * This object specifies information such as session locale or URI context. + */ +public final class SerializerSessionArgs extends BeanSessionArgs { + + /** + * Default session arguments. + */ + public static final SerializerSessionArgs DEFAULT = new SerializerSessionArgs(ObjectMap.EMPTY_MAP, null, null, null, null, null); + + final Method javaMethod; + final UriContext uriContext; + + /** + * Constructor. + * + * @param properties + * Session-level properties. + * These override context-level properties. + * Can be <jk>null</jk>. + * @param javaMethod + * The java method that called this serializer, usually the method in a REST servlet. + * Can be <jk>null</jk>. + * @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>). + * Can be <jk>null</jk>. + * @param uriContext + * The URI context. + * Identifies the current request URI used for resolution of URIs to absolute or root-relative form. + */ + public SerializerSessionArgs(ObjectMap properties, Method javaMethod, Locale locale, TimeZone timeZone, MediaType mediaType, UriContext uriContext) { + super(properties, locale, timeZone, mediaType); + this.javaMethod = javaMethod; + this.uriContext = uriContext; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/serializer/WriterSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/WriterSerializer.java b/juneau-core/src/main/java/org/apache/juneau/serializer/WriterSerializer.java index fc782d7..11d2138 100644 --- a/juneau-core/src/main/java/org/apache/juneau/serializer/WriterSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/serializer/WriterSerializer.java @@ -12,29 +12,13 @@ // *************************************************************************************************************************** package org.apache.juneau.serializer; -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.utils.*; /** * Subclass of {@link Serializer} for character-based serializers. * - * <h5 class='section'>Description:</h5> - * - * This class is typically the parent class of all character-based serializers. - * It has 2 abstract methods to implement... - * <ul class='spaced-list'> - * <li> - * {@link #createSession(ObjectMap, Method, Locale, TimeZone, MediaType, UriContext)} - * <li> - * {@link #doSerialize(SerializerSession, SerializerOutput, Object)} - * </ul> - * * <h6 class='topic'>@Produces annotation</h6> * * The media types that this serializer can produce is specified through the {@link Produces @Produces} annotation. @@ -54,16 +38,24 @@ public abstract class WriterSerializer extends Serializer { super(propertyStore); } - @Override /* Serializer */ - public boolean isWriterSerializer() { - return true; - } + + //-------------------------------------------------------------------------------- + // Abstract methods + //-------------------------------------------------------------------------------- + + @Override /* SerializerSession */ + public abstract WriterSerializerSession createSession(SerializerSessionArgs args); //-------------------------------------------------------------------------------- // Other methods //-------------------------------------------------------------------------------- + @Override /* Serializer */ + public final boolean isWriterSerializer() { + return true; + } + /** * Convenience method for serializing an object to a <code>String</code>. * @@ -71,12 +63,9 @@ public abstract class WriterSerializer extends Serializer { * @return The output serialized to a string. * @throws SerializeException If a problem occurred trying to convert the output. */ - @Override + @Override /* Serializer */ public final String serialize(Object o) throws SerializeException { - StringWriter w = new StringWriter(); - SerializerOutput out = new SerializerOutput(w); - serialize(createSession(), out, o); - return w.toString(); + return createSession(null).serialize(o); } /** @@ -90,10 +79,7 @@ public abstract class WriterSerializer extends Serializer { */ public final String toString(Object o) { try { - StringWriter w = new StringWriter(); - SerializerOutput out = new SerializerOutput(w); - serialize(createSession(), out, o); - return w.toString(); + return serialize(o); } catch (Exception e) { throw new RuntimeException(e); } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/serializer/WriterSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/serializer/WriterSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/serializer/WriterSerializerSession.java new file mode 100644 index 0000000..b91085f --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/serializer/WriterSerializerSession.java @@ -0,0 +1,80 @@ +// *************************************************************************************************************************** +// * 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.serializer; + +import java.io.*; + +/** + * Subclass of {@link SerializerSession} for character-based serializers. + * + * <h5 class='section'>Description:</h5> + * + * This class is typically the parent class of all character-based serializers. + * <br>It has 1 abstract method to implement... + * <ul class='spaced-list'> + * <li> + * {@link #doSerialize(SerializerPipe, Object)} + * </ul> + * + * <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 abstract class WriterSerializerSession extends SerializerSession { + + /** + * 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 WriterSerializerSession(SerializerContext ctx, SerializerSessionArgs args) { + super(ctx, args); + } + + /** + * Constructor for sessions that don't require context. + * + * @param args + * Runtime session arguments. + */ + protected WriterSerializerSession(SerializerSessionArgs args) { + this(null, args); + } + + @Override /* SerializerSession */ + public final boolean isWriterSerializer() { + return true; + } + + /** + * Convenience method for serializing an object to a <code>String</code>. + * + * @param o The object to serialize. + * @return The output serialized to a string. + * @throws SerializeException If a problem occurred trying to convert the output. + */ + @Override /* SerializerSession */ + public final String serialize(Object o) throws SerializeException { + StringWriter w = new StringWriter(); + serialize(w, o); + return w.toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializer.java b/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializer.java index c9053a4..77f722b 100644 --- a/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializer.java +++ b/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializer.java @@ -12,8 +12,6 @@ // *************************************************************************************************************************** package org.apache.juneau.soap; -import static org.apache.juneau.soap.SoapXmlSerializerContext.*; - import org.apache.juneau.*; import org.apache.juneau.annotation.*; import org.apache.juneau.serializer.*; @@ -44,6 +42,8 @@ import org.apache.juneau.xml.*; @Produces(value="text/xml+soap",contentType="text/xml") public final class SoapXmlSerializer extends XmlSerializer { + private final SoapXmlSerializerContext ctx; + /** * Constructor. * @@ -51,35 +51,11 @@ public final class SoapXmlSerializer extends XmlSerializer { */ public SoapXmlSerializer(PropertyStore propertyStore) { super(propertyStore); - } - - - //-------------------------------------------------------------------------------- - // Overridden methods - //-------------------------------------------------------------------------------- - - @Override /* Serializer */ - protected void doSerialize(SerializerSession session, SerializerOutput out, Object o) throws Exception { - XmlSerializerSession s = (XmlSerializerSession)session; - XmlWriter w = s.getXmlWriter(out); - w.append("<?xml") - .attr("version", "1.0") - .attr("encoding", "UTF-8") - .appendln("?>"); - w.oTag("soap", "Envelope") - .attr("xmlns", "soap", s.getProperty(SOAPXML_SOAPAction, "http://www.w3.org/2003/05/soap-envelope")) - .appendln(">"); - w.sTag(1, "soap", "Body").nl(1); - s.indent += 2; - w.flush(); - super.doSerialize(s, out, o); - w.ie(1).eTag("soap", "Body").nl(1); - w.eTag("soap", "Envelope").nl(0); + this.ctx = createContext(SoapXmlSerializerContext.class); } @Override /* Serializer */ - public ObjectMap getResponseHeaders(ObjectMap properties) { - return new ObjectMap(super.getResponseHeaders(properties)) - .append("SOAPAction", properties.getString(SOAPXML_SOAPAction, "http://www.w3.org/2003/05/soap-envelope")); + public WriterSerializerSession createSession(SerializerSessionArgs args) { + return new SoapXmlSerializerSession(ctx, args); } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerContext.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerContext.java b/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerContext.java index e6eb5d6..77f64cc 100644 --- a/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerContext.java +++ b/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerContext.java @@ -12,6 +12,8 @@ // *************************************************************************************************************************** package org.apache.juneau.soap; +import org.apache.juneau.*; +import org.apache.juneau.xml.*; /** * Properties associated with the {@link SoapXmlSerializer} class. @@ -40,7 +42,19 @@ package org.apache.juneau.soap; * </li> * </ul> */ -public final class SoapXmlSerializerContext { +public final class SoapXmlSerializerContext extends XmlSerializerContext { + + /** + * Constructor + * + * <p> + * Typically only called from {@link PropertyStore#getContext(Class)}. + * + * @param ps The property store that created this context. + */ + public SoapXmlSerializerContext(PropertyStore ps) { + super(ps); + } /** * <b>Configuration property:</b> The <code>SOAPAction</code> HTTP header value to set on responses. http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerSession.java b/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerSession.java new file mode 100644 index 0000000..3960607 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/soap/SoapXmlSerializerSession.java @@ -0,0 +1,75 @@ +// *************************************************************************************************************************** +// * 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.soap; + +import static org.apache.juneau.soap.SoapXmlSerializerContext.*; + +import java.util.*; + +import org.apache.juneau.serializer.*; +import org.apache.juneau.utils.*; +import org.apache.juneau.xml.*; + +/** + * Session object that lives for the duration of a single use of {@link SoapXmlSerializer}. + * + * <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 SoapXmlSerializerSession extends XmlSerializerSession { + + /** + * 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}. + */ + public SoapXmlSerializerSession(SoapXmlSerializerContext ctx, SerializerSessionArgs args) { + super(ctx, args); + } + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* SerializerSession */ + protected void doSerialize(SerializerPipe out, Object o) throws Exception { + XmlWriter w = getXmlWriter(out); + w.append("<?xml") + .attr("version", "1.0") + .attr("encoding", "UTF-8") + .appendln("?>"); + w.oTag("soap", "Envelope") + .attr("xmlns", "soap", getProperty(SOAPXML_SOAPAction, "http://www.w3.org/2003/05/soap-envelope")) + .appendln(">"); + w.sTag(1, "soap", "Body").nl(1); + indent += 2; + w.flush(); + super.doSerialize(out, o); + w.ie(1).eTag("soap", "Body").nl(1); + w.eTag("soap", "Envelope").nl(0); + } + + @Override /* Serializer */ + public Map<String,String> getResponseHeaders() { + return new AMap<String,String>().append("SOAPAction", getProperty(SOAPXML_SOAPAction, "http://www.w3.org/2003/05/soap-envelope")); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/2a37f310/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java b/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java index cc8a19c..7410a7d 100644 --- a/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java +++ b/juneau-core/src/main/java/org/apache/juneau/uon/UonParser.java @@ -13,17 +13,10 @@ package org.apache.juneau.uon; import static org.apache.juneau.uon.UonParserContext.*; -import static org.apache.juneau.internal.StringUtils.*; - -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 UON (a notation for URL-encoded query parameter values) text into POJO models. @@ -45,7 +38,6 @@ import org.apache.juneau.transform.*; * <li>{@link BeanContext} * </ul> */ -@SuppressWarnings({ "rawtypes", "unchecked" }) @Consumes("text/uon") public class UonParser extends ReaderParser { @@ -55,11 +47,6 @@ public class UonParser extends ReaderParser { /** Reusable instance of {@link UonParser} with decodeChars set to true. */ public static final UonParser DEFAULT_DECODING = new UonParser.Decoding(PropertyStore.create()); - // Characters that need to be preceeded with an escape character. - private static final AsciiSet escapedChars = new AsciiSet("~'\u0001\u0002"); - - private static final char AMP='\u0001', EQ='\u0002'; // Flags set in reader to denote & and = characters. - /** Default parser, decoding. */ public static class Decoding extends UonParser { @@ -70,12 +57,7 @@ public class UonParser extends ReaderParser { * @param propertyStore The property store containing all the settings for this object. */ public Decoding(PropertyStore propertyStore) { - super(propertyStore); - } - - @Override /* CoreObject */ - protected ObjectMap getOverrideProperties() { - return super.getOverrideProperties().append(UON_decodeChars, true); + super(propertyStore.copy().append(UON_decodeChars, true)); } } @@ -98,696 +80,16 @@ public class UonParser extends ReaderParser { } /** - * Workhorse method. - * - * @param session The parser context for this parse. - * @param eType The class type being parsed, or <jk>null</jk> if unknown. - * @param r The reader being parsed. - * @param outer The outer object (for constructing nested inner classes). - * @param isUrlParamValue - * If <jk>true</jk>, then we're parsing a top-level URL-encoded value which is treated a bit different than the - * default case. - * @param pMeta The current bean property being parsed. - * @return The parsed object. - * @throws Exception - */ - protected <T> T parseAnything(UonParserSession session, ClassMeta<T> eType, ParserReader r, Object outer, - boolean isUrlParamValue, BeanPropertyMeta pMeta) throws Exception { - - if (eType == null) - eType = (ClassMeta<T>)object(); - PojoSwap<T,Object> transform = (PojoSwap<T,Object>)eType.getPojoSwap(); - ClassMeta<?> sType = eType.getSerializedClassMeta(); - - Object o = null; - - int c = r.peekSkipWs(); - - if (c == -1 || c == AMP) { - // If parameter is blank and it's an array or collection, return an empty list. - if (sType.isCollectionOrArray()) - o = sType.newInstance(); - else if (sType.isString() || sType.isObject()) - o = ""; - else if (sType.isPrimitive()) - o = sType.getPrimitiveDefault(); - // Otherwise, leave null. - } else if (sType.isVoid()) { - String s = parseString(session, r, isUrlParamValue); - if (s != null) - throw new ParseException(session, "Expected ''null'' for void value, but was ''{0}''.", s); - } else if (sType.isObject()) { - if (c == '(') { - ObjectMap m = new ObjectMap(session); - parseIntoMap(session, r, m, string(), object(), pMeta); - o = session.cast(m, pMeta, eType); - } else if (c == '@') { - Collection l = new ObjectList(session); - o = parseIntoCollection(session, r, l, sType, isUrlParamValue, pMeta); - } else { - String s = parseString(session, r, isUrlParamValue); - if (c != '\'') { - if ("true".equals(s) || "false".equals(s)) - o = Boolean.valueOf(s); - else if (! "null".equals(s)) { - if (isNumeric(s)) - o = StringUtils.parseNumber(s, Number.class); - else - o = s; - } - } else { - o = s; - } - } - } else if (sType.isBoolean()) { - o = parseBoolean(session, r); - } else if (sType.isCharSequence()) { - o = parseString(session, r, isUrlParamValue); - } else if (sType.isChar()) { - String s = parseString(session, r, isUrlParamValue); - o = s == null ? null : s.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 = parseIntoMap(session, r, m, sType.getKeyType(), sType.getValueType(), pMeta); - } else if (sType.isCollection()) { - if (c == '(') { - ObjectMap m = new ObjectMap(session); - parseIntoMap(session, r, m, string(), object(), pMeta); - // Handle case where it's a collection, but serialized as a map with a _type or _value key. - if (m.containsKey(session.getBeanTypePropertyName(sType))) - o = session.cast(m, pMeta, eType); - // Handle case where it's a collection, but only a single value was specified. - else { - Collection l = ( - sType.canCreateNewInstance(outer) - ? (Collection)sType.newInstance(outer) - : new ObjectList(session) - ); - l.add(m.cast(sType.getElementType())); - o = l; - } - } else { - Collection l = ( - sType.canCreateNewInstance(outer) - ? (Collection)sType.newInstance(outer) - : new ObjectList(session) - ); - o = parseIntoCollection(session, r, l, sType, isUrlParamValue, pMeta); - } - } else if (sType.canCreateNewBean(outer)) { - BeanMap m = session.newBeanMap(outer, sType.getInnerClass()); - m = parseIntoBeanMap(session, r, m); - o = m == null ? null : m.getBean(); - } else if (sType.canCreateNewInstanceFromString(outer)) { - String s = parseString(session, r, isUrlParamValue); - if (s != null) - o = sType.newInstanceFromString(outer, s); - } else if (sType.canCreateNewInstanceFromNumber(outer)) { - o = sType.newInstanceFromNumber(session, outer, parseNumber(session, r, sType.getNewInstanceFromNumberClass())); - } else if (sType.isArray() || sType.isArgs()) { - if (c == '(') { - ObjectMap m = new ObjectMap(session); - parseIntoMap(session, r, m, string(), object(), pMeta); - // Handle case where it's an array, but serialized as a map with a _type or _value key. - if (m.containsKey(session.getBeanTypePropertyName(sType))) - o = session.cast(m, pMeta, eType); - // Handle case where it's an array, but only a single value was specified. - else { - ArrayList l = new ArrayList(1); - l.add(m.cast(sType.getElementType())); - o = session.toArray(sType, l); - } - } else { - ArrayList l = (ArrayList)parseIntoCollection(session, r, new ArrayList(), sType, isUrlParamValue, pMeta); - o = session.toArray(sType, l); - } - } else if (c == '(') { - // It could be a non-bean with _type attribute. - ObjectMap m = new ObjectMap(session); - parseIntoMap(session, r, m, string(), object(), pMeta); - if (m.containsKey(session.getBeanTypePropertyName(sType))) - o = session.cast(m, pMeta, eType); - else - throw new ParseException(session, "Class ''{0}'' could not be instantiated. Reason: ''{1}''", - sType.getInnerClass().getName(), sType.getNotABeanReason()); - } else if (c == 'n') { - r.read(); - parseNull(session, r); - } else { - throw new ParseException(session, "Class ''{0}'' could not be instantiated. Reason: ''{1}''", - sType.getInnerClass().getName(), sType.getNotABeanReason()); - } - - if (o == null && sType.isPrimitive()) - o = sType.getPrimitiveDefault(); - if (transform != null && o != null) - o = transform.unswap(session, o, eType); - - if (outer != null) - setParent(eType, o, outer); - - return (T)o; - } - - private <K,V> Map<K,V> parseIntoMap(UonParserSession 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 c = r.read(); - if (c == -1 || c == AMP) - return null; - if (c == 'n') - return (Map<K,V>)parseNull(session, r); - if (c != '(') - throw new ParseException(session, "Expected '(' at beginning of object."); - - final int S1=1; // Looking for attrName start. - final int S2=2; // Found attrName end, looking for =. - final int S3=3; // Found =, looking for valStart. - final int S4=4; // Looking for , or ) - boolean isInEscape = false; - - int state = S1; - K currAttr = null; - while (c != -1 && c != AMP) { - c = r.read(); - if (! isInEscape) { - if (state == S1) { - if (c == ')') - return m; - if (Character.isWhitespace(c)) - skipSpace(r); - else { - r.unread(); - Object attr = parseAttr(session, r, session.isDecodeChars()); - currAttr = attr == null ? null : convertAttrToType(session, m, session.trim(attr.toString()), keyType); - state = S2; - c = 0; // Avoid isInEscape if c was '\' - } - } else if (state == S2) { - if (c == EQ || c == '=') - state = S3; - else if (c == -1 || c == ',' || c == ')' || c == AMP) { - if (currAttr == null) { - // Value was '%00' - r.unread(); - return null; - } - m.put(currAttr, null); - if (c == ')' || c == -1 || c == AMP) - return m; - state = S1; - } - } else if (state == S3) { - if (c == -1 || c == ',' || c == ')' || c == AMP) { - V value = convertAttrToType(session, m, "", valueType); - m.put(currAttr, value); - if (c == -1 || c == ')' || c == AMP) - return m; - state = S1; - } else { - V value = parseAnything(session, valueType, r.unread(), m, false, pMeta); - setName(valueType, value, currAttr); - m.put(currAttr, value); - state = S4; - c = 0; // Avoid isInEscape if c was '\' - } - } else if (state == S4) { - if (c == ',') - state = S1; - else if (c == ')' || c == -1 || c == AMP) { - return m; - } - } - } - isInEscape = isInEscape(c, r, isInEscape); - } - if (state == S1) - throw new ParseException(session, "Could not find attribute name on object."); - if (state == S2) - throw new ParseException(session, "Could not find '=' following attribute name on object."); - if (state == S3) - throw new ParseException(session, "Dangling '=' found in object entry"); - if (state == S4) - throw new ParseException(session, "Could not find ')' marking end of object."); - - return null; // Unreachable. - } - - private <E> Collection<E> parseIntoCollection(UonParserSession session, ParserReader r, Collection<E> l, - ClassMeta<E> type, boolean isUrlParamValue, BeanPropertyMeta pMeta) throws Exception { - - int c = r.readSkipWs(); - if (c == -1 || c == AMP) - return null; - if (c == 'n') - return (Collection<E>)parseNull(session, r); - - int argIndex = 0; - - // If we're parsing a top-level parameter, we're allowed to have comma-delimited lists outside parenthesis (e.g. "&foo=1,2,3&bar=a,b,c") - // This is not allowed at lower levels since we use comma's as end delimiters. - boolean isInParens = (c == '@'); - if (! isInParens) { - if (isUrlParamValue) - r.unread(); - else - throw new ParseException(session, "Could not find '(' marking beginning of collection."); - } else { - r.read(); - } - - if (isInParens) { - final int S1=1; // Looking for starting of first entry. - final int S2=2; // Looking for starting of subsequent entries. - final int S3=3; // Looking for , or ) after first entry. - - int state = S1; - while (c != -1 && c != AMP) { - c = r.read(); - if (state == S1 || state == S2) { - if (c == ')') { - if (state == S2) { - l.add((E)parseAnything(session, type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), - r.unread(), l, false, pMeta)); - r.read(); - } - return l; - } else if (Character.isWhitespace(c)) { - skipSpace(r); - } else { - l.add((E)parseAnything(session, type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), - r.unread(), l, false, pMeta)); - state = S3; - } - } else if (state == S3) { - if (c == ',') { - state = S2; - } else if (c == ')') { - return l; - } - } - } - if (state == S1 || state == S2) - throw new ParseException(session, "Could not find start of entry in array."); - if (state == S3) - throw new ParseException(session, "Could not find end of entry in array."); - - } else { - final int S1=1; // Looking for starting of entry. - final int S2=2; // Looking for , or & or END after first entry. - - int state = S1; - while (c != -1 && c != AMP) { - c = r.read(); - if (state == S1) { - if (Character.isWhitespace(c)) { - skipSpace(r); - } else { - l.add((E)parseAnything(session, type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), - r.unread(), l, false, pMeta)); - state = S2; - } - } else if (state == S2) { - if (c == ',') { - state = S1; - } else if (Character.isWhitespace(c)) { - skipSpace(r); - } else if (c == AMP || c == -1) { - r.unread(); - return l; - } - } - } - } - - return null; // Unreachable. - } - - private <T> BeanMap<T> parseIntoBeanMap(UonParserSession session, ParserReader r, BeanMap<T> m) throws Exception { - - int c = r.readSkipWs(); - if (c == -1 || c == AMP) - return null; - if (c == 'n') - return (BeanMap<T>)parseNull(session, r); - if (c != '(') - throw new ParseException(session, "Expected '(' at beginning of object."); - - final int S1=1; // Looking for attrName start. - final int S2=2; // Found attrName end, looking for =. - final int S3=3; // Found =, looking for valStart. - final int S4=4; // Looking for , or } - boolean isInEscape = false; - - int state = S1; - String currAttr = ""; - int currAttrLine = -1, currAttrCol = -1; - while (c != -1 && c != AMP) { - c = r.read(); - if (! isInEscape) { - if (state == S1) { - if (c == ')' || c == -1 || c == AMP) { - return m; - } - if (Character.isWhitespace(c)) - skipSpace(r); - else { - r.unread(); - currAttrLine= r.getLine(); - currAttrCol = r.getColumn(); - currAttr = parseAttrName(session, r, session.isDecodeChars()); - if (currAttr == null) // Value was '%00' - return null; - state = S2; - } - } else if (state == S2) { - if (c == EQ || c == '=') - state = S3; - else if (c == -1 || c == ',' || c == ')' || c == AMP) { - m.put(currAttr, null); - if (c == ')' || c == -1 || c == AMP) - return m; - state = S1; - } - } else if (state == S3) { - if (c == -1 || c == ',' || c == ')' || c == AMP) { - if (! currAttr.equals(session.getBeanTypePropertyName(m.getClassMeta()))) { - BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr); - if (pMeta == null) { - session.onUnknownProperty(currAttr, m, currAttrLine, currAttrCol); - } else { - Object value = session.convertToType("", pMeta.getClassMeta()); - pMeta.set(m, currAttr, value); - } - } - if (c == -1 || c == ')' || c == AMP) - return m; - state = S1; - } else { - if (! currAttr.equals(session.getBeanTypePropertyName(m.getClassMeta()))) { - BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr); - if (pMeta == null) { - session.onUnknownProperty(currAttr, m, currAttrLine, currAttrCol); - parseAnything(session, object(), r.unread(), m.getBean(false), false, null); // Read content anyway to ignore it - } else { - session.setCurrentProperty(pMeta); - ClassMeta<?> cm = pMeta.getClassMeta(); - Object value = parseAnything(session, cm, r.unread(), m.getBean(false), false, pMeta); - setName(cm, value, currAttr); - pMeta.set(m, currAttr, value); - session.setCurrentProperty(null); - } - } - state = S4; - } - } else if (state == S4) { - if (c == ',') - state = S1; - else if (c == ')' || c == -1 || c == AMP) { - return m; - } - } - } - isInEscape = isInEscape(c, r, isInEscape); - } - if (state == S1) - throw new ParseException(session, "Could not find attribute name on object."); - if (state == S2) - throw new ParseException(session, "Could not find '=' following attribute name on object."); - if (state == S3) - throw new ParseException(session, "Could not find value following '=' on object."); - if (state == S4) - throw new ParseException(session, "Could not find ')' marking end of object."); - - return null; // Unreachable. - } - - private static Object parseNull(UonParserSession session, ParserReader r) throws Exception { - String s = parseString(session, r, false); - if ("ull".equals(s)) - return null; - throw new ParseException(session, "Unexpected character sequence: ''{0}''", s); - } - - /** - * Convenience method for parsing an attribute from the specified parser. - * - * @param session - * @param r - * @param encoded - * @return The parsed object - * @throws Exception - */ - protected static final Object parseAttr(UonParserSession session, ParserReader r, boolean encoded) throws Exception { - Object attr; - attr = parseAttrName(session, r, encoded); - return attr; - } - - /** - * Parses an attribute name from the specified reader. - * - * @param session - * @param r - * @param encoded - * @return The parsed attribute name. - * @throws Exception - */ - protected static String parseAttrName(UonParserSession session, ParserReader r, boolean encoded) throws Exception { - - // If string is of form 'xxx', we're looking for ' at the end. - // Otherwise, we're looking for '&' or '=' or WS or -1 denoting the end of this string. - - int c = r.peekSkipWs(); - if (c == '\'') - return parsePString(session, r); - - r.mark(); - boolean isInEscape = false; - if (encoded) { - while (c != -1) { - c = r.read(); - if (! isInEscape) { - if (c == AMP || c == EQ || c == -1 || Character.isWhitespace(c)) { - if (c != -1) - r.unread(); - String s = r.getMarked(); - return ("null".equals(s) ? null : s); - } - } - else if (c == AMP) - r.replace('&'); - else if (c == EQ) - r.replace('='); - isInEscape = isInEscape(c, r, isInEscape); - } - } else { - while (c != -1) { - c = r.read(); - if (! isInEscape) { - if (c == '=' || c == -1 || Character.isWhitespace(c)) { - if (c != -1) - r.unread(); - String s = r.getMarked(); - return ("null".equals(s) ? null : session.trim(s)); - } - } - isInEscape = isInEscape(c, r, isInEscape); - } - } - - // We should never get here. - throw new ParseException(session, "Unexpected condition."); - } - - - /** - * Returns true if the next character in the stream is preceded by an escape '~' character. - * - * @param c The current character. - * @param r The reader. - * @param prevIsInEscape What the flag was last time. - */ - private static final boolean isInEscape(int c, ParserReader r, boolean prevIsInEscape) throws Exception { - if (c == '~' && ! prevIsInEscape) { - c = r.peek(); - if (escapedChars.contains(c)) { - r.delete(); - return true; - } - } - return false; - } - - /** - * Parses a string value from the specified reader. - * - * @param session - * @param r - * @param isUrlParamValue - * @return The parsed string. - * @throws Exception - */ - protected static String parseString(UonParserSession session, ParserReader r, boolean isUrlParamValue) throws Exception { - - // If string is of form 'xxx', we're looking for ' at the end. - // Otherwise, we're looking for ',' or ')' or -1 denoting the end of this string. - - int c = r.peekSkipWs(); - if (c == '\'') - return parsePString(session, r); - - r.mark(); - boolean isInEscape = false; - String s = null; - AsciiSet endChars = (isUrlParamValue ? endCharsParam : endCharsNormal); - while (c != -1) { - c = r.read(); - if (! isInEscape) { - // If this is a URL parameter value, we're looking for: & - // If not, we're looking for: &,) - if (endChars.contains(c)) { - r.unread(); - c = -1; - } - } - if (c == -1) - s = r.getMarked(); - else if (c == EQ) - r.replace('='); - else if (Character.isWhitespace(c) && ! isUrlParamValue) { - s = r.getMarked(0, -1); - skipSpace(r); - c = -1; - } - isInEscape = isInEscape(c, r, isInEscape); - } - - if (isUrlParamValue) - s = trim(s); - - return ("null".equals(s) ? null : session.trim(s)); - } - - private static final AsciiSet endCharsParam = new AsciiSet(""+AMP), endCharsNormal = new AsciiSet(",)"+AMP); - - - /*= - * Parses a string of the form "'foo'" - * All whitespace within parenthesis are preserved. - */ - static String parsePString(UonParserSession session, ParserReader r) throws Exception { - - r.read(); // Skip first quote. - r.mark(); - int c = 0; - - boolean isInEscape = false; - while (c != -1) { - c = r.read(); - if (! isInEscape) { - if (c == '\'') - return session.trim(r.getMarked(0, -1)); - } - if (c == EQ) - r.replace('='); - isInEscape = isInEscape(c, r, isInEscape); - } - throw new ParseException(session, "Unmatched parenthesis"); - } - - private static Boolean parseBoolean(UonParserSession session, ParserReader r) throws Exception { - String s = parseString(session, r, false); - if (s == null || s.equals("null")) - return null; - if (s.equals("true")) - return true; - if (s.equals("false")) - return false; - throw new ParseException(session, "Unrecognized syntax for boolean. ''{0}''.", s); - } - - private static Number parseNumber(UonParserSession session, ParserReader r, Class<? extends Number> c) throws Exception { - String s = parseString(session, r, false); - if (s == null) - return null; - return StringUtils.parseNumber(s, c); - } - - /* - * 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(UonParserSession session, ParserReader r) throws Exception { - while (true) { - int c = r.read(); - if (c == -1) - return; - if (! Character.isWhitespace(c)) - throw new ParseException(session, "Remainder after parse: ''{0}''.", (char)c); - } - } - - private static void skipSpace(ParserReader r) throws Exception { - int c = 0; - while ((c = r.read()) != -1) { - if (c <= 2 || ! Character.isWhitespace(c)) { - r.unread(); - return; - } - } - } - - /** * Create a UON parser session for parsing parameter values. * - * @param input * @return A new parser session. */ - protected final UonParserSession createParameterSession(Object input) { - return new UonParserSession(ctx, input); + protected final UonParserSession createParameterSession() { + return new UonParserSession(ctx); } - - //-------------------------------------------------------------------------------- - // Entry point methods - //-------------------------------------------------------------------------------- - @Override /* Parser */ - public UonParserSession createSession(Object input, ObjectMap op, Method javaMethod, Object outer, Locale locale, - TimeZone timeZone, MediaType mediaType) { - return new UonParserSession(ctx, op, input, javaMethod, outer, locale, timeZone, mediaType); - } - - @Override /* Parser */ - protected <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception { - UonParserSession s = (UonParserSession)session; - UonReader r = s.getReader(); - T o = parseAnything(s, type, r, s.getOuter(), true, 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 { - UonParserSession s = (UonParserSession)session; - UonReader r = s.getReader(); - m = parseIntoMap(s, r, m, (ClassMeta<K>)session.getClassMeta(keyType), (ClassMeta<V>)session.getClassMeta(valueType), null); - validateEnd(s, r); - return m; - } - - @Override /* ReaderParser */ - protected <E> Collection<E> doParseIntoCollection(ParserSession session, Collection<E> c, Type elementType) throws Exception { - UonParserSession s = (UonParserSession)session; - UonReader r = s.getReader(); - c = parseIntoCollection(s, r, c, (ClassMeta<E>)session.getClassMeta(elementType), false, null); - validateEnd(s, r); - return c; + public UonParserSession createSession(ParserSessionArgs args) { + return new UonParserSession(ctx, args); } }
