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 org.apache.bcel.classfile.Utility;
032:
033: import org.jibx.binding.classes.*;
034: import org.jibx.runtime.JiBXException;
035: import org.jibx.runtime.QName;
036:
037: /**
038: * String conversion handling. Defines serialization handling for converting
039: * to and from a <code>String</code> value. This uses an inheritance approach,
040: * where each serialization definition is initialized based on the handling
041: * set for the containing definition of the same (or parent class) type.
042: *
043: * @author Dennis M. Sosnoski
044: * @version 1.0
045: */
046:
047: public abstract class StringConversion {
048: //
049: // Constants for code generation.
050:
051: protected static final String UNMARSHAL_OPT_ATTRIBUTE = "org.jibx.runtime.impl.UnmarshallingContext.attributeText";
052: protected static final String UNMARSHAL_OPT_ELEMENT = "org.jibx.runtime.impl.UnmarshallingContext.parseElementText";
053: protected static final String UNMARSHAL_OPT_SIGNATURE = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)"
054: + "Ljava/lang/String;";
055: protected static final String UNMARSHAL_REQ_ATTRIBUTE = "org.jibx.runtime.impl.UnmarshallingContext.attributeText";
056: protected static final String UNMARSHAL_REQ_ELEMENT = "org.jibx.runtime.impl.UnmarshallingContext.parseElementText";
057: protected static final String UNMARSHAL_REQ_SIGNATURE = "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
058: protected static final String MARSHAL_ATTRIBUTE = "org.jibx.runtime.impl.MarshallingContext.attribute";
059: protected static final String MARSHAL_ELEMENT = "org.jibx.runtime.impl.MarshallingContext.element";
060: protected static final String MARSHAL_SIGNATURE = "(ILjava/lang/String;Ljava/lang/String;)"
061: + "Lorg/jibx/runtime/impl/MarshallingContext;";
062: protected static final String COMPARE_OBJECTS_METHOD = "org.jibx.runtime.Utility.isEqual";
063: protected static final String COMPARE_OBJECTS_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Object;)Z";
064: protected static final String[] DESERIALIZER_SIGNATURES = {
065: "(Ljava/lang/String;)",
066: "(Ljava/lang/String;Lorg/jibx/runtime/IUnmarshallingContext;)" };
067:
068: // values used for name in marshalling; must be 1 or 2
069: public static final int MARSHAL_NAME_VALUES = 2;
070:
071: //
072: // Actual instance data
073:
074: /** Default value used for this type (wrapper for primitives, otherwise
075: <code>String</code> or <code>null</code>). */
076: protected Object m_default;
077:
078: /** Serializer method information. */
079: protected ClassItem m_serializer;
080:
081: /** Deserializer method information. */
082: protected ClassItem m_deserializer;
083:
084: /** Fully qualified name of class handled by conversion. */
085: protected String m_typeName;
086:
087: /** Signature of class handled by conversion. */
088: protected String m_typeSignature;
089:
090: /**
091: * Constructor. This internal form only initializes the type information.
092: *
093: * @param type fully qualified name of class handled by conversion
094: */
095:
096: private StringConversion(String type) {
097: m_typeName = type;
098: m_typeSignature = Utility.getSignature(type);
099: }
100:
101: /**
102: * Constructor. Initializes conversion handling based on the supplied
103: * inherited handling.
104: *
105: * @param type fully qualified name of class handled by conversion
106: * @param inherit conversion information inherited by this conversion
107: */
108:
109: protected StringConversion(String type, StringConversion inherit) {
110: this (type);
111: m_default = inherit.m_default;
112: m_serializer = inherit.m_serializer;
113: m_deserializer = inherit.m_deserializer;
114: }
115:
116: /**
117: * Constructor. Initializes conversion handling based on argument values.
118: * This form is only used for constructing the default set of conversions.
119: * Because of this, it throws an unchecked exception on error.
120: *
121: * @param dflt default value object (wrapped value for primitive types,
122: * otherwise <code>String</code>)
123: * @param ser fully qualified name of serialization method
124: * @param deser fully qualified name of deserialization method
125: * @param type fully qualified name of class handled by conversion
126: */
127:
128: /*package*/StringConversion(Object dflt, String ser, String deser,
129: String type) {
130: this (type);
131: m_default = dflt;
132: try {
133: if (ser != null) {
134: setSerializer(ser);
135: }
136: if (deser != null) {
137: setDeserializer(deser);
138: }
139: } catch (JiBXException ex) {
140: throw new IllegalArgumentException(ex.getMessage());
141: }
142: }
143:
144: /**
145: * Get name of type handled by this conversion.
146: *
147: * @return fully qualified class name of type handled by conversion
148: */
149:
150: public String getTypeName() {
151: return m_typeName;
152: }
153:
154: /**
155: * Generate code to convert <code>String</code> representation. The
156: * code generated by this method assumes that the <code>String</code>
157: * value has already been pushed on the stack. It consumes this and
158: * leaves the converted value on the stack.
159: *
160: * @param mb method builder
161: * @throws JiBXException if error in configuration
162: */
163:
164: public abstract void genFromText(ContextMethodBuilder mb)
165: throws JiBXException;
166:
167: /**
168: * Generate code to parse and convert optional attribute or element. This
169: * abstract base class method must be implemented by every subclass. The
170: * code generated by this method assumes that the unmarshalling context
171: * and name information for the attribute or element have already
172: * been pushed on the stack. It consumes these and leaves the converted
173: * value (or converted default value, if the item itself is missing) on
174: * the stack.
175: *
176: * @param attr item is an attribute (vs element) flag
177: * @param mb method builder
178: * @throws JiBXException if error in configuration
179: */
180:
181: public abstract void genParseOptional(boolean attr,
182: ContextMethodBuilder mb) throws JiBXException;
183:
184: /**
185: * Generate code to parse and convert required attribute or element. This
186: * abstract base class method must be implemented by every subclass. The
187: * code generated by this method assumes that the unmarshalling context and
188: * name information for the attribute or element have already been pushed
189: * on the stack. It consumes these and leaves the converted value on the
190: * stack.
191: *
192: * @param attr item is an attribute (vs element) flag
193: * @param mb method builder
194: * @throws JiBXException if error in configuration
195: */
196:
197: public abstract void genParseRequired(boolean attr,
198: ContextMethodBuilder mb) throws JiBXException;
199:
200: /**
201: * Generate code to write <code>String</code> value to generated document.
202: * The code generated by this method assumes that the marshalling context,
203: * the name information, and the actual value to be converted have already
204: * been pushed on the stack. It consumes these, leaving the marshalling
205: * context on the stack.
206: *
207: * @param attr item is an attribute (vs element) flag
208: * @param mb method builder
209: */
210:
211: public void genWriteText(boolean attr, ContextMethodBuilder mb) {
212:
213: // append code to call the appropriate generic marshalling context
214: // String method
215: String name = attr ? MARSHAL_ATTRIBUTE : MARSHAL_ELEMENT;
216: mb.appendCallVirtual(name, MARSHAL_SIGNATURE);
217: }
218:
219: /**
220: * Generate code to pop values from stack.
221: *
222: * @param count number of values to be popped
223: * @param mb method builder
224: */
225:
226: public void genPopValues(int count, ContextMethodBuilder mb) {
227: while (--count >= 0) {
228: if (mb.isStackTopLong()) {
229: mb.appendPOP2();
230: } else {
231: mb.appendPOP();
232: }
233: }
234: }
235:
236: /**
237: * Generate code to check if an optional value is not equal to the default.
238: * This abstract base class method must be implemented by every subclass.
239: * The code generated by this method assumes that the actual value to be
240: * converted has already been pushed on the stack. It consumes this,
241: * leaving the converted text reference on the stack if it's not equal to
242: * the default value.
243: *
244: * @param type fully qualified class name for value on stack
245: * @param mb method builder
246: * @param extra count of extra words to be popped from stack if missing
247: * @return handle for branch taken when value is equal to the default
248: * (target must be set by caller)
249: * @throws JiBXException if error in configuration
250: */
251:
252: protected abstract BranchWrapper genToOptionalText(String type,
253: ContextMethodBuilder mb, int extra) throws JiBXException;
254:
255: /**
256: * Generate code to convert value to a <code>String</code>. The code
257: * generated by this method assumes that the actual value to be converted
258: * has already been pushed on the stack. It consumes this, leaving the
259: * converted text reference on the stack.
260: *
261: * @param type fully qualified class name for value on stack
262: * @param mb method builder
263: * @throws JiBXException if error in configuration
264: */
265:
266: public void genToText(String type, ContextMethodBuilder mb)
267: throws JiBXException {
268:
269: // check if a serializer is used for this type
270: if (m_serializer != null) {
271:
272: // just generate call to the serializer (adding any checked
273: // exceptions thrown by the serializer to the list needing
274: // handling)
275: if (!isPrimitive()) {
276: mb.appendCreateCast(type, m_serializer
277: .getArgumentType(0));
278: }
279: mb.addMethodExceptions(m_serializer);
280: if (m_serializer.getArgumentCount() > 1) {
281: mb.loadContext();
282: }
283: mb.appendCall(m_serializer);
284:
285: } else {
286:
287: // make sure this is a string
288: mb.appendCreateCast(type, "java.lang.String");
289: }
290: }
291:
292: /**
293: * Generate code to convert and write optional value to generated document.
294: * The generated code first tests if the value is the same as the supplied
295: * default, and if so skips writing. The code assumes that the marshalling
296: * context, the name information, and the actual value to be converted have
297: * already been pushed on the stack. It consumes these, leaving only the
298: * marshalling context on the stack.
299: *
300: * @param attr item is an attribute (vs element) flag
301: * @param type fully qualified class name for value on stack
302: * @param mb method builder
303: * @throws JiBXException if error in configuration
304: */
305:
306: public void genWriteOptional(boolean attr, String type,
307: ContextMethodBuilder mb) throws JiBXException {
308:
309: // start with code to convert value to String, if it's not equal to the
310: // default value
311: BranchWrapper toend = genToOptionalText(type, mb,
312: MARSHAL_NAME_VALUES);
313:
314: // next use standard write code, followed by targeting branch
315: genWriteText(attr, mb);
316: if (toend != null) {
317: mb.targetNext(toend);
318: }
319: }
320:
321: /**
322: * Generate code to convert and write required value to generated document.
323: * The code generated by this method assumes that the marshalling context,
324: * the name information, and the actual value to be converted have already
325: * been pushed on the stack. It consumes these, leaving the returned
326: * marshalling context on the stack.
327: *
328: * @param attr item is an attribute (vs element) flag
329: * @param type fully qualified class name for value on stack
330: * @param mb method builder
331: * @throws JiBXException if error in configuration
332: */
333:
334: public void genWriteRequired(boolean attr, String type,
335: ContextMethodBuilder mb) throws JiBXException {
336:
337: // generate code to convert to text, followed by code to marshal text
338: genToText(type, mb);
339: genWriteText(attr, mb);
340: }
341:
342: /**
343: * Check if the type handled by this conversion is of a primitive type.
344: *
345: * @return <code>true</code> if a primitive type, <code>false</code> if an
346: * object type
347: */
348:
349: public abstract boolean isPrimitive();
350:
351: /**
352: * Set serializer for conversion. This finds the named static method and
353: * sets it as the serializer to be used for this conversion. The serializer
354: * method is expected to take a single argument of either the handled
355: * type or a superclass or interface of the handled type, and to return a
356: * <code>String</code> result.
357: *
358: * @param ser fully qualified class and method name of serializer
359: * @throws JiBXException if serializer not found or not usable
360: */
361:
362: protected void setSerializer(String ser) throws JiBXException {
363:
364: // build all possible signature variations
365: String[] tsigs = ClassItem.getSignatureVariants(m_typeName);
366: String[] msigs = new String[tsigs.length * 2];
367: for (int i = 0; i < tsigs.length; i++) {
368: msigs[i * 2] = "(" + tsigs[i] + ")Ljava/lang/String;";
369: msigs[i * 2 + 1] = "("
370: + tsigs[i]
371: + "Lorg/jibx/runtime/IMarshallingContext;)Ljava/lang/String;";
372: }
373:
374: // find a matching static method
375: ClassItem method = ClassItem.findStaticMethod(ser, msigs);
376:
377: // report error if method not found
378: if (method == null) {
379: throw new JiBXException("Serializer " + ser + " not found");
380: } else {
381: m_serializer = method;
382: }
383: }
384:
385: /**
386: * Set deserializer for conversion. This finds the named static method and
387: * sets it as the deserializer to be used for this conversion. The
388: * deserializer method is expected to take a single argument of type
389: * <code>String</code>, and to return a value of the handled type or a
390: * subtype of that type.
391: *
392: * @param deser fully qualified class and method name of deserializer
393: * @throws JiBXException if deserializer not found or not usable
394: */
395:
396: protected void setDeserializer(String deser) throws JiBXException {
397:
398: // find a matching static method
399: ClassItem method = ClassItem.findStaticMethod(deser,
400: DESERIALIZER_SIGNATURES);
401:
402: // report error if method not found or incompatible
403: if (method == null) {
404: throw new JiBXException("Deserializer " + deser
405: + " not found");
406: } else if (ClassItem.isAssignable(method.getTypeName(),
407: m_typeName)) {
408: m_deserializer = method;
409: } else {
410: throw new JiBXException("Deserializer " + deser
411: + " returns wrong type");
412: }
413: }
414:
415: /**
416: * Convert text representation into default value object. Each subclass
417: * must implement this with the appropriate conversion handling.
418: *
419: * @param text value representation to be converted
420: * @return converted default value object
421: * @throws JiBXException on conversion error
422: */
423:
424: protected abstract Object convertDefault(String text)
425: throws JiBXException;
426:
427: /**
428: * Derive from existing formatting information. This abstract base class
429: * method must be implemented by every subclass. It allows constructing
430: * a new instance from an existing format of the same or an ancestor
431: * type, with the properties of the existing format copied to the new
432: * instance except where overridden by the supplied values.
433: *
434: * @param type fully qualified name of class handled by conversion
435: * @param ser fully qualified name of serialization method
436: * (<code>null</code> if inherited)
437: * @param dser fully qualified name of deserialization method
438: * (<code>null</code> if inherited)
439: * @param dflt default value text (<code>null</code> if inherited)
440: * @return new instance initialized from existing one
441: * @throws JiBXException if error in configuration information
442: */
443:
444: public abstract StringConversion derive(String type, String ser,
445: String dser, String dflt) throws JiBXException;
446: }
|