diff -r 000000000000 -r 373ffda63c9a src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/property/SingleMapNodeProperty.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/property/SingleMapNodeProperty.java Wed Apr 27 01:27:09 2016 +0800 @@ -0,0 +1,292 @@ +/* + * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.xml.internal.bind.v2.runtime.property; + +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.Collection; +import java.util.Collections; +import java.util.Arrays; +import java.util.Set; + +import javax.xml.stream.XMLStreamException; +import javax.xml.namespace.QName; + +import com.sun.xml.internal.bind.api.AccessorException; +import com.sun.xml.internal.bind.v2.ClassFactory; +import com.sun.xml.internal.bind.v2.util.QNameMap; +import com.sun.xml.internal.bind.v2.model.core.PropertyKind; +import com.sun.xml.internal.bind.v2.model.runtime.RuntimeMapPropertyInfo; +import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl; +import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo; +import com.sun.xml.internal.bind.v2.runtime.Name; +import com.sun.xml.internal.bind.v2.runtime.XMLSerializer; +import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor; +import com.sun.xml.internal.bind.v2.runtime.unmarshaller.ChildLoader; +import com.sun.xml.internal.bind.v2.runtime.unmarshaller.TagName; +import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader; +import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Receiver; +import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext; +import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.State; + +import org.xml.sax.SAXException; + +/** + * @author Kohsuke Kawaguchi + */ +final class SingleMapNodeProperty extends PropertyImpl { + + private final Accessor acc; + /** + * The tag name that surrounds the whole property. + */ + private final Name tagName; + /** + * The tag name that corresponds to the 'entry' element. + */ + private final Name entryTag; + private final Name keyTag; + private final Name valueTag; + + private final boolean nillable; + + private JaxBeanInfo keyBeanInfo; + private JaxBeanInfo valueBeanInfo; + + /** + * The implementation class for this property. + * If the property is null, we create an instance of this class. + */ + private final Class mapImplClass; + + public SingleMapNodeProperty(JAXBContextImpl context, RuntimeMapPropertyInfo prop) { + super(context, prop); + acc = prop.getAccessor().optimize(context); + this.tagName = context.nameBuilder.createElementName(prop.getXmlName()); + this.entryTag = context.nameBuilder.createElementName("","entry"); + this.keyTag = context.nameBuilder.createElementName("","key"); + this.valueTag = context.nameBuilder.createElementName("","value"); + this.nillable = prop.isCollectionNillable(); + this.keyBeanInfo = context.getOrCreate(prop.getKeyType()); + this.valueBeanInfo = context.getOrCreate(prop.getValueType()); + + // infer the implementation class + //noinspection unchecked + Class sig = (Class) Utils.REFLECTION_NAVIGATOR.erasure(prop.getRawType()); + mapImplClass = ClassFactory.inferImplClass(sig,knownImplClasses); + // TODO: error check for mapImplClass==null + // what is the error reporting path for this part of the code? + } + + private static final Class[] knownImplClasses = { + HashMap.class, TreeMap.class, LinkedHashMap.class + }; + + public void reset(BeanT bean) throws AccessorException { + acc.set(bean,null); + } + + + /** + * A Map property can never be ID. + */ + public String getIdValue(BeanT bean) { + return null; + } + + public PropertyKind getKind() { + return PropertyKind.MAP; + } + + public void buildChildElementUnmarshallers(UnmarshallerChain chain, QNameMap handlers) { + keyLoader = keyBeanInfo.getLoader(chain.context,true); + valueLoader = valueBeanInfo.getLoader(chain.context,true); + handlers.put(tagName,new ChildLoader(itemsLoader,null)); + } + + private Loader keyLoader; + private Loader valueLoader; + + /** + * Handles <items> and </items>. + * + * The target will be set to a {@link Map}. + */ + private final Loader itemsLoader = new Loader(false) { + + private ThreadLocal target = new ThreadLocal(); + private ThreadLocal map = new ThreadLocal(); + private int depthCounter = 0; // needed to clean ThreadLocals + + @Override + public void startElement(UnmarshallingContext.State state, TagName ea) throws SAXException { + // create or obtain the Map object + try { + target.set((BeanT)state.prev.target); + map.set(acc.get(target.get())); + depthCounter++; + if(map.get() == null) { + map.set(ClassFactory.create(mapImplClass)); + } + map.get().clear(); + state.target = map.get(); + } catch (AccessorException e) { + // recover from error by setting a dummy Map that receives and discards the values + handleGenericException(e,true); + state.target = new HashMap(); + } + } + + @Override + public void leaveElement(State state, TagName ea) throws SAXException { + super.leaveElement(state, ea); + try { + acc.set(target.get(), map.get()); + if (--depthCounter == 0) { + target.remove(); + map.remove(); + } + } catch (AccessorException ex) { + handleGenericException(ex,true); + } + } + + @Override + public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException { + if(ea.matches(entryTag)) { + state.loader = entryLoader; + } else { + super.childElement(state,ea); + } + } + + @Override + public Collection getExpectedChildElements() { + return Collections.singleton(entryTag.toQName()); + } + }; + + /** + * Handles <entry> and </entry>. + * + * The target will be set to a {@link Map}. + */ + private final Loader entryLoader = new Loader(false) { + @Override + public void startElement(UnmarshallingContext.State state, TagName ea) { + state.target = new Object[2]; // this is inefficient + } + + @Override + public void leaveElement(UnmarshallingContext.State state, TagName ea) { + Object[] keyValue = (Object[])state.target; + Map map = (Map) state.prev.target; + map.put(keyValue[0],keyValue[1]); + } + + @Override + public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException { + if(ea.matches(keyTag)) { + state.loader = keyLoader; + state.receiver = keyReceiver; + return; + } + if(ea.matches(valueTag)) { + state.loader = valueLoader; + state.receiver = valueReceiver; + return; + } + super.childElement(state,ea); + } + + @Override + public Collection getExpectedChildElements() { + return Arrays.asList(keyTag.toQName(),valueTag.toQName()); + } + }; + + private static final class ReceiverImpl implements Receiver { + private final int index; + public ReceiverImpl(int index) { + this.index = index; + } + public void receive(UnmarshallingContext.State state, Object o) { + ((Object[])state.target)[index] = o; + } + } + + private static final Receiver keyReceiver = new ReceiverImpl(0); + private static final Receiver valueReceiver = new ReceiverImpl(1); + + @Override + public void serializeBody(BeanT o, XMLSerializer w, Object outerPeer) throws SAXException, AccessorException, IOException, XMLStreamException { + ValueT v = acc.get(o); + if(v!=null) { + bareStartTag(w,tagName,v); + for( Map.Entry e : (Set)v.entrySet() ) { + bareStartTag(w,entryTag,null); + + Object key = e.getKey(); + if(key!=null) { + w.startElement(keyTag,key); + w.childAsXsiType(key,fieldName,keyBeanInfo, false); + w.endElement(); + } + + Object value = e.getValue(); + if(value!=null) { + w.startElement(valueTag,value); + w.childAsXsiType(value,fieldName,valueBeanInfo, false); + w.endElement(); + } + + w.endElement(); + } + w.endElement(); + } else + if(nillable) { + w.startElement(tagName,null); + w.writeXsiNilTrue(); + w.endElement(); + } + } + + private void bareStartTag(XMLSerializer w, Name tagName, Object peer) throws IOException, XMLStreamException, SAXException { + w.startElement(tagName,peer); + w.endNamespaceDecls(peer); + w.endAttributes(); + } + + @Override + public Accessor getElementPropertyAccessor(String nsUri, String localName) { + if(tagName.equals(nsUri,localName)) + return acc; + return null; + } +}