001: /*
002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.xml.internal.bind.v2.runtime.property;
027:
028: import java.io.IOException;
029: import java.util.HashMap;
030: import java.util.LinkedHashMap;
031: import java.util.Map;
032: import java.util.TreeMap;
033: import java.util.Collection;
034: import java.util.Collections;
035: import java.util.Arrays;
036: import java.util.Set;
037:
038: import javax.xml.stream.XMLStreamException;
039: import javax.xml.namespace.QName;
040:
041: import com.sun.xml.internal.bind.api.AccessorException;
042: import com.sun.xml.internal.bind.v2.ClassFactory;
043: import com.sun.xml.internal.bind.v2.util.QNameMap;
044: import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
045: import com.sun.xml.internal.bind.v2.model.nav.ReflectionNavigator;
046: import com.sun.xml.internal.bind.v2.model.runtime.RuntimeMapPropertyInfo;
047: import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
048: import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo;
049: import com.sun.xml.internal.bind.v2.runtime.Name;
050: import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
051: import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor;
052: import com.sun.xml.internal.bind.v2.runtime.unmarshaller.ChildLoader;
053: import com.sun.xml.internal.bind.v2.runtime.unmarshaller.TagName;
054: import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader;
055: import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Receiver;
056: import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext;
057: import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiTypeLoader;
058:
059: import org.xml.sax.SAXException;
060:
061: /**
062: * @author Kohsuke Kawaguchi
063: */
064: final class SingleMapNodeProperty<BeanT, ValueT extends Map> extends
065: PropertyImpl<BeanT> {
066:
067: private final Accessor<BeanT, ValueT> acc;
068: /**
069: * The tag name that surrounds the whole property.
070: */
071: private final Name tagName;
072: /**
073: * The tag name that corresponds to the 'entry' element.
074: */
075: private final Name entryTag;
076: private final Name keyTag;
077: private final Name valueTag;
078:
079: private final boolean nillable;
080:
081: private JaxBeanInfo keyBeanInfo;
082: private JaxBeanInfo valueBeanInfo;
083:
084: /**
085: * The implementation class for this property.
086: * If the property is null, we create an instance of this class.
087: */
088: private final Class<? extends ValueT> mapImplClass;
089:
090: public SingleMapNodeProperty(JAXBContextImpl context,
091: RuntimeMapPropertyInfo prop) {
092: super (context, prop);
093: acc = prop.getAccessor().optimize();
094: this .tagName = context.nameBuilder.createElementName(prop
095: .getXmlName());
096: this .entryTag = context.nameBuilder.createElementName("",
097: "entry");
098: this .keyTag = context.nameBuilder.createElementName("", "key");
099: this .valueTag = context.nameBuilder.createElementName("",
100: "value");
101: this .nillable = prop.isCollectionNillable();
102: this .keyBeanInfo = context.getOrCreate(prop.getKeyType());
103: this .valueBeanInfo = context.getOrCreate(prop.getValueType());
104:
105: // infer the implementation class
106: Class<ValueT> sig = ReflectionNavigator.REFLECTION.erasure(prop
107: .getRawType());
108: mapImplClass = ClassFactory.inferImplClass(sig,
109: knownImplClasses);
110: // TODO: error check for mapImplClass==null
111: // what is the error reporting path for this part of the code?
112: }
113:
114: private static final Class[] knownImplClasses = { HashMap.class,
115: TreeMap.class, LinkedHashMap.class };
116:
117: public void reset(BeanT bean) throws AccessorException {
118: acc.set(bean, null);
119: }
120:
121: /**
122: * A Map property can never be ID.
123: */
124: public String getIdValue(BeanT bean) {
125: return null;
126: }
127:
128: public PropertyKind getKind() {
129: return PropertyKind.MAP;
130: }
131:
132: public void buildChildElementUnmarshallers(UnmarshallerChain chain,
133: QNameMap<ChildLoader> handlers) {
134: keyLoader = keyBeanInfo.getLoader(chain.context, true);
135: valueLoader = valueBeanInfo.getLoader(chain.context, true);
136: handlers.put(tagName, new ChildLoader(itemsLoader, null));
137: }
138:
139: private Loader keyLoader;
140: private Loader valueLoader;
141:
142: /**
143: * Handles <items> and </items>.
144: *
145: * The target will be set to a {@link Map}.
146: */
147: private final Loader itemsLoader = new Loader(false) {
148: @Override
149: public void startElement(UnmarshallingContext.State state,
150: TagName ea) throws SAXException {
151: // create or obtain the Map object
152: try {
153: BeanT target = (BeanT) state.prev.target;
154: ValueT map = acc.get(target);
155: if (map == null) {
156: map = ClassFactory.create(mapImplClass);
157: acc.set(target, map);
158: }
159: map.clear();
160: state.target = map;
161: } catch (AccessorException e) {
162: // recover from error by setting a dummy Map that receives and discards the values
163: handleGenericException(e, true);
164: state.target = new HashMap();
165: }
166: }
167:
168: @Override
169: public void childElement(UnmarshallingContext.State state,
170: TagName ea) throws SAXException {
171: if (ea.matches(entryTag)) {
172: state.loader = entryLoader;
173: } else {
174: super .childElement(state, ea);
175: }
176: }
177:
178: @Override
179: public Collection<QName> getExpectedChildElements() {
180: return Collections.singleton(entryTag.toQName());
181: }
182: };
183:
184: /**
185: * Handles <entry> and </entry>.
186: *
187: * The target will be set to a {@link Map}.
188: */
189: private final Loader entryLoader = new Loader(false) {
190: @Override
191: public void startElement(UnmarshallingContext.State state,
192: TagName ea) {
193: state.target = new Object[2]; // this is inefficient
194: }
195:
196: @Override
197: public void leaveElement(UnmarshallingContext.State state,
198: TagName ea) {
199: Object[] keyValue = (Object[]) state.target;
200: Map map = (Map) state.prev.target;
201: map.put(keyValue[0], keyValue[1]);
202: }
203:
204: @Override
205: public void childElement(UnmarshallingContext.State state,
206: TagName ea) throws SAXException {
207: if (ea.matches(keyTag)) {
208: state.loader = keyLoader;
209: state.receiver = keyReceiver;
210: return;
211: }
212: if (ea.matches(valueTag)) {
213: state.loader = valueLoader;
214: state.receiver = valueReceiver;
215: return;
216: }
217: super .childElement(state, ea);
218: }
219:
220: @Override
221: public Collection<QName> getExpectedChildElements() {
222: return Arrays.asList(keyTag.toQName(), valueTag.toQName());
223: }
224: };
225:
226: private static final class ReceiverImpl implements Receiver {
227: private final int index;
228:
229: public ReceiverImpl(int index) {
230: this .index = index;
231: }
232:
233: public void receive(UnmarshallingContext.State state, Object o) {
234: ((Object[]) state.target)[index] = o;
235: }
236: }
237:
238: private static final Receiver keyReceiver = new ReceiverImpl(0);
239: private static final Receiver valueReceiver = new ReceiverImpl(1);
240:
241: public void serializeBody(BeanT o, XMLSerializer w, Object outerPeer)
242: throws SAXException, AccessorException, IOException,
243: 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);
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);
261: w.endElement();
262: }
263:
264: w.endElement();
265: }
266: w.endElement();
267: } else if (nillable) {
268: w.startElement(tagName, null);
269: w.writeXsiNilTrue();
270: w.endElement();
271: }
272: }
273:
274: private void bareStartTag(XMLSerializer w, Name tagName, Object peer)
275: 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,
283: String localName) {
284: if (tagName.equals(nsUri, localName))
285: return acc;
286: return null;
287: }
288: }
|