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.jibx.binding.classes.*;
032: import org.jibx.runtime.JiBXException;
033:
034: /**
035: * Object string conversion handling. Defines serialization handling for
036: * converting objects to and from a <code>String</code> value. The default is
037: * to just use the object <code>toString()</code> method for serialization and
038: * a constructor from a <code>String</code> value for deserialization.
039: * <code>java.lang.String</code> itself is a special case, with no added code
040: * used by default for either serializing or deserializing.
041: * <code>java.lang.Object</code> is also a special case, with no added code
042: * used by default for deserializing (the <code>String</code> value is used
043: * directly). Other classes must either implement <code>toString()</code> and
044: * a constructor from <code>String</code>, or use custom serializers and/or
045: * deserializers.
046: *
047: * @author Dennis M. Sosnoski
048: * @version 1.0
049: */
050:
051: public class ObjectStringConversion extends StringConversion {
052: //
053: // Constants for code generation.
054:
055: private static final String TOSTRING_METHOD = "toString";
056: private static final String TOSTRING_SIGNATURE = "()Ljava/lang/String;";
057: private static final String FROMSTRING_SIGNATURE = "(Ljava/lang/String;)V";
058:
059: //
060: // Actual instance data
061:
062: /** Flag for conversion from <code>String</code> needed (type is anything
063: other than <code>String</code> or <code>Object</code>) */
064: private boolean m_needDeserialize;
065:
066: /** Initializer used for creating instance from <code>String</code>
067: (only used if no conversion needed and no deserializer supplied;
068: may be <code>null</code>) */
069: private ClassItem m_initFromString;
070:
071: /** Flag for conversion to <code>String</code> needed (type is anything
072: other than <code>String</code>) */
073: private boolean m_needSerialize;
074:
075: /** <code>toString()</code> method for converting instance to
076: <code>String</code> (only used if conversion needed and no serializer
077: supplied; may be <code>null</code>) */
078: private ClassItem m_instToString;
079:
080: /**
081: * Constructor. Initializes conversion handling based on the supplied
082: * inherited handling.
083: *
084: * @param type fully qualified name of class handled by conversion
085: * @param inherit conversion information inherited by this conversion
086: * @throws JiBXException if error in configuration
087: */
088:
089: /*package*/ObjectStringConversion(String type,
090: ObjectStringConversion inherit) throws JiBXException {
091: super (type, inherit);
092: if (type.equals(inherit.m_typeName)) {
093: m_needDeserialize = inherit.m_needDeserialize;
094: m_initFromString = inherit.m_initFromString;
095: m_needSerialize = inherit.m_needSerialize;
096: m_instToString = inherit.m_instToString;
097: } else {
098: initMethods();
099: }
100: }
101:
102: /**
103: * Constructor. Initializes conversion handling based on argument values.
104: * This form is only used for constructing the default set of conversions.
105: * Because of this, it throws an unchecked exception on error.
106: *
107: * @param dflt default value object (wrapped value for primitive types,
108: * otherwise <code>String</code>)
109: * @param ser fully qualified name of serialization method
110: * @param deser fully qualified name of deserialization method
111: * @param type fully qualified name of class handled by conversion
112: */
113:
114: /*package*/ObjectStringConversion(Object dflt, String ser,
115: String deser, String type) {
116: super (dflt, ser, deser, type);
117: try {
118: initMethods();
119: } catch (JiBXException ex) {
120: throw new IllegalArgumentException(ex.getMessage());
121: }
122: }
123:
124: /**
125: * Initialize methods used for conversion of types without serializer or
126: * deserializer. Sets flags for types needed, with errors thrown at time
127: * of attempted use rather than at definition time. That offers the
128: * advantages of simpler handling (we don't need to know which directions
129: * are supported in a binding) and more flexibility (can support nested
130: * partial definitions cleanly).
131: */
132:
133: private void initMethods() throws JiBXException {
134: if (!"java.lang.String".equals(m_typeName)) {
135: m_needSerialize = true;
136: m_needDeserialize = !"java.lang.Object".equals(m_typeName);
137: ClassFile cf = ClassCache.getClassFile(m_typeName);
138: m_initFromString = cf
139: .getInitializerMethod(FROMSTRING_SIGNATURE);
140: m_instToString = cf.getMethod(TOSTRING_METHOD,
141: TOSTRING_SIGNATURE);
142: }
143: }
144:
145: /**
146: * Generate code to convert <code>String</code> representation. The
147: * code generated by this method assumes that the <code>String</code>
148: * value has already been pushed on the stack. It consumes this and
149: * leaves the converted value on the stack.
150: *
151: * @param mb method builder
152: */
153:
154: public void genFromText(ContextMethodBuilder mb)
155: throws JiBXException {
156:
157: // first generate code to duplicate value and check for null, with
158: // duplicate replaced by explicit null if already null (confusing
159: // in the bytecode, but will be optimized out by any native code
160: // generation)
161: mb.appendDUP();
162: BranchWrapper ifnnull = mb.appendIFNONNULL(this );
163: mb.appendPOP();
164: mb.appendACONST_NULL();
165: BranchWrapper toend = mb.appendUnconditionalBranch(this );
166: mb.targetNext(ifnnull);
167:
168: // check if a deserializer is used for this type
169: if (m_deserializer != null) {
170:
171: // just generate call to the deserializer (adding any checked
172: // exceptions thrown by the deserializer to the list needing
173: // handling)
174: mb.addMethodExceptions(m_deserializer);
175: if (m_deserializer.getArgumentCount() > 1) {
176: mb.loadContext();
177: }
178: mb.appendCall(m_deserializer);
179:
180: } else if (m_initFromString != null) {
181:
182: // generate code to create an instance of object and pass text value
183: // to constructor
184: mb.appendCreateNew(m_typeName);
185: mb.appendDUP_X1();
186: mb.appendSWAP();
187: mb.appendCallInit(m_typeName, FROMSTRING_SIGNATURE);
188:
189: } else if (m_needDeserialize) {
190: throw new JiBXException(
191: "No deserializer for "
192: + m_typeName
193: + "; define deserializer or constructor from java.lang.String");
194: }
195:
196: // finish by setting target for null case branch
197: mb.targetNext(toend);
198: }
199:
200: /**
201: * Generate code to parse and convert optional attribute or element. The
202: * code generated by this method assumes that the unmarshalling context
203: * and name information for the attribute or element have already
204: * been pushed on the stack. It consumes these and leaves the converted
205: * value (or converted default value, if the item itself is missing) on
206: * the stack.
207: *
208: * @param attr item is an attribute (vs element) flag
209: * @param mb method builder
210: * @throws JiBXException if error in configuration
211: */
212:
213: public void genParseOptional(boolean attr, ContextMethodBuilder mb)
214: throws JiBXException {
215:
216: // first part of generated instruction sequence is to push the default
217: // value, then call the appropriate unmarshalling context method to get
218: // the value as a String
219: mb.appendLoadConstant((String) m_default);
220: String name = attr ? UNMARSHAL_OPT_ATTRIBUTE
221: : UNMARSHAL_OPT_ELEMENT;
222: mb.appendCallVirtual(name, UNMARSHAL_OPT_SIGNATURE);
223:
224: // second part is to actually convert to an instance of the type
225: genFromText(mb);
226: }
227:
228: /**
229: * Generate code to parse and convert required attribute or element. The
230: * code generated by this method assumes that the unmarshalling context and
231: * name information for the attribute or element have already been pushed
232: * on the stack. It consumes these and leaves the converted value on the
233: * stack.
234: *
235: * @param attr item is an attribute (vs element) flag
236: * @param mb method builder
237: * @throws JiBXException if error in configuration
238: */
239:
240: public void genParseRequired(boolean attr, ContextMethodBuilder mb)
241: throws JiBXException {
242:
243: // first part of generated instruction sequence is a call to the
244: // appropriate unmarshalling context method to get the value as a
245: // String
246: String name = attr ? UNMARSHAL_REQ_ATTRIBUTE
247: : UNMARSHAL_REQ_ELEMENT;
248: mb.appendCallVirtual(name, UNMARSHAL_REQ_SIGNATURE);
249:
250: // second part is to actually convert to an instance of the type
251: genFromText(mb);
252: }
253:
254: /**
255: * Shared code generation for converting instance of type to
256: * <code>String</code>. This override of the base class method checks for
257: * serialization using the <code>toString</code> method and implements that
258: * case directly, while calling the base class method for normal handling.
259: * The code generated by this method assumes that the reference to the
260: * instance to be converted is on the stack. It consumes the reference,
261: * replacing it with the corresponding <code>String</code> value.
262: *
263: * @param type fully qualified class name for value on stack
264: * @param mb marshal method builder
265: * @throws JiBXException if error in configuration
266: */
267:
268: public void genToText(String type, ContextMethodBuilder mb)
269: throws JiBXException {
270: if (m_serializer == null && m_needSerialize) {
271:
272: // report error if no handling available
273: if (m_instToString == null) {
274: throw new JiBXException("No serializer for "
275: + m_typeName
276: + "; define serializer or toString() method");
277: } else {
278:
279: // generate code to call toString() method of instance (adding
280: // any checked exceptions thrown by the method to the list
281: // needing handling)
282: mb.addMethodExceptions(m_instToString);
283: mb.appendCall(m_instToString);
284:
285: }
286: } else {
287: super .genToText(type, mb);
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: // check if the default value is just null
310: if (m_default == null) {
311:
312: // generate code to call the serializer and get String value on
313: // stack
314: genToText(type, mb);
315: return null;
316:
317: } else {
318:
319: // start with code to call the serializer and get the String value
320: // on stack
321: genToText(type, mb);
322:
323: // add code to check if the serialized text is different from the
324: // default, by duplicating the returned reference, pushing the
325: // default, and calling the object comparison method. This is
326: // followed by a branch if the comparison says they're not equal
327: // (nonzero result, since it's a boolean value).
328: mb.appendDUP();
329: mb.appendLoadConstant((String) m_default);
330: mb.appendCallStatic(COMPARE_OBJECTS_METHOD,
331: COMPARE_OBJECTS_SIGNATURE);
332: BranchWrapper iffalse = mb.appendIFEQ(this );
333:
334: // finish by discarding copy of object reference on stack when
335: // equal to the default
336: genPopValues(extra + 1, mb);
337: BranchWrapper toend = mb.appendUnconditionalBranch(this );
338: mb.targetNext(iffalse);
339: return toend;
340:
341: }
342: }
343:
344: /**
345: * Check if the type handled by this conversion is of a primitive type.
346: *
347: * @return <code>false</code> to indicate object type
348: */
349:
350: public boolean isPrimitive() {
351: return false;
352: }
353:
354: /**
355: * Convert text representation into default value object. For object types
356: * this just returns the text value.
357: *
358: * @param text value representation to be converted
359: * @return converted default value object
360: * @throws JiBXException on conversion error
361: */
362:
363: protected Object convertDefault(String text) throws JiBXException {
364: return text;
365: }
366:
367: /**
368: * Derive from existing formatting information. This allows constructing
369: * a new instance from an existing format of the same or an ancestor
370: * type, with the properties of the existing format copied to the new
371: * instance except where overridden by the supplied values.
372: *
373: * @param type fully qualified name of class handled by conversion
374: * @param ser fully qualified name of serialization method
375: * (<code>null</code> if inherited)
376: * @param dser fully qualified name of deserialization method
377: * (<code>null</code> if inherited)
378: * @param dflt default value text (<code>null</code> if inherited)
379: * @return new instance initialized from existing one
380: * @throws JiBXException if error in configuration information
381: */
382:
383: public StringConversion derive(String type, String ser,
384: String dser, String dflt) throws JiBXException {
385: if (type == null) {
386: type = m_typeName;
387: }
388: StringConversion inst = new ObjectStringConversion(type, this);
389: if (ser != null) {
390: inst.setSerializer(ser);
391: }
392: if (dser != null) {
393: inst.setDeserializer(dser);
394: }
395: if (dflt != null) {
396: inst.m_default = inst.convertDefault(dflt);
397: }
398: return inst;
399: }
400: }
|