1 /*
   2  * Copyright (c) 1997, 2011, 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.util.HashMap;
  30 import java.util.LinkedHashMap;
  31 import java.util.Map;
  32 import java.util.TreeMap;
  33 import java.util.Collection;
  34 import java.util.Collections;
  35 import java.util.Arrays;
  36 import java.util.Set;
  37 
  38 import javax.xml.stream.XMLStreamException;
  39 import javax.xml.namespace.QName;
  40 
  41 import com.sun.xml.internal.bind.api.AccessorException;
  42 import com.sun.xml.internal.bind.v2.ClassFactory;
  43 import com.sun.xml.internal.bind.v2.util.QNameMap;
  44 import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
  45 import com.sun.xml.internal.bind.v2.model.nav.ReflectionNavigator;
  46 import com.sun.xml.internal.bind.v2.model.runtime.RuntimeMapPropertyInfo;
  47 import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
  48 import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo;
  49 import com.sun.xml.internal.bind.v2.runtime.Name;
  50 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
  51 import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor;
  52 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.ChildLoader;
  53 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.TagName;
  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.UnmarshallingContext;
  57 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.State;
  58 
  59 import org.xml.sax.SAXException;
  60 
  61 /**
  62  * @author Kohsuke Kawaguchi
  63  */
  64 final class SingleMapNodeProperty<BeanT,ValueT extends Map> extends PropertyImpl<BeanT> {
  65 
  66     private final Accessor<BeanT,ValueT> acc;
  67     /**
  68      * The tag name that surrounds the whole property.
  69      */
  70     private final Name tagName;
  71     /**
  72      * The tag name that corresponds to the 'entry' element.
  73      */
  74     private final Name entryTag;
  75     private final Name keyTag;
  76     private final Name valueTag;
  77 
  78     private final boolean nillable;
  79 
  80     private JaxBeanInfo keyBeanInfo;
  81     private JaxBeanInfo valueBeanInfo;
  82 
  83     /**
  84      * The implementation class for this property.
  85      * If the property is null, we create an instance of this class.
  86      */
  87     private final Class<? extends ValueT> mapImplClass;
  88 
  89     public SingleMapNodeProperty(JAXBContextImpl context, RuntimeMapPropertyInfo prop) {
  90         super(context, prop);
  91         acc = prop.getAccessor().optimize(context);
  92         this.tagName = context.nameBuilder.createElementName(prop.getXmlName());
  93         this.entryTag = context.nameBuilder.createElementName("","entry");
  94         this.keyTag = context.nameBuilder.createElementName("","key");
  95         this.valueTag = context.nameBuilder.createElementName("","value");
  96         this.nillable = prop.isCollectionNillable();
  97         this.keyBeanInfo = context.getOrCreate(prop.getKeyType());
  98         this.valueBeanInfo = context.getOrCreate(prop.getValueType());
  99 
 100         // infer the implementation class
 101         Class<ValueT> sig = ReflectionNavigator.REFLECTION.erasure(prop.getRawType());
 102         mapImplClass = ClassFactory.inferImplClass(sig,knownImplClasses);
 103         // TODO: error check for mapImplClass==null
 104         // what is the error reporting path for this part of the code?
 105     }
 106 
 107     private static final Class[] knownImplClasses = {
 108         HashMap.class, TreeMap.class, LinkedHashMap.class
 109     };
 110 
 111     public void reset(BeanT bean) throws AccessorException {
 112         acc.set(bean,null);
 113     }
 114 
 115 
 116     /**
 117      * A Map property can never be ID.
 118      */
 119     public String getIdValue(BeanT bean) {
 120         return null;
 121     }
 122 
 123     public PropertyKind getKind() {
 124         return PropertyKind.MAP;
 125     }
 126 
 127     public void buildChildElementUnmarshallers(UnmarshallerChain chain, QNameMap<ChildLoader> handlers) {
 128         keyLoader = keyBeanInfo.getLoader(chain.context,true);
 129         valueLoader = valueBeanInfo.getLoader(chain.context,true);
 130         handlers.put(tagName,new ChildLoader(itemsLoader,null));
 131     }
 132 
 133     private Loader keyLoader;
 134     private Loader valueLoader;
 135 
 136     /**
 137      * Handles &lt;items> and &lt;/items>.
 138      *
 139      * The target will be set to a {@link Map}.
 140      */
 141     private final Loader itemsLoader = new Loader(false) {
 142 
 143         private ThreadLocal<BeanT> target = new ThreadLocal<BeanT>();
 144         private ThreadLocal<ValueT> map = new ThreadLocal<ValueT>();
 145 
 146         @Override
 147         public void startElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
 148             // create or obtain the Map object
 149             try {
 150                 target.set((BeanT)state.prev.target);
 151                 map.set(acc.get(target.get()));
 152                 if(map.get() == null) {
 153                     map.set(ClassFactory.create(mapImplClass));
 154                 }
 155                 map.get().clear();
 156                 state.target = map.get();
 157             } catch (AccessorException e) {
 158                 // recover from error by setting a dummy Map that receives and discards the values
 159                 handleGenericException(e,true);
 160                 state.target = new HashMap();
 161             }
 162         }
 163 
 164         @Override
 165         public void leaveElement(State state, TagName ea) throws SAXException {
 166             super.leaveElement(state, ea);
 167             try {
 168                 acc.set(target.get(), map.get());
 169                 target.remove();
 170             } catch (AccessorException ex) {
 171                 handleGenericException(ex,true);
 172             }
 173         }
 174 
 175         @Override
 176         public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
 177             if(ea.matches(entryTag)) {
 178                 state.loader = entryLoader;
 179             } else {
 180                 super.childElement(state,ea);
 181             }
 182         }
 183 
 184         @Override
 185         public Collection<QName> getExpectedChildElements() {
 186             return Collections.singleton(entryTag.toQName());
 187         }
 188     };
 189 
 190     /**
 191      * Handles &lt;entry> and &lt;/entry>.
 192      *
 193      * The target will be set to a {@link Map}.
 194      */
 195     private final Loader entryLoader = new Loader(false) {
 196         @Override
 197         public void startElement(UnmarshallingContext.State state, TagName ea) {
 198             state.target = new Object[2];  // this is inefficient
 199         }
 200 
 201         @Override
 202         public void leaveElement(UnmarshallingContext.State state, TagName ea) {
 203             Object[] keyValue = (Object[])state.target;
 204             Map map = (Map) state.prev.target;
 205             map.put(keyValue[0],keyValue[1]);
 206         }
 207 
 208         @Override
 209         public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
 210             if(ea.matches(keyTag)) {
 211                 state.loader = keyLoader;
 212                 state.receiver = keyReceiver;
 213                 return;
 214             }
 215             if(ea.matches(valueTag)) {
 216                 state.loader = valueLoader;
 217                 state.receiver = valueReceiver;
 218                 return;
 219             }
 220             super.childElement(state,ea);
 221         }
 222 
 223         @Override
 224         public Collection<QName> getExpectedChildElements() {
 225             return Arrays.asList(keyTag.toQName(),valueTag.toQName());
 226         }
 227     };
 228 
 229     private static final class ReceiverImpl implements Receiver {
 230         private final int index;
 231         public ReceiverImpl(int index) {
 232             this.index = index;
 233         }
 234         public void receive(UnmarshallingContext.State state, Object o) {
 235             ((Object[])state.target)[index] = o;
 236         }
 237     }
 238 
 239     private static final Receiver keyReceiver = new ReceiverImpl(0);
 240     private static final Receiver valueReceiver = new ReceiverImpl(1);
 241 
 242     @Override
 243     public void serializeBody(BeanT o, XMLSerializer w, Object outerPeer) throws SAXException, AccessorException, IOException, XMLStreamException {
 244         ValueT v = acc.get(o);
 245         if(v!=null) {
 246             bareStartTag(w,tagName,v);
 247             for( Map.Entry e : (Set<Map.Entry>)v.entrySet() ) {
 248                 bareStartTag(w,entryTag,null);
 249 
 250                 Object key = e.getKey();
 251                 if(key!=null) {
 252                     w.startElement(keyTag,key);
 253                     w.childAsXsiType(key,fieldName,keyBeanInfo, false);
 254                     w.endElement();
 255                 }
 256 
 257                 Object value = e.getValue();
 258                 if(value!=null) {
 259                     w.startElement(valueTag,value);
 260                     w.childAsXsiType(value,fieldName,valueBeanInfo, false);
 261                     w.endElement();
 262                 }
 263 
 264                 w.endElement();
 265             }
 266             w.endElement();
 267         } else
 268         if(nillable) {
 269             w.startElement(tagName,null);
 270             w.writeXsiNilTrue();
 271             w.endElement();
 272         }
 273     }
 274 
 275     private void bareStartTag(XMLSerializer w, Name tagName, Object peer) throws IOException, XMLStreamException, SAXException {
 276         w.startElement(tagName,peer);
 277         w.endNamespaceDecls(peer);
 278         w.endAttributes();
 279     }
 280 
 281     @Override
 282     public Accessor getElementPropertyAccessor(String nsUri, String localName) {
 283         if(tagName.equals(nsUri,localName))
 284             return acc;
 285         return null;
 286     }
 287 }