/* * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.xml.internal.bind.v2.runtime.reflect; import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.LinkedList; import java.util.HashSet; import java.util.TreeSet; import java.util.Stack; import java.util.concurrent.Callable; import javax.xml.bind.JAXBException; import com.sun.istack.internal.SAXException2; import com.sun.xml.internal.bind.api.AccessorException; import com.sun.xml.internal.bind.v2.ClassFactory; import com.sun.xml.internal.bind.v2.TODO; import com.sun.xml.internal.bind.v2.model.core.Adapter; import com.sun.xml.internal.bind.v2.model.core.ID; import com.sun.xml.internal.bind.v2.runtime.XMLSerializer; import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Patcher; import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext; import com.sun.xml.internal.bind.v2.runtime.unmarshaller.LocatorEx; import org.xml.sax.SAXException; /** * Used to list individual values of a multi-value property, and * to pack individual values into a multi-value property. * * @author Kohsuke Kawaguchi (kk@kohsuke.org) */ public abstract class Lister { protected Lister() {} /** * Iterates values of a multi-value property. * * @param context * This parameter is used to support ID/IDREF handling. */ public abstract ListIterator iterator(PropT multiValueProp, XMLSerializer context); /** * Setting values to a multi-value property starts by creating * a transient object called "pack" from the current field. */ public abstract PackT startPacking(BeanT bean, Accessor acc) throws AccessorException; /** * Once the {@link #startPacking} is called, you can * add values to the pack by using this method. */ public abstract void addToPack( PackT pack, ItemT newValue ) throws AccessorException; /** * Finally, call this method to * wraps up the {@code pack}. This method may update the field of * the given bean. */ public abstract void endPacking( PackT pack, BeanT bean, Accessor acc ) throws AccessorException; /** * Clears the values of the property. */ public abstract void reset(BeanT o,Accessor acc) throws AccessorException; /** * Gets a reference to the appropriate {@link Lister} object * if the field is a multi-value field. Otherwise null. * * @param fieldType * the type of the field that stores the collection * @param idness * ID-ness of the property. * @param adapter * adapter to be used for individual items. can be null. */ public static Lister create(Type fieldType,ID idness, Adapter adapter) { Class rawType = (Class) Utils.REFLECTION_NAVIGATOR.erasure(fieldType); Class itemType; Lister l; if( rawType.isArray() ) { itemType = rawType.getComponentType(); l = getArrayLister(itemType); } else if( Collection.class.isAssignableFrom(rawType) ) { Type bt = Utils.REFLECTION_NAVIGATOR.getBaseClass(fieldType,Collection.class); if(bt instanceof ParameterizedType) itemType = (Class) Utils.REFLECTION_NAVIGATOR.erasure(((ParameterizedType)bt).getActualTypeArguments()[0]); else itemType = Object.class; l = new CollectionLister(getImplClass(rawType)); } else return null; if(idness==ID.IDREF) l = new IDREFS(l,itemType); if(adapter!=null) l = new AdaptedLister(l,adapter.adapterType); return l; } private static Class getImplClass(Class fieldType) { return ClassFactory.inferImplClass(fieldType,COLLECTION_IMPL_CLASSES); } /** * Cache instances of {@link ArrayLister}s. */ private static final Map> arrayListerCache = Collections.synchronizedMap(new WeakHashMap>()); /** * Creates a lister for array type. */ private static Lister getArrayLister( Class componentType ) { Lister l=null; if(componentType.isPrimitive()) l = primitiveArrayListers.get(componentType); else { WeakReference wr = arrayListerCache.get(componentType); if(wr!=null) l = wr.get(); if(l==null) { l = new ArrayLister(componentType); arrayListerCache.put(componentType,new WeakReference(l)); } } assert l!=null; return l; } /** * {@link Lister} for an array. * *

* Array packing is slower, but we expect this to be used less frequently than * the {@link CollectionLister}. */ private static final class ArrayLister extends Lister> { private final Class itemType; public ArrayLister(Class itemType) { this.itemType = itemType; } public ListIterator iterator(final ItemT[] objects, XMLSerializer context) { return new ListIterator() { int idx=0; public boolean hasNext() { return idx acc) { return new Pack(itemType); } public void addToPack(Pack objects, ItemT o) { objects.add(o); } public void endPacking( Pack pack, BeanT bean, Accessor acc ) throws AccessorException { acc.set(bean,pack.build()); } public void reset(BeanT o,Accessor acc) throws AccessorException { acc.set(o,(ItemT[])Array.newInstance(itemType,0)); } } public static final class Pack extends ArrayList { private final Class itemType; public Pack(Class itemType) { this.itemType = itemType; } public ItemT[] build() { return super.toArray( (ItemT[])Array.newInstance(itemType,size()) ); } } /** * Listers for the primitive type arrays, keyed by their primitive Class object. */ /*package*/ static final Map primitiveArrayListers = new HashMap(); static { // register primitive array listers PrimitiveArrayListerBoolean.register(); PrimitiveArrayListerByte.register(); PrimitiveArrayListerCharacter.register(); PrimitiveArrayListerDouble.register(); PrimitiveArrayListerFloat.register(); PrimitiveArrayListerInteger.register(); PrimitiveArrayListerLong.register(); PrimitiveArrayListerShort.register(); } /** * {@link Lister} for a collection */ public static final class CollectionLister extends Lister { /** * Sometimes we need to create a new instance of a collection. * This is such an implementation class. */ private final Class implClass; public CollectionLister(Class implClass) { this.implClass = implClass; } public ListIterator iterator(T collection, XMLSerializer context) { final Iterator itr = collection.iterator(); return new ListIterator() { public boolean hasNext() { return itr.hasNext(); } public Object next() { return itr.next(); } }; } public T startPacking(BeanT bean, Accessor acc) throws AccessorException { T collection = acc.get(bean); if(collection==null) { collection = ClassFactory.create(implClass); if(!acc.isAdapted()) acc.set(bean,collection); } collection.clear(); return collection; } public void addToPack(T collection, Object o) { collection.add(o); } public void endPacking( T collection, BeanT bean, Accessor acc ) throws AccessorException { // this needs to be done in the endPacking, because // sometimes the accessor uses an adapter, and the adapter needs to see // the whole thing. // but always doing so causes a problem when this collection property // is getter-only // invoke set when possible (see Issue 488) try { if (acc.isAdapted()) { acc.set(bean,collection); } } catch (AccessorException ae) { if(acc.isAdapted()) throw ae; } } public void reset(BeanT bean, Accessor acc) throws AccessorException { T collection = acc.get(bean); if(collection == null) { return; } collection.clear(); } } /** * {@link Lister} for IDREFS. */ private static final class IDREFS extends Lister.Pack> { private final Lister core; /** * Expected type to which IDREF resolves to. */ private final Class itemType; public IDREFS(Lister core, Class itemType) { this.core = core; this.itemType = itemType; } public ListIterator iterator(PropT prop, XMLSerializer context) { final ListIterator i = core.iterator(prop,context); return new IDREFSIterator(i, context); } public Pack startPacking(BeanT bean, Accessor acc) { return new Pack(bean,acc); } public void addToPack(Pack pack, String item) { pack.add(item); } public void endPacking(Pack pack, BeanT bean, Accessor acc) { } public void reset(BeanT bean, Accessor acc) throws AccessorException { core.reset(bean,acc); } /** * PackT for this lister. */ private class Pack implements Patcher { private final BeanT bean; private final List idrefs = new ArrayList(); private final UnmarshallingContext context; private final Accessor acc; private final LocatorEx location; public Pack(BeanT bean, Accessor acc) { this.bean = bean; this.acc = acc; this.context = UnmarshallingContext.getInstance(); this.location = new LocatorEx.Snapshot(context.getLocator()); context.addPatcher(this); } public void add(String item) { idrefs.add(item); } /** * Resolves IDREFS and fill in the actual array. */ public void run() throws SAXException { try { Object pack = core.startPacking(bean,acc); for( String id : idrefs ) { Callable callable = context.getObjectFromId(id,itemType); Object t; try { t = (callable!=null) ? callable.call() : null; } catch (SAXException e) { throw e; } catch (Exception e) { throw new SAXException2(e); } if(t==null) { context.errorUnresolvedIDREF(bean,id,location); } else { TODO.prototype(); // TODO: check if the type of t is proper. core.addToPack(pack,t); } } core.endPacking(pack,bean,acc); } catch (AccessorException e) { context.handleError(e); } } } } /** * {@link Iterator} for IDREFS lister. * *

* Only in ArrayElementProperty we need to get the actual * referenced object. This is a kind of ugly way to make that work. */ public static final class IDREFSIterator implements ListIterator { private final ListIterator i; private final XMLSerializer context; private Object last; private IDREFSIterator(ListIterator i, XMLSerializer context) { this.i = i; this.context = context; } public boolean hasNext() { return i.hasNext(); } /** * Returns the last referenced object (not just its ID) */ public Object last() { return last; } public String next() throws SAXException, JAXBException { last = i.next(); String id = context.grammar.getBeanInfo(last,true).getId(last,context); if(id==null) { context.errorMissingId(last); } return id; } } /** * Gets the special {@link Lister} used to recover from an error. */ @SuppressWarnings("unchecked") public static Lister getErrorInstance() { return ERROR; } public static final Lister ERROR = new Lister() { public ListIterator iterator(Object o, XMLSerializer context) { return EMPTY_ITERATOR; } public Object startPacking(Object o, Accessor accessor) { return null; } public void addToPack(Object o, Object o1) { } public void endPacking(Object o, Object o1, Accessor accessor) { } public void reset(Object o, Accessor accessor) { } }; private static final ListIterator EMPTY_ITERATOR = new ListIterator() { public boolean hasNext() { return false; } public Object next() { throw new IllegalStateException(); } }; private static final Class[] COLLECTION_IMPL_CLASSES = new Class[] { ArrayList.class, LinkedList.class, HashSet.class, TreeSet.class, Stack.class }; }