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.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.runtime.RuntimeMapPropertyInfo;
  46 import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
  47 import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo;
  48 import com.sun.xml.internal.bind.v2.runtime.Name;
  49 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
  50 import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor;
  51 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.ChildLoader;
  52 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.TagName;
  53 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader;
  54 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Receiver;
  55 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext;
  56 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.State;
  57 
  58 import org.xml.sax.SAXException;
  59 
  60 /**
  61  * @author Kohsuke Kawaguchi
  62  */
  63 final class SingleMapNodeProperty<BeanT,ValueT extends Map> extends PropertyImpl<BeanT> {
  64 
  65     private final Accessor<BeanT,ValueT> acc;
  66     /**
  67      * The tag name that surrounds the whole property.
  68      */
  69     private final Name tagName;
  70     /**
  71      * The tag name that corresponds to the 'entry' element.
  72      */
  73     private final Name entryTag;
  74     private final Name keyTag;
  75     private final Name valueTag;
  76 
  77     private final boolean nillable;
  78 
  79     private JaxBeanInfo keyBeanInfo;
  80     private JaxBeanInfo valueBeanInfo;
  81 
  82     /**
  83      * The implementation class for this property.
  84      * If the property is null, we create an instance of this class.
  85      */
  86     private final Class<? extends ValueT> mapImplClass;
  87 
  88     public SingleMapNodeProperty(JAXBContextImpl context, RuntimeMapPropertyInfo prop) {
  89         super(context, prop);
  90         acc = prop.getAccessor().optimize(context);
  91         this.tagName = context.nameBuilder.createElementName(prop.getXmlName());
  92         this.entryTag = context.nameBuilder.createElementName("","entry");
  93         this.keyTag = context.nameBuilder.createElementName("","key");
  94         this.valueTag = context.nameBuilder.createElementName("","value");
  95         this.nillable = prop.isCollectionNillable();
  96         this.keyBeanInfo = context.getOrCreate(prop.getKeyType());
  97         this.valueBeanInfo = context.getOrCreate(prop.getValueType());
  98 
  99         // infer the implementation class
 100         //noinspection unchecked
 101         Class<ValueT> sig = (Class<ValueT>) Utils.REFLECTION_NAVIGATOR.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<Stack<BeanT>> target = new ThreadLocal<Stack<BeanT>>();
 144         private ThreadLocal<Stack<ValueT>> map = new ThreadLocal<Stack<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                 BeanT target = (BeanT) state.getPrev().getTarget();
 151                 ValueT mapValue = acc.get(target);
 152                 if(mapValue == null)
 153                     mapValue = ClassFactory.create(mapImplClass);
 154                 else
 155                     mapValue.clear();
 156 
 157                 Stack.push(this.target, target);
 158                 Stack.push(map, mapValue);
 159                 state.setTarget(mapValue);
 160             } catch (AccessorException e) {
 161                 // recover from error by setting a dummy Map that receives and discards the values
 162                 handleGenericException(e,true);
 163                 state.setTarget(new HashMap());
 164             }
 165         }
 166 
 167         @Override
 168         public void leaveElement(State state, TagName ea) throws SAXException {
 169             super.leaveElement(state, ea);
 170             try {
 171                 acc.set(Stack.pop(target), Stack.pop(map));
 172             } catch (AccessorException ex) {
 173                 handleGenericException(ex,true);
 174             }
 175         }
 176 
 177         @Override
 178         public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
 179             if(ea.matches(entryTag)) {
 180                 state.setLoader(entryLoader);
 181             } else {
 182                 super.childElement(state,ea);
 183             }
 184         }
 185 
 186         @Override
 187         public Collection<QName> getExpectedChildElements() {
 188             return Collections.singleton(entryTag.toQName());
 189         }
 190     };
 191 
 192     /**
 193      * Handles &lt;entry> and &lt;/entry>.
 194      *
 195      * The target will be set to a {@link Map}.
 196      */
 197     private final Loader entryLoader = new Loader(false) {
 198         @Override
 199         public void startElement(UnmarshallingContext.State state, TagName ea) {
 200             state.setTarget(new Object[2]);  // this is inefficient
 201         }
 202 
 203         @Override
 204         public void leaveElement(UnmarshallingContext.State state, TagName ea) {
 205             Object[] keyValue = (Object[])state.getTarget();
 206             Map map = (Map) state.getPrev().getTarget();
 207             map.put(keyValue[0],keyValue[1]);
 208         }
 209 
 210         @Override
 211         public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
 212             if(ea.matches(keyTag)) {
 213                 state.setLoader(keyLoader);
 214                 state.setReceiver(keyReceiver);
 215                 return;
 216             }
 217             if(ea.matches(valueTag)) {
 218                 state.setLoader(valueLoader);
 219                 state.setReceiver(valueReceiver);
 220                 return;
 221             }
 222             super.childElement(state,ea);
 223         }
 224 
 225         @Override
 226         public Collection<QName> getExpectedChildElements() {
 227             return Arrays.asList(keyTag.toQName(),valueTag.toQName());
 228         }
 229     };
 230 
 231     private static final class ReceiverImpl implements Receiver {
 232         private final int index;
 233         public ReceiverImpl(int index) {
 234             this.index = index;
 235         }
 236         public void receive(UnmarshallingContext.State state, Object o) {
 237             ((Object[])state.getTarget())[index] = o;
 238         }
 239     }
 240 
 241     private static final Receiver keyReceiver = new ReceiverImpl(0);
 242     private static final Receiver valueReceiver = new ReceiverImpl(1);
 243 
 244     @Override
 245     public void serializeBody(BeanT o, XMLSerializer w, Object outerPeer) throws SAXException, AccessorException, IOException, XMLStreamException {
 246         ValueT v = acc.get(o);
 247         if(v!=null) {
 248             bareStartTag(w,tagName,v);
 249             for( Map.Entry e : (Set<Map.Entry>)v.entrySet() ) {
 250                 bareStartTag(w,entryTag,null);
 251 
 252                 Object key = e.getKey();
 253                 if(key!=null) {
 254                     w.startElement(keyTag,key);
 255                     w.childAsXsiType(key,fieldName,keyBeanInfo, false);
 256                     w.endElement();
 257                 }
 258 
 259                 Object value = e.getValue();
 260                 if(value!=null) {
 261                     w.startElement(valueTag,value);
 262                     w.childAsXsiType(value,fieldName,valueBeanInfo, false);
 263                     w.endElement();
 264                 }
 265 
 266                 w.endElement();
 267             }
 268             w.endElement();
 269         } else
 270         if(nillable) {
 271             w.startElement(tagName,null);
 272             w.writeXsiNilTrue();
 273             w.endElement();
 274         }
 275     }
 276 
 277     private void bareStartTag(XMLSerializer w, Name tagName, Object peer) throws IOException, XMLStreamException, SAXException {
 278         w.startElement(tagName,peer);
 279         w.endNamespaceDecls(peer);
 280         w.endAttributes();
 281     }
 282 
 283     @Override
 284     public Accessor getElementPropertyAccessor(String nsUri, String localName) {
 285         if(tagName.equals(nsUri,localName))
 286             return acc;
 287         return null;
 288     }
 289 
 290     private static final class Stack<T> {
 291         private Stack<T> parent;
 292         private T value;
 293 
 294         private Stack(Stack<T> parent, T value) {
 295             this.parent = parent;
 296             this.value = value;
 297         }
 298 
 299         private Stack(T value) {
 300             this.value = value;
 301         }
 302 
 303         private static <T> void push(ThreadLocal<Stack<T>> holder, T value) {
 304             Stack<T> parent = holder.get();
 305             if (parent == null)
 306                 holder.set(new Stack<T>(value));
 307             else
 308                 holder.set(new Stack<T>(parent, value));
 309         }
 310 
 311         private static <T> T pop(ThreadLocal<Stack<T>> holder) {
 312             Stack<T> current = holder.get();
 313             if (current.parent == null)
 314                 holder.remove();
 315             else
 316                 holder.set(current.parent);
 317             return current.value;
 318         }
 319 
 320     }
 321 }