1 /*
   2  * Copyright (c) 1997, 2014, 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 
  26 package com.sun.xml.internal.bind.v2.runtime.reflect;
  27 
  28 import java.lang.ref.WeakReference;
  29 import java.lang.reflect.Array;
  30 import java.lang.reflect.ParameterizedType;
  31 import java.lang.reflect.Type;
  32 import java.util.ArrayList;
  33 import java.util.Collection;
  34 import java.util.Collections;
  35 import java.util.HashMap;
  36 import java.util.Iterator;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.WeakHashMap;
  40 import java.util.LinkedList;
  41 import java.util.HashSet;
  42 import java.util.TreeSet;
  43 import java.util.Stack;
  44 import java.util.concurrent.Callable;
  45 
  46 import javax.xml.bind.JAXBException;
  47 
  48 import com.sun.istack.internal.SAXException2;
  49 import com.sun.xml.internal.bind.api.AccessorException;
  50 import com.sun.xml.internal.bind.v2.ClassFactory;
  51 import com.sun.xml.internal.bind.v2.TODO;
  52 import com.sun.xml.internal.bind.v2.model.core.Adapter;
  53 import com.sun.xml.internal.bind.v2.model.core.ID;
  54 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
  55 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Patcher;
  56 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext;
  57 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.LocatorEx;
  58 
  59 import 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  */
  67 public 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 }