/*
Copyright (c) 2003-2004, Dennis M. Sosnoski
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of JiBX nor the names of its contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jibx.binding.def;
import org.jibx.binding.classes.*;
import org.jibx.runtime.JiBXException;
/**
* Object string conversion handling. Defines serialization handling for
* converting objects to and from a String
value. The default is
* to just use the object toString()
method for serialization and
* a constructor from a String
value for deserialization.
* java.lang.String
itself is a special case, with no added code
* used by default for either serializing or deserializing.
* java.lang.Object
is also a special case, with no added code
* used by default for deserializing (the String
value is used
* directly). Other classes must either implement toString()
and
* a constructor from String
, or use custom serializers and/or
* deserializers.
*
* @author Dennis M. Sosnoski
* @version 1.0
*/
public class ObjectStringConversion extends StringConversion
{
//
// Constants for code generation.
private static final String TOSTRING_METHOD = "toString";
private static final String TOSTRING_SIGNATURE =
"()Ljava/lang/String;";
private static final String FROMSTRING_SIGNATURE =
"(Ljava/lang/String;)V";
//
// Actual instance data
/** Flag for conversion from String
needed (type is anything
other than String
or Object
) */
private boolean m_needDeserialize;
/** Initializer used for creating instance from String
(only used if no conversion needed and no deserializer supplied;
may be null
) */
private ClassItem m_initFromString;
/** Flag for conversion to String
needed (type is anything
other than String
) */
private boolean m_needSerialize;
/** toString()
method for converting instance to
String
(only used if conversion needed and no serializer
supplied; may be null
) */
private ClassItem m_instToString;
/**
* Constructor. Initializes conversion handling based on the supplied
* inherited handling.
*
* @param type fully qualified name of class handled by conversion
* @param inherit conversion information inherited by this conversion
* @throws JiBXException if error in configuration
*/
/*package*/ ObjectStringConversion(String type,
ObjectStringConversion inherit)
throws JiBXException {
super(type, inherit);
if (type.equals(inherit.m_typeName)) {
m_needDeserialize = inherit.m_needDeserialize;
m_initFromString = inherit.m_initFromString;
m_needSerialize = inherit.m_needSerialize;
m_instToString = inherit.m_instToString;
} else {
initMethods();
}
}
/**
* Constructor. Initializes conversion handling based on argument values.
* This form is only used for constructing the default set of conversions.
* Because of this, it throws an unchecked exception on error.
*
* @param dflt default value object (wrapped value for primitive types,
* otherwise String
)
* @param ser fully qualified name of serialization method
* @param deser fully qualified name of deserialization method
* @param type fully qualified name of class handled by conversion
*/
/*package*/ ObjectStringConversion(Object dflt, String ser, String deser,
String type) {
super(dflt, ser, deser, type);
try {
initMethods();
} catch (JiBXException ex) {
throw new IllegalArgumentException(ex.getMessage());
}
}
/**
* Initialize methods used for conversion of types without serializer or
* deserializer. Sets flags for types needed, with errors thrown at time
* of attempted use rather than at definition time. That offers the
* advantages of simpler handling (we don't need to know which directions
* are supported in a binding) and more flexibility (can support nested
* partial definitions cleanly).
*/
private void initMethods() throws JiBXException {
if (!"java.lang.String".equals(m_typeName)) {
m_needSerialize = true;
m_needDeserialize = !"java.lang.Object".equals(m_typeName);
ClassFile cf = ClassCache.getClassFile(m_typeName);
m_initFromString = cf.getInitializerMethod(FROMSTRING_SIGNATURE);
m_instToString = cf.getMethod(TOSTRING_METHOD, TOSTRING_SIGNATURE);
}
}
/**
* Generate code to convert String
representation. The
* code generated by this method assumes that the String
* value has already been pushed on the stack. It consumes this and
* leaves the converted value on the stack.
*
* @param mb method builder
*/
public void genFromText(ContextMethodBuilder mb) throws JiBXException {
// first generate code to duplicate value and check for null, with
// duplicate replaced by explicit null if already null (confusing
// in the bytecode, but will be optimized out by any native code
// generation)
mb.appendDUP();
BranchWrapper ifnnull = mb.appendIFNONNULL(this);
mb.appendPOP();
mb.appendACONST_NULL();
BranchWrapper toend = mb.appendUnconditionalBranch(this);
mb.targetNext(ifnnull);
// check if a deserializer is used for this type
if (m_deserializer != null) {
// just generate call to the deserializer (adding any checked
// exceptions thrown by the deserializer to the list needing
// handling)
mb.addMethodExceptions(m_deserializer);
if (m_deserializer.getArgumentCount() > 1) {
mb.loadContext();
}
mb.appendCall(m_deserializer);
} else if (m_initFromString != null) {
// generate code to create an instance of object and pass text value
// to constructor
mb.appendCreateNew(m_typeName);
mb.appendDUP_X1();
mb.appendSWAP();
mb.appendCallInit(m_typeName, FROMSTRING_SIGNATURE);
} else if (m_needDeserialize) {
throw new JiBXException("No deserializer for " + m_typeName +
"; define deserializer or constructor from java.lang.String");
}
// finish by setting target for null case branch
mb.targetNext(toend);
}
/**
* Generate code to parse and convert optional attribute or element. The
* code generated by this method assumes that the unmarshalling context
* and name information for the attribute or element have already
* been pushed on the stack. It consumes these and leaves the converted
* value (or converted default value, if the item itself is missing) on
* the stack.
*
* @param attr item is an attribute (vs element) flag
* @param mb method builder
* @throws JiBXException if error in configuration
*/
public void genParseOptional(boolean attr, ContextMethodBuilder mb)
throws JiBXException {
// first part of generated instruction sequence is to push the default
// value, then call the appropriate unmarshalling context method to get
// the value as a String
mb.appendLoadConstant((String)m_default);
String name = attr ? UNMARSHAL_OPT_ATTRIBUTE : UNMARSHAL_OPT_ELEMENT;
mb.appendCallVirtual(name, UNMARSHAL_OPT_SIGNATURE);
// second part is to actually convert to an instance of the type
genFromText(mb);
}
/**
* Generate code to parse and convert required attribute or element. The
* code generated by this method assumes that the unmarshalling context and
* name information for the attribute or element have already been pushed
* on the stack. It consumes these and leaves the converted value on the
* stack.
*
* @param attr item is an attribute (vs element) flag
* @param mb method builder
* @throws JiBXException if error in configuration
*/
public void genParseRequired(boolean attr, ContextMethodBuilder mb)
throws JiBXException {
// first part of generated instruction sequence is a call to the
// appropriate unmarshalling context method to get the value as a
// String
String name = attr ? UNMARSHAL_REQ_ATTRIBUTE : UNMARSHAL_REQ_ELEMENT;
mb.appendCallVirtual(name, UNMARSHAL_REQ_SIGNATURE);
// second part is to actually convert to an instance of the type
genFromText(mb);
}
/**
* Shared code generation for converting instance of type to
* String
. This override of the base class method checks for
* serialization using the toString
method and implements that
* case directly, while calling the base class method for normal handling.
* The code generated by this method assumes that the reference to the
* instance to be converted is on the stack. It consumes the reference,
* replacing it with the corresponding String
value.
*
* @param type fully qualified class name for value on stack
* @param mb marshal method builder
* @throws JiBXException if error in configuration
*/
public void genToText(String type, ContextMethodBuilder mb)
throws JiBXException {
if (m_serializer == null && m_needSerialize) {
// report error if no handling available
if (m_instToString == null) {
throw new JiBXException("No serializer for " + m_typeName +
"; define serializer or toString() method");
} else {
// generate code to call toString() method of instance (adding
// any checked exceptions thrown by the method to the list
// needing handling)
mb.addMethodExceptions(m_instToString);
mb.appendCall(m_instToString);
}
} else {
super.genToText(type, mb);
}
}
/**
* Generate code to check if an optional value is not equal to the default.
* The code generated by this method assumes that the actual value to be
* converted has already been pushed on the stack. It consumes this,
* leaving the converted text reference on the stack if it's not equal to
* the default value.
*
* @param type fully qualified class name for value on stack
* @param mb method builder
* @param extra count of extra values to be popped from stack if missing
* @return handle for branch taken when value is equal to the default
* (target must be set by caller)
* @throws JiBXException if error in configuration
*/
protected BranchWrapper genToOptionalText(String type,
ContextMethodBuilder mb, int extra) throws JiBXException {
// check if the default value is just null
if (m_default == null) {
// generate code to call the serializer and get String value on
// stack
genToText(type, mb);
return null;
} else {
// start with code to call the serializer and get the String value
// on stack
genToText(type, mb);
// add code to check if the serialized text is different from the
// default, by duplicating the returned reference, pushing the
// default, and calling the object comparison method. This is
// followed by a branch if the comparison says they're not equal
// (nonzero result, since it's a boolean value).
mb.appendDUP();
mb.appendLoadConstant((String)m_default);
mb.appendCallStatic(COMPARE_OBJECTS_METHOD,
COMPARE_OBJECTS_SIGNATURE);
BranchWrapper iffalse = mb.appendIFEQ(this);
// finish by discarding copy of object reference on stack when
// equal to the default
genPopValues(extra+1, mb);
BranchWrapper toend = mb.appendUnconditionalBranch(this);
mb.targetNext(iffalse);
return toend;
}
}
/**
* Check if the type handled by this conversion is of a primitive type.
*
* @return false
to indicate object type
*/
public boolean isPrimitive() {
return false;
}
/**
* Convert text representation into default value object. For object types
* this just returns the text value.
*
* @param text value representation to be converted
* @return converted default value object
* @throws JiBXException on conversion error
*/
protected Object convertDefault(String text) throws JiBXException {
return text;
}
/**
* Derive from existing formatting information. This allows constructing
* a new instance from an existing format of the same or an ancestor
* type, with the properties of the existing format copied to the new
* instance except where overridden by the supplied values.
*
* @param type fully qualified name of class handled by conversion
* @param ser fully qualified name of serialization method
* (null
if inherited)
* @param dser fully qualified name of deserialization method
* (null
if inherited)
* @param dflt default value text (null
if inherited)
* @return new instance initialized from existing one
* @throws JiBXException if error in configuration information
*/
public StringConversion derive(String type, String ser, String dser,
String dflt) throws JiBXException {
if (type == null) {
type = m_typeName;
}
StringConversion inst = new ObjectStringConversion(type, this);
if (ser != null) {
inst.setSerializer(ser);
}
if (dser != null) {
inst.setDeserializer(dser);
}
if (dflt != null) {
inst.m_default = inst.convertDefault(dflt);
}
return inst;
}
}