1 /*
2  * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Distribution License v. 1.0, which is available at
6  * http://www.eclipse.org/org/documents/edl-v10.php.
7  *
8  * SPDX-License-Identifier: BSD-3-Clause
9  */

10
11 package com.sun.xml.bind.v2.runtime.reflect;
12
13 import java.lang.ref.WeakReference;
14 import java.lang.reflect.Array;
15 import java.lang.reflect.ParameterizedType;
16 import java.lang.reflect.Type;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.WeakHashMap;
25 import java.util.LinkedList;
26 import java.util.HashSet;
27 import java.util.TreeSet;
28 import java.util.Stack;
29 import java.util.concurrent.Callable;
30
31 import javax.xml.bind.JAXBException;
32
33 import com.sun.istack.SAXException2;
34 import com.sun.xml.bind.api.AccessorException;
35 import com.sun.xml.bind.v2.ClassFactory;
36 import com.sun.xml.bind.v2.TODO;
37 import com.sun.xml.bind.v2.model.core.Adapter;
38 import com.sun.xml.bind.v2.model.core.ID;
39 import com.sun.xml.bind.v2.runtime.XMLSerializer;
40 import com.sun.xml.bind.v2.runtime.unmarshaller.Patcher;
41 import com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext;
42 import com.sun.xml.bind.v2.runtime.unmarshaller.LocatorEx;
43
44 import org.xml.sax.SAXException;
45
46 /**
47  * Used to list individual values of a multi-value property, and
48  * to pack individual values into a multi-value property.
49  *
50  * @author Kohsuke Kawaguchi (kk@kohsuke.org)
51  */

