1/*
2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.xml.internal.bind.v2.runtime.reflect;
27
28import java.lang.ref.WeakReference;
29import java.lang.reflect.Array;
30import java.lang.reflect.ParameterizedType;
31import java.lang.reflect.Type;
32import java.util.ArrayList;
33import java.util.Collection;
34import java.util.Collections;
35import java.util.HashMap;
36import java.util.Iterator;
37import java.util.List;
38import java.util.Map;
39import java.util.WeakHashMap;
40import java.util.LinkedList;
41import java.util.HashSet;
42import java.util.TreeSet;
43import java.util.Stack;
44import java.util.concurrent.Callable;
45
46import javax.xml.bind.JAXBException;
47
48import com.sun.istack.internal.SAXException2;
49import com.sun.xml.internal.bind.api.AccessorException;
50import com.sun.xml.internal.bind.v2.ClassFactory;
51import com.sun.xml.internal.bind.v2.TODO;
52import com.sun.xml.internal.bind.v2.model.core.Adapter;
53import com.sun.xml.internal.bind.v2.model.core.ID;
54import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
55import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Patcher;
56import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext;
57import com.sun.xml.internal.bind.v2.runtime.unmarshaller.LocatorEx;
58
59import org.xml.sax.SAXException;
60
61/**
62 * Used to list individual values of a multi-value property, and
63 * to pack individual values into a multi-value property.
64 *
65 * @author Kohsuke Kawaguchi (kk@kohsuke.org)
66 */
67public abstract class Lister<BeanT,PropT,ItemT,PackT> {
68
69    protected Lister() {}
70
71    /**
72     * Iterates values of a multi-value property.
73     *
74     * @param context
75     *      This parameter is used to support ID/IDREF handling.
76     */
77    public abstract ListIterator<ItemT> iterator(PropT multiValueProp, XMLSerializer context);
78
79    /**
80     * Setting values to a multi-value property starts by creating
81     * a transient object called "pack" from the current field.
82     */
83    public abstract PackT startPacking(BeanT bean, Accessor<BeanT, PropT> acc) throws AccessorException;
84
85    /**
86     * Once the {@link #startPacking} is called, you can
87     * add values to the pack by using this method.
88     */
89    public abstract void addToPack( PackT pack, ItemT newValue ) throws AccessorException;
90
91    /**
92     * Finally, call this method to
93     * wraps up the {@code pack}. This method may update the field of
94     * the given bean.
95     */
96    public abstract void endPacking( PackT pack, BeanT bean, Accessor<BeanT,PropT> acc ) throws AccessorException;
97
98    /**
99     * Clears the values of the property.
100     */
101    public abstract void reset(BeanT o,Accessor<BeanT,PropT> acc) throws AccessorException;
102
103
104    /**
105     * Gets a reference to the appropriate {@link Lister} object
106     * if the field is a multi-value field. Otherwise null.
107     *
108     * @param fieldType
109     *      the type of the field that stores the collection
110     * @param idness
111     *      ID-ness of the property.
112     * @param adapter
113     *      adapter to be used for individual items. can be null.
114     */
115    public static <BeanT,PropT,ItemT,PackT>
116        Lister<BeanT,PropT,ItemT,PackT> create(Type fieldType,ID idness, Adapter<Type,Class> adapter) {
117
118        Class rawType = (Class) Utils.REFLECTION_NAVIGATOR.erasure(fieldType);
119        Class itemType;
120
121        Lister l;
122        if( rawType.isArray() ) {
123            itemType = rawType.getComponentType();
124            l = getArrayLister(itemType);
125        } else
126        if( Collection.class.isAssignableFrom(rawType) ) {
127            Type bt = Utils.REFLECTION_NAVIGATOR.getBaseClass(fieldType,Collection.class);
128            if(bt instanceof ParameterizedType)
129                itemType = (Class) Utils.REFLECTION_NAVIGATOR.erasure(((ParameterizedType)bt).getActualTypeArguments()[0]);
130            else
131                itemType = Object.class;
132            l = new CollectionLister(getImplClass(rawType));
133        } else
134            return null;
135
136        if(idness==ID.IDREF)
137            l = new IDREFS(l,itemType);
138
139        if(adapter!=null)
140            l = new AdaptedLister(l,adapter.adapterType);
141
142        return l;
143    }
144
145    private static Class getImplClass(Class<?> fieldType) {
146        return ClassFactory.inferImplClass(fieldType,COLLECTION_IMPL_CLASSES);
147    }
148
149    /**
150     * Cache instances of {@link ArrayLister}s.
151     */
152    private static final Map<Class,WeakReference<Lister>> arrayListerCache =
153        Collections.synchronizedMap(new WeakHashMap<Class,WeakReference<Lister>>());
154
155    /**
156     * Creates a lister for array type.
157     */
158    private static Lister getArrayLister( Class componentType ) {
159        Lister l=null;
160        if(componentType.isPrimitive())
161            l = primitiveArrayListers.get(componentType);
162        else {
163            WeakReference<Lister> wr = arrayListerCache.get(componentType);
164            if(wr!=null)
165                l = wr.get();
166            if(l==null) {
167                l = new ArrayLister(componentType);
168                arrayListerCache.put(componentType,new WeakReference<Lister>(l));
169            }
170        }
171        assert l!=null;
172        return l;
173    }
174
175    /**
176     * {@link Lister} for an array.
177     *
178     * <p>
179     * Array packing is slower, but we expect this to be used less frequently than
180     * the {@link CollectionLister}.
181     */
182    private static final class ArrayLister<BeanT,ItemT> extends Lister<BeanT,ItemT[],ItemT,Pack<ItemT>> {
183
184        private final Class<ItemT> itemType;
185
186        public ArrayLister(Class<ItemT> itemType) {
187            this.itemType = itemType;
188        }
189
190        public ListIterator<ItemT> iterator(final ItemT[] objects, XMLSerializer context) {
191            return new ListIterator<ItemT>() {
192                int idx=0;
193                public boolean hasNext() {
194                    return idx<objects.length;
195                }
196
197                public ItemT next() {
198                    return objects[idx++];
199                }
200            };
201        }
202
203        public Pack startPacking(BeanT current, Accessor<BeanT, ItemT[]> acc) {
204            return new Pack<ItemT>(itemType);
205        }
206
207        public void addToPack(Pack<ItemT> objects, ItemT o) {
208            objects.add(o);
209        }
210
211        public void endPacking( Pack<ItemT> pack, BeanT bean, Accessor<BeanT,ItemT[]> acc ) throws AccessorException {
212            acc.set(bean,pack.build());
213        }
214
215        public void reset(BeanT o,Accessor<BeanT,ItemT[]> acc) throws AccessorException {
216            acc.set(o,(ItemT[])Array.newInstance(itemType,0));
217        }
218
219    }
220
221    public static final class Pack<ItemT> extends ArrayList<ItemT> {
222        private final Class<ItemT> itemType;
223
224        public Pack(Class<ItemT> itemType) {
225            this.itemType = itemType;
226        }
227
228        public ItemT[] build() {
229            return super.toArray( (ItemT[])Array.newInstance(itemType,size()) );
230        }
231    }
232
233    /**
234     * Listers for the primitive type arrays, keyed by their primitive Class object.
235     */
236    /*package*/ static final Map<Class,Lister> primitiveArrayListers = new HashMap<Class,Lister>();
237
238    static {
239        // register primitive array listers
240        PrimitiveArrayListerBoolean.register();
241        PrimitiveArrayListerByte.register();
242        PrimitiveArrayListerCharacter.register();
243        PrimitiveArrayListerDouble.register();
244        PrimitiveArrayListerFloat.register();
245        PrimitiveArrayListerInteger.register();
246        PrimitiveArrayListerLong.register();
247        PrimitiveArrayListerShort.register();
248    }
249
250    /**
251     * {@link Lister} for a collection
252     */
253    public static final class CollectionLister<BeanT,T extends Collection> extends Lister<BeanT,T,Object,T> {
254
255        /**
256         * Sometimes we need to create a new instance of a collection.
257         * This is such an implementation class.
258         */
259        private final Class<? extends T> implClass;
260
261        public CollectionLister(Class<? extends T> implClass) {
262            this.implClass = implClass;
263        }
264
265        public ListIterator iterator(T collection, XMLSerializer context) {
266            final Iterator itr = collection.iterator();
267            return new ListIterator() {
268                public boolean hasNext() {
269                    return itr.hasNext();
270                }
271                public Object next() {
272                    return itr.next();
273                }
274            };
275        }
276
277        public T startPacking(BeanT bean, Accessor<BeanT, T> acc) throws AccessorException {
278            T collection = acc.get(bean);
279            if(collection==null) {
280                collection = ClassFactory.create(implClass);
281                if(!acc.isAdapted())
282                    acc.set(bean,collection);
283            }
284            collection.clear();
285            return collection;
286        }
287
288        public void addToPack(T collection, Object o) {
289            collection.add(o);
290        }
291
292        public void endPacking( T collection, BeanT bean, Accessor<BeanT,T> acc ) throws AccessorException {
293            // this needs to be done in the endPacking, because
294            // sometimes the accessor uses an adapter, and the adapter needs to see
295            // the whole thing.
296
297            // but always doing so causes a problem when this collection property
298            // is getter-only
299
300            // invoke set when possible (see Issue 488)
301            try {
302                if (acc.isAdapted()) {
303                    acc.set(bean,collection);
304                }
305            } catch (AccessorException ae) {
306                if(acc.isAdapted()) throw ae;
307            }
308        }
309
310        public void reset(BeanT bean, Accessor<BeanT, T> acc) throws AccessorException {
311            T collection = acc.get(bean);
312            if(collection == null) {
313                return;
314            }
315            collection.clear();
316        }
317    }
318
319    /**
320     * {@link Lister} for IDREFS.
321     */
322    private static final class IDREFS<BeanT,PropT> extends Lister<BeanT,PropT,String,IDREFS<BeanT,PropT>.Pack> {
323        private final Lister<BeanT,PropT,Object,Object> core;
324        /**
325         * Expected type to which IDREF resolves to.
326         */
327        private final Class itemType;
328
329        public IDREFS(Lister core, Class itemType) {
330            this.core = core;
331            this.itemType = itemType;
332        }
333
334        public ListIterator<String> iterator(PropT prop, XMLSerializer context) {
335            final ListIterator i = core.iterator(prop,context);
336
337            return new IDREFSIterator(i, context);
338        }
339
340        public Pack startPacking(BeanT bean, Accessor<BeanT, PropT> acc) {
341            return new Pack(bean,acc);
342        }
343
344        public void addToPack(Pack pack, String item) {
345            pack.add(item);
346        }
347
348        public void endPacking(Pack pack, BeanT bean, Accessor<BeanT, PropT> acc) {
349        }
350
351        public void reset(BeanT bean, Accessor<BeanT, PropT> acc) throws AccessorException {
352            core.reset(bean,acc);
353        }
354
355        /**
356         * PackT for this lister.
357         */
358        private class Pack implements Patcher {
359            private final BeanT bean;
360            private final List<String> idrefs = new ArrayList<String>();
361            private final UnmarshallingContext context;
362            private final Accessor<BeanT,PropT> acc;
363            private final LocatorEx location;
364
365            public Pack(BeanT bean, Accessor<BeanT,PropT> acc) {
366                this.bean = bean;
367                this.acc = acc;
368                this.context = UnmarshallingContext.getInstance();
369                this.location = new LocatorEx.Snapshot(context.getLocator());
370                context.addPatcher(this);
371            }
372
373            public void add(String item) {
374                idrefs.add(item);
375            }
376
377            /**
378             * Resolves IDREFS and fill in the actual array.
379             */
380            public void run() throws SAXException {
381                try {
382                    Object pack = core.startPacking(bean,acc);
383
384                    for( String id : idrefs ) {
385                        Callable callable = context.getObjectFromId(id,itemType);
386                        Object t;
387
388                        try {
389                            t = (callable!=null) ? callable.call() : null;
390                        } catch (SAXException e) {
391                            throw e;
392                        } catch (Exception e) {
393                            throw new SAXException2(e);
394                        }
395
396                        if(t==null) {
397                            context.errorUnresolvedIDREF(bean,id,location);
398                        } else {
399                            TODO.prototype(); // TODO: check if the type of t is proper.
400                            core.addToPack(pack,t);
401                        }
402                    }
403
404                    core.endPacking(pack,bean,acc);
405                } catch (AccessorException e) {
406                    context.handleError(e);
407                }
408            }
409        }
410    }
411
412    /**
413     * {@link Iterator} for IDREFS lister.
414     *
415     * <p>
416     * Only in ArrayElementProperty we need to get the actual
417     * referenced object. This is a kind of ugly way to make that work.
418     */
419    public static final class IDREFSIterator implements ListIterator<String> {
420        private final ListIterator i;
421        private final XMLSerializer context;
422        private Object last;
423
424        private IDREFSIterator(ListIterator i, XMLSerializer context) {
425            this.i = i;
426            this.context = context;
427        }
428
429        public boolean hasNext() {
430            return i.hasNext();
431        }
432
433        /**
434         * Returns the last referenced object (not just its ID)
435         */
436        public Object last() {
437            return last;
438        }
439
440        public String next() throws SAXException, JAXBException {
441            last = i.next();
442            String id = context.grammar.getBeanInfo(last,true).getId(last,context);
443            if(id==null) {
444                context.errorMissingId(last);
445            }
446            return id;
447        }
448    }
449
450    /**
451     * Gets the special {@link Lister} used to recover from an error.
452     */
453    @SuppressWarnings("unchecked")
454    public static <A,B,C,D> Lister<A,B,C,D> getErrorInstance() {
455        return ERROR;
456    }
457
458    public static final Lister ERROR = new Lister() {
459        public ListIterator iterator(Object o, XMLSerializer context) {
460            return EMPTY_ITERATOR;
461        }
462
463        public Object startPacking(Object o, Accessor accessor) {
464            return null;
465        }
466
467        public void addToPack(Object o, Object o1) {
468        }
469
470        public void endPacking(Object o, Object o1, Accessor accessor) {
471        }
472
473        public void reset(Object o, Accessor accessor) {
474        }
475    };
476
477    private static final ListIterator EMPTY_ITERATOR = new ListIterator() {
478        public boolean hasNext() {
479            return false;
480        }
481
482        public Object next() {
483            throw new IllegalStateException();
484        }
485    };
486
487    private static final Class[] COLLECTION_IMPL_CLASSES = new Class[] {
488        ArrayList.class,
489        LinkedList.class,
490        HashSet.class,
491        TreeSet.class,
492        Stack.class
493    };
494}
495