http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java b/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java new file mode 100644 index 0000000..1a1bd79 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/parser/Parser.java @@ -0,0 +1,728 @@ +/*************************************************************************************************************************** + * 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.parser; + +import static org.apache.juneau.internal.StringUtils.*; + +import java.io.*; +import java.lang.reflect.*; +import java.text.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.annotation.*; +import org.apache.juneau.internal.*; +import org.apache.juneau.transform.*; +import org.apache.juneau.transforms.*; +import org.apache.juneau.utils.*; + +/** + * Parent class for all Juneau parsers. + * + * + * <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. + * + * + * <a id='ValidDataConversions'></a><h6 class='topic'>Valid data conversions</h6> + * Parsers can parse any parsable POJO types, as specified in the <a class='doclink' href='../package-summary.html#PojoCategories'>POJO Categories</a>. + * <p> + * Some examples of conversions are shown below... + * </p> + * <table class='styled'> + * <tr> + * <th>Data type</th> + * <th>Class type</th> + * <th>JSON example</th> + * <th>XML example</th> + * <th>Class examples</th> + * </tr> + * <tr> + * <td>object</td> + * <td>Maps, Java beans</td> + * <td class='code'>{name:<js>'John Smith'</js>,age:21}</td> + * <td class='code'><xt><object> + * <name</xt> <xa>type</xa>=<xs>'string'</xs><xt>></xt>John Smith<xt></name> + * <age</xt> <xa>type</xa>=<xs>'number'</xs><xt>></xt>21<xt></age> + * </object></xt></td> + * <td class='code'>HashMap, TreeMap<String,Integer></td> + * </tr> + * <tr> + * <td>array</td> + * <td>Collections, Java arrays</td> + * <td class='code'>[1,2,3]</td> + * <td class='code'><xt><array> + * <number></xt>1<xt></number> + * <number></xt>2<xt></number> + * <number></xt>3<xt></number> + * </array></xt></td> + * <td class='code'>List<Integer>, <jk>int</jk>[], Float[], Set<Person></td> + * </tr> + * <tr> + * <td>number</td> + * <td>Numbers</td> + * <td class='code'>123</td> + * <td class='code'><xt><number></xt>123<xt></number></xt></td> + * <td class='code'>Integer, Long, Float, <jk>int</jk></td> + * </tr> + * <tr> + * <td>boolean</td> + * <td>Booleans</td> + * <td class='code'><jk>true</jk></td> + * <td class='code'><xt><boolean></xt>true<xt></boolean></xt></td> + * <td class='code'>Boolean</td> + * </tr> + * <tr> + * <td>string</td> + * <td>CharSequences</td> + * <td class='code'><js>'foobar'</js></td> + * <td class='code'><xt><string></xt>foobar<xt></string></xt></td> + * <td class='code'>String, StringBuilder</td> + * </tr> + * </table> + * <p> + * In addition, any class types with {@link PojoTransform PojoTransforms} associated with them on the registered + * {@link #getBeanContext() beanContext} can also be passed in. + * <p> + * For example, if the {@link CalendarTransform} transform is used to generalize {@code Calendar} objects to {@code String} objects. When registered + * with this parser, you can construct {@code Calendar} objects from {@code Strings} using the following syntax... + * <p class='bcode'> + * Calendar c = parser.parse(<js>"'Sun Mar 03 04:05:06 EST 2001'"</js>, GregorianCalendar.<jk>class</jk>); + * <p> + * If <code>Object.<jk>class</jk></code> is specified as the target type, then the parser + * automatically determines the data types and generates the following object types... + * </p> + * <table class='styled'> + * <tr><th>JSON type</th><th>Class type</th></tr> + * <tr><td>object</td><td>{@link ObjectMap}</td></tr> + * <tr><td>array</td><td>{@link ObjectList}</td></tr> + * <tr><td>number</td><td>{@link Number} <br>(depending on length and format, could be {@link Integer}, {@link Double}, {@link Float}, etc...)</td></tr> + * <tr><td>boolean</td><td>{@link Boolean}</td></tr> + * <tr><td>string</td><td>{@link String}</td></tr> + * </table> + * + * + * <a id='SupportedTypes'></a><h6 class='topic'>Supported types</h6> + * <p> + * Several of the methods below take {@link Type} parameters to identify the type of + * object to create. Any of the following types can be passed in to these methods... + * </p> + * <ul> + * <li>{@link ClassMeta} + * <li>{@link Class} + * <li>{@link ParameterizedType} + * <li>{@link GenericArrayType} + * </ul> + * <p> + * However, {@code ParameterizedTypes} and {@code GenericArrayTypes} should not contain + * {@link WildcardType WildcardTypes} or {@link TypeVariable TypeVariables}. + * <p> + * Passing in <jk>null</jk> or <code>Object.<jk>class</jk></code> typically signifies that it's up to the parser + * to determine what object type is being parsed parsed based on the rules above. + + * + * @author James Bognar ([email protected]) + */ +public abstract class Parser extends CoreApi { + + /** General serializer properties currently set on this serializer. */ + private final List<ParserListener> listeners = new LinkedList<ParserListener>(); + private final String[] mediaTypes; + private final MediaRange[] mediaRanges; + + // Hidden constructor to force subclass from InputStreamParser or ReaderParser. + Parser() { + Consumes c = ReflectionUtils.getAnnotation(Consumes.class, getClass()); + if (c == null) + throw new RuntimeException(MessageFormat.format("Class ''{0}'' is missing the @Consumes annotation", getClass().getName())); + this.mediaTypes = c.value(); + for (int i = 0; i < mediaTypes.length; i++) { + mediaTypes[i] = mediaTypes[i].toLowerCase(Locale.ENGLISH); + } + + List<MediaRange> l = new LinkedList<MediaRange>(); + for (int i = 0; i < mediaTypes.length; i++) + l.addAll(Arrays.asList(MediaRange.parse(mediaTypes[i]))); + mediaRanges = l.toArray(new MediaRange[l.size()]); + } + + //-------------------------------------------------------------------------------- + // Abstract methods + //-------------------------------------------------------------------------------- + + /** + * Workhorse method. Subclasses are expected to implement this method. + * @param session The runtime session object returned by {@link #createSession(Object, ObjectMap, Method, Object)}. + * If <jk>null</jk>, one will be created using {@link #createSession(Object)}. + * @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. + * For example, when parsing JSON text, it may return a <code>String</code>, <code>Number</code>, <code>ObjectMap</code>, etc... + * + * @param <T> The class type of the object to create. + * @return The parsed object. + * @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. + */ + protected abstract <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception; + + /** + * Returns <jk>true</jk> if this parser subclasses from {@link ReaderParser}. + * + * @return <jk>true</jk> if this parser subclasses from {@link ReaderParser}. + */ + public abstract boolean isReaderParser(); + + //-------------------------------------------------------------------------------- + // Other methods + //-------------------------------------------------------------------------------- + + /** + * Parses the content of the reader and creates an object of the specified type. + * @param session The runtime session returned by {@link #createSession(Object, ObjectMap, Method, Object)}. + * @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. + * For example, when parsing JSON text, it may return a <code>String</code>, <code>Number</code>, <code>ObjectMap</code>, etc... + * + * @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 final <T> T parse(ParserSession session, ClassMeta<T> type) throws ParseException { + try { + return doParse(session, type); + } catch (ParseException e) { + throw e; + } catch (Exception e) { + throw new ParseException(session, e); + } finally { + session.close(); + } + } + + /** + * Parses the content of the reader and creates an object of the specified type. + * <p> + * Equivalent to calling <code>parser.parse(in, type, <jk>null</jk>);</code> + * + * @param input The input. + * <br>Character-based parsers can handle the following input class types: + * <ul> + * <li><jk>null</jk> + * <li>{@link Reader} + * <li>{@link CharSequence} + * <li>{@link InputStream} containing UTF-8 encoded text. + * <li>{@link File} containing system encoded text. + * </ul> + * <br>Stream-based parsers can handle the following input class types: + * <ul> + * <li><jk>null</jk> + * <li>{@link InputStream} + * <li><code><jk>byte</jk>[]</code> + * <li>{@link File} + * </ul> + * @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. + * For example, when parsing JSON text, it may return a <code>String</code>, <code>Number</code>, <code>ObjectMap</code>, etc... + * @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 final <T> T parse(Object input, ClassMeta<T> type) throws ParseException { + ParserSession session = createSession(input); + return parse(session, type); + } + + /** + * Parses input into the specified object type. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; + * MyBean b = p.parse(json, MyBean.<jk>class</jk>); + * </p> + * <p> + * This method equivalent to the following code: + * <p class='bcode'> + * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; + * ClassMeta<MyBean> cm = p.getBeanContext().getClassMeta(MyBean.<jk>class</jk>); + * MyBean b = p.parse(json, cm, <jk>null</jk>); + * </p> + * </dd> + * </dl> + * + * @param <T> The class type of the object to create. + * @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types. + * @param type 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 final <T> T parse(Object input, Class<T> type) throws ParseException { + ClassMeta<T> cm = getBeanContext().getClassMeta(type); + return parse(input, cm); + } + + /** + * Parses input into a map with specified key and value types. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; + * Map<String,MyBean> m = p.parseMap(json, LinkedHashMap.<jk>class</jk>, String.<jk>class</jk>, MyBean.<jk>class</jk>); + * </p> + * <p> + * A simpler approach is often to just extend the map class you want and just use the normal {@link #parse(Object, Class)} method: + * </p> + * <p class='bcode'> + * <jk>public static class</jk> MyMap <jk>extends</jk> LinkedHashMap<String,MyBean> {} + * + * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; + * Map<String,MyBean> m = p.parse(json, MyMap.<jk>class</jk>); + * </p> + * <p> + * This method equivalent to the following code: + * </p> + * <p class='bcode'> + * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; + * ClassMeta<Map<String,MyBean>> cm = p.getBeanContext().getMapClassMeta(LinkedList.<jk>class</jk>, String.<jk>class</jk>, MyBean.<jk>class</jk>); + * Map<String,MyBean> m = p.parse(json, cm, <jk>null</jk>); + * </p> + * </dd> + * </dl> + * + * @param <T> The class type of the object to create. + * @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types. + * @param mapClass The map class type. + * @param keyClass The key class type. + * @param valueClass The value class type. + * @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 final <K,V,T extends Map<K,V>> T parseMap(Object input, Class<T> mapClass, Class<K> keyClass, Class<V> valueClass) throws ParseException { + ClassMeta<T> cm = getBeanContext().getMapClassMeta(mapClass, keyClass, valueClass); + return parse(input, cm); + } + + /** + * Parses input into a collection with a specified element type. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; + * List<MyBean> l = p.parseCollection(json, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); + * </p> + * <p> + * A simpler approach is often to just extend the collection class you want and just use the normal {@link #parse(Object, Class)} method: + * </p> + * <p class='bcode'> + * <jk>public static class</jk> MyBeanCollection <jk>extends</jk> LinkedList<MyBean> {} + * + * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; + * List<MyBean> l = p.parse(json, MyBeanCollection.<jk>class</jk>); + * </p> + * <p> + * This method equivalent to the following code: + * </p> + * <p class='bcode'> + * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; + * ClassMeta<List<MyBean>> cm = p.getBeanContext().getCollectionClassMeta(LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); + * List<MyBean> l = p.parse(json, cm, <jk>null</jk>); + * </p> + * </dd> + * </dl> + * + * @param <T> The class type of the object to create. + * @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types. + * @param collectionClass The collection class type. + * @param entryClass The class type of entries in the collection. + * @return The parsed object. + * @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. + */ + public final <E,T extends Collection<E>> T parseCollection(Object input, Class<T> collectionClass, Class<E> entryClass) throws ParseException, IOException { + ClassMeta<T> cm = getBeanContext().getCollectionClassMeta(collectionClass, entryClass); + return parse(input, cm); + } + + /** + * Create the session object that will be passed in to the parse method. + * <p> + * It's up to implementers to decide what the session object looks like, although typically + * it's going to be a subclass of {@link ParserSession}. + * + * @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types. + * @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. + * @param outer The outer object for instantiating top-level non-static inner classes. + * @return The new context. + */ + public ParserSession createSession(Object input, ObjectMap properties, Method javaMethod, Object outer) { + return new ParserSession(getContext(ParserContext.class), getBeanContext(), input, properties, javaMethod, outer); + } + + /** + * Create a basic session object without overriding properties or specifying <code>javaMethod</code>. + * <p> + * Equivalent to calling <code>createSession(<jk>null</jk>, <jk>null</jk>)</code>. + * + * @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types. + * @return The new context. + */ + protected final ParserSession createSession(Object input) { + return createSession(input, null, null, null); + } + + //-------------------------------------------------------------------------------- + // 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 class='spaced-list'> + * <li>The various character-based constructors in {@link ObjectMap} (e.g. {@link ObjectMap#ObjectMap(CharSequence,Parser)}). + * </ul> + * + * @param <K> The key class type. + * @param <V> The value class type. + * @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types. + * @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 UnsupportedOperationException If not implemented. + */ + public final <K,V> Map<K,V> parseIntoMap(Object input, Map<K,V> m, Type keyType, Type valueType) throws ParseException { + ParserSession session = createSession(input); + try { + return doParseIntoMap(session, m, keyType, valueType); + } catch (ParseException e) { + throw e; + } catch (Exception e) { + throw new ParseException(session, e); + } finally { + session.close(); + } + } + + /** + * Implementation method. + * Default implementation throws an {@link UnsupportedOperationException}. + * @param session The runtime session object returned by {@link #createSession(Object, ObjectMap, Method, Object)}. + * If <jk>null</jk>, one will be created using {@link #createSession(Object)}. + * @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 Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. + */ + protected <K,V> Map<K,V> doParseIntoMap(ParserSession session, Map<K,V> m, Type keyType, Type valueType) throws Exception { + throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); + } + + /** + * Parses the contents of the specified reader and loads the results into the specified collection. + * <p> + * Used in the following locations: + * <ul class='spaced-list'> + * <li>The various character-based constructors in {@link ObjectList} (e.g. {@link ObjectList#ObjectList(CharSequence,Parser)}. + * </ul> + * + * @param <E> The element class type. + * @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types. + * @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 UnsupportedOperationException If not implemented. + */ + public final <E> Collection<E> parseIntoCollection(Object input, Collection<E> c, Type elementType) throws ParseException { + ParserSession session = createSession(input); + try { + return doParseIntoCollection(session, c, elementType); + } catch (ParseException e) { + throw e; + } catch (Exception e) { + throw new ParseException(session, e); + } finally { + session.close(); + } + } + + /** + * Implementation method. + * Default implementation throws an {@link UnsupportedOperationException}. + * @param session The runtime session object returned by {@link #createSession(Object, ObjectMap, Method, Object)}. + * If <jk>null</jk>, one will be created using {@link #createSession(Object)}. + * @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 Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. + */ + protected <E> Collection<E> doParseIntoCollection(ParserSession session, Collection<E> c, Type elementType) throws Exception { + throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); + } + + /** + * 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 class='spaced-list'> + * <li>Used to parse argument strings in the {@link PojoIntrospector#invokeMethod(Method, Reader)} method. + * </ul> + * + * @param input The input. Subclasses can support different input types. + * @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 UnsupportedOperationException If not implemented. + */ + public final Object[] parseArgs(Object input, ClassMeta<?>[] argTypes) throws ParseException { + if (argTypes == null || argTypes.length == 0) + return new Object[0]; + ParserSession session = createSession(input); + try { + return doParseArgs(session, argTypes); + } catch (ParseException e) { + throw e; + } catch (Exception e) { + throw new ParseException(session, e); + } finally { + session.close(); + } + } + + /** + * Implementation method. + * Default implementation throws an {@link UnsupportedOperationException}. + * @param session The runtime session object returned by {@link #createSession(Object, ObjectMap, Method, Object)}. + * If <jk>null</jk>, one will be created using {@link #createSession(Object)}. + * @param argTypes Specifies the type of objects to create for each entry in the array. + * + * @return An array of parsed objects. + * @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. + */ + protected Object[] doParseArgs(ParserSession session, ClassMeta<?>[] argTypes) throws Exception { + throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); + } + + //-------------------------------------------------------------------------------- + // Other methods + //-------------------------------------------------------------------------------- + + /** + * Adds a {@link ParserListener} to this parser to listen for parse events. + * + * @param listener The listener to associate with this parser. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public Parser addListener(ParserListener listener) throws LockedException { + checkLock(); + this.listeners.add(listener); + return this; + } + + /** + * Returns the current parser listeners associated with this parser. + * + * @return The current list of parser listeners. + */ + public List<ParserListener> getListeners() { + return listeners; + } + + /** + * Converts the specified string to the specified type. + * + * @param session The session object. + * @param outer The outer object if we're converting to an inner object that needs to be created within the context of an outer object. + * @param s The string to convert. + * @param type The class type to convert the string to. + * @return The string converted as an object of the specified type. + * @throws Exception If the input contains a syntax error or is malformed, or is not valid for the specified type. + * @param <T> The class type to convert the string to. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected <T> T convertAttrToType(ParserSession session, Object outer, String s, ClassMeta<T> type) throws Exception { + if (s == null) + return null; + + if (type == null) + type = (ClassMeta<T>)object(); + PojoTransform transform = type.getPojoTransform(); + ClassMeta<?> gType = type.getTransformedClassMeta(); + + Object o = s; + if (gType.isChar()) + o = s.charAt(0); + else if (gType.isNumber()) + if (type.canCreateNewInstanceFromNumber(outer)) + o = type.newInstanceFromNumber(outer, parseNumber(s, type.getNewInstanceFromNumberClass())); + else + o = parseNumber(s, (Class<? extends Number>)gType.getInnerClass()); + else if (gType.isBoolean()) + o = Boolean.parseBoolean(s); + else if (! (gType.isCharSequence() || gType.isObject())) { + if (gType.canCreateNewInstanceFromString(outer)) + o = gType.newInstanceFromString(outer, s); + else + throw new ParseException(session, "Invalid conversion from string to class ''{0}''", type); + } + + if (transform != null) + o = transform.normalize(o, type); + + return (T)o; + } + + /** + * Convenience method for calling the {@link ParentProperty @ParentProperty} method on + * the specified object if it exists. + * + * @param cm The class type of the object. + * @param o The object. + * @param parent The parent to set. + * @throws Exception + */ + protected void setParent(ClassMeta<?> cm, Object o, Object parent) throws Exception { + Method m = cm.getParentProperty(); + if (m != null) + m.invoke(o, parent); + } + + /** + * Convenience method for calling the {@link NameProperty @NameProperty} method on + * the specified object if it exists. + * + * @param cm The class type of the object. + * @param o The object. + * @param name The name to set. + * @throws Exception + */ + protected void setName(ClassMeta<?> cm, Object o, Object name) throws Exception { + if (cm != null) { + Method m = cm.getNameProperty(); + if (m != null) + m.invoke(o, name); + } + } + + /** + * Method that gets called when an unknown bean property name is encountered. + * + * @param session The parser session. + * @param propertyName The unknown bean property name. + * @param beanMap The bean that doesn't have the expected property. + * @param line The line number where the property was found. <code>-1</code> if line numbers are not available. + * @param col The column number where the property was found. <code>-1</code> if column numbers are not available. + * @throws ParseException Automatically thrown if {@link BeanContext#BEAN_ignoreUnknownBeanProperties} setting + * on this parser is <jk>false</jk> + * @param <T> The class type of the bean map that doesn't have the expected property. + */ + protected <T> void onUnknownProperty(ParserSession session, String propertyName, BeanMap<T> beanMap, int line, int col) throws ParseException { + if (propertyName.equals("uri") || propertyName.equals("type") || propertyName.equals("_class")) + return; + if (! session.getBeanContext().isIgnoreUnknownBeanProperties()) + throw new ParseException(session, "Unknown property ''{0}'' encountered while trying to parse into class ''{1}''", propertyName, beanMap.getClassMeta()); + if (listeners.size() > 0) + for (ParserListener listener : listeners) + listener.onUnknownProperty(propertyName, beanMap.getClassMeta().getInnerClass(), beanMap.getBean(), line, col); + } + + + /** + * Returns the media types handled based on the value of the {@link Consumes} annotation on the parser 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() { + return mediaTypes; + } + + /** + * Returns the results from {@link #getMediaTypes()} parsed as {@link MediaRange MediaRanges}. + * + * @return The list of media types parsed as ranges. Never <jk>null</jk>. + */ + public MediaRange[] getMediaRanges() { + return mediaRanges; + } + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* CoreApi */ + public Parser setProperty(String property, Object value) throws LockedException { + super.setProperty(property, value); + return this; + } + + @Override /* CoreApi */ + public Parser addNotBeanClasses(Class<?>...classes) throws LockedException { + super.addNotBeanClasses(classes); + return this; + } + + @Override /* CoreApi */ + public Parser addTransforms(Class<?>...classes) throws LockedException { + super.addTransforms(classes); + return this; + } + + @Override /* CoreApi */ + public <T> Parser addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException { + super.addImplClass(interfaceClass, implClass); + return this; + } + + @Override /* CoreApi */ + public Parser setClassLoader(ClassLoader classLoader) throws LockedException { + super.setClassLoader(classLoader); + return this; + } + + @Override /* Lockable */ + public Parser lock() { + super.lock(); + return this; + } + + @Override /* Lockable */ + public Parser clone() throws CloneNotSupportedException { + Parser c = (Parser)super.clone(); + return c; + } +}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java new file mode 100644 index 0000000..c806ffd --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserContext.java @@ -0,0 +1,57 @@ +/*************************************************************************************************************************** + * 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.parser; + +import org.apache.juneau.*; + +/** + * Parent class for all parser contexts. + * + * @author James Bognar ([email protected]) + */ +public class ParserContext extends Context { + + /** + * Debug mode ({@link Boolean}, default=<jk>false</jk>). + * <p> + * Enables the following additional information during parsing: + * <ul class='spaced-list'> + * <li>When bean setters throws exceptions, the exception includes the object stack information + * in order to determine how that method was invoked. + * </ul> + */ + public static final String PARSER_debug = "Parser.debug"; + + /** + * Trim parsed strings ({@link Boolean}, default=<jk>false</jk>). + * <p> + * If <jk>true</jk>, string values will be trimmed of whitespace using {@link String#trim()} before being added to the POJO. + */ + public static final String PARSER_trimStrings = "Parser.trimStrings"; + + + final boolean debug, trimStrings; + + /** + * Constructor. + * <p> + * Typically only called from {@link ContextFactory#getContext(Class)}. + * + * @param cf The factory that created this context. + */ + public ParserContext(ContextFactory cf) { + super(cf); + this.debug = cf.getProperty(PARSER_debug, boolean.class, false); + this.trimStrings = cf.getProperty(PARSER_trimStrings, boolean.class, false); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java new file mode 100644 index 0000000..0e810b8 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserGroup.java @@ -0,0 +1,314 @@ +/*************************************************************************************************************************** + * 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.parser; + +import static org.apache.juneau.internal.ArrayUtils.*; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.*; + +import org.apache.juneau.*; + +/** + * Represents a group of {@link Parser Parsers} that can be looked up by media type. + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Provides the following features: + * <ul class='spaced-list'> + * <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> + * + * + * <h6 class='topic'>Match ordering</h6> + * <p> + * Parsers are matched against <code>Content-Type</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 parsers to be overridden through subsequent calls. + * <p> + * For example, calling <code>g.append(P1.<jk>class</jk>,P2.<jk>class</jk>).append(P3.<jk>class</jk>,P4.<jk>class</jk>)</code> + * will result in the order <code>P3, P4, P1, P2</code>. + * + * + * <h6 class='topic'>Examples</h6> + * <p class='bcode'> + * <jc>// Construct a new parser group</jc> + * ParserGroup g = <jk>new</jk> ParserGroup(); + * + * <jc>// Add some parsers to it</jc> + * g.append(JsonParser.<jk>class</jk>, XmlParser.<jk>class</jk>); + * + * <jc>// Change settings on parsers simultaneously</jc> + * g.setProperty(BeanContext.<jsf>BEAN_beansRequireSerializable</jsf>, <jk>true</jk>) + * .addTransforms(CalendarTransform.ISO8601DT.<jk>class</jk>) + * .lock(); + * + * <jc>// Find the appropriate parser by Content-Type</jc> + * ReaderParser p = (ReaderParser)g.getParser(<js>"text/json"</js>); + * + * <jc>// Parse a bean from JSON</jc> + * String json = <js>"{...}"</js>; + * AddressBook addressBook = p.parse(json, AddressBook.<jk>class</jk>); + * </p> + * + * @author James Bognar ([email protected]) + */ +public final class ParserGroup extends Lockable { + + // Maps media-types to parsers. + private final Map<String,Parser> parserMap = new ConcurrentHashMap<String,Parser>(); + + // Maps Content-Type headers to matching media types. + private final Map<String,String> mediaTypeMappings = new ConcurrentHashMap<String,String>(); + + private final CopyOnWriteArrayList<Parser> parsers = new CopyOnWriteArrayList<Parser>(); + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Lock rl = lock.readLock(), wl = lock.writeLock(); + + + /** + * Registers the specified parsers with this group. + * + * @param p The parsers to append to this group. + * @return This object (for method chaining). + * @throws Exception Thrown if {@link Parser} could not be constructed. + */ + public ParserGroup append(Class<? extends Parser>...p) throws Exception { + checkLock(); + wl.lock(); + try { + for (Class<? extends Parser> c : reverse(p)) { + parserMap.clear(); + mediaTypeMappings.clear(); + try { + append(c); + } catch (NoClassDefFoundError e) { + // Ignore if dependent library not found (e.g. Jena). + System.err.println(e); + } + } + } finally { + wl.unlock(); + } + return this; + } + + /** + * Same as {@link #append(Class[])}, except specify a single class to avoid unchecked compile warnings. + * + * @param p The parser to append to this group. + * @return This object (for method chaining). + * @throws Exception Thrown if {@link Parser} could not be constructed. + */ + public ParserGroup append(Class<? extends Parser> p) throws Exception { + checkLock(); + wl.lock(); + try { + parserMap.clear(); + mediaTypeMappings.clear(); + parsers.add(0, p.newInstance()); + } catch (NoClassDefFoundError e) { + // Ignore if dependent library not found (e.g. Jena). + System.err.println(e); + } finally { + wl.unlock(); + } + return this; + } + + /** + * Returns the parser registered to handle the specified media type. + * <p> + * The media-type string must not contain any parameters such as <js>";charset=X"</js>. + * + * @param mediaType The media-type string (e.g. <js>"text/json"</js>). + * @return The REST parser that handles the specified request content type, or <jk>null</jk> if + * no parser is registered to handle it. + */ + public Parser getParser(String mediaType) { + Parser p = parserMap.get(mediaType); + if (p == null) { + String mt = findMatch(mediaType); + if (mt != null) + p = parserMap.get(mt); + } + return p; + } + + /** + * Searches the group for a parser that can handle the specified <l>Content-Type</l> header value. + * + * @param contentTypeHeader The HTTP <l>Content-Type</l> header value. + * @return The media type registered by one of the parsers that matches the <code>mediaType</code> string, + * or <jk>null</jk> if no media types matched. + */ + public String findMatch(String contentTypeHeader) { + rl.lock(); + try { + String mt = mediaTypeMappings.get(contentTypeHeader); + if (mt != null) + return mt; + + MediaRange[] mr = MediaRange.parse(contentTypeHeader); + if (mr.length == 0) + mr = MediaRange.parse("*/*"); + + for (MediaRange a : mr) { + for (Parser p : parsers) { + for (MediaRange a2 : p.getMediaRanges()) { + if (a.matches(a2)) { + mt = a2.getMediaType(); + mediaTypeMappings.put(contentTypeHeader, mt); + parserMap.put(mt, p); + return mt; + } + } + } + } + return null; + } finally { + rl.unlock(); + } + } + + /** + * Returns the media types that all parsers in this group can handle + * <p> + * Entries are ordered in the same order as the parsers in the group. + * + * @return The list of media types. + */ + public List<String> getSupportedMediaTypes() { + List<String> l = new ArrayList<String>(); + for (Parser p : parsers) + for (String mt : p.getMediaTypes()) + if (! l.contains(mt)) + l.add(mt); + return l; + } + + //-------------------------------------------------------------------------------- + // Convenience methods for setting properties on all parsers. + //-------------------------------------------------------------------------------- + + /** + * Shortcut for calling {@link Parser#setProperty(String, Object)} on all parsers 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 ParserGroup setProperty(String property, Object value) throws LockedException { + checkLock(); + for (Parser p : parsers) + p.setProperty(property, value); + return this; + } + + /** + * Shortcut for calling {@link Parser#setProperties(ObjectMap)} on all parsers 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 ParserGroup setProperties(ObjectMap properties) { + checkLock(); + for (Parser p : parsers) + p.setProperties(properties); + return this; + } + + /** + * Shortcut for calling {@link Parser#addNotBeanClasses(Class[])} on all parsers in this group. + * + * @param classes The classes to specify as not-beans to the underlying bean context of all parsers in this group. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public ParserGroup addNotBeanClasses(Class<?>...classes) throws LockedException { + checkLock(); + for (Parser p : parsers) + p.addNotBeanClasses(classes); + return this; + } + + /** + * Shortcut for calling {@link Parser#addTransforms(Class[])} on all parsers in this group. + * + * @param classes The classes to add bean transforms for to the underlying bean context of all parsers in this group. + * @throws LockedException If {@link #lock()} was called on this object. + * @return This object (for method chaining). + */ + public ParserGroup addTransforms(Class<?>...classes) throws LockedException { + checkLock(); + for (Parser p : parsers) + p.addTransforms(classes); + return this; + } + + /** + * Shortcut for calling {@link Parser#addImplClass(Class, Class)} on all parsers 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> ParserGroup addImplClass(Class<T> interfaceClass, Class<? extends T> implClass) throws LockedException { + checkLock(); + for (Parser p : parsers) + p.addImplClass(interfaceClass, implClass); + return this; + } + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + /** + * Locks this group and all parsers in this group. + */ + @Override /* Lockable */ + public ParserGroup lock() { + super.lock(); + for (Parser p : parsers) + p.lock(); + return this; + } + + /** + * Clones this group and all parsers in this group. + */ + @Override /* Lockable */ + public ParserGroup clone() throws CloneNotSupportedException { + ParserGroup g = new ParserGroup(); + + List<Parser> l = new ArrayList<Parser>(parsers.size()); + for (Parser p : parsers) + l.add(p.clone()); + + g.parsers.addAll(l); + + return g; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java new file mode 100644 index 0000000..b803166 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserListener.java @@ -0,0 +1,44 @@ +/*************************************************************************************************************************** + * 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.parser; + +import org.apache.juneau.*; + +/** + * Class for listening for certain parse events during a document parse. + * <p> + * Listeners can be registered with parsers through the {@link Parser#addListener(ParserListener)} method. + * </p> + * It should be noted that listeners are not automatically copied over to new parsers when a parser is cloned. + * + * @author James Bognar ([email protected]) + */ +public class ParserListener { + + /** + * Gets called when an unknown bean property is detected in a document. + * <p> + * This method only gets called if {@link BeanContext#BEAN_ignoreUnknownBeanProperties} setting is <jk>true</jk>. + * Otherwise, the parser will throw a {@link ParseException}. + * + * @param <T> The class type of the bean. + * @param propertyName The property name encountered in the document. + * @param beanClass The bean class. + * @param bean The bean. + * @param line The line number where the unknown property was found (-1 if parser doesn't support line/column indicators). + * @param col The column number where the unknown property was found (-1 if parser doesn't support line/column indicators). + */ + public <T> void onUnknownProperty(String propertyName, Class<T> beanClass, T bean, int line, int col) { + // Do something with information + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java new file mode 100644 index 0000000..df4dd2f --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserReader.java @@ -0,0 +1,382 @@ +/*************************************************************************************************************************** + * 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.parser; + +import java.io.*; + +import org.apache.juneau.internal.*; + +/** + * Similar to a {@link java.io.PushbackReader} with a pushback buffer of 1 character. + * <p> + * Code is optimized to work with a 1 character buffer. + * <p> + * Additionally keeps track of current line and column number, and provides the ability to set + * mark points and capture characters from the previous mark point. + * <p> + * <b>Warning:</b> Not thread safe. + * + * @author James Bognar ([email protected]) + */ +public class ParserReader extends Reader { + + /** Wrapped reader */ + protected Reader r; + + private char[] buff; // Internal character buffer + private int line = 1; // Current line number + private int column; // Current column number + private int iCurrent = 0; // Current pointer into character buffer + private int iMark = -1; // Mark position in buffer + private int iEnd = 0; // The last good character position in the buffer + private boolean endReached, holesExist; + + ParserReader() {} + + /** + * Constructor for input from a {@link CharSequence}. + * + * @param in The character sequence being read from. + */ + public ParserReader(CharSequence in) { + this.r = new CharSequenceReader(in); + if (in == null) + this.buff = new char[0]; + else + this.buff = new char[in.length() < 1024 ? in.length() : 1024]; + } + + /** + * Constructor for input from a {@link Reader}). + * + * @param r The Reader being wrapped. + */ + public ParserReader(Reader r) { + if (r instanceof ParserReader) + this.r = ((ParserReader)r).r; + else + this.r = r; + this.buff = new char[1024]; + } + + /** + * Returns the current line number position in this reader. + * + * @return The current line number. + */ + public final int getLine() { + return line; + } + + /** + * Returns the current column number position in this reader. + * + * @return The current column number. + */ + public final int getColumn() { + return column; + } + + /** + * Reads a single character. + * Note that this method does NOT process extended unicode characters (i.e. characters + * above 0x10000), but rather returns them as two <jk>char</jk>s. + * Use {@link #readCodePoint()} to ensure proper handling of extended unicode. + * + * @return The character read, or -1 if the end of the stream has been reached. + * @throws IOException If a problem occurred trying to read from the reader. + */ + @Override /* Reader */ + public final int read() throws IOException { + int c = readFromBuff(); + if (c == -1) + return -1; + if (c == '\n') { + line++; + column = 0; + } else { + column++; + } + return c; + } + + /** + * Same as {@link #read()} but detects and combines extended unicode characters (i.e. characters + * above 0x10000). + * + * @return The character read, or -1 if the end of the stream has been reached. + * @throws IOException If a problem occurred trying to read from the reader. + */ + public final int readCodePoint() throws IOException { + int c = read(); + + // Characters that take up 2 chars. + if (c >= 0xd800 && c <= 0xdbff) { + int low = read(); + if (low >= 0xdc00 && low <= 0xdfff) + c = 0x10000 + ((c - 0xd800) << 10) + (low - 0xdc00); + } + + return c; + } + + private final int readFromBuff() throws IOException { + while (iCurrent >= iEnd) { + if (endReached) + return -1; + + // If there's still space at the end of this buffer, fill it. + // Make sure there's at least 2 character spaces free for extended unicode characters. + //if (false) { + if (iEnd+1 < buff.length) { + int x = read(buff, iCurrent, buff.length-iEnd); + if (x == -1) { + endReached = true; + return -1; + } + iEnd += x; + + } else { + // If we're currently marking, then we want to copy from the current mark point + // to the beginning of the buffer and then fill in the remainder of buffer. + if (iMark >= 0) { + + // If we're marking from the beginning of the array, we double the size of the + // buffer. This isn't likely to occur often. + if (iMark == 0) { + char[] buff2 = new char[buff.length<<1]; + System.arraycopy(buff, 0, buff2, 0, buff.length); + buff = buff2; + + // Otherwise, we copy what's currently marked to the beginning of the buffer. + } else { + int copyBuff = iMark; + System.arraycopy(buff, copyBuff, buff, 0, buff.length - copyBuff); + iCurrent -= copyBuff; + iMark -= copyBuff; + } + int expected = buff.length - iCurrent; + + int x = read(buff, iCurrent, expected); + if (x == -1) { + endReached = true; + iEnd = iCurrent; + return -1; + } + iEnd = iCurrent + x; + } else { + // Copy the last 10 chars in the buffer to the beginning of the buffer. + int copyBuff = Math.min(iCurrent, 10); + System.arraycopy(buff, iCurrent-copyBuff, buff, 0, copyBuff); + + // Number of characters we expect to copy on the next read. + int expected = buff.length - copyBuff; + int x = read(buff, copyBuff, expected); + iCurrent = copyBuff; + if (x == -1) { + endReached = true; + iEnd = iCurrent; + return -1; + } + iEnd = iCurrent + x; + } + } + } + return buff[iCurrent++]; + } + + /** + * Start buffering the calls to read() so that the text can be gathered from the mark + * point on calling {@code getFromMarked()}. + */ + public final void mark() { + iMark = iCurrent; + } + + + /** + * Peeks the next character in the stream. + * <p> + * This is equivalent to doing a {@code read()} followed by an {@code unread()}. + * + * @return The peeked character, or (char)-1 if the end of the stream has been reached. + * @throws IOException If a problem occurred trying to read from the reader. + */ + public final int peek() throws IOException { + int c = read(); + if (c != -1) + unread(); + return c; + } + + /** + * Read the specified number of characters off the stream. + * + * @param num The number of characters to read. + * @return The characters packaged as a String. + * @throws IOException If a problem occurred trying to read from the reader. + */ + public final String read(int num) throws IOException { + char[] c = new char[num]; + for (int i = 0; i < num; i++) { + int c2 = read(); + if (c2 == -1) + return new String(c, 0, i); + c[i] = (char)c2; + } + return new String(c); + } + + /** + * Pushes the last read character back into the stream. + * + * @return This object (for method chaining). + * @throws IOException If a problem occurred trying to read from the reader. + */ + public final ParserReader unread() throws IOException { + if (iCurrent <= 0) + throw new IOException("Buffer underflow."); + iCurrent--; + column--; + return this; + } + + /** + * Close this reader and the underlying reader. + * + * @throws IOException If a problem occurred trying to read from the reader. + */ + @Override /* Reader */ + public void close() throws IOException { + r.close(); + } + + /** + * Returns the contents of the reusable character buffer as a string, and + * resets the buffer for next usage. + * + * @return The contents of the reusable character buffer as a string. + */ + public final String getMarked() { + return getMarked(0, 0); + } + + /** + * Same as {@link #getMarked()} except allows you to specify offsets + * into the buffer. + * <p> + * For example, to return the marked string, but trim the first and last characters, + * call the following: + * <p class='bcode'> + * getFromMarked(1, -1); + * </p> + * + * @param offsetStart The offset of the start position. + * @param offsetEnd The offset of the end position. + * @return The contents of the reusable character buffer as a string. + */ + public final String getMarked(int offsetStart, int offsetEnd) { + int offset = 0; + + // Holes are \u00FF 'delete' characters that we need to get rid of now. + if (holesExist) { + for (int i = iMark; i < iCurrent; i++) { + char c = buff[i]; + if (c == 127) + offset++; + else + buff[i-offset] = c; + } + holesExist = false; + } + int start = iMark + offsetStart, len = iCurrent - iMark + offsetEnd - offsetStart - offset; + String s = new String(buff, start, len); + iMark = -1; + return s; + } + + /** + * Trims off the last character in the marking buffer. + * Useful for removing escape characters from sequences. + * + * @return This object (for method chaining). + */ + public final ParserReader delete() { + return delete(1); + } + + /** + * Trims off the specified number of last characters in the marking buffer. + * Useful for removing escape characters from sequences. + * + * @param count The number of characters to delete. + * @return This object (for method chaining). + */ + public final ParserReader delete(int count) { + for (int i = 0; i < count; i++) + buff[iCurrent-i-1] = 127; + holesExist = true; + return this; + } + + /** + * Replaces the last character in the marking buffer with the specified character. + * <code>offset</code> must be at least <code>1</code> for normal characters, and + * <code>2</code> for extended unicode characters in order for the replacement + * to fit into the buffer. + * + * @param c The new character. + * @param offset The offset. + * @return This object (for method chaining). + * @throws IOException + */ + public final ParserReader replace(int c, int offset) throws IOException { + if (c < 0x10000) { + if (offset < 1) + throw new IOException("Buffer underflow."); + buff[iCurrent-offset] = (char)c; + } else { + if (offset < 2) + throw new IOException("Buffer underflow."); + c -= 0x10000; + buff[iCurrent-offset] = (char)(0xd800 + (c >> 10)); + buff[iCurrent-offset+1] = (char)(0xdc00 + (c & 0x3ff)); + offset--; + } + // Fill in the gap with DEL characters. + for (int i = 1; i < offset; i++) + buff[iCurrent-i] = 127; + holesExist |= (offset > 1); + return this; + } + + /** + * Replace the last read character in the buffer with the specified character. + * + * @param c The new character. + * @return This object (for method chaining). + * @throws IOException + */ + public final ParserReader replace(char c) throws IOException { + return replace(c, 1); + } + + /** + * Subclasses can override this method to provide additional filtering. + * Default implementation simply calls the same method on the underlying reader. + */ + @Override /* Reader */ + public int read(char[] cbuf, int off, int len) throws IOException { + return r.read(cbuf, off, len); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java b/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java new file mode 100644 index 0000000..948fad8 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/parser/ParserSession.java @@ -0,0 +1,310 @@ +/*************************************************************************************************************************** + * 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.parser; + +import static org.apache.juneau.parser.ParserContext.*; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.internal.*; + +/** + * Session object that lives for the duration of a single use of {@link Parser}. + * <p> + * This class is NOT thread safe. It is meant to be discarded after one-time use. + * + * @author James Bognar ([email protected]) + */ +public class ParserSession extends Session { + + private static JuneauLogger logger = JuneauLogger.getLogger(ParserSession.class); + + private final boolean debug, trimStrings; + private boolean closed; + private final BeanContext beanContext; + private final List<String> warnings = new LinkedList<String>(); + + private final ObjectMap properties; + private final Method javaMethod; + private final Object outer; + private final Object input; + private InputStream inputStream; + private Reader reader, noCloseReader; + private BeanPropertyMeta<?> currentProperty; + private ClassMeta<?> currentClass; + + /** + * 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 beanContext The bean context being used. + * @param input The input. + * <br>For character-based parsers, this can be any of the following types: + * <ul> + * <li><jk>null</jk> + * <li>{@link Reader} + * <li>{@link CharSequence} + * <li>{@link InputStream} containing UTF-8 encoded text. + * <li>{@link File} containing system encoded text. + * </ul> + * <br>For byte-based parsers, this can be any of the following types: + * <ul> + * <li><jk>null</jk> + * <li>{@link InputStream} + * <li><code><jk>byte</jk>[]</code> + * <li>{@link File} + * </ul> + * @param op The override properties. + * These override any context properties defined in the context. + * @param javaMethod The java method that called this parser, usually the method in a REST servlet. + * @param outer The outer object for instantiating top-level non-static inner classes. + */ + public ParserSession(ParserContext ctx, BeanContext beanContext, Object input, ObjectMap op, Method javaMethod, Object outer) { + super(ctx); + if (op == null || op.isEmpty()) { + debug = ctx.debug; + trimStrings = ctx.trimStrings; + } else { + debug = op.getBoolean(PARSER_debug, ctx.debug); + trimStrings = op.getBoolean(PARSER_trimStrings, ctx.trimStrings); + } + this.beanContext = beanContext; + this.input = input; + this.properties = op; + this.javaMethod = javaMethod; + this.outer = outer; + } + + /** + * Wraps the specified input object inside an input stream. + * Subclasses can override this method to implement their own input streams. + * + * @return The input object wrapped in an input stream, or <jk>null</jk> if the object is null. + * @throws ParseException If object could not be converted to an input stream. + */ + public InputStream getInputStream() throws ParseException { + if (input == null) + return null; + if (input instanceof InputStream) + return (InputStream)input; + if (input instanceof byte[]) + return new ByteArrayInputStream((byte[])input); + if (input instanceof File) + try { + inputStream = new FileInputStream((File)input); + return inputStream; + } catch (FileNotFoundException e) { + throw new ParseException(e); + } + throw new ParseException("Cannot convert object of type {0} to an InputStream.", input.getClass().getName()); + } + + + /** + * Wraps the specified input object inside a reader. + * Subclasses can override this method to implement their own readers. + * + * @return The input object wrapped in a Reader, or <jk>null</jk> if the object is null. + * @throws Exception If object could not be converted to a reader. + */ + public Reader getReader() throws Exception { + if (input == null) + return null; + if (input instanceof Reader) + return (Reader)input; + if (input instanceof CharSequence) { + if (reader == null) + reader = new ParserReader((CharSequence)input); + return reader; + } + if (input instanceof InputStream) { + if (noCloseReader == null) + noCloseReader = new InputStreamReader((InputStream)input, IOUtils.UTF8); + return noCloseReader; + } + if (input instanceof File) { + if (reader == null) + reader = new FileReader((File)input); + return reader; + } + throw new ParseException("Cannot convert object of type {0} to a Reader.", input.getClass().getName()); + } + + /** + * Returns information used to determine at what location in the parse a failure occurred. + * + * @return A map, typically containing something like <code>{line:123,column:456,currentProperty:"foobar"}</code> + */ + public Map<String,Object> getLastLocation() { + Map<String,Object> m = new LinkedHashMap<String,Object>(); + if (currentClass != null) + m.put("currentClass", currentClass.toString(true)); + if (currentProperty != null) + m.put("currentProperty", currentProperty); + return m; + } + + /** + * Returns the raw input object passed into this session. + * + * @return The raw input object passed into this session. + */ + protected Object getInput() { + return input; + } + + /** + * Returns the bean context in use for this session. + * + * @return The bean context in use for this session. + */ + public final BeanContext getBeanContext() { + return beanContext; + } + + /** + * Returns the Java method that invoked this parser. + * <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 parser. + */ + public final Method getJavaMethod() { + return javaMethod; + } + + /** + * Returns the outer object used for instantiating top-level non-static member classes. + * When using the REST API, this is the servlet object. + * + * @return The outer object. + */ + public final Object getOuter() { + return outer; + } + + /** + * Sets the current bean property being parsed for proper error messages. + * @param currentProperty The current property being parsed. + */ + public void setCurrentProperty(BeanPropertyMeta<?> currentProperty) { + this.currentProperty = currentProperty; + } + + /** + * Sets the current class being parsed for proper error messages. + * @param currentClass The current class being parsed. + */ + public void setCurrentClass(ClassMeta<?> currentClass) { + this.currentClass = currentClass; + } + + /** + * Returns the {@link ParserContext#PARSER_debug} setting value for this session. + * + * @return The {@link ParserContext#PARSER_debug} setting value for this session. + */ + public final boolean isDebug() { + return debug; + } + + /** + * Returns the {@link ParserContext#PARSER_trimStrings} setting value for this session. + * + * @return The {@link ParserContext#PARSER_trimStrings} setting value for this session. + */ + public final boolean isTrimStrings() { + return trimStrings; + } + + /** + * Returns the runtime properties associated with this context. + * + * @return The runtime properties associated with this context. + */ + public final ObjectMap getProperties() { + return properties; + } + + /** + * 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) { + logger.warning(msg, args); + msg = args.length == 0 ? msg : String.format(msg, args); + warnings.add((warnings.size() + 1) + ": " + msg); + } + + /** + * Trims the specified object if it's a <code>String</code> and {@link #isTrimStrings()} returns <jk>true</jk>. + * + * @param o The object to trim. + * @return The trimmmed string if it's a string. + */ + @SuppressWarnings("unchecked") + public final <K> K trim(K o) { + if (trimStrings && o instanceof String) + return (K)o.toString().trim(); + return o; + + } + + /** + * Trims the specified string if {@link ParserSession#isTrimStrings()} returns <jk>true</jk>. + * + * @param s The input string to trim. + * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>. + */ + public final String trim(String s) { + if (trimStrings && s != null) + return s.trim(); + return s; + } + + /** + * Perform cleanup on this context object if necessary. + * + * @throws ParseException If called more than once, or in debug mode and warnings occurred. + */ + public void close() throws ParseException { + if (closed) + throw new ParseException("Attempt to close ParserSession more than once."); + + try { + if (inputStream != null) + inputStream.close(); + if (reader != null) + reader.close(); + } catch (IOException e) { + throw new ParseException(e); + } + + if (debug && warnings.size() > 0) + throw new ParseException("Warnings occurred during parsing: \n" + StringUtils.join(warnings, "\n")); + closed = true; + } + + @Override /* Object */ + protected void finalize() throws Throwable { + if (! closed) + throw new RuntimeException("ParserSession was not closed."); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java b/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java new file mode 100644 index 0000000..34af8ff --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/parser/ReaderParser.java @@ -0,0 +1,45 @@ +/*************************************************************************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + ***************************************************************************************************************************/ +package org.apache.juneau.parser; + +import org.apache.juneau.annotation.*; + +/** + * 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(ParserSession, ClassMeta)</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 { + + @Override /* Parser */ + public boolean isReaderParser() { + return true; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/parser/package.html ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/parser/package.html b/juneau-core/src/main/java/org/apache/juneau/parser/package.html new file mode 100644 index 0000000..59c4cc8 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/parser/package.html @@ -0,0 +1,133 @@ +<!DOCTYPE HTML> +<!-- +/*************************************************************************************************************************** + * 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. + * + ***************************************************************************************************************************/ + --> +<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 org.apache.juneau.parser.ReaderParser} + or {@link org.apache.juneau.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 org.apache.juneau.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 class='spaced-list'> + <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 org.apache.juneau.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 org.apache.juneau.parser.ReaderParser} + or {@link org.apache.juneau.parser.InputStreamParser}. In each case, you simply need to implement a single + method and specify a {@link org.apache.juneau.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, ParserSession session) <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 org.apache.juneau.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 org.apache.juneau.json.JsonParser} class. + </p> +</div> + +</body> +</html> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java new file mode 100644 index 0000000..36ab2b8 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextParser.java @@ -0,0 +1,70 @@ +/*************************************************************************************************************************** + * 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.plaintext; + +import org.apache.juneau.*; +import org.apache.juneau.annotation.*; +import org.apache.juneau.internal.*; +import org.apache.juneau.parser.*; +import org.apache.juneau.transform.*; + +/** + * 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 transform if the object class has an {@link PojoTransform PojoTransform<?,String>} transform defined on it. + * + * + * <h6 class='topic'>Configurable properties</h6> + * <p> + * This class has the following properties associated with it: + * <ul> + * <li>{@link ParserContext} + * </ul> + * + * + * @author James Bognar ([email protected]) + */ +@Consumes("text/plain") +public final class PlainTextParser extends ReaderParser { + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Parser */ + protected <T> T doParse(ParserSession session, ClassMeta<T> type) throws Exception { + return session.getBeanContext().convertToType(IOUtils.read(session.getReader()), 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/e6bf97a8/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java new file mode 100644 index 0000000..fd916f3 --- /dev/null +++ b/juneau-core/src/main/java/org/apache/juneau/plaintext/PlainTextSerializer.java @@ -0,0 +1,69 @@ +/*************************************************************************************************************************** + * 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.plaintext; + +import org.apache.juneau.*; +import org.apache.juneau.annotation.*; +import org.apache.juneau.serializer.*; +import org.apache.juneau.transform.*; + +/** + * 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 transform if the object class has an {@link PojoTransform PojoTransform<?,String>} transform defined on it. + * + * + * <h6 class='topic'>Configurable properties</h6> + * <p> + * This class has the following properties associated with it: + * <ul> + * <li>{@link SerializerContext} + * <li>{@link BeanContext} + * </ul> + * + * + * @author James Bognar ([email protected]) + */ +@Produces("text/plain") +public final class PlainTextSerializer extends WriterSerializer { + + //-------------------------------------------------------------------------------- + // Overridden methods + //-------------------------------------------------------------------------------- + + @Override /* Serializer */ + protected void doSerialize(SerializerSession session, Object o) throws Exception { + session.getWriter().write(o == null ? "null" : session.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. + } + } +}