52 public abstract class Lister<BeanT,PropT,ItemT,PackT> {
53
54     protected Lister() {}
55
56     /**
57      * Iterates values of a multi-value property.
58      *
59      * @param context
60      *      This parameter is used to support ID/IDREF handling.
61      */

62     public abstract ListIterator<ItemT> iterator(PropT multiValueProp, XMLSerializer context);
63
64     /**
65      * Setting values to a multi-value property starts by creating
66      * a transient object called "pack" from the current field.
67      */

68     public abstract PackT startPacking(BeanT bean, Accessor<BeanT, PropT> acc) throws AccessorException;
69
70     /**
71      * Once the {@link #startPacking} is called, you can
72      * add values to the pack by using this method.
73      */

74     public abstract void addToPack( PackT pack, ItemT newValue ) throws AccessorException;
75
76     /**
77      * Finally, call this method to
78      * wraps up the {@code pack}. This method may update the field of
79      * the given bean.
80      */

81     public abstract void endPacking( PackT pack, BeanT bean, Accessor<BeanT,PropT> acc ) throws AccessorException;
82
83     /**
84      * Clears the values of the property.
85      */

86     public abstract void reset(BeanT o,Accessor<BeanT,PropT> acc) throws AccessorException;
87
88
89     /**
90      * Gets a reference to the appropriate {@link Lister} object
91      * if the field is a multi-value field. Otherwise null.
92      *
93      * @param fieldType
94      *      the type of the field that stores the collection
95      * @param idness
96      *      ID-ness of the property.
97      * @param adapter
98      *      adapter to be used for individual items. can be null.
99      */

100     public static <BeanT,PropT,ItemT,PackT>
101         Lister<BeanT,PropT,ItemT,PackT> create(Type fieldType,ID idness, Adapter<Type,Class> adapter) {
102
103         Class rawType = (Class) Utils.REFLECTION_NAVIGATOR.erasure(fieldType);
104         Class itemType;
105
106         Lister l;
107         if( rawType.isArray() ) {
108             itemType = rawType.getComponentType();
109             l = getArrayLister(itemType);
110         } else
111         if( Collection.class.isAssignableFrom(rawType) ) {
112             Type bt = Utils.REFLECTION_NAVIGATOR.getBaseClass(fieldType,Collection.class);
113             if(bt instanceof ParameterizedType)
114                 itemType = (Class) Utils.REFLECTION_NAVIGATOR.erasure(((ParameterizedType)bt).getActualTypeArguments()[0]);
115             else
116                 itemType = Object.class;
117             l = new CollectionLister(getImplClass(rawType));
118         } else
119             return null;
120
121         if(idness==ID.IDREF)
122             l = new IDREFS(l,itemType);
123
124         if(adapter!=null)
125             l = new AdaptedLister(l,adapter.adapterType);
126
127         return l;
128     }
129
130     private static Class getImplClass(Class<?> fieldType) {
131         return ClassFactory.inferImplClass(fieldType,COLLECTION_IMPL_CLASSES);
132     }
133
134     /**
135      * Cache instances of {@link ArrayLister}s.
136      */

137     private static final Map<Class,WeakReference<Lister>> arrayListerCache =
138         Collections.synchronizedMap(new WeakHashMap<Class,WeakReference<Lister>>());
139
140     /**
141      * Creates a lister for array type.
142      */

143     private static Lister getArrayLister( Class componentType ) {
144         Lister l=null;
145         if(componentType.isPrimitive())
146             l = primitiveArrayListers.get(componentType);
147         else {
148             WeakReference<Lister> wr = arrayListerCache.get(componentType);
149             if(wr!=null)
150                 l = wr.get();
151             if(l==null) {
152                 l = new ArrayLister(componentType);
153                 arrayListerCache.put(componentType,new WeakReference<Lister>(l));
154             }
155         }
156         assert l!=null;
157         return l;
158     }
159
160     /**
161      * {@link Lister} for an array.
162      *
163      * <p>
164      * Array packing is slower, but we expect this to be used less frequently than
165      * the {@link CollectionLister}.
166      */

167     private static final class ArrayLister<BeanT,ItemT> extends Lister<BeanT,ItemT[],ItemT,Pack<ItemT>> {
168
169         private final Class<ItemT> itemType;
170
171         public ArrayLister(Class<ItemT> itemType) {
172             this.itemType = itemType;
173         }
174
175         public ListIterator<ItemT> iterator(final ItemT[] objects, XMLSerializer context) {
176             return new ListIterator<ItemT>() {
177                 int idx=0;
178                 public boolean hasNext() {
179                     return idx<objects.length;
180                 }
181
182                 public ItemT next() {
183                     return objects[idx++];
184                 }
185             };
186         }
187
188         public Pack startPacking(BeanT current, Accessor<BeanT, ItemT[]> acc) {
189             return new Pack<ItemT>(itemType);
190         }
191
192         public void addToPack(Pack<ItemT> objects, ItemT o) {
193             objects.add(o);
194         }
195
196         public void endPacking( Pack<ItemT> pack, BeanT bean, Accessor<BeanT,ItemT[]> acc ) throws AccessorException {
197             acc.set(bean,pack.build());
198         }
199
200         public void reset(BeanT o,Accessor<BeanT,ItemT[]> acc) throws AccessorException {
201             acc.set(o,(ItemT[])Array.newInstance(itemType,0));
202         }
203
204     }
205
206     public static final class Pack<ItemT> extends ArrayList<ItemT> {
207         private final Class<ItemT> itemType;
208
209         public Pack(Class<ItemT> itemType) {
210             this.itemType = itemType;
211         }
212
213         public ItemT[] build() {
214             return super.toArray( (ItemT[])Array.newInstance(itemType,size()) );
215         }
216     }
217
218     /**
219      * Listers for the primitive type arrays, keyed by their primitive Class object.
220      */

221     /*package*/ static final Map<Class,Lister> primitiveArrayListers = new HashMap<Class,Lister>();
222
223     static {
224         // register primitive array listers
225         PrimitiveArrayListerBoolean.register();
226         PrimitiveArrayListerByte.register();
227         PrimitiveArrayListerCharacter.register();
228         PrimitiveArrayListerDouble.register();
229         PrimitiveArrayListerFloat.register();
230         PrimitiveArrayListerInteger.register();
231         PrimitiveArrayListerLong.register();
232         PrimitiveArrayListerShort.register();
233     }
234
235     /**
236      * {@link Lister} for a collection
237      */

238     public static final class CollectionLister<BeanT,T extends Collection> extends Lister<BeanT,T,Object,T> {
239
240         /**
241          * Sometimes we need to create a new instance of a collection.
242          * This is such an implementation class.
243          */

244         private final Class<? extends T> implClass;
245
246         public CollectionLister(Class<? extends T> implClass) {
247             this.implClass = implClass;
248         }
249
250         public ListIterator iterator(T collection, XMLSerializer context) {
251             final Iterator itr = collection.iterator();
252             return new ListIterator() {
253                 public boolean hasNext() {
254                     return itr.hasNext();
255                 }
256                 public Object next() {
257                     return itr.next();
258                 }
259             };
260         }
261
262         public T startPacking(BeanT bean, Accessor<BeanT, T> acc) throws AccessorException {
263             T collection = acc.get(bean);
264             if(collection==null) {
265                 collection = ClassFactory.create(implClass);
266                 if(!acc.isAdapted())
267                     acc.set(bean,collection);
268             }
269             collection.clear();
270             return collection;
271         }
272
273         public void addToPack(T collection, Object o) {
274             collection.add(o);
275         }
276
277         public void endPacking( T collection, BeanT bean, Accessor<BeanT,T> acc ) throws AccessorException {
278             // this needs to be done in the endPacking, because
279             // sometimes the accessor uses an adapter, and the adapter needs to see
280             // the whole thing.
281
282             // but always doing so causes a problem when this collection property
283             // is getter-only
284
285             // invoke set when possible (see Issue 488)
286             try {
287                 if (acc.isAdapted()) {
288                     acc.set(bean,collection);
289                 }
290             } catch (AccessorException ae) {
291                 if(acc.isAdapted()) throw ae;
292             }
293         }
294
295         public void reset(BeanT bean, Accessor<BeanT, T> acc) throws AccessorException {
296             T collection = acc.get(bean);
297             if(collection == null) {
298                 return;
299             }
300             collection.clear();
301         }
302     }
303
304     /**
305      * {@link Lister} for IDREFS.
306      */

307     private static final class IDREFS<BeanT,PropT> extends Lister<BeanT,PropT,String,IDREFS<BeanT,PropT>.Pack> {
308         private final Lister<BeanT,PropT,Object,Object> core;
309         /**
310          * Expected type to which IDREF resolves to.
311          */

312         private final Class itemType;
313
314         public IDREFS(Lister core, Class itemType) {
315             this.core = core;
316             this.itemType = itemType;
317         }
318
319         public ListIterator<String> iterator(PropT prop, XMLSerializer context) {
320             final ListIterator i = core.iterator(prop,context);
321
322             return new IDREFSIterator(i, context);
323         }
324
325         public Pack startPacking(BeanT bean, Accessor<BeanT, PropT> acc) {
326             return new Pack(bean,acc);
327         }
328
329         public void addToPack(Pack pack, String item) {
330             pack.add(item);
331         }
332
333         public void endPacking(Pack pack, BeanT bean, Accessor<BeanT, PropT> acc) {
334         }
335
336         public void reset(BeanT bean, Accessor<BeanT, PropT> acc) throws AccessorException {
337             core.reset(bean,acc);
338         }
339
340         /**
341          * PackT for this lister.
342          */

343         private class Pack implements Patcher {
344             private final BeanT bean;
345             private final List<String> idrefs = new ArrayList<String>();
346             private final UnmarshallingContext context;
347             private final Accessor<BeanT,PropT> acc;
348             private final LocatorEx location;
349
350             public Pack(BeanT bean, Accessor<BeanT,PropT> acc) {
351                 this.bean = bean;
352                 this.acc = acc;
353                 this.context = UnmarshallingContext.getInstance();
354                 this.location = new LocatorEx.Snapshot(context.getLocator());
355                 context.addPatcher(this);
356             }
357
358             public void add(String item) {
359                 idrefs.add(item);
360             }
361
362             /**
363              * Resolves IDREFS and fill in the actual array.
364              */

365             public void run() throws SAXException {
366                 try {
367                     Object pack = core.startPacking(bean,acc);
368
369                     for( String id : idrefs ) {
370                         Callable callable = context.getObjectFromId(id,itemType);
371                         Object t;
372
373                         try {
374                             t = (callable!=null) ? callable.call() : null;
375                         } catch (SAXException e) {
376                             throw e;
377                         } catch (Exception e) {
378                             throw new SAXException2(e);
379                         }
380
381                         if(t==null) {
382                             context.errorUnresolvedIDREF(bean,id,location);
383                         } else {
384                             TODO.prototype(); // TODO: check if the type of t is proper.
385                             core.addToPack(pack,t);
386                         }
387                     }
388
389                     core.endPacking(pack,bean,acc);
390                 } catch (AccessorException e) {
391                     context.handleError(e);
392                 }
393             }
394         }
395     }
396
397     /**
398      * {@link Iterator} for IDREFS lister.
399      *
400      * <p>
401      * Only in ArrayElementProperty we need to get the actual
402      * referenced object. This is a kind of ugly way to make that work.
403      */

404     public static final class IDREFSIterator implements ListIterator<String> {
405         private final ListIterator i;
406         private final XMLSerializer context;
407         private Object last;
408
409         private IDREFSIterator(ListIterator i, XMLSerializer context) {
410             this.i = i;
411             this.context = context;
412         }
413
414         public boolean hasNext() {
415             return i.hasNext();
416         }
417
418         /**
419          * Returns the last referenced object (not just its ID)
420          */

421         public Object last() {
422             return last;
423         }
424
425         public String next() throws SAXException, JAXBException {
426             last = i.next();
427             String id = context.grammar.getBeanInfo(last,true).getId(last,context);
428             if(id==null) {
429                 context.errorMissingId(last);
430             }
431             return id;
432         }
433     }
434
435     /**
436      * Gets the special {@link Lister} used to recover from an error.
437      */

438     @SuppressWarnings("unchecked")
439     public static <A,B,C,D> Lister<A,B,C,D> getErrorInstance() {
440         return ERROR;
441     }
442
443     public static final Lister ERROR = new Lister() {
444         public ListIterator iterator(Object o, XMLSerializer context) {
445             return EMPTY_ITERATOR;
446         }
447
448         public Object startPacking(Object o, Accessor accessor) {
449             return null;
450         }
451
452         public void addToPack(Object o, Object o1) {
453         }
454
455         public void endPacking(Object o, Object o1, Accessor accessor) {
456         }
457
458         public void reset(Object o, Accessor accessor) {
459         }
460     };
461
462     private static final ListIterator EMPTY_ITERATOR = new ListIterator() {
463         public boolean hasNext() {
464             return false;
465         }
466
467         public Object next() {
468             throw new IllegalStateException();
469         }
470     };
471
472     private static final Class[] COLLECTION_IMPL_CLASSES = new Class[] {
473         ArrayList.class,
474         LinkedList.class,
475         HashSet.class,
476         TreeSet.class,
477         Stack.class
478     };
479 }
480