1 /*
   2  * Copyright (c) 1997, 2012, 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.property;
  27 
  28 import java.io.IOException;
  29 import java.lang.reflect.Type;
  30 import java.util.HashMap;
  31 import java.util.List;
  32 import java.util.Map;
  33 
  34 import javax.xml.bind.JAXBException;
  35 import javax.xml.stream.XMLStreamException;
  36 
  37 import com.sun.xml.internal.bind.api.AccessorException;
  38 import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
  39 import com.sun.xml.internal.bind.v2.model.core.TypeRef;
  40 import com.sun.xml.internal.bind.v2.model.runtime.RuntimeElementPropertyInfo;
  41 import com.sun.xml.internal.bind.v2.model.runtime.RuntimeTypeRef;
  42 import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
  43 import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo;
  44 import com.sun.xml.internal.bind.v2.runtime.Name;
  45 import com.sun.xml.internal.bind.v2.runtime.RuntimeUtil;
  46 import com.sun.xml.internal.bind.v2.runtime.Transducer;
  47 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
  48 import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor;
  49 import com.sun.xml.internal.bind.v2.runtime.reflect.ListIterator;
  50 import com.sun.xml.internal.bind.v2.runtime.reflect.Lister;
  51 import com.sun.xml.internal.bind.v2.runtime.reflect.NullSafeAccessor;
  52 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.ChildLoader;
  53 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.DefaultValueLoaderDecorator;
  54 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader;
  55 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Receiver;
  56 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.TextLoader;
  57 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader;
  58 import com.sun.xml.internal.bind.v2.util.QNameMap;
  59 
  60 import org.xml.sax.SAXException;
  61 
  62 /**
  63  * {@link Property} implementation for multi-value property that maps to an element.
  64  *
  65  * @author Kohsuke Kawaguchi
  66  */
  67 abstract class ArrayElementProperty<BeanT,ListT,ItemT> extends ArrayERProperty<BeanT,ListT,ItemT> {
  68 
  69     private final Map<Class,TagAndType> typeMap  = new HashMap<Class,TagAndType>();
  70     /**
  71      * Set by the constructor and reset in the {@link #wrapUp()} method.
  72      */
  73     private Map<TypeRef<Type,Class>,JaxBeanInfo> refs = new HashMap<TypeRef<Type, Class>, JaxBeanInfo>();
  74     /**
  75      * Set by the constructor and reset in the {@link #wrapUp()} method.
  76      */
  77     protected RuntimeElementPropertyInfo prop;
  78 
  79     /**
  80      * Tag name used when we see null in the collection. Can be null.
  81      */
  82     private final Name nillableTagName;
  83 
  84     protected ArrayElementProperty(JAXBContextImpl grammar, RuntimeElementPropertyInfo prop) {
  85         super(grammar, prop, prop.getXmlName(), prop.isCollectionNillable());
  86         this.prop = prop;
  87 
  88         List<? extends RuntimeTypeRef> types = prop.getTypes();
  89 
  90         Name n = null;
  91 
  92         for (RuntimeTypeRef typeRef : types) {
  93             Class type = (Class)typeRef.getTarget().getType();
  94             if(type.isPrimitive())
  95                 type = RuntimeUtil.primitiveToBox.get(type);
  96 
  97             JaxBeanInfo beanInfo = grammar.getOrCreate(typeRef.getTarget());
  98             TagAndType tt = new TagAndType(
  99                                 grammar.nameBuilder.createElementName(typeRef.getTagName()),
 100                                 beanInfo);
 101             typeMap.put(type,tt);
 102             refs.put(typeRef,beanInfo);
 103             if(typeRef.isNillable() && n==null)
 104                 n = tt.tagName;
 105         }
 106 
 107         nillableTagName = n;
 108     }
 109 
 110     @Override
 111     public void wrapUp() {
 112         super.wrapUp();
 113         refs = null;
 114         prop = null;    // avoid keeping model objects live
 115     }
 116 
 117     protected void serializeListBody(BeanT beanT, XMLSerializer w, ListT list) throws IOException, XMLStreamException, SAXException, AccessorException {
 118         ListIterator<ItemT> itr = lister.iterator(list, w);
 119 
 120         boolean isIdref = itr instanceof Lister.IDREFSIterator; // UGLY
 121 
 122         while(itr.hasNext()) {
 123             try {
 124                 ItemT item = itr.next();
 125                 if (item != null) {
 126                     Class itemType = item.getClass();
 127                     if(isIdref)
 128                         // This should be the only place where we need to be aware
 129                         // that the iterator is iterating IDREFS.
 130                         itemType = ((Lister.IDREFSIterator)itr).last().getClass();
 131 
 132                     // normally, this returns non-null
 133                     TagAndType tt = typeMap.get(itemType);
 134                     while(tt==null && itemType!=null) {
 135                         // otherwise we'll just have to try the slow way
 136                         itemType = itemType.getSuperclass();
 137                         tt = typeMap.get(itemType);
 138                     }
 139 
 140                     if(tt==null) {
 141                         // item is not of the expected type.
 142 //                        w.reportError(new ValidationEventImpl(ValidationEvent.ERROR,
 143 //                            Messages.UNEXPECTED_JAVA_TYPE.format(
 144 //                                item.getClass().getName(),
 145 //                                getExpectedClassNameList()
 146 //                            ),
 147 //                            w.getCurrentLocation(fieldName)));
 148 //                        continue;
 149 
 150                         // see the similar code in SingleElementNodeProperty.
 151                         // for the purpose of simple type substitution, make it a non-error
 152 
 153                         w.startElement(typeMap.values().iterator().next().tagName,null);
 154                         w.childAsXsiType(item,fieldName,w.grammar.getBeanInfo(Object.class), false);
 155                     } else {
 156                         w.startElement(tt.tagName,null);
 157                         serializeItem(tt.beanInfo,item,w);
 158                     }
 159 
 160                     w.endElement();
 161                 } else {
 162                     if(nillableTagName!=null) {
 163                         w.startElement(nillableTagName,null);
 164                         w.writeXsiNilTrue();
 165                         w.endElement();
 166                     }
 167                 }
 168             } catch (JAXBException e) {
 169                 w.reportError(fieldName,e);
 170                 // recover by ignoring this item
 171             }
 172         }
 173     }
 174 
 175     /**
 176      * Serializes one item of the property.
 177      */
 178     protected abstract void serializeItem(JaxBeanInfo expected, ItemT item, XMLSerializer w) throws SAXException, AccessorException, IOException, XMLStreamException;
 179 
 180 
 181     public void createBodyUnmarshaller(UnmarshallerChain chain, QNameMap<ChildLoader> loaders) {
 182 
 183         // all items go to the same lister,
 184         // so they should share the same offset.
 185         int offset = chain.allocateOffset();
 186         Receiver recv = new ReceiverImpl(offset);
 187 
 188         for (RuntimeTypeRef typeRef : prop.getTypes()) {
 189 
 190             Name tagName = chain.context.nameBuilder.createElementName(typeRef.getTagName());
 191             Loader item = createItemUnmarshaller(chain,typeRef);
 192 
 193             if(typeRef.isNillable() || chain.context.allNillable)
 194                 item = new XsiNilLoader.Array(item);
 195             if(typeRef.getDefaultValue()!=null)
 196                 item = new DefaultValueLoaderDecorator(item,typeRef.getDefaultValue());
 197 
 198             loaders.put(tagName,new ChildLoader(item,recv));
 199         }
 200     }
 201 
 202     public final PropertyKind getKind() {
 203         return PropertyKind.ELEMENT;
 204     }
 205 
 206     /**
 207      * Creates a loader handler that unmarshals the body of the item.
 208      *
 209      * <p>
 210      * This will be sandwiched into <item> ... </item>.
 211      *
 212      * <p>
 213      * When unmarshalling the body of item, the Pack of {@link Lister} is available
 214      * as the handler state.
 215      *
 216      * @param chain
 217      * @param typeRef
 218      */
 219     private Loader createItemUnmarshaller(UnmarshallerChain chain, RuntimeTypeRef typeRef) {
 220         if(PropertyFactory.isLeaf(typeRef.getSource())) {
 221             final Transducer xducer = typeRef.getTransducer();
 222             return new TextLoader(xducer);
 223         } else {
 224             return refs.get(typeRef).getLoader(chain.context,true);
 225         }
 226     }
 227 
 228     public Accessor getElementPropertyAccessor(String nsUri, String localName) {
 229         if(wrapperTagName!=null) {
 230             if(wrapperTagName.equals(nsUri,localName))
 231                 return acc;
 232         } else {
 233             for (TagAndType tt : typeMap.values()) {
 234                 if(tt.tagName.equals(nsUri,localName))
 235                     // when we can't distinguish null and empty list, JAX-WS doesn't want to see
 236                     // null (just like any user apps), but since we are providing a raw accessor,
 237                     // which just grabs the value from the field, we wrap it so that it won't return
 238                     // null.
 239                     return new NullSafeAccessor<BeanT,ListT,Object>(acc,lister);
 240             }
 241         }
 242         return null;
 243     }
 244 }