/**
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
* statements and notices. Redistributions must also contain a
* copy of this document.
*
* 2. 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.
*
* 3. The name "Exolab" must not be used to endorse or promote
* products derived from this Software without prior written
* permission of Intalio, Inc. For written permission,
* please contact info@exolab.org.
*
* 4. Products derived from this Software may not be called "Exolab"
* nor may "Exolab" appear in their names without prior written
* permission of Intalio, Inc. Exolab is a registered
* trademark of Intalio, Inc.
*
* 5. Due credit should be given to the Exolab Project
* (http://www.exolab.org/).
*
* THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED 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
* INTALIO, INC. OR ITS 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.
*
* Copyright 1999-2004 (C) Intalio, Inc. All Rights Reserved.
*
* Portions of this file developed by Keith Visco after Jan 19 2005 are
* Copyright (C) Keith Visco. All Rights Reserverd.
*
* $Id$
*/
package org.exolab.castor.xml;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.core.util.Base64Decoder;
import org.castor.core.util.HexDecoder;
import org.castor.core.util.StringUtil;
import org.castor.xml.InternalContext;
import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.mapping.MapItem;
import org.exolab.castor.util.DefaultObjectFactory;
import org.exolab.castor.util.ObjectFactory;
import org.exolab.castor.xml.descriptors.PrimitivesClassDescriptor;
import org.exolab.castor.xml.descriptors.StringClassDescriptor;
import org.exolab.castor.xml.parsing.AnyNodeUnmarshalHandler;
import org.exolab.castor.xml.parsing.AttributeSetBuilder;
import org.exolab.castor.xml.parsing.NamespaceHandling;
import org.exolab.castor.xml.parsing.StrictElementHandler;
import org.exolab.castor.xml.parsing.UnmarshalListenerDelegate;
import org.exolab.castor.xml.parsing.UnmarshalStateStack;
import org.exolab.castor.xml.parsing.primitive.objects.PrimitiveObjectFactory;
import org.exolab.castor.xml.util.XMLFieldDescriptorImpl;
import org.xml.sax.AttributeList;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DocumentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* An unmarshaller to allowing unmarshaling of XML documents to
* Java Objects. The Class must specify
* the proper access methods (setters/getters) in order for instances
* of the Class to be properly unmarshaled.
*
* @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
* @version $Revision$ $Date: 2006-05-25 06:41:12 -0600 (Thu, 25 May 2006) $
*/
public final class UnmarshalHandler extends MarshalFramework
implements ContentHandler, DocumentHandler, ErrorHandler {
/**
* Logger from commons-logging.
*/
static final Log LOG = LogFactory.getLog(UnmarshalHandler.class);
/** resource bundle */
protected static ResourceBundle resourceBundle;
static {
resourceBundle = ResourceBundle.getBundle("UnmarshalHandlerMessages", Locale
.getDefault());
}
//---------------------------/
//- Private Class Variables -/
//---------------------------/
/**
* The error message when no class descriptor has been found
* TODO: move to resource bundle
*/
private static final String ERROR_DID_NOT_FIND_CLASSDESCRIPTOR =
"unable to find or create a ClassDescriptor for class: ";
/**
* The built-in XML prefix used for xml:space, xml:lang
* and, as the XML 1.0 Namespaces document specifies, are
* reserved for use by XML and XML related specs.
**/
private static final String XML_PREFIX = "xml";
/**
* Attribute name for default namespace declaration
**/
private static final String XMLNS = "xmlns";
/**
* The type attribute (xsi:type) used to denote the
* XML Schema type of the parent element
**/
private static final String XSI_TYPE = "type";
static final String XML_SPACE = "space";
static final String XML_SPACE_WITH_PREFIX = "xml:space";
static final String PRESERVE = "preserve";
//----------------------------/
//- Private Member Variables -/
//----------------------------/
private UnmarshalState _topState = null;
private Class<?> _topClass = null;
/**
* The top-level instance object, this may be set by the user
* by calling #setRootObject();.
**/
private Object _topObject = null;
/**
* Indicates whether or not collections should be cleared
* upon first use (to remove default values, or old values).
* False by default for backward compatibility.
*/
private boolean _clearCollections = false;
/**
* The SAX Document Locator.
**/
private Locator _locator = null;
/**
* The IDResolver for resolving IDReferences.
**/
private IDResolver _idResolver = null;
/**
* A flag indicating whether or not to perform validation.
**/
private boolean _validate = true;
/**
* Hashtable to store idReference and ReferenceInfo
*/
private Hashtable<String, ReferenceInfo> _resolveTable = new Hashtable<String, ReferenceInfo>();
private Map<Class<?>, String> _javaPackages = null;
private ClassLoader _loader = null;
private static final StringClassDescriptor STRING_DESCRIPTOR
= new StringClassDescriptor();
/**
* The AnyNode to add (if any).
*/
private org.exolab.castor.types.AnyNode _node = null;
/**
* A reference to the ObjectFactory used to create instances
* of the classes if the FieldHandler is not used.
*/
private ObjectFactory _objectFactory = new DefaultObjectFactory();
/**
* A boolean to indicate that objects should
* be re-used where appropriate.
**/
private boolean _reuseObjects = false;
/**
* A boolean that indicates attribute processing should
* be strict and an error should be flagged if any
* extra attributes exist.
**/
private boolean _strictAttributes = false;
/**
* The top-level xml:space value.
*/
private boolean _wsPreserve = false;
/**
* {@link StrictElementHandler} that deals with (potentially) ignorable content.
*/
private StrictElementHandler _strictElementHandler = new StrictElementHandler();
/**
* {@link UnmarshalStateStack} that saves UnmarshalStates on a stack.
*/
private UnmarshalStateStack _stateStack = new UnmarshalStateStack();
/**
* {@link UnmarshalListenerDelegate} that deals with UnmarshalListener calls.
*/
private UnmarshalListenerDelegate _delegateUnmarshalListener = new UnmarshalListenerDelegate();
private AnyNodeUnmarshalHandler _anyNodeHandler = null;
private NamespaceHandling _namespaceHandling = new NamespaceHandling();
private AttributeSetBuilder _attributeSetFactory = null;
//----------------/
//- Constructors -/
//----------------/
/**
* Creates a new UnmarshalHandler
* The "root" class will be obtained by looking into the mapping
* for a descriptor that matches the root element.
**/
protected UnmarshalHandler() {
this(null);
}
/**
* Creates a new UnmarshalHandler.
*
* @param topClass the Class to create the UnmarshalHandler for
*/
protected UnmarshalHandler(final Class<?> topClass) {
this(null, topClass);
}
/**
* Creates a new UnmarshalHandler.
* @param internalContext the {@link InternalContext} to use
* @param topClass the Class to work for
*/
protected UnmarshalHandler(final InternalContext internalContext, final Class<?> topClass) {
super(internalContext);
_idResolver = new IDResolverImpl();
_javaPackages = new HashMap<Class<?>, String>();
_topClass = topClass;
_anyNodeHandler = new AnyNodeUnmarshalHandler(_namespaceHandling);
_attributeSetFactory = new AttributeSetBuilder(_namespaceHandling);
}
/**
* Adds a mapping from the given namespace URI to the given
* package name
*
* @param nsURI the namespace URI to map from
* @param packageName the package name to map to
*/
public void addNamespaceToPackageMapping(String nsURI, String packageName)
{
_namespaceHandling.addNamespaceToPackageMapping(nsURI, packageName);
} //-- addNamespaceToPackageMapping
/**
* Returns the Object that the UnmarshalHandler is currently
* handling (within the object model), or null if the current
* element is a simpleType.
*
* @return the Object currently being unmarshalled, or null if the
* current element is a simpleType.
*/
public Object getCurrentObject() {
if (!_stateStack.isEmpty()) {
UnmarshalState state = _stateStack.getLastState();
if (state != null) {
return state.getObject();
}
}
return null;
} //-- getCurrentObject
/**
* Returns the "root" Object (ie. the entire object model)
* being unmarshalled.
*
* @return the root Object being unmarshalled.
**/
public Object getObject() {
if (_topState != null) {
return _topState.getObject();
}
return null;
}
/**
* Sets the ClassLoader to use when loading classes
*
* @param loader the ClassLoader to use
**/
public void setClassLoader(ClassLoader loader) {
_loader = loader;
} //-- setClassLoader
/**
* Sets whether or not to clear collections (including arrays)
* upon first use to remove default values. By default, and
* for backward compatibility with previous versions of Castor
* this value is false, indicating that collections are not
* cleared before initial use by Castor.
*
* @param clear the boolean value that when true indicates
* collections should be cleared upon first use.
*/
public void setClearCollections(boolean clear) {
_clearCollections = clear;
} //-- setClearCollections
/**
* Included for backward compatibility. Debug is replaced with
* commons-logging.
* @deprecated
**/
public void setDebug(boolean debug) {
// no-op
}
/**
* Sets the IDResolver to use when resolving IDREFs for
* which no associated element may exist in XML document.
*
* @param idResolver the IDResolver to use when resolving
* IDREFs for which no associated element may exist in the
* XML document.
**/
public void setIDResolver(final IDResolver idResolver) {
((IDResolverImpl) _idResolver).setResolver(idResolver);
}
/**
* Sets whether or not attributes that do not match
* a specific field should simply be ignored or
* reported as an error. By default, extra attributes
* are ignored.
*
* @param ignoreExtraAtts a boolean that when true will
* allow non-matched attributes to simply be ignored.
**/
public void setIgnoreExtraAttributes(boolean ignoreExtraAtts) {
_strictAttributes = (!ignoreExtraAtts);
} //-- setIgnoreExtraAttributes
/**
* Sets whether or not elements that do not match
* a specific field should simply be ignored or
* reported as an error. By default, extra attributes
* are ignored.
*
* @param ignoreExtraElems a boolean that when true will
* allow non-matched attributes to simply be ignored.
**/
public void setIgnoreExtraElements(boolean ignoreExtraElems) {
_strictElementHandler.setIgnoreExtraElements(ignoreExtraElems);
} //-- setIgnoreExtraElements
/**
* Custom logging replaced with commons-logging.
* @deprecated
**/
public void setLogWriter(PrintWriter printWriter) {
// no-op
} //-- setLogWriter
/**
* Sets a boolean that when true indicates that objects
* contained within the object model should be re-used
* where appropriate. This is only valid when unmarshalling
* to an existing object.
*
* @param reuse the boolean indicating whether or not
* to re-use existing objects in the object model.
**/
public void setReuseObjects(boolean reuse) {
_reuseObjects = reuse;
} //-- setReuseObjects
// /**
// * Sets the ClassDescriptorResolver to use for loading and
// * resolving ClassDescriptors
// *
// * @param cdResolver the ClassDescriptorResolver to use
// **/
// public void setResolver(XMLClassDescriptorResolver cdResolver) {
// this._cdResolver = cdResolver;
// } //-- setResolver
/**
* Sets the root (top-level) object to use for unmarshalling into.
*
* @param root the instance to unmarshal into.
**/
public void setRootObject(Object root) {
_topObject = root;
} //-- setRootObject
/**
* Sets an {@link org.exolab.castor.xml.UnmarshalListener}.
*
* @param listener the {@link org.exolab.castor.xml.UnmarshalListener} to use with this instance
* of the UnmarshalHandler.
* @deprecated please move to the new {@link org.castor.xml.UnmarshalListener} interface
*/
public void setUnmarshalListener (org.exolab.castor.xml.UnmarshalListener listener) {
_delegateUnmarshalListener.setUnmarshalListener(listener);
}
/**
* Sets an {@link org.castor.xml.UnmarshalListener}.
*
* @param listener the {@link org.castor.xml.UnmarshalListener} to use with this instance
* of the UnmarshalHandler.
*/
public void setUnmarshalListener (org.castor.xml.UnmarshalListener listener) {
_delegateUnmarshalListener.setUnmarshalListener(listener);
}
/**
* Sets the flag for validation.
*
* @param validate A boolean to indicate whether or not validation should be done
* during umarshalling.
* <br/>
* By default, validation will be performed.
*/
public void setValidation(boolean validate) {
this._validate = validate;
} //-- setValidation
/**
* Sets the top-level whitespace (xml:space) to either
* preserving or non preserving. The XML document
* can override this value using xml:space on specific
* elements. This sets the "default" behavior
* when xml:space="default".
*
* @param preserve a boolean that when true enables
* whitespace preserving by default.
*/
public void setWhitespacePreserve(boolean preserve) {
_wsPreserve = preserve;
} //-- setWhitespacePreserve
//-----------------------------------/
//- SAX Methods for DocumentHandler -/
//-----------------------------------/
public void characters(char[] ch, int start, int length)
throws SAXException {
new CharactersProcessor(this).compute(ch, start, length);
} //-- characters
public void endDocument() throws org.xml.sax.SAXException {
//-- I've found many application don't always call
//-- #endDocument, so I usually never put any
//-- important logic here
} //-- endDocument
public void endElement(String name) throws org.xml.sax.SAXException {
new EndElementProcessor(this).compute(name);
} //-- endElement
/**
* Decode binary data and return decoded value.
* @param descriptor {@link XMLFieldDescriptor} instance for the field whose value requires decoding.
* @param binaryData The binary data value to be decoded
* @return Decode data.
*/
byte[] decodeBinaryData(final XMLFieldDescriptor descriptor,
final String binaryData) {
//-- Base64/HexBinary decoding
byte[] decodedValue;
if ((descriptor.isMultivalued()
&& HexDecoder.DATA_TYPE.equals(descriptor.getComponentType()))
|| HexDecoder.DATA_TYPE.equals(descriptor.getSchemaType())) {
decodedValue = HexDecoder.decode(binaryData);
} else {
decodedValue = Base64Decoder.decode(binaryData);
}
return decodedValue;
}
/**
* <p>ContentHandler#endElement</p>
*
* Signals the end of an element
*
* @param localName The name of the element.
*/
public void endElement(String namespaceURI, String localName, String qName)
throws org.xml.sax.SAXException {
if (StringUtil.isEmpty(qName)) {
if (StringUtil.isEmpty(localName)) {
String error = resourceBundle
.getString("unmarshalHandler.error.localName.and.qName.null");
throw new SAXException(error);
}
qName = localName;
if (StringUtil.isNotEmpty(namespaceURI)) {
//-- rebuild qName, for now
String prefix = _namespaceHandling.getNamespacePrefix(namespaceURI);
if (StringUtil.isEmpty(prefix))
qName = prefix + ":" + localName;
}
}
endElement(qName);
} //-- endElement
/**
* Signals to end the namespace prefix mapping
*
* @param prefix the namespace prefix
*/
public void endPrefixMapping(String prefix)
throws SAXException
{
//-- nothing to do , already taken care of in
//-- endElement except if we are unmarshalling an
//-- AnyNode
if (_anyNodeHandler.hasAnyUnmarshaller()) {
_anyNodeHandler.endPrefixMapping(prefix);
}
} //-- endPrefixMapping
public void ignorableWhitespace(char[] ch, int start, int length)
throws org.xml.sax.SAXException {
// -- If we are skipping elements that have appeared in the XML but for
// -- which we have no mapping, skip the text and return
if (_strictElementHandler.skipElement()) {
return;
}
if (_stateStack.isEmpty()) {
return;
}
if (_anyNodeHandler.hasAnyUnmarshaller()) {
_anyNodeHandler.ignorableWhitespace(ch, start, length);
} else {
UnmarshalState state = _stateStack.getLastState();
if (state.isWhitespacePreserving()) {
if (state.getBuffer() == null)
state.setBuffer(new StringBuffer());
state.getBuffer().append(ch, start, length);
}
}
} // -- ignorableWhitespace
public void processingInstruction(String target, String data)
throws org.xml.sax.SAXException {
// -- do nothing for now
} // -- processingInstruction
public void setDocumentLocator(Locator locator) {
this._locator = locator;
} //-- setDocumentLocator
public Locator getDocumentLocator() {
return _locator;
} //-- getDocumentLocator
/**
* Signals that an entity was skipped by the parser
*
* @param name the skipped entity's name
*/
public void skippedEntity(String name)
throws SAXException
{
//-- do nothing
} //-- skippedEntity
/**
* Signals the start of a new document
*/
public void startDocument() throws org.xml.sax.SAXException {
//-- I've found many application don't always call
//-- #startDocument, so I usually never put any
//-- important logic here
} //-- startDocument
/**
* <p>ContentHandler#startElement</p>
*
* Signals the start of element.
*
* @param localName The name of the element.
* @param atts The AttributeList containing the associated attributes for the element.
*/
public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
throws org.xml.sax.SAXException {
if (LOG.isTraceEnabled()) {
String trace;
if (StringUtil.isNotEmpty(qName))
trace = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.log.trace.startElement"),
new Object[] { qName });
else
trace = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.log.trace.startElement"),
new Object[] { localName });
LOG.trace(trace);
}
//-- If we are skipping elements that have appeared in the XML but for
//-- which we have no mapping, increase the ignore depth counter and return
if(_strictElementHandler.skipStartElement()) {
return;
}
//-- if we are in an <any> section
//-- we delegate the event handling
if (_anyNodeHandler.hasAnyUnmarshaller()) {
_anyNodeHandler.startElement(namespaceURI, localName, qName, atts);
return;
}
//-- Create a new namespace scope if necessary and
//-- make sure the flag is reset to true
if(_namespaceHandling.isNewNamespaceScopeNecessary())
_namespaceHandling.startNamespaceScope();
//-- preserve parser passed arguments for any potential
//-- delegation
String tmpQName = null;
if (StringUtil.isEmpty(localName)) {
if (StringUtil.isEmpty(qName)) {
String error = resourceBundle.getString("unmarshalHandler.error.localName.and.qName.null");
throw new SAXException(error);
}
localName = qName;
tmpQName = qName;
}
else {
if (StringUtil.isEmpty(qName)) {
if (StringUtil.isEmpty(namespaceURI)) {
tmpQName = localName;
}
else {
String prefix = _namespaceHandling.getNamespacePrefix(namespaceURI);
if (StringUtil.isNotEmpty(prefix)) {
tmpQName = prefix + ":" + localName;
}
}
}
else {
tmpQName = qName;
}
}
_anyNodeHandler.preservePassedArguments(tmpQName, atts);
int idx = localName.indexOf(':');
if (idx >= 0) {
String prefix = localName.substring(0, idx);
localName = localName.substring(idx+1);
if (StringUtil.isEmpty(namespaceURI)) {
namespaceURI = _namespaceHandling.getNamespaceURI(prefix);
}
} else {
// check for default namespace declaration
String defaultNamespace = _namespaceHandling.getDefaultNamespaceURI();
// TODO[WG]: remove unnecessary check as it simply is wrong
if (defaultNamespace != null && !defaultNamespace.equals("http://castor.exolab.org")) {
namespaceURI = defaultNamespace;
}
//-- adjust empty namespace
if (StringUtil.isEmpty(namespaceURI))
namespaceURI = null;
}
//-- call private startElement
startElementProcessing(localName, namespaceURI, _attributeSetFactory.getAttributeSet(atts));
} //-- startElement
/**
* <p>DocumentHandler#startElement</p>
*
* Signals the start of element.
*
* @param name The name of the element.
* @param attList The AttributeList containing the associated attributes for the
* element.
* @deprecated
*/
public void startElement(String name, AttributeList attList)
throws org.xml.sax.SAXException {
if (LOG.isTraceEnabled()) {
String trace = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.log.trace.startElement"),
new Object[] { name });
LOG.trace(trace);
}
//-- If we are skipping elements that have appeared in the XML but for
//-- which we have no mapping, increase the ignore depth counter and return
if (_strictElementHandler.skipStartElement()) {
return;
}
//-- if we are in an <any> section
//-- we delegate the event handling
if (_anyNodeHandler.hasAnyUnmarshaller()) {
_anyNodeHandler.startElement(name, attList);
return;
}
_anyNodeHandler.preservePassedArguments(name, attList);
//-- The namespace of the given element
String namespace = null;
//-- Begin Namespace Handling :
//-- XXX Note: This code will change when we update the XML event API
_namespaceHandling.createNamespace();
String prefix = "";
//String qName = name;
int idx = name.indexOf(':');
if (idx >= 0) {
prefix = name.substring(0,idx);
name = name.substring(idx+1);
}
namespace = _namespaceHandling.getNamespaceURI(prefix);
//-- End Namespace Handling
//-- call private startElement method
startElementProcessing(name, namespace, _attributeSetFactory.getAttributeSet(attList));
} //-- startElement
/**
* Signals the start of an element with the given name.
*
* @param name the NCName of the element. It is an error
* if the name is a QName (ie. contains a prefix).
* @param namespace the namespace of the element. This may be null.
* Note: A null namespace is not the same as the default namespace unless
* the default namespace is also null.
* @param atts the AttributeSet containing the attributes associated
* with the element.
*/
void startElementProcessing(String name, String namespace, AttributeSet atts)
throws SAXException {
new StartElementProcessor(this).compute(name, namespace, atts);
}
void processFirstElement(String name, String namespace,
AttributeSet atts, String xmlSpace) throws SAXException {
if (_topClass == null) {
if (_topObject != null) {
_topClass = _topObject.getClass();
}
}
// if (_cdResolver == null) {
// if (_topClass == null) {
// String err = "The class for the root element '" +
// name + "' could not be found.";
// throw new SAXException(err);
// }
// _cdResolver = (XMLClassDescriptorResolver) ClassDescriptorResolverFactory.createClassDescriptorResolver(BindingType.XML);
// _cdResolver.setClassLoader(_loader);
// }
if (getInternalContext().getXMLClassDescriptorResolver() == null) {
// Joachim 2007-09-04 check is new
String message = resourceBundle.getString("unmarshalHandler.log.warn.class.descriptor.not.set");
LOG.warn(message);
throw new IllegalStateException(message);
}
_topState = new UnmarshalState();
_topState.setElementName(name);
_topState.setWhitespacePreserving((xmlSpace != null) ? PRESERVE.equals(xmlSpace) : _wsPreserve);
XMLClassDescriptor classDesc = null;
//-- If _topClass is null, then we need to search
//-- the resolver for one
String instanceClassname = null;
if (_topClass == null) {
//-- check for xsi:type
instanceClassname = getInstanceType(atts, null);
if (instanceClassname != null) {
//-- first try loading class directly
try {
_topClass = loadClass(instanceClassname, null);
}
catch(ClassNotFoundException cnfe) {}
if (_topClass == null) {
classDesc = getClassDescriptor(instanceClassname);
if (classDesc != null) {
_topClass = classDesc.getJavaClass();
}
if (_topClass == null) {
String error = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.error.class.not.found"),
new Object[] { instanceClassname });
throw new SAXException(error);
}
}
}
else {
classDesc = resolveByXMLName(name, namespace, null);
if (classDesc == null) {
classDesc = getClassDescriptor(name, _loader);
if (classDesc == null) {
classDesc = getClassDescriptor(getJavaNaming().toJavaClassName(name));
}
}
if (classDesc != null) {
_topClass = classDesc.getJavaClass();
}
}
if (_topClass == null) {
String err = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.error.class.root.not.found"),
new Object[] { name });
throw new SAXException(err);
}
}
//-- create a "fake" FieldDescriptor for the root element
XMLFieldDescriptorImpl fieldDesc
= new XMLFieldDescriptorImpl(_topClass,
name,
name,
NodeType.Element);
_topState.setFieldDescriptor(fieldDesc);
//-- look for XMLClassDescriptor if null
//-- always check resolver first
if (classDesc == null) {
classDesc = getClassDescriptor(_topClass);
}
//-- check for top-level primitives
if (classDesc == null) {
if (isPrimitive(_topClass)) {
classDesc = new PrimitivesClassDescriptor(_topClass);
fieldDesc.setIncremental(false);
_topState.setPrimitiveOrImmutable(true);
}
}
fieldDesc.setClassDescriptor(classDesc);
if (classDesc == null) {
//-- report error
if ((!isPrimitive(_topClass)) &&
(!Serializable.class.isAssignableFrom( _topClass ))) {
throw new SAXException(MarshalException.NON_SERIALIZABLE_ERR);
}
String err = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.error.create.class.descriptor"),
new Object[] { _topClass.getName() });
throw new SAXException(err);
}
_topState.setClassDescriptor(classDesc);
_topState.setType(_topClass);
if ((_topObject == null) && (!_topState.isPrimitiveOrImmutable())) {
// Retrieving the xsi:type attribute, if present
String topPackage = getJavaPackage(_topClass);
if (instanceClassname == null) {
instanceClassname = getInstanceType(atts, topPackage);
} else {
//-- instance type already processed above, reset
//-- to null to prevent entering next block
instanceClassname = null;
}
if (instanceClassname != null) {
Class<?> instanceClass = null;
try {
XMLClassDescriptor xcd = getClassDescriptor(instanceClassname);
boolean loadClass = true;
if (xcd != null) {
instanceClass = xcd.getJavaClass();
if (instanceClass != null) {
loadClass = (!instanceClassname.equals(instanceClass.getName()));
}
}
if (loadClass) {
try {
instanceClass = loadClass(instanceClassname, null);
}
catch(ClassNotFoundException cnfe) {
//-- revert back to ClassDescriptor's associated
//-- class
if (xcd != null) {
instanceClass = xcd.getJavaClass();
}
}
}
if (instanceClass == null) {
String error = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.error.class.not.found"),
new Object[] { instanceClassname });
throw new SAXException(error);
}
if (!_topClass.isAssignableFrom(instanceClass)) {
String err = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.error.not.subclass"),
new Object[] { instanceClass, _topClass });
throw new SAXException(err);
}
}
catch(Exception ex) {
String err = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.error.unable.instantiate"),
new Object[] { instanceClassname });
throw new SAXException(err, ex);
}
//-- try to create instance of the given Class
Arguments args = processConstructorArgs(atts, classDesc);
_topState.setObject(createInstance(instanceClass, args));
} else {
//-- no xsi type information present
//-- try to create instance of the given Class
Arguments args = processConstructorArgs(atts, classDesc);
_topState.setObject(createInstance(_topClass, args));
}
} else {
//-- otherwise use _topObject
_topState.setObject(_topObject);
}
_stateStack.pushState(_topState);
if (!_topState.isPrimitiveOrImmutable()) {
//--The top object has just been initialized
//--notify the listener
Object stateObject = _topState.getObject();
Object parentObject = (_topState.getParent() == null) ? null
: _topState.getParent().getObject();
_delegateUnmarshalListener.initialized(stateObject, parentObject);
processAttributes(atts, classDesc);
_delegateUnmarshalListener.attributesProcessed(stateObject, parentObject);
_namespaceHandling.processNamespaces(classDesc,_stateStack.getLastState().getObject());
}
String pkg = getJavaPackage(_topClass);
if (getMappedPackage(namespace) == null) {
addNamespaceToPackageMapping(namespace, pkg);
}
}
/**
* Indicates whether validation is enabled or not.
* @return True if validation is enabled.
*/
boolean isValidating() {
return _validate;
}
/**
* Signals to start the namespace - prefix mapping
*
* @param prefix the namespace prefix to map
* @param uri the namespace URI
*/
public void startPrefixMapping(String prefix, String uri)
throws SAXException
{
//-- Patch for Xerces 2.x bug
//-- prevent attempting to declare "XML" namespace
if (Namespaces.XML_NAMESPACE_PREFIX.equals(prefix) &&
Namespaces.XML_NAMESPACE.equals(uri))
{
return;
}
else if (XMLNS.equals(prefix)) {
return;
}
//-- end Xerces 2.x bug
//-- Forward the call to SAX2ANY
//-- or create a namespace node
if (_anyNodeHandler.hasAnyUnmarshaller()) {
_anyNodeHandler.startPrefixMapping(prefix, uri);
}
else if(_namespaceHandling.isNewNamespaceScopeNecessary()) {
_namespaceHandling.stopNamespaceScope();
}
_namespaceHandling.addNamespace(prefix, uri);
/*
//-- add namespace declarations to set of current attributes
String attName = null;
if ((prefix == null) || (prefix.length() == 0))
attName = XMLNS_DECL;
else
attName = XMLNS_PREFIX + prefix;
_currentAtts.addAttribute(attName, uri);
*/
} //-- startPrefixMapping
//------------------------------------/
//- org.xml.sax.ErrorHandler methods -/
//------------------------------------/
public void error(SAXParseException exception)
throws org.xml.sax.SAXException
{
String error = MessageFormat
.format(resourceBundle
.getString("unmarshalHandler.error.sax.exception"),
new Object[] { exception.getMessage(),
exception.getLineNumber(), exception.getColumnNumber()});
throw new SAXException (error, exception);
} //-- error
public void fatalError(SAXParseException exception)
throws org.xml.sax.SAXException
{
this.error(exception);
} //-- fatalError
public void warning(SAXParseException exception)
throws org.xml.sax.SAXException
{
this.error(exception);
} //-- warning
//---------------------/
//- Protected Methods -/
//---------------------/
// TODO: Joachim 2007-09-04 remove me
// /**
// * Sets the current Castor configuration. Currently this
// * Configuration is only used during Validation (which is
// * why this method is currently protected, since it has
// * no effect at this point on the actual configuration of
// * the unmarshaller)
// *
// * Currently, this method should only be called by the
// * Unmarshaller.
// */
// protected void setConfiguration(Configuration config) {
// _config = config;
// } //-- setConfiguration
//-------------------/
//- Private Methods -/
//-------------------/
/**
* Adds the given reference to the "queue" until the referenced object
* has been unmarshalled.
*
* @param idRef the ID being referenced
* @param parent the target/parent object for the field
* @param descriptor the XMLFieldDescriptor for the field
*/
void addReference(final String idRef, final Object parent,
final XMLFieldDescriptor descriptor) {
ReferenceInfo refInfo = new ReferenceInfo(idRef, parent, descriptor);
refInfo.setNext(_resolveTable.get(idRef));
_resolveTable.put(idRef, refInfo);
}
/**
* Creates an instance of the given class /type, using
* the arguments provided (if there are any).
* @param type The class type to be used during instantiation
* @param args (Optional) arguments to be used during instantiation
*/
Object createInstance(final Class<?> type, final Arguments args)
throws SAXException {
Object instance = null;
try {
if (args == null) {
instance = _objectFactory.createInstance(type);
} else {
instance = _objectFactory.createInstance(type, args.getTypes(),
args.getValues());
}
} catch (Exception ex) {
String error = MessageFormat
.format(resourceBundle
.getString("unmarshalHandler.error.unable.instantiate"),
new Object[] { type.getName() });
throw new SAXException(error, ex);
}
return instance;
} // -- createInstance
/**
* Returns the resolved instance type attribute (xsi:type).
* If present the instance type attribute is resolved into
* a java class name and then returned.
*
* @param atts the AttributeList to search for the instance type
* attribute.
* @return the java class name corresponding to the value of
* the instance type attribute, or null if no instance type
* attribute exists in the given AttributeList.
*/
String getInstanceType(AttributeSet atts, String currentPackage)
throws SAXException
{
if (atts == null) return null;
//-- find xsi:type attribute
String type = atts.getValue(XSI_TYPE, XSI_NAMESPACE);
if (type != null) {
if (type.startsWith(JAVA_PREFIX)) {
return type.substring(JAVA_PREFIX.length());
}
// check for namespace prefix in type
int idx = type.indexOf(':');
String typeNamespaceURI = null;
if (idx >= 0) {
// there is a namespace prefix
String prefix = type.substring(0, idx);
type = type.substring(idx + 1);
typeNamespaceURI = _namespaceHandling.getNamespaceURI(prefix);
}
//-- Retrieve the type corresponding to the schema name and
//-- return it.
XMLClassDescriptor classDesc = null;
try {
classDesc = getInternalContext().getXMLClassDescriptorResolver().resolveByXMLName(type, typeNamespaceURI, _loader);
if (classDesc != null)
return classDesc.getJavaClass().getName();
//-- if class descriptor is not found here, then no descriptors
//-- existed in memory...try to load one based on name of
//-- Schema type
final String className = getJavaNaming().toJavaClassName(type);
String adjClassName = className;
String mappedPackage = getMappedPackage(typeNamespaceURI);
if ((mappedPackage != null) && (mappedPackage.length() > 0)) {
adjClassName = mappedPackage + "." + className;
}
classDesc = getInternalContext().getXMLClassDescriptorResolver().resolve(adjClassName, _loader);
if (classDesc != null)
return classDesc.getJavaClass().getName();
//-- try to use "current Package"
if (StringUtil.isNotEmpty(currentPackage)) {
adjClassName = currentPackage + '.' + className;
}
classDesc = getInternalContext().getXMLClassDescriptorResolver().resolve(adjClassName, _loader);
if (classDesc != null)
return classDesc.getJavaClass().getName();
//-- Still can't find type, this may be due to an
//-- attempt to unmarshal an older XML instance
//-- that was marshalled with a previous Castor. A
//-- bug fix in the XMLMappingLoader prevents old
//-- xsi:type that are missing the "java:"
classDesc = getInternalContext().getXMLClassDescriptorResolver().resolve(type, _loader);
if (classDesc != null)
return classDesc.getJavaClass().getName();
}
catch(ResolverException rx) {
throw new SAXException(rx);
}
}
return null;
} //-- getInstanceType
/**
* Looks up the package name from the given namespace URI.
*
* @param namespace the namespace URI to lookup
* @return the package name or null.
*/
private String getMappedPackage(final String namespace) {
return _namespaceHandling.getMappedPackage(namespace);
}
/**
* Processes the given attribute list, and attempts to add each
* {@link Attributes} to the current {@link Object} on the stack.
*
* @param atts the AttributeSet to process
* @param classDesc the classDesc to use during processing
**/
void processAttributes(final AttributeSet atts, XMLClassDescriptor classDesc)
throws SAXException {
//-- handle empty attributes
if ((atts == null) || (atts.getSize() == 0)) {
if (classDesc != null) {
XMLFieldDescriptor[] descriptors
= classDesc.getAttributeDescriptors();
for (int i = 0; i < descriptors.length; i++) {
XMLFieldDescriptor descriptor = descriptors[i];
if (descriptor == null) {
continue;
}
//-- Since many attributes represent primitive
//-- fields, we add an extra validation check here
//-- in case the class doesn't have a "has-method".
if (descriptor.isRequired() && (isValidating() || LOG.isDebugEnabled())) {
String errorMsg;
if (_locator != null) {
errorMsg = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.error.attribute.missing.location"),
classDesc.getXMLName(), descriptor
.getXMLName(), _locator
.getLineNumber(), _locator
.getColumnNumber());
} else {
errorMsg = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.error.attribute.missing"),
classDesc.getXMLName(), descriptor
.getXMLName());
}
if (isValidating()) {
throw new SAXException(errorMsg);
}
LOG.debug(errorMsg);
}
}
}
return;
}
UnmarshalState state = _stateStack.getLastState();
Object object = state.getObject();
if (classDesc == null) {
classDesc = state.getClassDescriptor();
if (classDesc == null) {
//-- no class desc, cannot process atts
//-- except for wrapper/location atts
processWrapperAttributes(atts);
return;
}
}
//-- First loop through Attribute Descriptors.
//-- Then, if we have any attributes which
//-- haven't been processed we can ask
//-- the XMLClassDescriptor for the FieldDescriptor.
boolean[] processedAtts = new boolean[atts.getSize()];
XMLFieldDescriptor[] descriptors = classDesc.getAttributeDescriptors();
for (XMLFieldDescriptor descriptor : descriptors) {
String name = descriptor.getXMLName();
String namespace = descriptor.getNameSpaceURI();
String path = descriptor.getLocationPath();
StringBuffer fullAttributePath = new StringBuffer();
if (StringUtil.isNotEmpty(path)) {
fullAttributePath.append(path + "/");
}
fullAttributePath.append(name);
if (!name.equals(fullAttributePath.toString())) {
int index = atts.getIndex(name, namespace);
if (index >= 0) {
processedAtts[index] = true;
}
continue;
}
int index = atts.getIndex(name, namespace);
String attValue = null;
if (index >= 0) {
attValue = atts.getValue(index);
processedAtts[index] = true;
}
try {
processAttribute(name, namespace, attValue, descriptor, classDesc, object);
} catch (IllegalStateException ise) {
String error = MessageFormat
.format(resourceBundle
.getString("unmarshalHandler.error.unable.add.attribute"),
new Object[] { name, state.getClassDescriptor().getJavaClass().getName(), ise });
throw new SAXException(error, ise);
}
}
//-- Handle any non processed attributes...
//-- This is useful for descriptors that might use
//-- wild-cards or other types of matching...as well
//-- as backward compatibility...attribute descriptors
//-- were erronously getting set with the default
//-- namespace by the source generator...this is
//-- also true of the generated classes for the
//-- Mapping Framework...we need to clean this up
//-- at some point in the future.
for (int i = 0; i < processedAtts.length; i++) {
if (processedAtts[i]) {
continue;
}
String namespace = atts.getNamespace(i);
String name = atts.getName(i);
//-- skip XSI attributes
if (XSI_NAMESPACE.equals(namespace)) {
if (NIL_ATTR.equals(name)) {
String value = atts.getValue(i);
state.setNil(("true".equals(value)));
}
continue;
}
if (name.startsWith(XML_PREFIX + ':')) {
//-- XML specification specific attribute
//-- It should be safe to ignore these...but
//-- if you think otherwise...let use know!
if (LOG.isDebugEnabled()) {
String debugMsg = MessageFormat
.format(resourceBundle
.getString("unmarshalHandler.log.debug.ignore.extra.attribute"),
new Object[] { name, state.getClassDescriptor().getJavaClass().getName() });
LOG.debug(debugMsg);
}
continue;
}
//-- This really should handle namespace...but it currently
//-- doesn't. Ignoring namespaces also helps with the
//-- backward compatibility issue mentioned above.
XMLFieldDescriptor descriptor =
classDesc.getFieldDescriptor(name, namespace, NodeType.Attribute);
if (descriptor == null) {
//-- check for nested attribute...loop through
//-- stack and find correct descriptor
String path = state.getElementName();
StringBuffer pathBuf = null;
while (_stateStack.hasAnotherParentState()) {
UnmarshalState targetState = _stateStack.removeParentState();
if (targetState.isWrapper()) {
//path = targetState.elementName + "/" + path;
pathBuf = resetStringBuffer(pathBuf);
pathBuf.append(targetState.getElementName());
pathBuf.append('/');
pathBuf.append(path);
path = pathBuf.toString();
continue;
}
classDesc = targetState.getClassDescriptor();
descriptor = classDesc.getFieldDescriptor(name, namespace, NodeType.Attribute);
if (descriptor != null) {
String tmpPath = descriptor.getLocationPath();
if (path.equals(StringUtil.defaultString(tmpPath))) {
break; //-- found
}
}
pathBuf = resetStringBuffer(pathBuf);
pathBuf.append(targetState.getElementName());
pathBuf.append('/');
pathBuf.append(path);
path = pathBuf.toString();
//path = targetState.elementName + "/" + path;
//-- reset descriptor to make sure we don't
//-- exit the loop with a reference to a
//-- potentially incorrect one.
descriptor = null;
}
}
if (descriptor == null) {
if (_strictAttributes) {
//-- handle error
String errorMsg = MessageFormat
.format(resourceBundle
.getString("unmarshalHandler.error.strict.attribute.error"),
new Object[] { name, state.getElementName() });
throw new SAXException(errorMsg);
}
continue;
}
try {
processAttribute(name, namespace, atts.getValue(i), descriptor, classDesc, object);
} catch (IllegalStateException ise) {
String errorMsg = MessageFormat
.format(resourceBundle
.getString("unmarshalHandler.error.unable.add.attribute"),
new Object[] { name, state.getClassDescriptor().getJavaClass().getName(), ise });
throw new SAXException(errorMsg, ise);
}
}
}
/**
* Returns either the passed in StringBuffer and sets its length to 0, or if
* the StringBuffer is null, an empty StringBuffer
*
* @param buffer
* a StringBuffer, can be null
* @return returns an empty StringBuffer
*/
private StringBuffer resetStringBuffer(StringBuffer buffer) {
if (buffer == null)
return new StringBuffer();
buffer.setLength(0);
return buffer;
}
/**
* Processes the given AttributeSet for wrapper elements.
*
* @param atts the AttributeSet to process
* @throws SAXException If the AttributeSet cannot be processed
*/
void processWrapperAttributes(final AttributeSet atts)
throws SAXException {
UnmarshalState state = _stateStack.getLastState();
//-- loop through attributes and look for the
//-- ancestor objects that they may belong to
for (int i = 0; i < atts.getSize(); i++) {
String name = atts.getName(i);
String namespace = atts.getNamespace(i);
//-- skip XSI attributes
if (XSI_NAMESPACE.equals(namespace)) {
continue;
}
XMLFieldDescriptor descriptor = null;
XMLClassDescriptor classDesc = null;
//-- check for nested attribute...loop through
//-- stack and find correct descriptor
String path = state.getElementName();
StringBuffer pathBuf = null;
UnmarshalState targetState = null;
while (_stateStack.hasAnotherParentState()) {
targetState = _stateStack.removeParentState();
if (targetState.isWrapper()) {
pathBuf = resetStringBuffer(pathBuf);
pathBuf.append(targetState.getElementName());
pathBuf.append('/');
pathBuf.append(path);
path = pathBuf.toString();
continue;
}
classDesc = targetState.getClassDescriptor();
XMLFieldDescriptor[] descriptors = classDesc.getAttributeDescriptors();
boolean found = false;
for (int a = 0; a < descriptors.length; a++) {
descriptor = descriptors[a];
if (descriptor == null) {
continue;
}
if (descriptor.matches(name)) {
String tmpPath = descriptor.getLocationPath();
if (path.equals(StringUtil.defaultString(tmpPath))) {
found = true;
break;
}
}
}
if (found) {
break;
}
pathBuf = resetStringBuffer(pathBuf);
pathBuf.append(targetState.getElementName());
pathBuf.append('/');
pathBuf.append(path);
path = pathBuf.toString();
//-- reset descriptor to make sure we don't
//-- exit the loop with a reference to a
//-- potentially incorrect one.
descriptor = null;
}
if (descriptor != null) {
try {
processAttribute(name, namespace, atts.getValue(i),
descriptor, classDesc, targetState.getObject());
} catch (IllegalStateException ise) {
String errorMsg = MessageFormat
.format(resourceBundle
.getString("unmarshalHandler.error.unable.add.attribute"),
new Object[] { name, state.getClassDescriptor().getJavaClass().getName(), ise });
throw new SAXException(errorMsg, ise);
}
}
}
}
/**
* Processes the given Attribute.
**/
private void processAttribute
(final String attName, final String attNamespace, String attValue,
XMLFieldDescriptor descriptor,
final XMLClassDescriptor classDesc,
Object parent) throws SAXException {
//Object value = attValue;
while (descriptor.isContainer()) {
FieldHandler handler = descriptor.getHandler();
Object containerObject = handler.getValue(parent);
if (containerObject == null) {
containerObject = handler.newInstance(parent);
handler.setValue(parent, containerObject);
}
ClassDescriptor containerClassDesc =
((XMLFieldDescriptorImpl) descriptor).getClassDescriptor();
descriptor = ((XMLClassDescriptor) containerClassDesc).getFieldDescriptor(
attName, attNamespace, NodeType.Attribute);
parent = containerObject;
}
if (attValue == null) {
//-- Since many attributes represent primitive
//-- fields, we add an extra validation check here
//-- in case the class doesn't have a "has-method".
if (descriptor.isRequired() && isValidating()) {
String errorMsg;
if (_locator != null) {
errorMsg = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.error.attribute.missing.location"),
new Object[] { classDesc.getXMLName(),
attName, _locator.getLineNumber(),
_locator.getColumnNumber() });
} else {
errorMsg = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.error.attribute.missing"),
new Object[] { classDesc.getXMLName(),
attName });
}
throw new SAXException(errorMsg);
}
return;
}
//-- if this is the identity then save id
if (classDesc.getIdentity() == descriptor) {
try {
((IDResolverImpl) _idResolver).bind(attValue, parent,
isValidating() && !getInternalContext().getLenientIdValidation());
} catch (ValidationException e) {
String errorMsg = MessageFormat
.format(resourceBundle
.getString("unmarshalHandler.error.duplicated.id"),
new Object[] { attValue });
throw new SAXException(errorMsg, e);
}
//-- save key in current state
UnmarshalState state = _stateStack.getLastState();
state.setKey(attValue);
//-- resolve waiting references
resolveReferences(attValue, parent);
} else if (descriptor.isReference()) {
//-- if this is an IDREF(S) then resolve reference(s)
if (descriptor.isMultivalued()) {
StringTokenizer st = new StringTokenizer(attValue);
while (st.hasMoreTokens()) {
processIDREF(st.nextToken(), descriptor, parent);
}
} else {
processIDREF(attValue, descriptor, parent);
}
//-- object values have been set by processIDREF
//-- simply return
return;
}
//-- if it's a constructor argument, we can exit at this point
//-- since constructor arguments have already been set
if (descriptor.isConstructorArgument()) {
return;
}
//-- attribute handler
FieldHandler handler = descriptor.getHandler();
if (handler == null) {
return;
}
//-- attribute field type
Class<?> type = descriptor.getFieldType();
String valueType = descriptor.getSchemaType();
boolean isPrimative = isPrimitive(type);
boolean isQName = StringUtil.equals(valueType, QNAME_NAME);
boolean isByteArray = false;
if (type.isArray()) {
isByteArray = (type.getComponentType() == Byte.TYPE);
}
//-- if this is an multi-value attribute
if (descriptor.isMultivalued()) {
StringTokenizer attrValueTokenizer = new StringTokenizer(attValue);
while (attrValueTokenizer.hasMoreTokens()) {
attValue = attrValueTokenizer.nextToken();
setAttributeValueOnObject(attValue, descriptor, parent, handler,
type, isPrimative, isQName, isByteArray);
}
} else {
setAttributeValueOnObject(attValue, descriptor, parent, handler,
type, isPrimative, isQName, isByteArray);
}
}
/**
* Sets the value of an attribute on the target object, using the {@link FieldHandler}
* provided.
* @param attValue The attribute value.
* @param descriptor Corresponding {@link XMLFieldDescriptor} instance for the attribute processed.
* @param parent Parent object into which attribute value needs to be 'injected'.
* @param handler {@link FieldHandler} used for 'value injection'.
* @param type {@link Class} type.
* @param isPrimitive Indicates whether the attribute value represents a primitive value.
* @param isQName Indicates whether the attribute value represents a QName value.
* @param isByteArray Indicates whether the attribute value represents a byte array.
* @throws SAXException If there's a problem 'injecting' the attribute value into the target field.
*/
private void setAttributeValueOnObject(final String attValue,
final XMLFieldDescriptor descriptor,
final Object parent,
final FieldHandler handler,
final Class<?> type,
final boolean isPrimitive,
final boolean isQName,
final boolean isByteArray) throws SAXException {
//-- value to set
Object value = attValue;
//-- special type conversion for primitives
if (isPrimitive) {
value = toPrimitiveObject(type, attValue, descriptor);
}
//-- special byte[]s provessing, if required
if (isByteArray) {
if (attValue == null) {
value = new byte[0];
} else {
//-- Base64/hexbinary decoding
if (HexDecoder.DATA_TYPE.equals(descriptor.getComponentType())) {
value = HexDecoder.decode(attValue);
} else {
value = Base64Decoder.decode(attValue);
}
}
}
//-- QName resolution (ns:value -> {URI}value), if required
if (isQName) {
value = _namespaceHandling.resolveNamespace(value);
}
//-- set value
handler.setValue(parent, value);
}
/**
* Processes the given attribute set, and creates the
* constructor arguments.
*
* @param atts the AttributeSet to process
* @param classDesc the XMLClassDescriptor of the objec
* @return the array of constructor argument values.
* @throws SAXException If there's a problem creating the constructor argument set.
*/
Arguments processConstructorArgs
(final AttributeSet atts, final XMLClassDescriptor classDesc)
throws SAXException {
if (classDesc == null) {
return new Arguments();
}
//-- Loop through Attribute Descriptors and build
//-- the argument array
//-- NOTE: Due to IDREF being able to reference an
//-- un-yet unmarshalled object, we cannot handle
//-- references as constructor arguments.
//-- kvisco - 20030421
int count = 0;
XMLFieldDescriptor[] descriptors = classDesc.getAttributeDescriptors();
for (XMLFieldDescriptor fieldDescriptor : descriptors) {
if (fieldDescriptor == null) {
continue;
}
if (fieldDescriptor.isConstructorArgument()) {
++count;
}
}
Arguments args = new Arguments();
if (count == 0) {
return args;
}
args.setValues(new Object[count]);
args.setTypes(new Class[count]);
for (XMLFieldDescriptor descriptor : descriptors) {
if (descriptor == null) {
continue;
}
if (!descriptor.isConstructorArgument()) {
continue;
}
int argIndex = descriptor.getConstructorArgumentIndex();
if (argIndex >= count) {
String errorMsg = MessageFormat
.format(resourceBundle
.getString("unmarshalHandler.error.index.out.of.bound"),
new Object[] { argIndex });
throw new SAXException(errorMsg);
}
args.setType(argIndex, descriptor.getFieldType());
String name = descriptor.getXMLName();
String namespace = descriptor.getNameSpaceURI();
int index = atts.getIndex(name, namespace);
if (index >= 0) {
Object value = atts.getValue(index);
//-- check for proper type and do type
//-- conversion
if (isPrimitive(args.getType(argIndex))) {
value = toPrimitiveObject(args.getType(argIndex), (String) value, descriptor);
} else {
// check whether we are looking at an enum-style object, and if so,
// convert the (string) value
value = convertToEnumObject(descriptor, value);
}
//check if the value is a QName that needs to
//be resolved (ns:value -> {URI}value)
String valueType = descriptor.getSchemaType();
if (StringUtil.equals(valueType, QNAME_NAME)) {
value = _namespaceHandling.resolveNamespace(value);
}
args.setValue(argIndex, value);
} else {
if (isPrimitive(args.getType(argIndex))) {
args.setValue(argIndex, toPrimitiveObject(args.getType(argIndex), null, descriptor));
} else {
args.setValue(argIndex, null);
}
}
}
return args;
}
/**
* Checks whether the actual value passed in should be converted to an enum-style class
* instance.
*
* @param descriptor The {@link XMLFieldDescriptor} instance in question.
* @param value The actual value (which might need conversion).
* @return The value, potentially converted to an enum-style class.
*/
private Object convertToEnumObject(final XMLFieldDescriptor descriptor, Object value) {
Class<?> fieldType = descriptor.getFieldType();
Method valueOfMethod;
try {
valueOfMethod = fieldType.getMethod("valueOf", new Class[] {String.class});
if (valueOfMethod != null
&& Modifier.isStatic(valueOfMethod.getModifiers())) {
Class<?> returnType = valueOfMethod.getReturnType();
if (returnType.isAssignableFrom(fieldType)) {
Object enumObject = valueOfMethod.invoke(null, new Object[] {value});
value = enumObject;
}
}
} catch (SecurityException e) {
// TODO: well, cannot do anything about it
} catch (NoSuchMethodException e) {
// TODO: nothing to do, as it simply isn't an enum-style class
} catch (IllegalArgumentException e) {
// TODO: cannot really happen
} catch (IllegalAccessException e) {
// TODO: indicates that the valueOf() method isn't public
} catch (InvocationTargetException e) {
// TODO: hmm .. what else
}
return value;
}
/**
* Processes the given IDREF.
*
* @param idRef the ID of the object in which to reference
* @param descriptor the current FieldDescriptor
* @param parent the current parent object
* @return true if the ID was found and resolved properly
*/
boolean processIDREF (final String idRef, final XMLFieldDescriptor descriptor,
final Object parent) {
Object value = _idResolver.resolve(idRef);
if (value == null) {
//-- save state to resolve later
addReference(idRef, parent, descriptor);
} else {
FieldHandler handler = descriptor.getHandler();
if (handler != null) {
handler.setValue(parent, value);
}
}
return (value != null);
}
/**
* Finds and returns an XMLClassDescriptor for the given class name.
* If a ClassDescriptor could not be found one will attempt to
* be generated.
* @param className the name of the class to find the descriptor for
**/
private XMLClassDescriptor getClassDescriptor (String className)
throws SAXException
{
Class<?> type = null;
try {
//-- use specified ClassLoader if necessary
if (_loader != null) {
type = _loader.loadClass(className);
}
//-- no loader available use Class.forName
else type = Class.forName(className);
}
catch (ClassNotFoundException cnfe) {
return null;
}
return getClassDescriptor(type);
} //-- getClassDescriptor
/**
* Finds and returns an XMLClassDescriptor for the given class. If
* a ClassDescriptor could not be found one will attempt to
* be generated.
* @param cls the Class to get the ClassDescriptor for
**/
XMLClassDescriptor getClassDescriptor(final Class<?> cls)
throws SAXException {
if (cls == null) { return null; }
//-- special case for strings
if (cls == String.class) { return STRING_DESCRIPTOR; }
if (cls.isArray()) { return null; }
if (isPrimitive(cls)) { return null; }
// TODO Joachim
// if (_cdResolver == null)
// _cdResolver = (XMLClassDescriptorResolver)
// ClassDescriptorResolverFactory.createClassDescriptorResolver(BindingType.XML);
XMLClassDescriptor classDesc = null;
try {
InternalContext ctx = getInternalContext();
classDesc = (XMLClassDescriptor) ctx.getXMLClassDescriptorResolver().resolve(cls);
} catch (ResolverException rx) {
// TODO
}
if (classDesc != null) {
return new InternalXMLClassDescriptor(classDesc);
}
if (LOG.isDebugEnabled()) {
LOG.debug(ERROR_DID_NOT_FIND_CLASSDESCRIPTOR + cls.getName());
}
return classDesc;
}
/**
* Finds and returns a ClassDescriptor for the given class. If
* a ClassDescriptor could not be found one will attempt to
* be generated.
* @param className the name of the class to get the Descriptor for
**/
XMLClassDescriptor getClassDescriptor
(String className, ClassLoader loader)
throws SAXException
{
// TODO: Joachim
// if (_cdResolver == null)
// _cdResolver = (XMLClassDescriptorResolver)
// ClassDescriptorResolverFactory.createClassDescriptorResolver(BindingType.XML);
XMLClassDescriptor classDesc = null;
try {
classDesc = getInternalContext().getXMLClassDescriptorResolver().resolve(className, loader);
}
catch(ResolverException rx) {
throw new SAXException(rx);
}
if (classDesc != null) {
return new InternalXMLClassDescriptor(classDesc);
}
if (LOG.isDebugEnabled()) {
LOG.debug(ERROR_DID_NOT_FIND_CLASSDESCRIPTOR + className);
}
return classDesc;
} //-- getClassDescriptor
/**
* Returns the XMLClassLoader
*/
XMLClassDescriptor resolveByXMLName
(String name, String namespace, ClassLoader loader)
throws SAXException
{
try {
return getInternalContext().getXMLClassDescriptorResolver().resolveByXMLName(name, namespace, loader);
}
catch(ResolverException rx) {
throw new SAXException(rx);
}
}
/**
* Returns the package for the given Class
*
* @param type the Class to return the package of
* @return the package for the given Class
**/
String getJavaPackage(Class<?> type)
{
if (type == null)
return null;
String pkg = _javaPackages.get(type);
if(pkg == null)
{
pkg = type.getName();
int idx = pkg.lastIndexOf('.');
if (idx > 0)
pkg = pkg.substring(0,idx);
else
pkg = "";
_javaPackages.put(type, pkg);
}
return pkg;
} //-- getJavaPackage
/**
* Returns the name of a class, handles array types
* @return the name of a class, handles array types
**/
String className(Class<?> type) {
if (type.isArray()) {
return className(type.getComponentType()) + "[]";
}
return type.getName();
} //-- className
/**
* Checks the given StringBuffer to determine if it only
* contains whitespace.
*
* @param sb the StringBuffer to check
* @return true if the only whitespace characters were
* found in the given StringBuffer
**/
static boolean isWhitespace(StringBuffer sb) {
for (int i = 0; i < sb.length(); i++) {
char ch = sb.charAt(i);
switch (ch) {
case ' ':
case '\n':
case '\t':
case '\r':
break;
default:
return false;
}
}
return true;
} //-- isWhitespace
/**
* Loads and returns the class with the given class name using the
* given loader.
* @param className the name of the class to load
* @param loader the ClassLoader to use, this may be null.
**/
Class<?> loadClass(String className, ClassLoader loader)
throws ClassNotFoundException
{
//-- use passed in loader
if ( loader != null )
return loader.loadClass(className);
//-- use internal loader
else if (_loader != null)
return _loader.loadClass(className);
//-- no loader available use Class.forName
return Class.forName(className);
} //-- loadClass
/**
* Resolves the current set of waiting references for the given Id.
* @param id the id that references are waiting for.
* @param value the value of the resolved id.
* @throws SAXException Indicates a problem resolving an IDREF
**/
private void resolveReferences(final String id, final Object value)
throws org.xml.sax.SAXException {
if ((id == null) || (value == null)) {
return;
}
if (_resolveTable == null) {
return;
}
ReferenceInfo refInfo = _resolveTable.remove(id);
while (refInfo != null) {
try {
FieldHandler handler = refInfo.getDescriptor().getHandler();
if (handler != null) {
handler.setValue(refInfo.getTarget(), value);
}
//-- special handling for MapItems
if (refInfo.getTarget() instanceof MapItem) {
resolveReferences(refInfo.getTarget().toString(), refInfo.getTarget());
}
} catch (java.lang.IllegalStateException ise) {
String errorMsg = MessageFormat
.format(resourceBundle
.getString("unmarshalHandler.error.resolving.idRef"),
new Object[] { id, ise.toString() });
throw new SAXException(errorMsg, ise);
}
refInfo = refInfo.getNext();
}
}
/**
* Converts a String to the given primitive object type.
*
* @param type the class type of the primitive in which
* to convert the String to
* @param value the String to convert to a primitive
* @param fieldDesc Descriptor for the given field (value)
* @return the new primitive Object
* @exception SAXException If the String cannot be converted to a primitive object type
*/
Object toPrimitiveObject
(final Class<?> type, final String value, final XMLFieldDescriptor fieldDesc)
throws SAXException {
try {
return toPrimitiveObject(type, value);
} catch (Exception ex) {
UnmarshalState state = _stateStack.getLastState();
if (state != null) {
if (state.getObject() != null) {
String errorMsg = MessageFormat
.format(
resourceBundle
.getString("unmarshalHandler.error.unmarshal.field.of.class"),
new Object[] { fieldDesc.getFieldName(),
state.getObject().getClass().getName() });
throw new SAXException(errorMsg, ex);
}
}
String errorMsg = MessageFormat.format(resourceBundle
.getString("unmarshalHandler.error.unmarshal.field"),
new Object[] { fieldDesc.getFieldName() });
throw new SAXException(errorMsg, ex);
}
}
/**
* Converts a {@link String} to the given primitive object type.
*
* @param type the class type of the primitive in which
* to convert the String to
* @param value the {@link String} to convert to a primitive
* @return the new primitive {@link Object}
*/
public static Object toPrimitiveObject(final Class<?> type, String value) {
return PrimitiveObjectFactory.getObject(type, value);
}
/**
* Internal class used for passing constructor argument
* information.
*/
class Arguments {
/**
* Constructor argument values.
*/
private Object[] _values = null;
/**
* Constructor argument types.
*/
private Class<?>[] _types = null;
/**
* Returns the number of constructor arguments.
* @return The number of constructor arguments.
*/
public int size() {
if (_values == null) {
return 0;
}
return _values.length;
}
public Class<?>[] getTypes() {
return _types;
}
public Object[] getValues() {
return _values;
}
public Class<?> getType(int index) {
return _types[index];
}
public void setValues(Object[] values) {
_values = values;
}
public void setValue(int index, Object value) {
_values[index] = value;
}
public void setTypes(Class<?>[] types) {
_types = types;
}
public void setType(int index, Class<?> type) {
_types[index] = type;
}
}
/**
* A class for handling Arrays during unmarshalling.
*
* @author <a href="mailto:kvisco@intalio.com">kvisco@intalio.com</a>
*/
public static class ArrayHandler {
Class<?> _componentType = null;
ArrayList<Object> _items = null;
/**
* Creates a new ArrayHandler
*
* @param componentType the ComponentType for the array.
*/
ArrayHandler(final Class<?> componentType) {
if (componentType == null) {
String errMsg = resourceBundle.getString("unmarshalHandler.error.componentType.null");
throw new IllegalArgumentException(errMsg);
}
_componentType = componentType;
_items = new ArrayList<Object>();
} //-- ArrayHandler
/**
* Adds the given object to the underlying array.
* @param obj The object to be added to the underlying array.
*/
public void addObject(final Object obj) {
if (obj == null) {
return;
}
/* disable check for now until we write a
small function to handle primitive and their
associated wrapper classes
if (!_componentType.isAssignableFrom(obj.getClass())) {
String err = obj.getClass().getName() + " is not an instanceof " +
_componentType.getName();
throw new IllegalArgumentException(err);
}
*/
_items.add(obj);
}
/**
* Returns the data handled by this class as an array.
* @return The data handled internally in the form of an array.
*/
public Object getObject() {
int size = _items.size();
Object array = Array.newInstance(_componentType, size);
for (int i = 0; i < size; i++) {
Array.set(array, i, _items.get(i));
}
return array;
}
/**
* Returns the component type handled by this class.
* @return The component type handled by this class.
*/
public Class<?> componentType() {
return _componentType;
}
} //-- ArrayHandler
/**
* Returns the ObjectFactory instance in use.
* @return the ObjectFactory instance in use.
*/
public ObjectFactory getObjectFactory() {
return _objectFactory;
}
/**
* Sets a (custom) ObjectFactory instance.
* @param objectFactory A (custom) ObjectFactory instance
*/
public void setObjectFactory(ObjectFactory objectFactory) {
_objectFactory = objectFactory;
}
/**
* Returnss a refrence to the {@link UnmarshalStateStack} instance currently in use.
* @return The {@link UnmarshalStateStack} in use.
*/
public UnmarshalStateStack getStateStack() {
return _stateStack;
}
/**
* Returns the top {@link UnmarshalState} instance from the {@link UnmarshalStateStack}.
* @return The top {@link UnmarshalState} instance.
*/
public UnmarshalState getTopState() {
return _topState;
}
/**
* Returns the {@link StrictElementHandler} in use.
* @return The {@link StrictElementHandler} in use.
*/
public StrictElementHandler getStrictElementHandler() {
return _strictElementHandler;
}
/**
* Returns the {@link NamespaceHandling} in use.
* @return The currently active {@link NamespaceHandling} instance.
*/
public NamespaceHandling getNamespaceHandling() {
return _namespaceHandling;
}
/**
* Returns the current {@link ClassLoader} in use.
* @return The {@link ClassLoader} in use.
*/
public ClassLoader getClassLoader() {
return _loader;
}
/**
* Returns the currently used {@link AnyNodeUnmarshalHandler} instance.
* @return The {@link AnyNodeUnmarshalHandler} in use.
*/
public AnyNodeUnmarshalHandler getAnyNodeHandler() {
return _anyNodeHandler;
}
/**
* Returns the currently active {@link UnmarshalListenerDelegate} instance
* @return The active {@link UnmarshalListenerDelegate} in use.
*/
public UnmarshalListenerDelegate getDelegateUnmarshalListener() {
return _delegateUnmarshalListener;
}
/**
* Indicats whether Object instances should be re-used.
* @return True if object instances should be re-used.
*/
public boolean isReuseObjects() {
return _reuseObjects;
}
/**
* Hashtable to store idReference and ReferenceInfo
* @return Hashtable
*/
public Hashtable<String, ReferenceInfo> getResolveTable() {
return _resolveTable;
}
/**
* returns the AnyNode (if any).
* @return AnyNode, could be null
*/
public org.exolab.castor.types.AnyNode getAnyNode() {
return _node;
}
/**
* sets the AnyNode
* @param node AnyNode
*/
public void setAnyNode(org.exolab.castor.types.AnyNode node) {
_node = node;
}
/**
* Indicates whether it's necessary to clear any collection or not.
* @return True if it's necessary to clear any collection.
*/
public boolean isClearCollections() {
return _clearCollections;
}
}