001: /*
002: Copyright (c) 2003-2004, 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.binding.def;
030:
031: import java.lang.reflect.InvocationTargetException;
032: import java.lang.reflect.Method;
033:
034: import org.jibx.binding.classes.*;
035: import org.jibx.runtime.JiBXException;
036:
037: /**
038: * Primitive string conversion handling. Class for handling serialization
039: * converting a primitive type to and from <code>String</code> values.
040: *
041: * @author Dennis M. Sosnoski
042: * @version 1.0
043: */
044:
045: public class PrimitiveStringConversion extends StringConversion {
046: //
047: // Static class references
048:
049: private static ClassFile s_unmarshalClass;
050: {
051: try {
052: s_unmarshalClass = ClassCache
053: .getClassFile("org.jibx.runtime.impl.UnmarshallingContext");
054: } catch (JiBXException ex) { /* no handling required */
055: }
056: }
057:
058: //
059: // enum for comparison types of primitive values
060: private static final int INT_TYPE = 0;
061: private static final int LONG_TYPE = 1;
062: private static final int FLOAT_TYPE = 2;
063: private static final int DOUBLE_TYPE = 3;
064:
065: //
066: // Constants for code generation.
067:
068: /** Class providing basic conversion methods. */
069: private static final String UTILITY_CLASS_NAME = "org.jibx.runtime.Utility";
070:
071: /** Unmarshal method signature leading portion. */
072: private static final String UNMARSHAL_SIG_LEAD = "(Ljava/lang/String;Ljava/lang/String;";
073:
074: /** Constant argument type array for finding conversion methods. */
075: private static final Class[] SINGLE_STRING_ARGS = new Class[] { String.class };
076:
077: //
078: // Actual instance data
079:
080: /** Marshalling requires conversion to text flag. */
081: private boolean m_isMarshalText;
082:
083: /** Unmarshalling requires conversion to text flag. */
084: private boolean m_isUnmarshalText;
085:
086: /** Unmarshalling context method for optional attribute. */
087: private ClassItem m_unmarshalOptAttribute;
088:
089: /** Unmarshalling context method for optional element. */
090: private ClassItem m_unmarshalOptElement;
091:
092: /** Unmarshalling context method for required attribute. */
093: private ClassItem m_unmarshalReqAttribute;
094:
095: /** Unmarshalling context method for required element. */
096: private ClassItem m_unmarshalReqElement;
097:
098: /** Comparison and marshal type of value (INT_TYPE, LONG_TYPE, FLOAT_TYPE,
099: or DOUBLE_TYPE) */
100: private int m_valueType;
101:
102: /** Name of value type on stack. */
103: private String m_stackType;
104:
105: /**
106: * Constructor. Initializes conversion handling based on the supplied
107: * inherited handling.
108: *
109: * @param type name of primitive type handled by conversion
110: * @param inherit conversion information inherited by this conversion
111: */
112:
113: protected PrimitiveStringConversion(String type,
114: PrimitiveStringConversion inherit) {
115: super (type, inherit);
116: m_isMarshalText = inherit.m_isMarshalText;
117: m_isUnmarshalText = inherit.m_isUnmarshalText;
118: m_unmarshalOptAttribute = inherit.m_unmarshalOptAttribute;
119: m_unmarshalOptElement = inherit.m_unmarshalOptElement;
120: m_unmarshalReqAttribute = inherit.m_unmarshalReqAttribute;
121: m_unmarshalReqElement = inherit.m_unmarshalReqElement;
122: m_valueType = inherit.m_valueType;
123: m_stackType = inherit.m_stackType;
124: }
125:
126: /**
127: * Constructor. Initializes conversion handling based on argument values.
128: * This form is only used for constructing the default set of conversions.
129: *
130: * @param cls class of primitive type handled by conversion
131: * @param dflt default value object (wrapped value, or <code>String</code>
132: * or <code>null</code> with special deserializer)
133: * @param ts name of utility class static method for converting value to
134: * <code>String</code>
135: * @param fs name of utility class static method for converting
136: * <code>String</code> to value
137: * @param uattr unmarshalling context method name for attribute value
138: * @param uelem unmarshalling context method name for element value
139: */
140:
141: public PrimitiveStringConversion(Class cls, Object dflt,
142: String code, String ts, String fs, String uattr,
143: String uelem) {
144: super (dflt, UTILITY_CLASS_NAME + '.' + ts, UTILITY_CLASS_NAME
145: + '.' + fs, cls.getName());
146: m_isMarshalText = m_isUnmarshalText = false;
147: String sig = UNMARSHAL_SIG_LEAD + code + ')' + code;
148: m_unmarshalOptAttribute = s_unmarshalClass
149: .getMethod(uattr, sig);
150: m_unmarshalOptElement = s_unmarshalClass.getMethod(uelem, sig);
151: sig = UNMARSHAL_SIG_LEAD + ')' + code;
152: m_unmarshalReqAttribute = s_unmarshalClass
153: .getMethod(uattr, sig);
154: m_unmarshalReqElement = s_unmarshalClass.getMethod(uelem, sig);
155: if (cls == Long.TYPE) {
156: m_valueType = LONG_TYPE;
157: m_stackType = "long";
158: } else if (cls == Float.TYPE) {
159: m_valueType = FLOAT_TYPE;
160: m_stackType = "float";
161: } else if (cls == Double.TYPE) {
162: m_valueType = DOUBLE_TYPE;
163: m_stackType = "double";
164: } else {
165: m_valueType = INT_TYPE;
166: m_stackType = "int";
167: }
168: }
169:
170: /**
171: * Generate code to convert <code>String</code> representation. The
172: * code generated by this method assumes that the <code>String</code>
173: * value has already been pushed on the stack. It consumes this and
174: * leaves the converted value on the stack.
175: *
176: * @param mb method builder
177: */
178:
179: public void genFromText(ContextMethodBuilder mb) {
180:
181: // check if a deserializer is used for this type
182: if (m_deserializer != null) {
183:
184: // just generate call to the deserializer (adding any checked
185: // exceptions thrown by the deserializer to the list needing
186: // handling)
187: mb.addMethodExceptions(m_deserializer);
188: if (m_deserializer.getArgumentCount() > 1) {
189: mb.loadContext();
190: }
191: mb.appendCall(m_deserializer);
192: }
193: }
194:
195: /**
196: * Push default value on stack. Just adds the appropriate instruction to
197: * the list for the method.
198: *
199: * @param mb method builder
200: */
201:
202: protected void pushDefault(ContextMethodBuilder mb) {
203: mb.appendLoadConstant(m_default);
204: }
205:
206: /**
207: * Generate code to parse and convert optional attribute or element. The
208: * code generated by this method assumes that the unmarshalling context
209: * and name information for the attribute or element have already
210: * been pushed on the stack. It consumes these and leaves the converted
211: * value (or default value, if the item itself is missing) on the stack.
212: *
213: * @param attr item is an attribute (vs element) flag
214: * @param mb method builder
215: * @throws JiBXException if error in configuration
216: */
217:
218: public void genParseOptional(boolean attr, ContextMethodBuilder mb)
219: throws JiBXException {
220:
221: // choose between custom deserializer or standard built-in method
222: if (m_isUnmarshalText) {
223:
224: // first part of generated instruction sequence is to push the
225: // default value text, then call the appropriate unmarshalling
226: // context method to get the value as a String
227: String dflt;
228: if (m_default instanceof String || m_default == null) {
229: dflt = (String) m_default;
230: } else {
231: dflt = m_default.toString();
232: }
233: mb.appendLoadConstant(dflt);
234: String name = attr ? UNMARSHAL_OPT_ATTRIBUTE
235: : UNMARSHAL_OPT_ELEMENT;
236: mb.appendCallVirtual(name, UNMARSHAL_OPT_SIGNATURE);
237:
238: // second part is to generate call to deserializer
239: genFromText(mb);
240:
241: } else {
242:
243: // generated instruction sequence just pushes the unwrapped default
244: // value, then calls the appropriate unmarshalling context method
245: // to get the value as a primitive
246: pushDefault(mb);
247: mb.appendCall(attr ? m_unmarshalOptAttribute
248: : m_unmarshalOptElement);
249:
250: }
251: }
252:
253: /**
254: * Generate code to parse and convert required attribute or element. The
255: * code generated by this method assumes that the unmarshalling context and
256: * name information for the attribute or element have already been pushed
257: * on the stack. It consumes these and leaves the converted value on the
258: * stack.
259: *
260: * @param attr item is an attribute (vs element) flag
261: * @param mb method builder
262: * @throws JiBXException if error in configuration
263: */
264:
265: public void genParseRequired(boolean attr, ContextMethodBuilder mb)
266: throws JiBXException {
267:
268: // choose between custom deserializer or standard built-in method
269: if (m_isUnmarshalText) {
270:
271: // first part of generated instruction sequence is a call to
272: // the appropriate unmarshalling context method to get the value
273: // as a String
274: String name = attr ? UNMARSHAL_REQ_ATTRIBUTE
275: : UNMARSHAL_REQ_ELEMENT;
276: mb.appendCallVirtual(name, UNMARSHAL_REQ_SIGNATURE);
277:
278: // second part is to generate call to deserializer
279: genFromText(mb);
280:
281: } else {
282:
283: // generated instruction sequence just calls the appropriate
284: // unmarshalling context method to get the value as a primitive
285: mb.appendCall(attr ? m_unmarshalReqAttribute
286: : m_unmarshalReqElement);
287:
288: }
289: }
290:
291: /**
292: * Generate code to check if an optional value is not equal to the default.
293: * The code generated by this method assumes that the actual value to be
294: * converted has already been pushed on the stack. It consumes this,
295: * leaving the converted text reference on the stack if it's not equal to
296: * the default value.
297: *
298: * @param type fully qualified class name for value on stack
299: * @param mb method builder
300: * @param extra count of extra values to be popped from stack if missing
301: * @return handle for branch taken when value is equal to the default
302: * (target must be set by caller)
303: * @throws JiBXException if error in configuration
304: */
305:
306: protected BranchWrapper genToOptionalText(String type,
307: ContextMethodBuilder mb, int extra) throws JiBXException {
308:
309: // set instructions based on value size
310: if (m_valueType == LONG_TYPE || m_valueType == DOUBLE_TYPE) {
311: mb.appendDUP2();
312: } else {
313: mb.appendDUP();
314: }
315: extra++;
316:
317: // first add code to check if the value is different from the default,
318: // by duplicating the value, pushing the default, and executing the
319: // appropriate branch comparison
320:
321: // TODO: this should not be done inline, but necessary for now
322: Object value = m_default;
323: if (m_isUnmarshalText) {
324: try {
325: String mname = m_deserializer.getName();
326: String cname = m_deserializer.getClassFile().getName();
327: Class clas = ClassFile.loadClass(cname);
328: if (clas == null) {
329: throw new JiBXException("Deserializer class "
330: + cname
331: + " not found for converting default value");
332: } else {
333:
334: // try first to find a declared method, then a public one
335: Method meth;
336: try {
337: meth = clas.getDeclaredMethod(mname,
338: SINGLE_STRING_ARGS);
339: meth.setAccessible(true);
340: } catch (NoSuchMethodException ex) {
341: meth = clas
342: .getMethod(mname, SINGLE_STRING_ARGS);
343: }
344: String text;
345: if (value instanceof String || value == null) {
346: text = (String) value;
347: } else {
348: text = value.toString();
349: }
350: value = meth.invoke(null, new Object[] { text });
351:
352: }
353: } catch (IllegalAccessException ex) {
354: throw new JiBXException(
355: "Conversion method not accessible", ex);
356: } catch (InvocationTargetException ex) {
357: throw new JiBXException("Internal error", ex);
358: } catch (NoSuchMethodException ex) {
359: throw new JiBXException("Internal error", ex);
360: }
361: }
362: mb.appendLoadConstant(value);
363:
364: BranchWrapper ifne = null;
365: switch (m_valueType) {
366:
367: case LONG_TYPE:
368: mb.appendLCMP();
369: break;
370:
371: case FLOAT_TYPE:
372: mb.appendFCMPG();
373: break;
374:
375: case DOUBLE_TYPE:
376: mb.appendDCMPG();
377: break;
378:
379: default:
380: ifne = mb.appendIF_ICMPNE(this );
381: break;
382:
383: }
384: if (ifne == null) {
385: ifne = mb.appendIFNE(this );
386: }
387:
388: // generate code for branch not taken case, popping the value from
389: // stack along with extra parameters, and branching past using code
390: genPopValues(extra, mb);
391: BranchWrapper toend = mb.appendUnconditionalBranch(this );
392: mb.targetNext(ifne);
393: genToText(m_stackType, mb);
394: return toend;
395: }
396:
397: /**
398: * Convert text representation into default value object. This override of
399: * the base class method uses reflection to call the actual deserialization
400: * method, returning the wrapped result value. If a custom deserializer is
401: * defined this just returns the <code>String</code> value directly.
402: *
403: * @param text value representation to be converted
404: * @return converted default value object
405: * @throws JiBXException on conversion error
406: */
407:
408: protected Object convertDefault(String text) throws JiBXException {
409: if (!m_isUnmarshalText) {
410: try {
411: String mname = m_deserializer.getName();
412: String cname = m_deserializer.getClassFile().getName();
413: Class clas = ClassFile.loadClass(cname);
414: if (clas == null) {
415: throw new JiBXException("Deserializer class "
416: + cname
417: + " not found for converting default value");
418: } else {
419:
420: // try first to find a declared method, then a public one
421: Method meth;
422: try {
423: meth = clas.getDeclaredMethod(mname,
424: SINGLE_STRING_ARGS);
425: meth.setAccessible(true);
426: } catch (NoSuchMethodException ex) {
427: meth = clas
428: .getMethod(mname, SINGLE_STRING_ARGS);
429: }
430: return meth.invoke(null, new Object[] { text });
431:
432: }
433: } catch (IllegalAccessException ex) {
434: throw new JiBXException(
435: "Conversion method not accessible", ex);
436: } catch (InvocationTargetException ex) {
437: throw new JiBXException("Internal error", ex);
438: } catch (NoSuchMethodException ex) {
439: throw new JiBXException("Internal error", ex);
440: }
441: } else {
442: return text;
443: }
444: }
445:
446: /**
447: * Check if the type handled by this conversion is of a primitive type.
448: *
449: * @return <code>true</code> to indicate primitive type
450: */
451:
452: public boolean isPrimitive() {
453: return true;
454: }
455:
456: /**
457: * Set serializer for conversion. This override of the base class method
458: * sets a flag to indicate that values must be converted to text before
459: * they are written to a document after executing the base class processing.
460: *
461: * @param ser fully qualified class and method name of serializer
462: * @throws JiBXException if serializer not found or not usable
463: */
464:
465: protected void setSerializer(String ser) throws JiBXException {
466: super .setSerializer(ser);
467: m_isMarshalText = true;
468: }
469:
470: /**
471: * Set deserializer for conversion. This override of the base class method
472: * sets a flag to indicate that values must be read from a document as text
473: * and converted as a separate step after executing the base class
474: * processing.
475: *
476: * @param deser fully qualified class and method name of deserializer
477: * @throws JiBXException if deserializer not found or not usable
478: */
479:
480: protected void setDeserializer(String deser) throws JiBXException {
481: super .setDeserializer(deser);
482: m_isUnmarshalText = true;
483: }
484:
485: /**
486: * Derive from existing formatting information. This allows constructing
487: * a new instance from an existing format of the same or an ancestor
488: * type, with the properties of the existing format copied to the new
489: * instance except where overridden by the supplied values.
490: *
491: * @param type fully qualified name of class handled by conversion
492: * @param ser fully qualified name of serialization method
493: * (<code>null</code> if inherited)
494: * @param dser fully qualified name of deserialization method
495: * (<code>null</code> if inherited)
496: * @param dflt default value text (<code>null</code> if inherited)
497: * @return new instance initialized from existing one
498: * @throws JiBXException if error in configuration information
499: */
500:
501: public StringConversion derive(String type, String ser,
502: String dser, String dflt) throws JiBXException {
503: if (type == null) {
504: type = m_typeName;
505: }
506: StringConversion inst = new PrimitiveStringConversion(type,
507: this);
508: if (ser != null) {
509: inst.setSerializer(ser);
510: }
511: if (dser != null) {
512: inst.setDeserializer(dser);
513: }
514: if (dflt != null) {
515: inst.m_default = inst.convertDefault(dflt);
516: }
517: return inst;
518: }
519: }
|