http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/ReaderParser.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/ReaderParser.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/ReaderParser.java new file mode 100755 index 0000000..865b95e --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/ReaderParser.java @@ -0,0 +1,394 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.parser; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.utils.*; + +/** + * Subclass of {@link Parser} for characters-based parsers. + * + * + * <h6 class='topic'>Description</h6> + * <p> + * This class is typically the parent class of all character-based parsers. + * It has 1 abstract method to implement... + * <ul> + * <li><code>parse(Reader, ClassMeta, ParserContext)</code> + * </ul> + * + * + * <h6 class='topic'>@Consumes annotation</h6> + * <p> + * The media types that this parser can handle is specified through the {@link Consumes @Consumes} annotation. + * <p> + * However, the media types can also be specified programmatically by overriding the {@link #getMediaTypes()} method. + * + * + * @author James Bognar ([email protected]) + */ +public abstract class ReaderParser extends Parser<Reader> { + + //-------------------------------------------------------------------------------- + // Abstract methods + //-------------------------------------------------------------------------------- + + @Override /* Parser */ + protected abstract <T> T doParse(Reader in, int estimatedSize, ClassMeta<T> type, ParserContext ctx) throws ParseException, IOException; + + //-------------------------------------------------------------------------------- + // Other methods + //-------------------------------------------------------------------------------- + + /** + * Same as <code>parse(Reader, Class)</code> except parses from a <code>CharSequence</code>. + * + * @param in The string containing the input. + * @param type The class type of the object to create. + * If <jk>null</jk> or <code>Object.<jk>class</jk></code>, object type is based on what's being parsed. + * @param <T> The class type of the object to create. + * @return The parsed object. + * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type. + */ + public <T> T parse(CharSequence in, Class<T> type) throws ParseException { + return parse(in, getBeanContext().getClassMeta(type)); + } + + /** + * Same as <code>parse(Reader, ClassMeta)</code> except parses from a <code>CharSequence</code>. + * + * @param in The string containing the input. + * @param type The class type of the object to create. + * If <jk>null</jk> or <code>Object.<jk>class</jk></code>, object type is based on what's being parsed. + * @param <T> The class type of the object to create. + * @return The parsed object. + * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type. + */ + public <T> T parse(CharSequence in, ClassMeta<T> type) throws ParseException { + try { + if (in == null) + return null; + return parse(wrapReader(in), in.length(), type, createContext()); + } catch (IOException e) { + throw new ParseException(e); // Won't happen since it's a StringReader. + } + } + + /** + * Same as <code>parseMap(Reader, Class, Class, Class)</code> except parses from a <code>CharSequence</code>. + * + * @param <T> The map class type. + * @param <K> The class type of the map keys. + * @param <V> The class type of the map values. + * @param in The string containing the input. + * @param mapClass The map class type. + * @param keyClass The class type of the map keys. + * @param valueClass The class type of the map values. + * @return A new map instance. + * @throws ParseException + */ + public <K,V,T extends Map<K,V>> T parseMap(CharSequence in, Class<T> mapClass, Class<K> keyClass, Class<V> valueClass) throws ParseException { + ClassMeta<T> cm = getBeanContext().getMapClassMeta(mapClass, keyClass, valueClass); + return parse(in, cm); + } + + /** + * Same as <code>parseCollection(Reader, Class, Class)</code> except parses from a <code>CharSequence</code>. + * + * @param <T> The collection class type. + * @param <E> The class type of the collection entries. + * @param in The string containing the input. + * @param collectionClass The map class type. + * @param entryClass collectionClass + * @return A new collection instance. + * @throws ParseException + */ + public <E,T extends Collection<E>> T parseCollection(CharSequence in, Class<T> collectionClass, Class<E> entryClass) throws ParseException { + ClassMeta<T> cm = getBeanContext().getCollectionClassMeta(collectionClass, entryClass); + return parse(in, cm); + } + + /** + * Wraps the specified character sequence inside a reader. + * Subclasses can override this method to implement their own readers. + * + * @param in The string being wrapped. + * @return The string wrapped in a reader, or <jk>null</jk> if the <code>CharSequence</code> is null. + */ + protected Reader wrapReader(CharSequence in) { + return new CharSequenceReader(in); + } + + //-------------------------------------------------------------------------------- + // Optional methods + //-------------------------------------------------------------------------------- + + /** + * Parses the contents of the specified reader and loads the results into the specified map. + * <p> + * Reader must contain something that serializes to a map (such as text containing a JSON object). + * <p> + * Used in the following locations: + * <ul> + * <li>The various character-based constructors in {@link ObjectMap} (e.g. {@link ObjectMap#ObjectMap(CharSequence, ReaderParser)}). + * </ul> + * + * @param <K> The key class type. + * @param <V> The value class type. + * @param in The reader containing the input. + * @param estimatedSize The estimated size of the input, or <code>-1</code> if unknown. + * @param m The map being loaded. + * @param keyType The class type of the keys, or <jk>null</jk> to default to <code>String.<jk>class</jk></code>.<br> + * @param valueType The class type of the values, or <jk>null</jk> to default to whatever is being parsed.<br> + * @return The same map that was passed in to allow this method to be chained. + * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type. + * @throws IOException If a problem occurred trying to read from the reader. + * @throws UnsupportedOperationException If not implemented. + */ + public final <K,V> Map<K,V> parseIntoMap(Reader in, int estimatedSize, Map<K,V> m, Type keyType, Type valueType) throws ParseException, IOException { + ParserContext ctx = createContext(); + try { + if (in == null) + throw new IOException("Null input stream or reader passed to parser."); + return doParseIntoMap(in, estimatedSize, m, keyType, valueType, ctx); + } finally { + ctx.close(); + } + } + + /** + * Implementation method. + * Default implementation throws an {@link UnsupportedOperationException}. + * + * @param in The input. Must represent an array. + * @param estimatedSize The estimated size of the input, or <code>-1</code> if unknown. + * @param m The map being loaded. + * @param keyType The class type of the keys, or <jk>null</jk> to default to <code>String.<jk>class</jk></code>.<br> + * @param valueType The class type of the values, or <jk>null</jk> to default to whatever is being parsed.<br> + * @param ctx The runtime context object returned by {@link #createContext(ObjectMap, Method, Object)}. + * If <jk>null</jk>, one will be created using {@link #createContext()}. + * @return The same map that was passed in to allow this method to be chained. + * @throws ParseException Occurs if syntax error detected in input. + * @throws IOException Occurs if thrown from <code>Reader</code> + */ + protected <K,V> Map<K,V> doParseIntoMap(Reader in, int estimatedSize, Map<K,V> m, Type keyType, Type valueType, ParserContext ctx) throws ParseException, IOException { + throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); + } + + /** + * Same as {@link #parseIntoMap(Reader, int, Map, Type, Type)} except reads from a <code>CharSequence</code>. + * + * @param in The input. Must represent an array. + * @param m The map being loaded. + * @param keyType The class type of the keys, or <jk>null</jk> to default to <code>String.<jk>class</jk></code>.<br> + * @param valueType The class type of the values, or <jk>null</jk> to default to whatever is being parsed.<br> + * @return The same map that was passed in to allow this method to be chained. + * @throws ParseException Occurs if syntax error detected in input. + */ + public final <K,V> Map<K,V> parseIntoMap(CharSequence in, Map<K,V> m, Type keyType, Type valueType) throws ParseException { + try { + if (in == null) + return null; + return parseIntoMap(wrapReader(in), in.length(), m, keyType, valueType); + } catch (IOException e) { + throw new ParseException(e); // Won't happen. + } + } + + /** + * Parses the contents of the specified reader and loads the results into the specified collection. + * <p> + * Used in the following locations: + * <ul> + * <li>The various character-based constructors in {@link ObjectList} (e.g. {@link ObjectList#ObjectList(CharSequence, ReaderParser)}. + * </ul> + * + * @param <E> The element class type. + * @param in The reader containing the input. + * @param estimatedSize The estimated size of the input, or <code>-1</code> if unknown. + * @param c The collection being loaded. + * @param elementType The class type of the elements, or <jk>null</jk> to default to whatever is being parsed. + * @return The same collection that was passed in to allow this method to be chained. + * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type. + * @throws IOException If a problem occurred trying to read from the reader. + * @throws UnsupportedOperationException If not implemented. + */ + public final <E> Collection<E> parseIntoCollection(Reader in, int estimatedSize, Collection<E> c, Type elementType) throws ParseException, IOException { + ParserContext ctx = createContext(); + try { + if (in == null) + throw new IOException("Null reader passed to parser."); + return doParseIntoCollection(in, estimatedSize, c, elementType, ctx); + } finally { + ctx.close(); + } + } + + /** + * Implementation method. + * Default implementation throws an {@link UnsupportedOperationException}. + * + * @param in The input. Must represent an array. + * @param estimatedSize The estimated size of the input, or <code>-1</code> if unknown. + * @param c The collection being loaded. + * @param elementType The class type of the elements, or <jk>null</jk> to default to whatever is being parsed. + * @param ctx The runtime context object returned by {@link #createContext(ObjectMap, Method, Object)}. + * If <jk>null</jk>, one will be created using {@link #createContext()}. + * @return The same collection that was passed in to allow this method to be chained. + * @throws ParseException Occurs if syntax error detected in input. + * @throws IOException Occurs if thrown from <code>Reader</code> + */ + protected <E> Collection<E> doParseIntoCollection(Reader in, int estimatedSize, Collection<E> c, Type elementType, ParserContext ctx) throws ParseException, IOException { + throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); + } + + /** + * Same as {@link #parseIntoCollection(Reader, int, Collection, Type)} except reads from a <code>CharSequence</code>. + * + * @param in The input. Must represent an array. + * @param c The collection being loaded. + * @param elementType The class type of the elements, or <jk>null</jk> to default to whatever is being parsed. + * @return The same collection that was passed in to allow this method to be chained. + * @throws ParseException Occurs if syntax error detected in input. + */ + public final <E> Collection<E> parseIntoCollection(CharSequence in, Collection<E> c, Type elementType) throws ParseException { + try { + return parseIntoCollection(wrapReader(in), in.length(), c, elementType); + } catch (IOException e) { + throw new ParseException(e); // Won't happen. + } + } + + /** + * Parses the specified array input with each entry in the object defined by the {@code argTypes} + * argument. + * <p> + * Used for converting arrays (e.g. <js>"[arg1,arg2,...]"</js>) into an {@code Object[]} that can be passed + * to the {@code Method.invoke(target, args)} method. + * <p> + * Used in the following locations: + * <ul> + * <li>Used to parse argument strings in the {@link PojoIntrospector#invokeMethod(Method, Reader)} method. + * </ul> + * + * @param in The input. Must represent an array. + * @param estimatedSize The estimated size of the input, or <code>-1</code> if unknown. + * @param argTypes Specifies the type of objects to create for each entry in the array. + * @return An array of parsed objects. + * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type. + * @throws IOException If a problem occurred trying to read from the reader. + * @throws UnsupportedOperationException If not implemented. + */ + public final Object[] parseArgs(Reader in, int estimatedSize, ClassMeta<?>[] argTypes) throws ParseException, IOException { + if (in == null) + throw new IOException("Null reader passed to parser."); + if (argTypes == null || argTypes.length == 0) + return new Object[0]; + ParserContext ctx = createContext(); + try { + return doParseArgs(in, estimatedSize, argTypes, ctx); + } finally { + ctx.close(); + } + } + + /** + * Implementation method. + * Default implementation throws an {@link UnsupportedOperationException}. + * + * @param in The input. Must represent an array. + * @param estimatedSize The estimated size of the input, or <code>-1</code> if unknown. + * @param argTypes Specifies the type of objects to create for each entry in the array. + * @param ctx The runtime context object returned by {@link #createContext(ObjectMap, Method, Object)}. + * If <jk>null</jk>, one will be created using {@link #createContext()}. + * @return An array of parsed objects. + * @throws ParseException Occurs if syntax error detected in input. + * @throws IOException Occurs if thrown from <code>Reader</code> + */ + protected Object[] doParseArgs(Reader in, int estimatedSize, ClassMeta<?>[] argTypes, ParserContext ctx) throws ParseException, IOException { + throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); + } + + /** + * Same as {@link #parseArgs(Reader, int, ClassMeta[])} except reads from a <code>CharSequence</code>. + * + * @param in The input. Must represent an array. + * @param argTypes Specifies the type of objects to create for each entry in the array. + * @return An array of parsed objects. + * @throws ParseException Occurs if syntax error detected in input. + */ + public Object[] parseArgs(CharSequence in, ClassMeta<?>[] argTypes) throws ParseException { + try { + return parseArgs(wrapReader(in), in.length(), argTypes); + } catch (IOException e) { + throw new ParseException(e); // Won't happen. + } + } + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Parser */ + public boolean isReaderParser() { + return true; + } + + @Override /* Parser */ + public ReaderParser setProperty(String property, Object value) throws LockedException { + super.setProperty(property, value); + return this; + } + + @Override /* CoreApi */ + public ReaderParser setProperties(ObjectMap properties) throws LockedException { + super.setProperties(properties); + return this; + } + + @Override /* CoreApi */ + public ReaderParser addNotBeanClasses(Class<?>...classes) throws LockedException { + super.addNotBeanClasses(classes); + return this; + } + + @Override /* CoreApi */ + public ReaderParser addFilters(Class<?>...classes) throws LockedException { + super.addFilters(classes); + return this; + } + + @Override /* CoreApi */ + public <T> ReaderParser addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException { + super.addImplClass(interfaceClass, implClass); + return this; + } + + @Override /* CoreApi */ + public ReaderParser setClassLoader(ClassLoader classLoader) throws LockedException { + super.setClassLoader(classLoader); + return this; + } + + @Override /* Lockable */ + public ReaderParser lock() { + super.lock(); + return this; + } + + @Override /* Lockable */ + public ReaderParser clone() throws CloneNotSupportedException { + return (ReaderParser)super.clone(); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/package.html ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/package.html b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/package.html new file mode 100755 index 0000000..ccee463 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/parser/package.html @@ -0,0 +1,126 @@ +<!DOCTYPE HTML> +<!-- + Licensed Materials - Property of IBM + (c) Copyright IBM Corporation 2014. All Rights Reserved. + + Note to U.S. Government Users Restricted Rights: + Use, duplication or disclosure restricted by GSA ADP Schedule + Contract with IBM Corp. + --> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <style type="text/css"> + /* For viewing in Page Designer */ + @IMPORT url("../../../../../../javadoc.css"); + + /* For viewing in REST interface */ + @IMPORT url("../htdocs/javadoc.css"); + body { + margin: 20px; + } + </style> + <script> + /* Replace all @code and @link tags. */ + window.onload = function() { + document.body.innerHTML = document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>'); + document.body.innerHTML = document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, '<code>$3</code>'); + } + </script> +</head> +<body> +<p>Parser API</p> + +<script> + function toggle(x) { + var div = x.nextSibling; + while (div != null && div.nodeType != 1) + div = div.nextSibling; + if (div != null) { + var d = div.style.display; + if (d == 'block' || d == '') { + div.style.display = 'none'; + x.className += " closed"; + } else { + div.style.display = 'block'; + x.className = x.className.replace(/(?:^|\s)closed(?!\S)/g , '' ); + } + } + } +</script> + +<a id='TOC'></a><h5 class='toc'>Table of Contents</h5> +<ol class='toc'> + <li><p><a class='doclink' href='#Parser'>Parser API</a></p> + <ol> + <li><p><a class='doclink' href='#ParserGroup'>The ParserGroup class</a></p> + </ol> + <li><p><a class='doclink' href='#DefiningParser'>Defining a new Parser</a></p> +</ol> + +<!-- ======================================================================================================== --> +<a id="Parser"></a> +<h2 class='topic' onclick='toggle(this)'>1 - Parser API</h2> +<div class='topic'> + <p> + The parser API is designed to be easily extensible by developers. <br> + If you are writing your own parser, you will typically subclass directly from either {@link com.ibm.juno.core.parser.ReaderParser} + or {@link com.ibm.juno.core.parser.InputStreamParser}.<br> + </p> + + <!-- ======================================================================================================== --> + <a id="ParserGroup"></a> + <h3 class='topic' onclick='toggle(this)'>1.1 - The ParserGroup class</h3> + <div class='topic'> + <p> + The {@link com.ibm.juno.core.parser.ParserGroup} class represents a group of parser registered with the media types they handle. + </p> + + <h6 class='topic'>Features</h6> + <p> + The <code>ParserGroup</code> class provides the following features: + <ul> + <li>Finds parsers based on HTTP <code>Content-Type</code> header values. + <li>Sets common properties on all parsers in a single method call. + <li>Locks all parsers in a single method call. + <li>Clones existing groups and all parsers within the group in a single method call. + </ul> + + <p> + Refer to {@link com.ibm.juno.core.parser.ParserGroup} for additional information. + </p> + </div> +</div> + + +<!-- ======================================================================================================== --> +<a id="DefiningParser"></a> +<h2 class='topic' onclick='toggle(this)'>2 - Defining a new Parser</h2> +<div class='topic'> + <p> + Defining a new parser is quite simple if you subclass directly from {@link com.ibm.juno.core.parser.ReaderParser} + or {@link com.ibm.juno.core.parser.InputStreamParser}. In each case, you simply need to implement a single + method and specify a {@link com.ibm.juno.core.annotation.Consumes} annotation. + </p> + <p> + The following example shows a simple parser that converts input streams to images using standard JRE classes. + </p> + <p class='bcode'> + <ja>@Consumes</ja>({<js>"image/png"</js>,<js>"image/jpeg"</js>}) + <jk>public static class</jk> ImageParser <jk>extends</jk> InputStreamParser { + <ja>@Override</ja> + <jk>public</jk> <T> T parse(InputStream in, ClassMeta<T> type, ParserContext ctx) <jk>throws</jk> ParseException, IOException { + BufferedImage image = ImageIO.<jsm>read</jsm>(in); + <jk>return</jk> (T)image; + } + } + </p> + <p> + Parsers that take advantage of the entire {@link com.ibm.juno.core.CoreApi} interface to be able to parse arbitrary beans and POJOs is + considerably more complex and outside the scope of this document. + If developing such a parser, the best course of action would be to replicate what occurs in the {@link com.ibm.juno.core.json.JsonParser} class. + </p> +</div> + +</body> +</html> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.class new file mode 100755 index 0000000..319f116 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.java new file mode 100755 index 0000000..4aa7ad1 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextParser.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.plaintext; + +import java.io.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.filter.*; +import com.ibm.juno.core.parser.*; +import com.ibm.juno.core.utils.*; + +/** + * Parsers HTTP plain text request bodies into <a href='../package-summary.html#PojoCategories'>Group 5</a> POJOs. + * + * + * <h6 class='topic'>Media types</h6> + * <p> + * Handles <code>Accept</code> types: <code>text/plain</code> + * <p> + * Produces <code>Content-Type</code> types: <code>text/plain</code> + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Essentially just converts plain text to POJOs via static <code>fromString()</code> or <code>valueOf()</code>, or + * through constructors that take a single string argument. + * <p> + * Also parses objects using a filter if the object class has an {@link PojoFilter PojoFilter<?,String>} filter defined on it. + * + * + * <h6 class='topic'>Configurable properties</h6> + * <p> + * This class has the following properties associated with it: + * <ul> + * <li>{@link BeanContextProperties} + * </ul> + * + * + * @author James Bognar ([email protected]) + */ +@Consumes("text/plain") +public final class PlainTextParser extends ReaderParser { + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Parser */ + protected <T> T doParse(Reader in, int estimatedSize, ClassMeta<T> type, ParserContext ctx) throws IOException, ParseException { + return ctx.getBeanContext().convertToType(IOUtils.read(in), type); + } + + @Override /* Lockable */ + public PlainTextParser clone() { + try { + return (PlainTextParser)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); // Shouldn't happen. + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.class new file mode 100755 index 0000000..63a5321 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.java new file mode 100755 index 0000000..9466161 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/PlainTextSerializer.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.plaintext; + +import java.io.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.filter.*; +import com.ibm.juno.core.serializer.*; + +/** + * Serializes POJOs to plain text using just the <code>toString()</code> method on the serialized object. + * + * + * <h6 class='topic'>Media types</h6> + * <p> + * Handles <code>Accept</code> types: <code>text/plain</code> + * <p> + * Produces <code>Content-Type</code> types: <code>text/plain</code> + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Essentially converts POJOs to plain text using the <code>toString()</code> method. + * <p> + * Also serializes objects using a filter if the object class has an {@link PojoFilter PojoFilter<?,String>} filter defined on it. + * + * + * <h6 class='topic'>Configurable properties</h6> + * <p> + * This class has the following properties associated with it: + * <ul> + * <li>{@link BeanContextProperties} + * </ul> + * + * + * @author James Bognar ([email protected]) + */ +@Produces("text/plain") +public final class PlainTextSerializer extends WriterSerializer { + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Serializer */ + protected void doSerialize(Object o, Writer out, SerializerContext ctx) throws IOException, SerializeException { + out.write(o == null ? "null" : ctx.getBeanContext().convertToType(o, String.class)); + } + + @Override /* Serializer */ + public PlainTextSerializer clone() { + try { + return (PlainTextSerializer)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); // Shouldn't happen. + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/package.html ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/package.html b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/package.html new file mode 100755 index 0000000..a0b213f --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/plaintext/package.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<!-- + Licensed Materials - Property of IBM + (c) Copyright IBM Corporation 2014. All Rights Reserved. + + Note to U.S. Government Users Restricted Rights: + Use, duplication or disclosure restricted by GSA ADP Schedule + Contract with IBM Corp. + --> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <style type="text/css"> + /* For viewing in Page Designer */ + @IMPORT url("../../../../../../javadoc.css"); + + /* For viewing in REST interface */ + @IMPORT url("../htdocs/javadoc.css"); + body { + margin: 20px; + } + </style> + <script> + /* Replace all @code and @link tags. */ + window.onload = function() { + document.body.innerHTML = document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>'); + document.body.innerHTML = document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, '<code>$3</code>'); + } + </script> +</head> +<body> +<p>Plain-text serialization and parsing support</p> +</body> +</html> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.class new file mode 100755 index 0000000..0428518 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.java new file mode 100755 index 0000000..a609751 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/OutputStreamSerializer.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * © Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.serializer; + +import java.io.*; + +import com.ibm.juno.core.annotation.*; + +/** + * Subclass of {@link Serializer} for byte-based serializers. + * + * + * <h6 class='topic'>Description</h6> + * <p> + * This class is typically the parent class of all byte-based serializers. + * It has 1 abstract method to implement... + * <ul> + * <li>{@link #doSerialize(Object, OutputStream, SerializerContext)} + * </ul> + * + * + * <h6 class='topic'>@Produces annotation</h6> + * <p> + * The media types that this serializer can produce is specified through the {@link Produces @Produces} annotation. + * <p> + * However, the media types can also be specified programmatically by overriding the {@link #getMediaTypes()} + * and {@link #getResponseContentType()} methods. + * + * @author James Bognar ([email protected]) + */ +public abstract class OutputStreamSerializer extends Serializer<OutputStream> { + + @Override /* Serializer */ + public boolean isWriterSerializer() { + return false; + } + + //-------------------------------------------------------------------------------- + // Abstract methods + //-------------------------------------------------------------------------------- + + @Override /* Serializer */ + protected abstract void doSerialize(Object o, OutputStream out, SerializerContext ctx) throws IOException, SerializeException; + +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.class new file mode 100755 index 0000000..54d57e1 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.java new file mode 100755 index 0000000..9b0e2da --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializeException.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.serializer; + +import java.text.*; + +/** + * General exception thrown whenever an error occurs during serialization. + * + * @author James Bognar ([email protected]) + */ +public final class SerializeException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + * @param msg The error message. + * @param args Optional printf arguments to replace in the error message. + */ + public SerializeException(String msg, Object... args) { + super(args.length == 0 ? msg : MessageFormat.format(msg, args)); + } + + /** + * Constructor. + * + * @param cause The cause. + */ + public SerializeException(Throwable cause) { + super(cause == null ? null : cause.getLocalizedMessage()); + initCause(cause); + } + + /** + * Sets the inner cause for this exception. + * + * @param cause The inner cause. + * @return This object (for method chaining). + */ + @Override /* Throwable */ + public synchronized SerializeException initCause(Throwable cause) { + super.initCause(cause); + return this; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.class new file mode 100755 index 0000000..0a67c62 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.java new file mode 100755 index 0000000..1d3cf51 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/Serializer.java @@ -0,0 +1,377 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.serializer; + +import static com.ibm.juno.core.utils.ClassUtils.*; + +import java.io.*; +import java.lang.reflect.*; +import java.text.*; +import java.util.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.annotation.*; +import com.ibm.juno.core.filter.*; +import com.ibm.juno.core.soap.*; +import com.ibm.juno.core.utils.*; + +/** + * Parent class for all Juno serializers. + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Base serializer class that serves as the parent class for all serializers. + * <p> + * Subclasses should extend directly from {@link OutputStreamSerializer} or {@link WriterSerializer}. + * + * + * <h6 class='topic'>@Produces annotation</h6> + * <p> + * The media types that this serializer can produce is specified through the {@link Produces @Produces} annotation. + * <p> + * However, the media types can also be specified programmatically by overriding the {@link #getMediaTypes()} + * and {@link #getResponseContentType()} methods. + * + * + * <h6 class='topic'>Configurable properties</h6> + * See {@link SerializerProperties} for a list of configurable properties that can be set on this class + * using the {@link #setProperty(String, Object)} method. + * + * @param <W> The output stream or writer class type. + * @author James Bognar ([email protected]) + */ +public abstract class Serializer<W> extends CoreApi { + + /** General serializer properties currently set on this serializer. */ + protected transient SerializerProperties sp = new SerializerProperties(); + private String[] mediaTypes; + private String contentType; + + // Hidden constructor to force subclass from OuputStreamSerializer or WriterSerializer. + Serializer() {} + + /** + * Returns <jk>true</jk> if this parser subclasses from {@link WriterSerializer}. + * + * @return <jk>true</jk> if this parser subclasses from {@link WriterSerializer}. + */ + public abstract boolean isWriterSerializer(); + + //-------------------------------------------------------------------------------- + // Abstract methods + //-------------------------------------------------------------------------------- + + /** + * Serializes a POJO to the specified output stream or writer. + * <p> + * This method should NOT close the context object. + * + * @param o The object to serialize. + * @param out The writer or output stream to write to. + * @param ctx The serializer context object return by {@link #createContext(ObjectMap, Method)}.<br> + * If <jk>null</jk>, context is created using {@link #createContext()}. + * + * @throws IOException If a problem occurred trying to write to the writer. + * @throws SerializeException If a problem occurred trying to convert the output. + */ + protected abstract void doSerialize(Object o, W out, SerializerContext ctx) throws IOException, SerializeException; + + //-------------------------------------------------------------------------------- + // Other methods + //-------------------------------------------------------------------------------- + + /** + * Calls {@link #serialize(Object, Object, SerializerContext)} but intercepts {@link StackOverflowError} exceptions + * and wraps them in a useful message. + * @param o The object to serialize. + * @param out The writer or output stream to write to. + * @param ctx The serializer context object return by {@link #createContext(ObjectMap, Method)}.<br> + * If <jk>null</jk>, context is created using {@link #createContext()}. + * + * @throws IOException If a problem occurred trying to write to the writer. + * @throws SerializeException If a problem occurred trying to convert the output. + */ + public final void serialize(Object o, W out, SerializerContext ctx) throws IOException, SerializeException { + try { + doSerialize(o, out, ctx); + } catch (StackOverflowError e) { + throw new SerializeException("Stack overflow occurred. This can occur when trying to serialize models containing loops. It's recommended you use the SerializerProperties.SERIALIZER_detectRecursions setting to help locate the loop.").initCause(e); + } finally { + ctx.close(); + } + } + + /** + * Serializes a POJO to the specified output stream or writer. + * <p> + * Equivalent to calling <code>serializer.serialize(o, out, <jk>null</jk>);</code> + * + * @param o The object to serialize. + * @param out The writer or output stream to write to. + * + * @throws IOException If a problem occurred trying to write to the writer. + * @throws SerializeException If a problem occurred trying to convert the output. + */ + public final void serialize(Object o, W out) throws IOException, SerializeException { + SerializerContext ctx = createContext(); + serialize(o, out, ctx); + } + + /** + * Create the context object that will be passed in to the serialize method. + * <p> + * It's up to implementers to decide what the context object looks like, although typically + * it's going to be a subclass of {@link SerializerContext}. + * + * @param properties Optional additional properties. + * @param javaMethod Java method that invoked this serializer. + * When using the REST API, this is the Java method invoked by the REST call. + * Can be used to access annotations defined on the method or class. + * @return The new context. + */ + public SerializerContext createContext(ObjectMap properties, Method javaMethod) { + return new SerializerContext(getBeanContext(), sp, properties, javaMethod); + } + + /** + * Create a basic context object without overriding properties or specifying <code>javaMethod</code>. + * <p> + * Equivalent to calling <code>createContext(<jk>null</jk>, <jk>null</jk>)</code>. + * + * @return The new context. + */ + protected SerializerContext createContext() { + return createContext(null, null); + } + + /** + * 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 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); + } + + /** + * Generalize the specified object if a filter is associated with it. + * + * @param ctx The context that exists for the duration of a single serialize. + * @param o The object to generalize. + * @param type The type of object. + * @return The generalized object, or <jk>null</jk> if the object is <jk>null</jk>. + * @throws SerializeException If a problem occurred trying to convert the output. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected final Object generalize(SerializerContext ctx, Object o, ClassMeta<?> type) throws SerializeException { + if (o == null) + return null; + PojoFilter f = (type == null || type.isObject() ? ctx.getBeanContext().getClassMeta(o.getClass()).getPojoFilter() : type.getPojoFilter()); + if (f == null) + return o; + return f.filter(o); + } + + /** + * Returns <jk>true</jk> if the specified value should not be serialized. + * + * @param ctx The context that exists for the duration of a single serialize. + * @param cm The class type of the object being serialized. + * @param attrName The bean attribute name, or <jk>null</jk> if this isn't a bean attribute. + * @param value The object being serialized. + * @return <jk>true</jk> if the specified value should not be serialized. + * @throws SerializeException + */ + protected final boolean canIgnoreValue(SerializerContext ctx, ClassMeta<?> cm, String attrName, Object value) throws SerializeException { + + if (ctx.isTrimNulls() && value == null) + return true; + + if (value == null) + return false; + + if (cm == null) + cm = object(); + + if (ctx.isTrimEmptyLists()) { + if (cm.isArray() || (cm.isObject() && value.getClass().isArray())) { + if (((Object[])value).length == 0) + return true; + } + if (cm.isCollection() || (cm.isObject() && isParentClass(Collection.class, value.getClass()))) { + if (((Collection<?>)value).isEmpty()) + return true; + } + } + + if (ctx.isTrimEmptyMaps()) { + if (cm.isMap() || (cm.isObject() && isParentClass(Map.class, value.getClass()))) { + if (((Map<?,?>)value).isEmpty()) + return true; + } + } + + if (ctx.isTrimNulls() && ctx.willRecurse(attrName, value, cm)) + return true; + + return false; + } + + /** + * Sorts the specified map if {@link SerializerContext#isSortMaps()} returns <jk>true</jk>. + * + * @param ctx The context that exists for the duration of a single serialize. + * @param m The map being sorted. + * @return A new sorted {@link TreeMap}. + */ + protected final <K,V> Map<K,V> sort(SerializerContext ctx, Map<K,V> m) { + if (ctx.isSortMaps() && m != null && (! m.isEmpty()) && m.keySet().iterator().next() instanceof Comparable<?>) + return new TreeMap<K,V>(m); + return m; + } + + /** + * Sorts the specified collection if {@link SerializerContext#isSortCollections()} returns <jk>true</jk>. + * + * @param ctx The context that exists for the duration of a single serialize. + * @param c The collection being sorted. + * @return A new sorted {@link TreeSet}. + */ + protected final <E> Collection<E> sort(SerializerContext ctx, Collection<E> c) { + if (ctx.isSortCollections() && c != null && (! c.isEmpty()) && c.iterator().next() instanceof Comparable<?>) + return new TreeSet<E>(c); + return c; + } + + /** + * Returns the media types handled based on the value of the {@link Produces} annotation on the serializer class. + * <p> + * This method can be overridden by subclasses to determine the media types programatically. + * + * @return The list of media types. Never <jk>null</jk>. + */ + public String[] getMediaTypes() { + if (mediaTypes == null) { + Produces p = ReflectionUtils.getAnnotation(Produces.class, getClass()); + if (p == null) + throw new RuntimeException(MessageFormat.format("Class ''{0}'' is missing the @Produces annotation", getClass().getName())); + mediaTypes = p.value(); + } + return mediaTypes; + } + + /** + * 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 standalone (i.e. outside of a REST server or client). + * + * @param properties Optional run-time properties (the same that are passed to {@link WriterSerializer#doSerialize(Object, Writer, SerializerContext)}. + * Can be <jk>null</jk>. + * @return The HTTP headers to set on HTTP requests. + * Can be <jk>null</jk>. + */ + public ObjectMap getResponseHeaders(ObjectMap properties) { + return new ObjectMap(getBeanContext()); + } + + /** + * Optional method that returns the response <code>Content-Type</code> for this serializer if it is different from the matched media type. + * <p> + * This method is specified to override the content type for this serializer. + * For example, the {@link com.ibm.juno.core.json.JsonSerializer.Simple} class returns that it handles media type <js>"text/json+simple"</js>, but returns + * <js>"text/json"</js> as the actual content type. + * This allows clients to request specific 'flavors' of content using specialized <code>Accept</code> header values. + * <p> + * This method is typically meaningless if the serializer is being used standalone (i.e. outside of a REST server or client). + * + * @return The response content type. If <jk>null</jk>, then the matched media type is used. + */ + public String getResponseContentType() { + if (contentType == null) { + Produces p = getClass().getAnnotation(Produces.class); + if (p == null) + contentType = ""; + else { + contentType = p.contentType(); + if (contentType.isEmpty()) + contentType = p.value()[0]; + } + } + return (contentType.isEmpty() ? null : contentType); + } + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* CoreApi */ + public Serializer<W> setProperty(String property, Object value) throws LockedException { + checkLock(); + if (! sp.setProperty(property, value)) + super.setProperty(property, value); + return this; + } + + @Override /* CoreApi */ + public Serializer<W> addNotBeanClasses(Class<?>...classes) throws LockedException { + super.addNotBeanClasses(classes); + return this; + } + + @Override /* CoreApi */ + public Serializer<W> addFilters(Class<?>...classes) throws LockedException { + super.addFilters(classes); + return this; + } + + @Override /* CoreApi */ + public <T> Serializer<W> addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException { + super.addImplClass(interfaceClass, implClass); + return this; + } + + @Override /* CoreApi */ + public Serializer<W> setClassLoader(ClassLoader classLoader) throws LockedException { + super.setClassLoader(classLoader); + return this; + } + + @Override /* CoreApi */ + public Serializer<W> lock() { + super.lock(); + return this; + } + + @Override /* CoreApi */ + public Serializer<W> clone() throws CloneNotSupportedException { + @SuppressWarnings("unchecked") + Serializer<W> c = (Serializer<W>)super.clone(); + c.sp = sp.clone(); + return c; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$1.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$1.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$1.class new file mode 100755 index 0000000..81d75cb Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$1.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$StackElement.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$StackElement.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$StackElement.class new file mode 100755 index 0000000..f298463 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext$StackElement.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.class new file mode 100755 index 0000000..1d86e46 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.java new file mode 100755 index 0000000..f646b0a --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerContext.java @@ -0,0 +1,464 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.serializer; + +import static com.ibm.juno.core.serializer.SerializerProperties.*; + +import java.lang.reflect.*; +import java.text.*; +import java.util.*; +import java.util.logging.*; + +import com.ibm.juno.core.*; +import com.ibm.juno.core.utils.*; + +/** + * Context object that lives for the duration of a single serialization of {@link Serializer} and its subclasses. + * <p> + * Used by serializers for the following purposes: + * <ul> + * <li>Keeping track of how deep it is in a model for indentation purposes. + * <li>Ensuring infinite loops don't occur by setting a limit on how deep to traverse a model. + * <li>Ensuring infinite loops don't occur from loops in the model (when detectRecursions is enabled. + * <li>Allowing serializer properties to be overridden on method calls. + * </ul> + * + * @author James Bognar ([email protected]) + */ +public class SerializerContext { + + private static Logger logger = Logger.getLogger(SerializerContext.class.getName()); + + private final int maxDepth, initialDepth; + private final boolean + debug, + detectRecursions, + ignoreRecursions, + useIndentation, + addClassAttrs, + trimNulls, + trimEmptyLists, + trimEmptyMaps, + sortCollections, + sortMaps; + private final char quoteChar; + private final String relativeUriBase, absolutePathUriBase; + private final ObjectMap overrideProperties; + + /** The current indentation depth into the model. */ + public int indent; + + /** Contains the current objects in the current branch of the model. */ + private Map<Object,Object> set; + + /** Contains the current objects in the current branch of the model. */ + private LinkedList<StackElement> stack; + + /** If 'true', then we're at a leaf in the model (i.e. a String, Number, Boolean, or null). */ + private boolean isBottom; + + /** Any warnings encountered. */ + private final List<String> warnings = new LinkedList<String>(); + + /** The bean context being used in this context. */ + private final BeanContext beanContext; + + /** Java method that invoked this serializer. */ + private final Method javaMethod; + + /** + * Create a new HashStack with the specified options. + * + * @param beanContext The bean context being used by the serializer. + * @param sp The default serializer properties. + * @param op The override properties. + * @param javaMethod Java method that invoked this serializer. + * When using the REST API, this is the Java method invoked by the REST call. + * Can be used to access annotations defined on the method or class. + */ + public SerializerContext(BeanContext beanContext, SerializerProperties sp, ObjectMap op, Method javaMethod) { + this.beanContext = beanContext; + this.javaMethod = javaMethod; + if (op == null || op.isEmpty()) { + overrideProperties = new ObjectMap(); + maxDepth = sp.maxDepth; + initialDepth = sp.initialDepth; + debug = sp.debug; + detectRecursions = sp.detectRecursions; + ignoreRecursions = sp.ignoreRecursions; + useIndentation = sp.useIndentation; + addClassAttrs = sp.addClassAttrs; + trimNulls = sp.trimNulls; + trimEmptyLists = sp.trimEmptyLists; + trimEmptyMaps = sp.trimEmptyMaps; + quoteChar = sp.quoteChar; + relativeUriBase = resolveRelativeUriBase(sp.relativeUriBase); + absolutePathUriBase = resolveAbsolutePathUriBase(sp.absolutePathUriBase); + sortCollections = sp.sortCollections; + sortMaps = sp.sortMaps; + } else { + overrideProperties = op; + maxDepth = op.getInt(SERIALIZER_maxDepth, sp.maxDepth); + initialDepth = op.getInt(SERIALIZER_initialDepth, sp.initialDepth); + debug = op.getBoolean(SERIALIZER_debug, sp.debug); + detectRecursions = op.getBoolean(SERIALIZER_detectRecursions, sp.detectRecursions); + ignoreRecursions = op.getBoolean(SERIALIZER_ignoreRecursions, sp.ignoreRecursions); + useIndentation = op.getBoolean(SERIALIZER_useIndentation, sp.useIndentation); + addClassAttrs = op.getBoolean(SERIALIZER_addClassAttrs, sp.addClassAttrs); + trimNulls = op.getBoolean(SERIALIZER_trimNullProperties, sp.trimNulls); + trimEmptyLists = op.getBoolean(SERIALIZER_trimEmptyLists, sp.trimEmptyLists); + trimEmptyMaps = op.getBoolean(SERIALIZER_trimEmptyMaps, sp.trimEmptyMaps); + quoteChar = op.getString(SERIALIZER_quoteChar, ""+sp.quoteChar).charAt(0); + relativeUriBase = resolveRelativeUriBase(op.getString(SERIALIZER_relativeUriBase, sp.relativeUriBase)); + absolutePathUriBase = resolveAbsolutePathUriBase(op.getString(SERIALIZER_absolutePathUriBase, sp.absolutePathUriBase)); + sortCollections = op.getBoolean(SERIALIZER_sortCollections, sp.sortMaps); + sortMaps = op.getBoolean(SERIALIZER_sortMaps, sp.sortMaps); + } + + this.indent = initialDepth; + if (detectRecursions || debug) { + set = new IdentityHashMap<Object,Object>(); + stack = new LinkedList<StackElement>(); + } + } + + private String resolveRelativeUriBase(String s) { + if (StringUtils.isEmpty(s)) + return null; + if (s.equals("/")) + return s; + else if (StringUtils.endsWith(s, '/')) + s = s.substring(0, s.length()-1); + return s; + } + + private String resolveAbsolutePathUriBase(String s) { + if (StringUtils.isEmpty(s)) + return null; + if (StringUtils.endsWith(s, '/')) + s = s.substring(0, s.length()-1); + return s; + } + + /** + * Returns the bean context associated with this context. + * + * @return The bean context associated with this context. + */ + public final BeanContext getBeanContext() { + return beanContext; + } + + /** + * Returns the Java method that invoked this serializer. + * <p> + * When using the REST API, this is the Java method invoked by the REST call. + * Can be used to access annotations defined on the method or class. + * + * @return The Java method that invoked this serializer. + */ + public final Method getJavaMethod() { + return javaMethod; + } + + /** + * Returns the runtime properties associated with this context. + * + * @return The runtime properties associated with this context. + */ + public final ObjectMap getProperties() { + return overrideProperties; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_maxDepth} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_maxDepth} setting value in this context. + */ + public final int getMaxDepth() { + return maxDepth; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_initialDepth} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_initialDepth} setting value in this context. + */ + public final int getInitialDepth() { + return initialDepth; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_debug} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_debug} setting value in this context. + */ + public final boolean isDebug() { + return debug; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_detectRecursions} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_detectRecursions} setting value in this context. + */ + public final boolean isDetectRecursions() { + return detectRecursions; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_ignoreRecursions} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_ignoreRecursions} setting value in this context. + */ + public final boolean isIgnoreRecursions() { + return ignoreRecursions; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_useIndentation} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_useIndentation} setting value in this context. + */ + public final boolean isUseIndentation() { + return useIndentation; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_addClassAttrs} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_addClassAttrs} setting value in this context. + */ + public final boolean isAddClassAttrs() { + return addClassAttrs; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_quoteChar} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_quoteChar} setting value in this context. + */ + public final char getQuoteChar() { + return quoteChar; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_trimNullProperties} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_trimNullProperties} setting value in this context. + */ + public final boolean isTrimNulls() { + return trimNulls; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_trimEmptyLists} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_trimEmptyLists} setting value in this context. + */ + public final boolean isTrimEmptyLists() { + return trimEmptyLists; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_trimEmptyMaps} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_trimEmptyMaps} setting value in this context. + */ + public final boolean isTrimEmptyMaps() { + return trimEmptyMaps; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_sortCollections} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_sortCollections} setting value in this context. + */ + public final boolean isSortCollections() { + return sortCollections; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_sortMaps} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_sortMaps} setting value in this context. + */ + public final boolean isSortMaps() { + return sortMaps; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_relativeUriBase} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_relativeUriBase} setting value in this context. + */ + public final String getRelativeUriBase() { + return relativeUriBase; + } + + /** + * Returns the {@link SerializerProperties#SERIALIZER_absolutePathUriBase} setting value in this context. + * + * @return The {@link SerializerProperties#SERIALIZER_absolutePathUriBase} setting value in this context. + */ + public final String getAbsolutePathUriBase() { + return absolutePathUriBase; + } + + /** + * Push the specified object onto the stack. + * + * @param attrName The attribute name. + * @param o The current object being serialized. + * @param eType The expected class type. + * @return The {@link ClassMeta} of the object so that <code>instanceof</code> operations + * only need to be performed once (since they can be expensive).<br> + * @throws SerializeException + */ + public ClassMeta<?> push(String attrName, Object o, ClassMeta<?> eType) throws SerializeException { + indent++; + isBottom = true; + if (o == null) + return null; + Class<?> c = o.getClass(); + ClassMeta<?> cm = (eType != null && c == eType.getInnerClass()) ? eType : beanContext.getClassMeta(c); + if (cm.isCharSequence() || cm.isNumber() || cm.isBoolean()) + return cm; + if (detectRecursions || debug) { + if (stack.size() > maxDepth) + return null; + if (willRecurse(attrName, o, cm)) + return null; + isBottom = false; + stack.add(new StackElement(stack.size(), attrName, o, cm)); + if (debug) + logger.info(getStack(false)); + set.put(o, o); + } + return cm; + } + + /** + * Returns <jk>true</jk> if {@link SerializerProperties#SERIALIZER_detectRecursions} is enabled, and the specified + * object is already higher up in the serialization chain. + * + * @param attrName The bean property attribute name, or some other identifier. + * @param o The object to check for recursion. + * @param cm The metadata on the object class. + * @return <jk>true</jk> if recursion detected. + * @throws SerializeException + */ + public boolean willRecurse(String attrName, Object o, ClassMeta<?> cm) throws SerializeException { + if (! (detectRecursions || debug)) + return false; + if (! set.containsKey(o)) + return false; + if (ignoreRecursions && ! debug) + return true; + + stack.add(new StackElement(stack.size(), attrName, o, cm)); + throw new SerializeException("Recursion occurred, stack={0}", getStack(true)); + } + + /** + * Pop an object off the stack. + */ + public void pop() { + indent--; + if ((detectRecursions || debug) && ! isBottom) { + Object o = stack.removeLast().o; + Object o2 = set.remove(o); + if (o2 == null) + addWarning("Couldn't remove object of type ''{0}'' on attribute ''{1}'' from object stack.", o.getClass().getName(), stack); + } + isBottom = false; + } + + /** + * The current indentation depth. + * + * @return The current indentation depth. + */ + public int getIndent() { + return indent; + } + + /** + * Logs a warning message. + * + * @param msg The warning message. + * @param args Optional printf arguments to replace in the error message. + */ + public void addWarning(String msg, Object... args) { + msg = args.length == 0 ? msg : MessageFormat.format(msg, args); + logger.warning(msg); + warnings.add(warnings.size() + 1 + ": " + msg); + } + + /** + * 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 addBeanGetterWarning(BeanPropertyMeta<?> p, Throwable t) { + String prefix = (debug ? getStack(false) + ": " : ""); + addWarning("{0}Could not call getValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix, p.getName(), p.getBeanMeta().getClassMeta(), t.getLocalizedMessage()); + } + + /** + * Perform cleanup on this context object if necessary. + * + * @throws SerializeException + */ + public void close() throws SerializeException { + if (debug && warnings.size() > 0) + throw new SerializeException("Warnings occurred during serialization: \n" + StringUtils.join(warnings, "\n")); + } + + private static class StackElement { + private int depth; + private String name; + private Object o; + private ClassMeta<?> aType; + + private StackElement(int depth, String name, Object o, ClassMeta<?> aType) { + this.depth = depth; + this.name = name; + this.o = o; + this.aType = aType; + } + + private String toString(boolean simple) { + StringBuilder sb = new StringBuilder().append('[').append(depth).append(']'); + sb.append(StringUtils.isEmpty(name) ? "<noname>" : name).append(":"); + sb.append(aType.toString(simple)); + if (aType != aType.getFilteredClassMeta()) + sb.append("/").append(aType.getFilteredClassMeta().toString(simple)); + return sb.toString(); + } + } + + private String getStack(boolean full) { + StringBuilder sb = new StringBuilder(); + for (StackElement e : stack) { + if (full) { + sb.append("\n\t"); + for (int i = 1; i < e.depth; i++) + sb.append(" "); + if (e.depth > 0) + sb.append("->"); + sb.append(e.toString(false)); + } else { + sb.append(" > ").append(e.toString(true)); + } + } + return sb.toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup$SerializerEntry.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup$SerializerEntry.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup$SerializerEntry.class new file mode 100755 index 0000000..ad993fd Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup$SerializerEntry.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.class new file mode 100755 index 0000000..b2c10d7 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.java b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.java new file mode 100755 index 0000000..509ed58 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerGroup.java @@ -0,0 +1,345 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2011, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.core.serializer; + +import static com.ibm.juno.core.utils.ArrayUtils.*; + +import java.io.*; +import java.util.*; + +import com.ibm.juno.core.*; + +/** + * Represents a group of {@link Serializer Serializers} that can be looked up by media type. + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Provides the following features: + * <ul> + * <li>Finds serializers based on HTTP <code>Accept</code> header values. + * <li>Sets common properties on all serializers in a single method call. + * <li>Locks all serializers in a single method call. + * <li>Clones existing groups and all serializers within the group in a single method call. + * </ul> + * + * + * <h6 class='topic'>Match ordering</h6> + * <p> + * Serializers are matched against <code>Accept</code> strings in the order they exist in this group. + * <p> + * Adding new entries will cause the entries to be prepended to the group. + * This allows for previous serializers to be overridden through subsequent calls. + * <p> + * For example, calling <code>g.append(S1.<jk>class</jk>,S2.<jk>class</jk>).append(S3.<jk>class</jk>,S4.<jk>class</jk>)</code> + * will result in the order <code>S3, S4, S1, S2</code>. + * + * + * <h6 class='topic'>Examples</h6> + * <p class='bcode'> + * <jc>// Construct a new serializer group</jc> + * SerializerGroup g = <jk>new</jk> SerializerGroup(); + * + * <jc>// Add some serializers to it</jc> + * g.append(JsonSerializer.<jk>class</jk>, XmlSerializer.<jk>class</jk>); + * + * <jc>// Change settings for all serializers in the group and lock it.</jc> + * g.setProperty(SerializerProperties.<jsf>SERIALIZER_useIndentation</jsf>, <jk>true</jk>) + * .addFilters(CalendarFilter.ISO8601DT.<jk>class</jk>) + * .lock(); + * + * <jc>// Find the appropriate serializer by Accept type</jc> + * String mediaTypeMatch = g.findMatch(<js>"text/foo, text/json;q=0.8, text/*;q:0.6, *\/*;q=0.0"</js>); + * WriterSerializer s = (WriterSerializer)g.getSerializer(mediaTypeMatch); + * + * <jc>// Serialize a bean to JSON text </jc> + * AddressBook addressBook = <jk>new</jk> AddressBook(); <jc>// Bean to serialize.</jc> + * String json = s.serialize(addressBook); + * </p> + * + * @author James Bognar ([email protected]) + */ +public final class SerializerGroup extends Lockable { + + // Maps media-types to serializers. + private transient Map<String,SerializerEntry> entryMap = new HashMap<String,SerializerEntry>(); + private transient LinkedList<SerializerEntry> tempEntries = new LinkedList<SerializerEntry>(); + private transient SerializerEntry[] entries; + + + /** + * Registers the specified REST serializers with this serializer group. + * + * @param s The serializers to append to this group. + * @return This object (for method chaining). + */ + public SerializerGroup append(Serializer<?>...s) { + checkLock(); + entries = null; + for (Serializer<?> ss : reverse(s)) { + SerializerEntry e = new SerializerEntry(ss); + tempEntries.addFirst(e); + for (String mediaType : e.mediaTypes) + entryMap.put(mediaType, e); + } + return this; + } + + /** + * Same as {@link #append(Serializer[])}, except specify classes instead of class instances + * of {@link Serializer}. + * <p> + * Note that this can only be used on {@link Serializer Serializers} with public no-arg constructors. + * + * @param s The serializers to append to this group. + * @return This object (for method chaining). + * @throws Exception Thrown if {@link Serializer} could not be constructed. + */ + public SerializerGroup append(Class<? extends Serializer<?>>...s) throws Exception { + checkLock(); + for (Class<? extends Serializer<?>> ss : reverse(s)) + try { + append(ss.newInstance()); + } catch (NoClassDefFoundError e) { + // Ignore if dependent library not found (e.g. Jena). + System.err.println(e); + } + return this; + } + + /** + * Same as {@link #append(Class[])}, except specify a single class to avoid unchecked compile warnings. + * + * @param c The serializer to append to this group. + * @return This object (for method chaining). + * @throws Exception Thrown if {@link Serializer} could not be constructed. + */ + public SerializerGroup append(Class<? extends Serializer<?>> c) throws Exception { + checkLock(); + try { + append(c.newInstance()); + } catch (NoClassDefFoundError e) { + // Ignore if dependent library not found (e.g. Jena). + System.err.println(e); + } + return this; + } + + /** + * Returns the serializer registered to handle the specified media type. + * <p> + * The media-type string must not contain any parameters or q-values. + * + * @param mediaType The media-type string (e.g. <js>"text/json"</js> + * @return The serializer that handles the specified accept content type, or <jk>null</jk> if + * no serializer is registered to handle it. + */ + public Serializer<?> getSerializer(String mediaType) { + SerializerEntry e = entryMap.get(mediaType); + return (e == null ? null : e.serializer); + } + + /** + * Searches the group for a serializer that can handle the specified <code>Accept</code> value. + * <p> + * The <code>accept</code> value complies with the syntax described in RFC2616, Section 14.1, as described below: + * <p class='bcode'> + * Accept = "Accept" ":" + * #( media-range [ accept-params ] ) + * + * media-range = ( "*\/*" + * | ( type "/" "*" ) + * | ( type "/" subtype ) + * ) *( ";" parameter ) + * accept-params = ";" "q" "=" qvalue *( accept-extension ) + * accept-extension = ";" token [ "=" ( token | quoted-string ) ] + * </p> + * <p> + * The general idea behind having the serializer resolution be a two-step process is so that + * the matched media type can be passed in to the {@link WriterSerializer#doSerialize(Object, Writer, SerializerContext)} method. + * For example... + * <p class='bcode'> + * String acceptHeaderValue = request.getHeader(<js>"Accept"</js>); + * String matchingMediaType = group.findMatch(acceptHeaderValue); + * if (matchingMediaType == <jk>null</jk>) + * <jk>throw new</jk> RestException(<jsf>SC_NOT_ACCEPTABLE</jsf>); + * WriterSerializer s = (WriterSerializer)group.getSerializer(matchingMediaType); + * s.serialize(getPojo(), response.getWriter(), response.getProperties(), matchingMediaType); + * </p> + * + * @param accept The accept string. + * @return The media type registered by one of the parsers that matches the <code>accept</code> string, + * or <jk>null</jk> if no media types matched. + */ + public String findMatch(String accept) { + MediaRange[] mr = MediaRange.parse(accept); + if (mr.length == 0) + mr = MediaRange.parse("*/*"); + + for (MediaRange a : mr) + for (SerializerEntry e : getEntries()) + for (MediaRange a2 : e.mediaRanges) + if (a.matches(a2)) + return a2.getMediaType(); + + return null; + } + + /** + * Returns the media types that all serializers in this group can handle + * <p> + * Entries are ordered in the same order as the serializers in the group. + * + * @return The list of media types. + */ + public List<String> getSupportedMediaTypes() { + List<String> l = new ArrayList<String>(); + for (SerializerEntry e : getEntries()) + for (String mt : e.mediaTypes) + if (! l.contains(mt)) + l.add(mt); + return l; + } + + private SerializerEntry[] getEntries() { + if (entries == null) + entries = tempEntries.toArray(new SerializerEntry[tempEntries.size()]); + return entries; + } + + static class SerializerEntry { + Serializer<?> serializer; + MediaRange[] mediaRanges; + String[] mediaTypes; + + SerializerEntry(Serializer<?> s) { + serializer = s; + + mediaTypes = new String[s.getMediaTypes().length]; + int i = 0; + for (String mt : s.getMediaTypes()) + mediaTypes[i++] = mt.toLowerCase(Locale.ENGLISH); + + List<MediaRange> l = new LinkedList<MediaRange>(); + for (i = 0; i < mediaTypes.length; i++) + l.addAll(Arrays.asList(MediaRange.parse(mediaTypes[i]))); + mediaRanges = l.toArray(new MediaRange[l.size()]); + } + } + + + //-------------------------------------------------------------------------------- + // Convenience methods for setting properties on all serializers. + //-------------------------------------------------------------------------------- + + /** + * Shortcut for calling {@link Serializer#setProperty(String, Object)} on all serializers in this group. + * + * @param property The property name. + * @param value The property value. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public SerializerGroup setProperty(String property, Object value) throws LockedException { + checkLock(); + for (SerializerEntry e : getEntries()) + e.serializer.setProperty(property, value); + return this; + } + + /** + * Shortcut for calling {@link Serializer#setProperties(ObjectMap)} on all serializers in this group. + * + * @param properties The properties to set. Ignored if <jk>null</jk>. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public SerializerGroup setProperties(ObjectMap properties) { + checkLock(); + if (properties != null) + for (Map.Entry<String,Object> e : properties.entrySet()) + setProperty(e.getKey(), e.getValue()); + return this; + } + + /** + * Shortcut for calling {@link Serializer#addNotBeanClasses(Class[])} on all serializers in this group. + * + * @param classes The classes to specify as not-beans to the underlying bean context of all serializers in this group. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public SerializerGroup addNotBeanClasses(Class<?>...classes) throws LockedException { + checkLock(); + for (SerializerEntry e : getEntries()) + e.serializer.addNotBeanClasses(classes); + return this; + } + + /** + * Shortcut for calling {@link Serializer#addFilters(Class[])} on all serializers in this group. + * + * @param classes The classes to add bean filters for to the underlying bean context of all serializers in this group. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public SerializerGroup addFilters(Class<?>...classes) throws LockedException { + checkLock(); + for (SerializerEntry e : getEntries()) + e.serializer.addFilters(classes); + return this; + } + + /** + * Shortcut for calling {@link Serializer#addImplClass(Class, Class)} on all serializers in this group. + * + * @param <T> The interface or abstract class type. + * @param interfaceClass The interface or abstract class. + * @param implClass The implementation class. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public <T> SerializerGroup addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException { + checkLock(); + for (SerializerEntry e : getEntries()) + e.serializer.addImplClass(interfaceClass, implClass); + return this; + } + + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + /** + * Locks this group and all serializers in this group. + */ + @Override /* Lockable */ + public SerializerGroup lock() { + super.lock(); + for (SerializerEntry e : getEntries()) + e.serializer.lock(); + return this; + } + + /** + * Clones this group and all serializers in this group. + */ + @Override /* Lockable */ + public SerializerGroup clone() throws CloneNotSupportedException { + SerializerGroup c = (SerializerGroup)super.clone(); + c.entryMap = new HashMap<String,SerializerEntry>(); + c.tempEntries = new LinkedList<SerializerEntry>(); + c.entries = null; + SerializerEntry[] e = getEntries(); + for (int i = e.length-1; i >= 0; i--) + c.append(e[i].serializer.clone()); + return c; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerProperties.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerProperties.class b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerProperties.class new file mode 100755 index 0000000..ace6ce9 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/core/com/ibm/juno/core/serializer/SerializerProperties.class differ
