001: /*
002: Copyright (c) 2004-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.math.BigDecimal;
032: import java.math.BigInteger;
033: import java.util.Date;
034: import java.util.HashMap;
035: import java.util.Iterator;
036: import java.util.Map;
037:
038: import org.jibx.runtime.EnumSet;
039: import org.jibx.runtime.IAliasable;
040: import org.jibx.runtime.IMarshaller;
041: import org.jibx.runtime.IMarshallingContext;
042: import org.jibx.runtime.IUnmarshaller;
043: import org.jibx.runtime.IUnmarshallingContext;
044: import org.jibx.runtime.IXMLWriter;
045: import org.jibx.runtime.JiBXException;
046: import org.jibx.runtime.Utility;
047: import org.jibx.runtime.impl.MarshallingContext;
048: import org.jibx.runtime.impl.UnmarshallingContext;
049:
050: /**
051: * <p>Custom marshaller/unmarshaller for <code>java.util.Map</code>
052: * instances. This handles mapping hash maps with string keys and values that
053: * match basic schema datatypes to and from XML. The key objects are marshalled
054: * as simple text values, using the <code>toString()</code> method to convert
055: * them to <code>String</code> if they are not already of that type. When
056: * unmarshalling the keys are always treated as <code>String</code> values. The
057: * corresponding values can be any of the object types corresponding to basic
058: * schema data types, and are marshalled with xsi:type attributes to specify the
059: * type of each value. The types currently supported are <code>Byte</code>,
060: * <code>Double</code>, <code>Float</code>, <code>Integer</code>,
061: * <code>Long</code>, <code>java.util.Date</code> (as xsd:dateTime),
062: * <code>java.sql.Date</code> (as xsd:date), <code>java.sql.Time</code> (as
063: * xsd:time), <code>java.math.BigDecimal</code> (with no exponent allowed, as
064: * xsd:decimal), and <code>java.math.BigInteger</code> (as xsd:integer). The
065: * xsd:type attribute is checked when unmarshalling values to select the proper
066: * deserialization and value type. The name of the top-level element in the XML
067: * structure can be configured in the binding definition, but the rest of the
068: * names are predefined and set in the code (though the namespace configured for
069: * the top-level element will be used with all the names).</p>
070: *
071: * <p>The net effect is that the XML structure will always be of the form:</p>
072: *
073: * <pre><map-name size="6" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
074: * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
075: * <entry key="name" xsi:type="xsd:string">John Smith</entry>
076: * <entry key="street" xsi:type="xsd:string">12345 Happy Lane</entry>
077: * <entry key="city" xsi:type="xsd:string">Plunk</entry>
078: * <entry key="state" xsi:type="xsd:string">WA</entry>
079: * <entry key="rating" xsi:type="xsd:int">6</entry>
080: * <entry key="joined" xsi:type="xsd:dateTime">2002-08-06T00:13:31Z</entry>
081: * </map-name></pre>
082: *
083: * <p>where "map-name" is the configured top-level element name, the "size"
084: * attribute is the number of pairs in the hash map, and the "entry" elements
085: * are the actual entries in the hash map.</p>
086: *
087: * <p>For unmarshalling this requires an active namespace declaration with a
088: * prefix for the schema namespace. All xsi:type attribute values must use this
089: * prefix. If more than one prefix is declared for the schema namespace, the
090: * innermost one declared must be used.</p>
091: *
092: * @author Dennis M. Sosnoski
093: * @version 1.0
094: */
095:
096: public class HashMapperStringToSchemaType implements IMarshaller,
097: IUnmarshaller, IAliasable {
098:
099: //
100: // Basic constants used in code
101:
102: private static final String SIZE_ATTRIBUTE_NAME = "size";
103: private static final String ENTRY_ELEMENT_NAME = "entry";
104: private static final String KEY_ATTRIBUTE_NAME = "key";
105: private static final String TYPE_ATTRIBUTE_NAME = "type";
106: private static final String XSI_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema-instance";
107: private static final String XSD_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema";
108: private static final String[] SCHEMA_NAMESPACE_URIS = {
109: XSI_NAMESPACE_URI, XSD_NAMESPACE_URI };
110: private static final String XSI_NAMESPACE_PREFIX = "xsi";
111: private static final String XSD_NAMESPACE_PREFIX = "xsd";
112: private static final String[] SCHEMA_NAMESPACE_PREFIXES = {
113: XSI_NAMESPACE_PREFIX, XSD_NAMESPACE_PREFIX };
114: private static final String XSD_PREFIX_LEAD = "xsd:";
115: private static final int DEFAULT_SIZE = 10;
116:
117: //
118: // Supported XML schema type correspondences enumeration
119:
120: // numeric values for types
121: public static final int BOOLEAN_TYPE = 0;
122: public static final int BYTE_TYPE = 1;
123: public static final int DOUBLE_TYPE = 2;
124: public static final int FLOAT_TYPE = 3;
125: public static final int INT_TYPE = 4;
126: public static final int LONG_TYPE = 5;
127: public static final int SHORT_TYPE = 6;
128: public static final int DATETIME_TYPE = 7;
129: public static final int DECIMAL_TYPE = 8;
130: public static final int INTEGER_TYPE = 9;
131: public static final int BYTERRAY_TYPE = 10;
132: public static final int STRING_TYPE = 11;
133: //#!j2me{
134: public static final int DATE_TYPE = 12;
135: public static final int TIME_TYPE = 13;
136: //#j2me}
137:
138: // enumeration definition (string order must match numeric list, above)
139: private static final EnumSet s_javaTypesEnum = new EnumSet(
140: BOOLEAN_TYPE, new String[] { "java.lang.Boolean",
141: "java.lang.Byte", "java.lang.Double",
142: "java.lang.Float", "java.lang.Integer",
143: "java.lang.Long", "java.lang.Short",
144: "java.util.Date", "java.math.BigDecimal",
145: "java.math.BigInteger", "byte[]",
146: "java.lang.String",
147: //#!j2me{
148: "java.sql.Date", "java.sql.Time",
149: //#j2me}
150: });
151:
152: // corresponding schema types (string order must match numeric list, above)
153: private static final EnumSet s_schemaTypesEnum = new EnumSet(
154: BOOLEAN_TYPE, new String[] { "boolean", "byte", "double",
155: "float", "int", "long", "short", "dateTime",
156: "decimal", "integer", "base64Binary", "string",
157: //#!j2me{
158: "date", "time",
159: //#j2me}
160: });
161:
162: //
163: // Member fields
164:
165: private String m_uri;
166: private int m_index;
167: private String m_name;
168:
169: /**
170: * Default constructor. This uses a pre-defined name for the top-level
171: * element. It'll be used by JiBX when no name information is supplied by
172: * the mapping which references this custom marshaller/unmarshaller.
173: */
174:
175: public HashMapperStringToSchemaType() {
176: m_uri = null;
177: m_index = 0;
178: m_name = "hashmap";
179: }
180:
181: /**
182: * Aliased constructor. This takes a name definition for the top-level
183: * element. It'll be used by JiBX when a name is supplied by the mapping
184: * which references this custom marshaller/unmarshaller.
185: *
186: * @param uri namespace URI for the top-level element (also used for all
187: * other names within the binding)
188: * @param index namespace index corresponding to the defined URI within the
189: * marshalling context definitions
190: * @param name local name for the top-level element
191: */
192:
193: public HashMapperStringToSchemaType(String uri, int index,
194: String name) {
195: m_uri = uri;
196: m_index = index;
197: m_name = name;
198: }
199:
200: /* (non-Javadoc)
201: * @see org.jibx.runtime.IMarshaller#isExtension(int)
202: */
203:
204: public boolean isExtension(int index) {
205: return false;
206: }
207:
208: /* (non-Javadoc)
209: * @see org.jibx.runtime.IMarshaller#marshal(java.lang.Object,
210: * org.jibx.runtime.IMarshallingContext)
211: */
212:
213: public void marshal(Object obj, IMarshallingContext ictx)
214: throws JiBXException {
215:
216: // make sure the parameters are as expected
217: if (!(obj instanceof Map)) {
218: throw new JiBXException(
219: "Invalid object type for marshaller");
220: } else if (!(ictx instanceof MarshallingContext)) {
221: throw new JiBXException(
222: "Invalid object type for marshaller");
223: } else {
224:
225: // start by setting up added namespaces
226: MarshallingContext ctx = (MarshallingContext) ictx;
227: IXMLWriter xwrite = ctx.getXmlWriter();
228: int ixsi = xwrite.getNamespaces().length;
229: String[][] extens = xwrite.getExtensionNamespaces();
230: if (extens != null) {
231: for (int i = 0; i < extens.length; i++) {
232: ixsi += extens[i].length;
233: }
234: }
235: xwrite.pushExtensionNamespaces(SCHEMA_NAMESPACE_URIS);
236:
237: // generate start tag for containing element
238: Map map = (Map) obj;
239: ctx.startTagNamespaces(m_index, m_name,
240: new int[] { ixsi, ixsi + 1 },
241: SCHEMA_NAMESPACE_PREFIXES).attribute(m_index,
242: SIZE_ATTRIBUTE_NAME, map.size())
243: .closeStartContent();
244:
245: // loop through all entries in hashmap
246: Iterator iter = map.entrySet().iterator();
247: while (iter.hasNext()) {
248:
249: // first make sure we have a value
250: Map.Entry entry = (Map.Entry) iter.next();
251: Object value = entry.getValue();
252: if (value != null) {
253:
254: // write element with key attribute
255: ctx.startTagAttributes(m_index, ENTRY_ELEMENT_NAME);
256: ctx.attribute(m_index, KEY_ATTRIBUTE_NAME, entry
257: .getKey().toString());
258:
259: // translate value object class to schema type
260: String tname = value.getClass().getName();
261: int type = s_javaTypesEnum.getValue(tname);
262: if (type < 0) {
263: throw new JiBXException("Value of type "
264: + tname + " with key " + entry.getKey()
265: + " is not a supported type");
266: }
267:
268: // generate xsi:type attribute for value
269: ctx.attribute(ixsi, TYPE_ATTRIBUTE_NAME,
270: XSD_PREFIX_LEAD
271: + s_schemaTypesEnum.getName(type));
272: ctx.closeStartContent();
273:
274: // handle the actual value conversion based on type
275: switch (type) {
276:
277: case BOOLEAN_TYPE:
278: ctx.content(Utility
279: .serializeBoolean(((Boolean) value)
280: .booleanValue()));
281: break;
282:
283: case BYTE_TYPE:
284: ctx.content(Utility
285: .serializeByte(((Byte) value)
286: .byteValue()));
287: break;
288:
289: case DOUBLE_TYPE:
290: ctx.content(Utility
291: .serializeDouble(((Double) value)
292: .doubleValue()));
293: break;
294:
295: case FLOAT_TYPE:
296: ctx.content(Utility
297: .serializeFloat(((Float) value)
298: .floatValue()));
299: break;
300:
301: case INT_TYPE:
302: ctx.content(((Integer) value).intValue());
303: break;
304:
305: case LONG_TYPE:
306: ctx.content(Utility
307: .serializeLong(((Long) value)
308: .longValue()));
309: break;
310:
311: case SHORT_TYPE:
312: ctx.content(Utility
313: .serializeShort(((Short) value)
314: .shortValue()));
315: break;
316:
317: case DATETIME_TYPE:
318: ctx.content(Utility
319: .serializeDateTime((Date) value));
320: break;
321:
322: //#!j2me{
323: case DATE_TYPE:
324: ctx
325: .content(Utility
326: .serializeSqlDate((java.sql.Date) value));
327: break;
328:
329: case TIME_TYPE:
330: ctx
331: .content(Utility
332: .serializeSqlTime((java.sql.Time) value));
333: break;
334: //#j2me}
335:
336: case BYTERRAY_TYPE:
337: ctx.content(Utility
338: .serializeBase64((byte[]) value));
339: break;
340:
341: case DECIMAL_TYPE:
342: case INTEGER_TYPE:
343: case STRING_TYPE:
344: ctx.content(value.toString());
345: break;
346: }
347:
348: // finish with close tag for entry element
349: ctx.endTag(m_index, ENTRY_ELEMENT_NAME);
350: }
351: }
352:
353: // finish with end tag for container element
354: ctx.endTag(m_index, m_name);
355: xwrite.popExtensionNamespaces();
356: }
357: }
358:
359: /* (non-Javadoc)
360: * @see org.jibx.runtime.IUnmarshaller#isPresent(org.jibx.runtime.IUnmarshallingContext)
361: */
362:
363: public boolean isPresent(IUnmarshallingContext ctx)
364: throws JiBXException {
365: return ctx.isAt(m_uri, m_name);
366: }
367:
368: /* (non-Javadoc)
369: * @see org.jibx.runtime.IUnmarshaller#unmarshal(java.lang.Object,
370: * org.jibx.runtime.IUnmarshallingContext)
371: */
372:
373: public Object unmarshal(Object obj, IUnmarshallingContext ictx)
374: throws JiBXException {
375:
376: // make sure we're at the appropriate start tag
377: UnmarshallingContext ctx = (UnmarshallingContext) ictx;
378: if (!ctx.isAt(m_uri, m_name)) {
379: ctx.throwStartTagNameError(m_uri, m_name);
380: }
381:
382: // lookup the prefixes assigned to required namespaces
383: int nscnt = ctx.getActiveNamespaceCount();
384: String xsdlead = null;
385: for (int i = nscnt - 1; i >= 0; i--) {
386: String uri = ctx.getActiveNamespaceUri(i);
387: if (XSD_NAMESPACE_URI.equals(uri)) {
388: String prefix = ctx.getActiveNamespacePrefix(i);
389: if (!"".equals(prefix)) {
390: xsdlead = prefix + ':';
391: break;
392: }
393: }
394: }
395: if (xsdlead == null) {
396: throw new JiBXException(
397: "Missing required schema namespace declaration");
398: }
399:
400: // create new hashmap if needed
401: int size = ctx.attributeInt(m_uri, SIZE_ATTRIBUTE_NAME,
402: DEFAULT_SIZE);
403: Map map = (Map) obj;
404: if (map == null) {
405: map = new HashMap(size);
406: }
407:
408: // process all entries present in document
409: ctx.parsePastStartTag(m_uri, m_name);
410: String tdflt = xsdlead + "string";
411: while (ctx.isAt(m_uri, ENTRY_ELEMENT_NAME)) {
412:
413: // unmarshal key and type from start tag attributes
414: Object key = ctx.attributeText(m_uri, KEY_ATTRIBUTE_NAME);
415: String tname = ctx.attributeText(XSI_NAMESPACE_URI,
416: TYPE_ATTRIBUTE_NAME, tdflt);
417:
418: // convert type name to type index number
419: int type = -1;
420: if (tname.startsWith(xsdlead)) {
421: type = s_schemaTypesEnum.getValue(tname
422: .substring(xsdlead.length()));
423: }
424: if (type < 0) {
425: throw new JiBXException("Value of type " + tname
426: + " with key " + key
427: + " is not a supported type");
428: }
429:
430: // deserialize content as specified type
431: String text = ctx.parseElementText(m_uri,
432: ENTRY_ELEMENT_NAME);
433: Object value = null;
434: switch (type) {
435:
436: case BOOLEAN_TYPE:
437: value = Utility.parseBoolean(text) ? Boolean.TRUE
438: : Boolean.FALSE;
439: break;
440:
441: case BYTE_TYPE:
442: value = new Byte(Utility.parseByte(text));
443: break;
444:
445: case DOUBLE_TYPE:
446: value = new Double(Utility.parseDouble(text));
447: break;
448:
449: case FLOAT_TYPE:
450: value = new Float(Utility.parseFloat(text));
451: break;
452:
453: case INT_TYPE:
454: value = new Integer(Utility.parseInt(text));
455: break;
456:
457: case LONG_TYPE:
458: value = new Long(Utility.parseLong(text));
459: break;
460:
461: case SHORT_TYPE:
462: value = new Short(Utility.parseShort(text));
463: break;
464:
465: case DATETIME_TYPE:
466: value = Utility.deserializeDateTime(text);
467: break;
468:
469: //#!j2me{
470: case DATE_TYPE:
471: value = Utility.deserializeSqlDate(text);
472: break;
473:
474: case TIME_TYPE:
475: value = Utility.deserializeSqlTime(text);
476: break;
477: //#j2me}
478:
479: case BYTERRAY_TYPE:
480: value = Utility.deserializeBase64(text);
481: break;
482:
483: case DECIMAL_TYPE:
484: value = new BigDecimal(text);
485: break;
486:
487: case INTEGER_TYPE:
488: value = new BigInteger(text);
489: break;
490:
491: case STRING_TYPE:
492: value = text;
493: break;
494: }
495:
496: // add key-value pair to map
497: map.put(key, value);
498: }
499:
500: // finish by skipping past wrapper end tag
501: ctx.parsePastEndTag(m_uri, m_name);
502: return map;
503: }
504: }
|