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         private int depthCounter = 0; // needed to clean ThreadLocals
 146 
 147         @Override
 148         public void startElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
 149             // create or obtain the Map object
 150             try {
 151                 target.set((BeanT)state.prev.target);
 152                 map.set(acc.get(target.get()));
 153                 depthCounter++;
 154                 if(map.get() == null) {
 155                     map.set(ClassFactory.create(mapImplClass));
 156                 }
 157                 map.get().clear();
 158                 state.target = map.get();
 159             } catch (AccessorException e) {
 160                 // recover from error by setting a dummy Map that receives and discards the values
 161                 handleGenericException(e,true);
 162                 state.target = new HashMap();
 163             }
 164         }
 165 
 166         @Override
 167         public void leaveElement(State state, TagName ea) throws SAXException {
 168             super.leaveElement(state, ea);
 169             try {
 170                 acc.set(target.get(), map.get());
 171                 if (--depthCounter == 0) {
 172                     target.remove();
 173                     map.remove();
 174                 }
 175             } catch (AccessorException ex) {
 176                 handleGenericException(ex,true);
 177             }
 178         }
 179 
 180         @Override
 181         public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
 182             if(ea.matches(entryTag)) {
 183                 state.loader = entryLoader;
 184             } else {
 185                 super.childElement(state,ea);
 186             }
 187         }
 188 
 189         @Override
 190         public Collection<QName> getExpectedChildElements() {
 191             return Collections.singleton(entryTag.toQName());
 192         }
 193     };
 194 
 195     /**
 196      * Handles &lt;entry> and &lt;/entry>.
 197      *
 198      * The target will be set to a {@link Map}.
 199      */
 200     private final Loader entryLoader = new Loader(false) {
 201         @Override
 202         public void startElement(UnmarshallingContext.State state, TagName ea) {
 203             state.target = new Object[2];  // this is inefficient
 204         }
 205 
 206         @Override
 207         public void leaveElement(UnmarshallingContext.State state, TagName ea) {
 208             Object[] keyValue = (Object[])state.target;
 209             Map map = (Map) state.prev.target;
 210             map.put(keyValue[0],keyValue[1]);
 211         }
 212 
 213         @Override
 214         public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
 215             if(ea.matches(keyTag)) {
 216                 state.loader = keyLoader;
 217                 state.receiver = keyReceiver;
 218                 return;
 219             }
 220             if(ea.matches(valueTag)) {
 221                 state.loader = valueLoader;
 222                 state.receiver = valueReceiver;
 223                 return;
 224             }
 225             super.childElement(state,ea);
 226         }
 227 
 228         @Override
 229         public Collection<QName> getExpectedChildElements() {
 230             return Arrays.asList(keyTag.toQName(),valueTag.toQName());
 231         }
 232     };
 233 
 234     private static final class ReceiverImpl implements Receiver {
 235         private final int index;
 236         public ReceiverImpl(int index) {
 237             this.index = index;
 238         }
 239         public void receive(UnmarshallingContext.State state, Object o) {
 240             ((Object[])state.target)[index] = o;
 241         }
 242     }
 243 
 244     private static final Receiver keyReceiver = new ReceiverImpl(0);
 245     private static final Receiver valueReceiver = new ReceiverImpl(1);
 246 
 247     @Override
 248     public void serializeBody(BeanT o, XMLSerializer w, Object outerPeer) throws SAXException, AccessorException, IOException, XMLStreamException {
 249         ValueT v = acc.get(o);
 250         if(v!=null) {
 251             bareStartTag(w,tagName,v);
 252             for( Map.Entry e : (Set<Map.Entry>)v.entrySet() ) {
 253                 bareStartTag(w,entryTag,null);
 254 
 255                 Object key = e.getKey();
 256                 if(key!=null) {
 257                     w.startElement(keyTag,key);
 258                     w.childAsXsiType(key,fieldName,keyBeanInfo, false);
 259                     w.endElement();
 260                 }
 261 
 262                 Object value = e.getValue();
 263                 if(value!=null) {
 264                     w.startElement(valueTag,value);
 265                     w.childAsXsiType(value,fieldName,valueBeanInfo, false);
 266                     w.endElement();
 267                 }
 268 
 269                 w.endElement();
 270             }
 271             w.endElement();
 272         } else
 273         if(nillable) {
 274             w.startElement(tagName,null);
 275             w.writeXsiNilTrue();
 276             w.endElement();
 277         }
 278     }
 279 
 280     private void bareStartTag(XMLSerializer w, Name tagName, Object peer) throws IOException, XMLStreamException, SAXException {
 281         w.startElement(tagName,peer);
 282         w.endNamespaceDecls(peer);
 283         w.endAttributes();
 284     }
 285 
 286     @Override
 287     public Accessor getElementPropertyAccessor(String nsUri, String localName) {
 288         if(tagName.equals(nsUri,localName))
 289             return acc;
 290         return null;
 291     }
 292 }