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 <items> and </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 <entry> and </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 }