All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.ObjectList Maven / Gradle / Ivy

There is a newer version: 9.0.1
Show newest version
// ***************************************************************************************************************************
// * 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;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

import org.apache.juneau.json.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.utils.*;

/**
 * Java implementation of a JSON array.
 *
 * 

* An extension of {@link LinkedList}, so all methods available to in that class are also available to this class. * *

* Note that the use of this class is optional. * The serializers will accept any objects that implement the {@link Collection} interface. * But this class provides some useful additional functionality when working with JSON models constructed from Java * Collections Framework objects. * For example, a constructor is provided for converting a JSON array string directly into a {@link List}. * It also contains accessor methods for to avoid common typecasting when accessing elements in a list. * *

Example:
*

* // Construct an empty List * List l = new ObjectList(); * * // Construct a list of objects using various methods * l = new ObjectList().append("foo").append(123).append(true); * l = new ObjectList().append("foo", 123, true); // Equivalent * l = new ObjectList("foo", 123, true); // Equivalent * * // Construct a list of integers from JSON * l = new ObjectList("[1,2,3]"); * * // Construct a list of generic ObjectMap objects from JSON * l = new ObjectList("[{foo:'bar'},{baz:'bing'}]"); * * // Construct a list of integers from XML * String xml = "<array><number>1</number><number>2</number><number>3</number></array>"; * l = new ObjectList(xml, DataFormat.XML); * l = (List)XmlParser.DEFAULT.parse(xml); // Equivalent * l = (List)XmlParser.DEFAULT.parse(Object.class, xml); // Equivalent * l = XmlParser.DEFAULT.parse(List.class, xml); // Equivalent * l = XmlParser.DEFAULT.parse(ObjectList.class, xml); // Equivalent * * // Construct JSON from ObjectList * l = new ObjectList("[{foo:'bar'},{baz:'bing'}]"); * String json = l.toString(); // Produces "[{foo:'bar'},{baz:'bing'}]" * json = l.toString(JsonSerializer.DEFAULT_CONDENSED); // Equivalent * json = JsonSerializer.DEFAULT_CONDENSED.serialize(l); // Equivalent * * // Get one of the entries in the list as an Integer * l = new ObjectList("[1,2,3]"); * Integer i = l.getInt(1); * i = l.get(Integer.class, 1); // Equivalent * * // Get one of the entries in the list as an Float * l = new ObjectList("[1,2,3]"); * Float f = l.getFloat(1); // Returns 2f * f = l.get(Float.class, 1); // Equivalent * * // Same as above, except converted to a String * l = new ObjectList("[1,2,3]"); * String s = l.getString(1); // Returns "2" * s = l.get(String.class, 1); // Equivalent * * // Get one of the entries in the list as a bean (converted to a bean if it isn't already one) * l = new ObjectList("[{name:'John Smith',age:45}]"); * Person p = l.get(Person.class, 0); * * // Iterate over a list of beans using the elements() method * ObjectList ObjectList = new ObjectList("[{name:'John Smith',age:45}]"); * for (Person p : ObjectList.elements(Person.class) { * // Do something with p * } *

* *

* This class is not thread safe. */ public class ObjectList extends LinkedList { private static final long serialVersionUID = 1L; private transient BeanSession session = null; private transient PojoRest pojoRest; /** * An empty read-only ObjectList. */ public static final ObjectList EMPTY_LIST = new ObjectList() { private static final long serialVersionUID = 1L; @Override /* List */ public void add(int location, Object object) { throw new UnsupportedOperationException(); } @Override /* List */ public ListIterator listIterator(final int location) { return Collections.emptyList().listIterator(location); } @Override /* List */ public Object remove(int location) { throw new UnsupportedOperationException(); } @Override /* List */ public Object set(int location, Object object) { throw new UnsupportedOperationException(); } @Override /* List */ public List subList(int start, int end) { return Collections.emptyList().subList(start, end); } }; /** * Construct a JSON array directly from text using the specified parser. * * @param s The string being parsed. * @param p The parser to use to parse the input. * @throws ParseException If the input contains a syntax error or is malformed. */ public ObjectList(CharSequence s, Parser p) throws ParseException { this(p == null ? BeanContext.DEFAULT.createSession() : p.getBeanContext().createSession()); if (p == null) p = JsonParser.DEFAULT; try { if (s != null) p.parseIntoCollection(s, this, session.object()); } catch (ParseException e) { throw new ParseException("Invalid input for {0} parser.\n---start---\n{1}\n---end---", p.getClass().getSimpleName(), s).initCause(e); } } /** * Shortcut for new ObjectList(String,JsonParser.DEFAULT); * * @param s The string being parsed. * @throws ParseException If the input contains a syntax error or is malformed. */ public ObjectList(CharSequence s) throws ParseException { this(s, null); } /** * Construct a JSON array directly from a reader using the specified parser. * * @param r * The reader to read from. * Will automatically be wrapped in a {@link BufferedReader} if it isn't already a BufferedReader. * @param p The parser to use to parse the input. * @throws ParseException If the input contains a syntax error or is malformed. * @throws IOException If a problem occurred trying to read from the reader. */ public ObjectList(Reader r, Parser p) throws ParseException, IOException { this(p == null ? BeanContext.DEFAULT.createSession() : p.getBeanContext().createSession()); parseReader(r, p); } /** * Shortcut for new ObjectList(reader, JsonParser.DEFAULT). * * @param r * The reader to read from. * The reader will be wrapped in a {@link BufferedReader} if it isn't already. * @throws ParseException If the input contains a syntax error or is malformed. * @throws IOException If a problem occurred trying to read from the reader. */ public ObjectList(Reader r) throws ParseException, IOException { this(BeanContext.DEFAULT.createSession()); parseReader(r, JsonParser.DEFAULT); } private void parseReader(Reader r, Parser p) throws ParseException { if (p == null) p = JsonParser.DEFAULT; p.parseIntoCollection(r, this, session.object()); } /** * Construct an empty JSON array. (i.e. an empty {@link LinkedList}). */ public ObjectList() { this(BeanContext.DEFAULT.createSession()); } /** * Construct an empty JSON array with the specified bean context. (i.e. an empty {@link LinkedList}). * * @param session The bean context to associate with this object list for creating beans. */ public ObjectList(BeanSession session) { super(); this.session = session; } /** * Construct a JSON array and fill it with the specified objects. * * @param o A list of objects to add to this list. */ public ObjectList(Object... o) { super(Arrays.asList(o)); } /** * Construct a JSON array and fill it with the specified collection of objects. * * @param c A list of objects to add to this list. */ public ObjectList(Collection c) { super(c); } /** * Override the default bean session used for converting POJOs. * *

* Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases. * *

* Useful if you're serializing/parsing beans with transforms defined. * * @param session The new bean session. * @return This object (for method chaining). */ public ObjectList setBeanSession(BeanSession session) { this.session = session; return this; } /** * Convenience method for adding multiple objects to this list. * * @param o The objects to add to the list. * @return This object (for method chaining). */ public ObjectList append(Object...o) { for (Object o2 : o) add(o2); return this; } /** * Get the entry at the specified index, converted to the specified type. * *

* This is the preferred get method for simple types. * *

Examples:
*

* ObjectList l = new ObjectList("..."); * * // Value converted to a string. * String s = l.get(1, String.class); * * // Value converted to a bean. * MyBean b = l.get(2, MyBean.class); * * // Value converted to a bean array. * MyBean[] ba = l.get(3, MyBean[].class); * * // Value converted to a linked-list of objects. * List l1 = l.get(4, LinkedList.class); * * // Value converted to a map of object keys/values. * Map m1 = l.get(5, TreeMap.class); *

* *

* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. * * @param index The index into this list. * @param type The type of object to convert the entry to. * @param The type of object to convert the entry to. * @return The converted entry. */ public T get(int index, Class type) { return session.convertToType(get(index), type); } /** * Get the entry at the specified index, converted to the specified type. * *

* The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). * *

Examples:
*

* ObjectList l = new ObjectList("..."); * * // Value converted to a linked-list of strings. * List<String> l1 = l.get(1, LinkedList.class, String.class); * * // Value converted to a linked-list of beans. * List<MyBean> l2 = l.get(2, LinkedList.class, MyBean.class); * * // Value converted to a linked-list of linked-lists of strings. * List<List<String>> l3 = l.get(3, LinkedList.class, LinkedList.class, String.class); * * // Value converted to a map of string keys/values. * Map<String,String> m1 = l.get(4, TreeMap.class, String.class, String.class); * * // Value converted to a map containing string keys and values of lists containing beans. * Map<String,List<MyBean>> m2 = l.get(5, TreeMap.class, String.class, List.class, MyBean.class); *

* *

* Collection classes are assumed to be followed by zero or one objects indicating the element type. * *

* Map classes are assumed to be followed by zero or two meta objects indicating the key and value types. * *

* The array can be arbitrarily long to indicate arbitrarily complex data structures. * *

* See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. * * @param index The index into this list. * @param type The type of object to convert the entry to. * @param args The type arguments of the type to convert the entry to. * @param The type of object to convert the entry to. * @return The converted entry. */ public T get(int index, Type type, Type...args) { return session.convertToType(get(index), type, args); } /** * Shortcut for calling get(index, String.class). * * @param index The index. * @return The converted value. */ public String getString(int index) { return get(index, String.class); } /** * Shortcut for calling get(index, Integer.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public Integer getInt(int index) { return get(index, Integer.class); } /** * Shortcut for calling get(index, Boolean.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public Boolean getBoolean(int index) { return get(index, Boolean.class); } /** * Shortcut for calling get(index, Long.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public Long getLong(int index) { return get(index, Long.class); } /** * Shortcut for calling get(index, Map.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public Map getMap(int index) { return get(index, Map.class); } /** * Same as {@link #getMap(int)} except converts the keys and values to the specified types. * * @param index The index. * @param keyType The key type class. * @param valType The value type class. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public Map getMap(int index, Class keyType, Class valType) { return session.convertToType(get(index), Map.class, keyType, valType); } /** * Shortcut for calling get(index, List.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public List getList(int index) { return get(index, List.class); } /** * Same as {@link #getList(int)} except converts the elements to the specified types. * * @param index The index. * @param elementType The element type class. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public List getList(int index, Class elementType) { return session.convertToType(get(index), List.class, elementType); } /** * Shortcut for calling get(index, ObjectMap.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public ObjectMap getObjectMap(int index) { return get(index, ObjectMap.class); } /** * Shortcut for calling get(index, ObjectList.class). * * @param index The index. * @return The converted value. * @throws InvalidDataConversionException If value cannot be converted. */ public ObjectList getObjectList(int index) { return get(index, ObjectList.class); } /** * Same as {@link #get(int,Class) get(int,Class)}, but the key is a slash-delimited path used to traverse entries in * this POJO. * *

* For example, the following code is equivalent: *

*

* ObjectMap m = getObjectMap(); * * // Long way * long l = m.getObjectMap("foo").getObjectList("bar").getObjectMap("0").getLong("baz"); * * // Using this method * long l = m.getAt("foo/bar/0/baz", long.class); *

* *

* This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @param type The class type. * * @param The class type. * @return The value, or null if the entry doesn't exist. */ public T getAt(String path, Class type) { return getPojoRest().get(path, type); } /** * Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections. * * @param path The path to the entry. * @param type The class type. * @param args The class parameter types. * * @param The class type. * @return The value, or null if the entry doesn't exist. */ public T getAt(String path, Type type, Type...args) { return getPojoRest().get(path, type, args); } /** * Same as {@link #set(int,Object) set(int,Object)}, but the key is a slash-delimited path used to traverse entries * in this POJO. * *

* For example, the following code is equivalent: *

*

* ObjectMap m = getObjectMap(); * * // Long way * m.getObjectMap("foo").getObjectList("bar").getObjectMap("0").put("baz", 123); * * // Using this method * m.putAt("foo/bar/0/baz", 123); *

* *

* This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @param o The new value. * @return The previous value, or null if the entry doesn't exist. */ public Object putAt(String path, Object o) { return getPojoRest().put(path, o); } /** * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays. * *

* For example, the following code is equivalent: *

*

* ObjectMap m = getObjectMap(); * * // Long way * m.getObjectMap("foo").getObjectList("bar").append(123); * * // Using this method * m.postAt("foo/bar", 123); *

* *

* This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @param o The new value. * @return The previous value, or null if the entry doesn't exist. */ public Object postAt(String path, Object o) { return getPojoRest().post(path, o); } /** * Similar to {@link #remove(int) remove(int)},but the key is a slash-delimited path used to traverse entries in * this POJO. * *

* For example, the following code is equivalent: *

*

* ObjectMap m = getObjectMap(); * * // Long way * m.getObjectMap("foo").getObjectList("bar").getObjectMap(1).remove("baz"); * * // Using this method * m.deleteAt("foo/bar/0/baz"); *

* *

* This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). * * @param path The path to the entry. * @return The previous value, or null if the entry doesn't exist. */ public Object deleteAt(String path) { return getPojoRest().delete(path); } /** * Creates an {@link Iterable} with elements of the specified child type. * *

* Attempts to convert the child objects to the correct type if they aren't already the correct type. * *

* The next() method on the returned iterator may throw a {@link InvalidDataConversionException} if * the next element cannot be converted to the specified type. * *

* See {@link BeanSession#convertToType(Object, ClassMeta)} for a description of valid conversions. * *

Example:
*

* // Iterate over a list of ObjectMaps. * ObjectList l = new ObjectList("[{foo:'bar'},{baz:123}]"); * for (ObjectMap m : l.elements(ObjectMap.class)) { * // Do something with m. * } * * // Iterate over a list of ints. * ObjectList l = new ObjectList("[1,2,3]"); * for (Integer i : l.elements(Integer.class)) { * // Do something with i. * } * * // Iterate over a list of beans. * // Automatically converts to beans. * ObjectList l = new ObjectList("[{name:'John Smith',age:45}]"); * for (Person p : l.elements(Person.class)) { * // Do something with p. * } *

* * @param The child object type. * @param childType The child object type. * @return A new Iterable object over this list. */ public Iterable elements(final Class childType) { final Iterator i = iterator(); return new Iterable() { @Override /* Iterable */ public Iterator iterator() { return new Iterator() { @Override /* Iterator */ public boolean hasNext() { return i.hasNext(); } @Override /* Iterator */ public E next() { return session.convertToType(i.next(), childType); } @Override /* Iterator */ public void remove() { i.remove(); } }; } }; } /** * Returns the {@link ClassMeta} of the class of the object at the specified index. * * @param index An index into this list, zero-based. * @return The data type of the object at the specified index, or null if the value is null. */ public ClassMeta getClassMeta(int index) { return session.getClassMetaForObject(get(index)); } private PojoRest getPojoRest() { if (pojoRest == null) pojoRest = new PojoRest(this); return pojoRest; } /** * Serialize this array to a string using the specified serializer. * * @param serializer The serializer to use to convert this object to a string. * @return This object as a serialized string. * @throws SerializeException If a problem occurred trying to convert the output. */ public String toString(WriterSerializer serializer) throws SerializeException { return serializer.serialize(this); } /** * Serialize this array to JSON using the {@link JsonSerializer#DEFAULT} serializer. */ @Override /* Object */ public String toString() { try { return this.toString(JsonSerializer.DEFAULT_LAX); } catch (SerializeException e) { return e.getLocalizedMessage(); } } /** * Convenience method for serializing this ObjectList to the specified Writer using the JsonSerializer.DEFAULT * serializer. * * @param w The writer to send the serialized contents of this object. * @throws IOException If a problem occurred trying to write to the writer. * @throws SerializeException If a problem occurred trying to convert the output. */ public void serializeTo(Writer w) throws IOException, SerializeException { JsonSerializer.DEFAULT.serialize(this); } }