001: /*
002: Copyright (c) 2003-2005, Dennis M. Sosnoski
003: All rights reserved.
004:
005: Redistribution and use in source and binary forms, with or without modification,
006: are permitted provided that the following conditions are met:
007:
008: * Redistributions of source code must retain the above copyright notice, this
009: list of conditions and the following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice,
011: this list of conditions and the following disclaimer in the documentation
012: and/or other materials provided with the distribution.
013: * Neither the name of JiBX nor the names of its contributors may be used
014: to endorse or promote products derived from this software without specific
015: prior written permission.
016:
017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: */
028:
029: package org.jibx.extras;
030:
031: import java.util.HashMap;
032: import java.util.Iterator;
033: import java.util.Map;
034:
035: import org.jibx.runtime.IAliasable;
036: import org.jibx.runtime.IMarshallable;
037: import org.jibx.runtime.IMarshaller;
038: import org.jibx.runtime.IMarshallingContext;
039: import org.jibx.runtime.IUnmarshaller;
040: import org.jibx.runtime.IUnmarshallingContext;
041: import org.jibx.runtime.JiBXException;
042: import org.jibx.runtime.impl.MarshallingContext;
043: import org.jibx.runtime.impl.UnmarshallingContext;
044:
045: /**
046: * <p>Custom marshaller/unmarshaller for <code>java.util.Map</code>
047: * instances. This handles mapping hash maps with simple keys and complex values
048: * to and from XML. There are a number of limitations, though. First off, the
049: * key objects are marshalled as simple text values, using the
050: * <code>toString()</code> method to convert them to <code>String</code>. When
051: * unmarshalling the keys are always treated as <code>String</code> values. The
052: * corresponding values can be any complex type with a <mapping> defined in
053: * the binding. The name of the top-level element in the XML structure can be
054: * configured in the binding definition, but the rest of the names are
055: * predefined and set in the code (though the namespace configured for the
056: * top-level element will be used with all the names).</p>
057: *
058: * <p>The net effect is that the XML structure will always be of the form:</p>
059: *
060: * <pre><map-name size="3">
061: * <entry key="38193">
062: * <customer state="WA" zip="98059">
063: * <name first-name="John" last-name="Smith"/>
064: * <street>12345 Happy Lane</street>
065: * <city>Plunk</city>
066: * </customer>
067: * </entry>
068: * <entry key="39122">
069: * <customer state="WA" zip="98094">
070: * <name first-name="Sally" last-name="Port"/>
071: * <street>932 Easy Street</street>
072: * <city>Fort Lewis</city>
073: * </customer>
074: * </entry>
075: * <entry key="83132">
076: * <customer state="WA" zip="98059">
077: * <name first-name="Mary" last-name="Smith"/>
078: * <street>12345 Happy Lane</street>
079: * <city>Plunk</city>
080: * </customer>
081: * </entry>
082: * </map-name></pre>
083: *
084: * <p>where "map-name" is the configured top-level element name, the "size"
085: * attribute is the number of pairs in the hash map, and the "entry" elements
086: * are the actual entries in the hash map.</p>
087: *
088: * <p>This is obviously not intended to handle all types of hash maps, but it
089: * should be useful as written for many applications and easily customized to
090: * handle other requirements.</p>
091: *
092: * @author Dennis M. Sosnoski
093: * @version 1.0
094: */
095:
096: public class HashMapperStringToComplex implements IMarshaller,
097: IUnmarshaller, IAliasable {
098:
099: private static final int DEFAULT_SIZE = 10;
100:
101: private String m_uri;
102: private int m_index;
103: private String m_name;
104:
105: /**
106: * Default constructor. This uses a pre-defined name for the top-level
107: * element. It'll be used by JiBX when no name information is supplied by
108: * the mapping which references this custom marshaller/unmarshaller.
109: */
110: public HashMapperStringToComplex() {
111: m_uri = null;
112: m_index = 0;
113: m_name = "hashmap";
114: }
115:
116: /**
117: * Aliased constructor. This takes a name definition for the top-level
118: * element. It'll be used by JiBX when a name is supplied by the mapping
119: * which references this custom marshaller/unmarshaller.
120: *
121: * @param uri namespace URI for the top-level element (also used for all
122: * other names within the binding)
123: * @param index namespace index corresponding to the defined URI within the
124: * marshalling context definitions
125: * @param name local name for the top-level element
126: */
127: public HashMapperStringToComplex(String uri, int index, String name) {
128: m_uri = uri;
129: m_index = index;
130: m_name = name;
131: }
132:
133: /**
134: * Method which can be overridden to supply a different name for the wrapper
135: * element attribute used to give the number of items present. If present,
136: * this attribute is used when unmarshalling to set the initial size of the
137: * hashmap. It will be generated when marshalling if the supplied name is
138: * non-<code>null</code>.
139: */
140: protected String getSizeAttributeName() {
141: return "size";
142: }
143:
144: /**
145: * Method which can be overridden to supply a different name for the element
146: * used to represent each item in the map.
147: */
148: protected String getEntryElementName() {
149: return "entry";
150: }
151:
152: /**
153: * Method which can be overridden to supply a different name for the
154: * attribute defining the key value for each item in the map.
155: */
156: protected String getKeyAttributeName() {
157: return "key";
158: }
159:
160: /* (non-Javadoc)
161: * @see org.jibx.runtime.IMarshaller#isExtension(int)
162: */
163: public boolean isExtension(int index) {
164: return false;
165: }
166:
167: /* (non-Javadoc)
168: * @see org.jibx.runtime.IMarshaller#marshal(java.lang.Object,
169: * org.jibx.runtime.IMarshallingContext)
170: */
171: public void marshal(Object obj, IMarshallingContext ictx)
172: throws JiBXException {
173:
174: // make sure the parameters are as expected
175: if (!(obj instanceof Map)) {
176: throw new JiBXException(
177: "Invalid object type for marshaller");
178: } else if (!(ictx instanceof MarshallingContext)) {
179: throw new JiBXException(
180: "Invalid object type for marshaller");
181: } else {
182:
183: // start by generating start tag for container
184: MarshallingContext ctx = (MarshallingContext) ictx;
185: Map map = (Map) obj;
186: ctx.startTagAttributes(m_index, m_name).attribute(m_index,
187: getSizeAttributeName(), map.size())
188: .closeStartContent();
189:
190: // loop through all entries in map
191: Iterator iter = map.entrySet().iterator();
192: while (iter.hasNext()) {
193: Map.Entry entry = (Map.Entry) iter.next();
194: ctx.startTagAttributes(m_index, getEntryElementName());
195: ctx.attribute(m_index, getKeyAttributeName(), entry
196: .getKey().toString());
197: ctx.closeStartContent();
198: if (entry.getValue() instanceof IMarshallable) {
199: ((IMarshallable) entry.getValue()).marshal(ctx);
200: ctx.endTag(m_index, getEntryElementName());
201: } else {
202: throw new JiBXException(
203: "Mapped value is not marshallable");
204: }
205: }
206:
207: // finish with end tag for container element
208: ctx.endTag(m_index, m_name);
209: }
210: }
211:
212: /* (non-Javadoc)
213: * @see org.jibx.runtime.IUnmarshaller#isPresent(org.jibx.runtime.IUnmarshallingContext)
214: */
215: public boolean isPresent(IUnmarshallingContext ctx)
216: throws JiBXException {
217: return ctx.isAt(m_uri, m_name);
218: }
219:
220: /* (non-Javadoc)
221: * @see org.jibx.runtime.IUnmarshaller#unmarshal(java.lang.Object,
222: * org.jibx.runtime.IUnmarshallingContext)
223: */
224: public Object unmarshal(Object obj, IUnmarshallingContext ictx)
225: throws JiBXException {
226:
227: // make sure we're at the appropriate start tag
228: UnmarshallingContext ctx = (UnmarshallingContext) ictx;
229: if (!ctx.isAt(m_uri, m_name)) {
230: ctx.throwStartTagNameError(m_uri, m_name);
231: }
232:
233: // create new hashmap if needed
234: int size = ctx.attributeInt(m_uri, getSizeAttributeName(),
235: DEFAULT_SIZE);
236: Map map = (Map) obj;
237: if (map == null) {
238: map = new HashMap(size);
239: }
240:
241: // process all entries present in document
242: ctx.parsePastStartTag(m_uri, m_name);
243: while (ctx.isAt(m_uri, getEntryElementName())) {
244: Object key = ctx.attributeText(m_uri,
245: getKeyAttributeName(), null);
246: ctx.parsePastStartTag(m_uri, getEntryElementName());
247: Object value = ctx.unmarshalElement();
248: map.put(key, value);
249: ctx.parsePastEndTag(m_uri, getEntryElementName());
250: }
251: ctx.parsePastEndTag(m_uri, m_name);
252: return map;
253: }
254: }
|