// ***************************************************************************************************************************
// * 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);
}
}