org.exolab.castor.xml.util.XMLClassDescriptorImpl Maven / Gradle / Ivy
/*
* 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
* [email protected].
*
* 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.
*
* This file was originally developed by Keith Visco during the course of employment at Intalio Inc.
* All portions of this file developed by Keith Visco after Jan 19 2005 are Copyright (C) 2005 Keith
* Visco. All Rights Reserved.
*
* $Id$
*/
package org.exolab.castor.xml.util;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.exolab.castor.mapping.AbstractFieldHandler;
import org.exolab.castor.mapping.AccessMode;
import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.mapping.FieldDescriptor;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.xml.FieldValidator;
import org.exolab.castor.xml.NodeType;
import org.exolab.castor.xml.TypeValidator;
import org.exolab.castor.xml.UnmarshalState;
import org.exolab.castor.xml.ValidationContext;
import org.exolab.castor.xml.ValidationException;
import org.exolab.castor.xml.Validator;
import org.exolab.castor.xml.XMLClassDescriptor;
import org.exolab.castor.xml.XMLFieldDescriptor;
import org.exolab.castor.xml.location.XPathLocation;
import org.exolab.castor.xml.util.resolvers.ResolveHelpers;
/**
* The core implementation of XMLClassDescriptor. This class is used by both generated source code
* as well as the XMLMappingLoader.
*
* @author Keith Visco
* @version $Revision$ $Date: 2006-04-13 06:47:36 -0600 (Thu, 13 Apr 2006) $
*/
public class XMLClassDescriptorImpl extends Validator implements XMLClassDescriptor {
/**
* The ALL compositor to signal the fields of the described class must all be present and valid,
* if they are required.
*/
private static final short ALL = 0;
/**
* The CHOICE compositor to signal the fields of the described class must be only a choice. They
* are mutually exclusive.
*/
private static final short CHOICE = 1;
/** The SEQUENCE compositor....currently is the same as ALL. */
private static final short SEQUENCE = 2;
private static final String NULL_CLASS_ERR =
"The Class passed as an argument to the constructor of "
+ "XMLClassDescriptorImpl may not be null.";
private static final String WILDCARD = "*";
/** The set of attribute descriptors. */
private XMLFieldDescriptors _attributes = null;
/** Cached attribute descriptors for improved performance. */
private XMLFieldDescriptor[] _attArray = null;
/** The Class that this ClassDescriptor describes. */
private Class _class = null;
/** A variable to keep track of the number of container fields. */
private int _containerCount = 0;
/** The XMLFieldDescriptor for text data. */
private XMLFieldDescriptor _contentDescriptor = null;
/** The TypeValidator to use for validation of the described class. */
private TypeValidator _validator = null;
/** The set of element descriptors. */
private XMLFieldDescriptors _elements = null;
/** Cached element descriptors for improved performance. */
private XMLFieldDescriptor[] _elemArray = null;
/** The namespace prefix that is to be used when marshalling. */
private String _nsPrefix = null;
/** The namespace URI used for both Marshalling and Unmarshalling. */
private String _nsURI = null;
/** The name of the XML element. */
private String _xmlName;
/**
* Flag to indicate XML refers to global element or element with anonymous type in XSD. Useful
* information for frameworks that use Castor for XML binding and generate additional schema
* definition elements.
*/
private boolean _elementDefinition = false;
/**
* The descriptor of the class which this class extends, or null if this is a top-level class.
*/
private XMLClassDescriptor _extends;
/** The field of the identity for this class. */
private FieldDescriptor _identity;
/** The access mode specified for this class. */
private AccessMode _accessMode;
/**
* A flag to indicate that this XMLClassDescriptor was created via introspection.
*/
private boolean _introspected = false;
private short _compositor = ALL;
/**
* Defines the sequence of elements for unmarshalling validation (to be used with compositor ==
* SEQUENCE only).
*/
private final List _sequenceOfElements = new ArrayList();
private List _substitutes = new LinkedList();
/** Map holding the properties set and read by Natures. */
private final Map _properties = new HashMap();
/** Map holding the available natures. */
private final Set _natures = new HashSet();
/**
* Creates an XMLClassDescriptor class used by the Marshalling Framework.
*
* @param type the Class type with which this ClassDescriptor describes.
*/
public XMLClassDescriptorImpl(final Class type) {
this();
if (type == null) {
throw new IllegalArgumentException(NULL_CLASS_ERR);
}
_class = type;
// useless, no name is known setXMLName(null);
}
/**
* Creates an XMLClassDescriptor class used by the Marshalling Framework.
*
* @param type the Class type with which this ClassDescriptor describes.
*/
public XMLClassDescriptorImpl(Class type, String xmlName) {
this();
if (type == null) {
throw new IllegalArgumentException(NULL_CLASS_ERR);
}
this._class = type;
setXMLName(xmlName);
} // -- XMLClassDescriptorImpl
/**
* Protected constructor used by this class, and subclasses only
*/
protected XMLClassDescriptorImpl() {
_attributes = new XMLFieldDescriptors(5);
_elements = new XMLFieldDescriptors(7);
} // -- XMLClassDescriptor
// ------------------/
// - Public Methods -/
// ------------------/
/**
* Adds the given XMLFieldDescriptor to the list of descriptors. The descriptor will be added to
* the appropriate list by calling XMLFieldDescriptor#getNodeType() to determine it's type.
*
* @param descriptor the XMLFieldDescriptor to add
*/
public void addFieldDescriptor(XMLFieldDescriptor descriptor) {
addFieldDescriptor(descriptor, true);
} // -- addFieldDescriptor
/**
* Returns true if the given XMLFieldDescriptor is contained within this XMLClassDescriptor.
*
* @return true if the XMLFieldDescriptor is part of this XMLClassDescriptor, otherwise false.
*/
public boolean contains(XMLFieldDescriptor descriptor) {
if (descriptor == null) {
return false;
}
if (_attributes.contains(descriptor)) {
return true;
}
if (_elements.contains(descriptor)) {
return true;
}
return descriptor.equals(_contentDescriptor);
} // -- contains
/**
* Returns the set of XMLFieldDescriptors for all members that should be marshalled as XML
* attributes.
*
* @return an array of XMLFieldDescriptors for all members that should be marshalled as XML
* attributes.
*/
public XMLFieldDescriptor[] getAttributeDescriptors() {
return getAttributeArray().clone();
} // getAttributeDescriptors
/**
* Returns the XMLFieldDescriptor for the member that should be marshalled as text content.
*
* @return the XMLFieldDescriptor for the member that should be marshalled as text content.
*/
public XMLFieldDescriptor getContentDescriptor() {
return _contentDescriptor;
} // getContentDescriptor
/**
* Returns the set of XMLFieldDescriptors for all members that should be marshalled as XML
* elements.
*
* @return an array of XMLFieldDescriptors for all members that should be marshalled as XML
* elements.
*/
public XMLFieldDescriptor[] getElementDescriptors() {
return getElementArray().clone();
} // getElementDescriptors
/**
* Checks whether the given XMLFieldDescriptor is the one actually expected, given the natural
* order as defined by a sequence definition
*
* @param elementDescriptor The XML field descriptor to be checked
* @throws ValidationException If the descriptor is not the one expected
*/
public void checkDescriptorForCorrectOrderWithinSequence(
final XMLFieldDescriptor elementDescriptor, UnmarshalState parentState, String xmlName)
throws ValidationException {
if (_compositor == SEQUENCE && !_sequenceOfElements.isEmpty()) {
if (parentState.getExpectedIndex() == _sequenceOfElements.size()) {
throw new ValidationException("Element with name " + xmlName + " passed to type "
+ getXMLName()
+ " in incorrect order; It is not allowed to be the last element of this sequence!");
}
XMLFieldDescriptor expectedElementDescriptor =
_sequenceOfElements.get(parentState.getExpectedIndex());
String expectedElementName = expectedElementDescriptor.getXMLName();
String elementName = xmlName;
boolean anyNode = expectedElementDescriptor.getFieldName().equals("_anyObject")
&& expectedElementName == null;
// choices
if (!anyNode && expectedElementDescriptor.getXMLName().equals("-error-if-this-is-used-")) {
// find possible names
List possibleNames = new ArrayList();
fillPossibleNames(possibleNames, expectedElementDescriptor);
// check name
if (!possibleNames.contains(elementName)) {
if (!expectedElementDescriptor.isRequired()) {
parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
checkDescriptorForCorrectOrderWithinSequence(elementDescriptor, parentState, xmlName);
} else {
throw new ValidationException("Element with name " + elementName + " passed to type "
+ getXMLName()
+ " in incorrect order; expected element has to be member of the expected choice.");
}
} else {
parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
}
return;
}
// multi valued flag
if (expectedElementDescriptor.isMultivalued() && !parentState.isWithinMultivaluedElement()) {
parentState.setWithinMultivaluedElement(true);
}
if (!anyNode && !(expectedElementName).equals(elementName)) {
// handle substitution groups !!!
List substitutes = expectedElementDescriptor.getSubstitutes();
if (substitutes != null && !substitutes.isEmpty()) {
if (substitutes.contains(elementName)) {
if (!parentState.isWithinMultivaluedElement()) {
parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
}
return;
}
}
// handle multi-valued fields
if (expectedElementDescriptor.isMultivalued()) {
parentState.setWithinMultivaluedElement(false);
parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
checkDescriptorForCorrectOrderWithinSequence(elementDescriptor, parentState, xmlName);
return;
}
// handle required fields
if (expectedElementDescriptor.isRequired()) {
throw new ValidationException("Element with name " + elementName + " passed to type "
+ getXMLName() + " in incorrect order; expected element with name '"
+ expectedElementName + "' or any other optional element declared prior to it.");
}
// non required field, proceed until next required field
parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
checkDescriptorForCorrectOrderWithinSequence(elementDescriptor, parentState, xmlName);
return;
}
if (!parentState.isWithinMultivaluedElement()) {
parentState.setExpectedIndex(parentState.getExpectedIndex() + 1);
}
}
}
private void fillPossibleNames(List possibleNames, XMLFieldDescriptor descriptor) {
XMLFieldDescriptor[] descriptors =
((XMLClassDescriptor) descriptor.getClassDescriptor()).getElementDescriptors();
if (descriptors.length == 0) {
return;
}
for (XMLFieldDescriptor fieldDescriptor : descriptors) {
if ("_items".equals(fieldDescriptor.getFieldName())
|| "-error-if-this-is-used-".equals(fieldDescriptor.getXMLName())) {
fillPossibleNames(possibleNames, fieldDescriptor);
} else {
possibleNames.add(fieldDescriptor.getXMLName());
}
}
}
/**
* Returns the XML field descriptor matching the given xml name and nodeType. If NodeType is null,
* then either an AttributeDescriptor, or ElementDescriptor may be returned. Null is returned if
* no matching descriptor is available.
*
* If an field is matched in one of the container field, it will return the container field that
* contain the field named 'name'
*
* @param name the xml name to match against
* @param nodeType the NodeType to match against, or null if the node type is not known.
* @return the matching descriptor, or null if no matching descriptor is available.
*/
public XMLFieldDescriptor getFieldDescriptor(String name, String namespace, NodeType nodeType) {
boolean wild = ((nodeType == null) || _introspected);
XMLFieldDescriptor result = null;
XMLFieldDescriptor[] attributes = _attArray;
XMLFieldDescriptor[] elements = _elemArray;
// TODO: clean up location patch
String location = null;
if (name != null) {
int idx = name.lastIndexOf('/');
if (idx >= 0) {
location = name.substring(0, idx);
name = name.substring(idx + 1);
}
}
if (wild || (nodeType == NodeType.Element)) {
if (elements == null)
elements = getElementArray();
// if (_compositor == SEQUENCE && sequenceOfElements.size() > 0) {
// XMLFieldDescriptor elementDescriptor = (XMLFieldDescriptor)
// sequenceOfElements.get(sequenceElementCount);
// String elementName = elementDescriptor.getXMLName();
// if (!elementName.equals(name)) {
// throw new IllegalArgumentException ("Element with name " + name + " passed to type " +
// getXMLName() + " in incorrect order; expected TODO.");
// } else {
// sequenceElementCount++;
// }
// }
for (int i = 0; i < elements.length; i++) {
XMLFieldDescriptor desc = elements[i];
if (desc == null)
continue;
if (location != null) {
if (!location.equals(desc.getLocationPath()))
continue;
}
if (desc.matches(name)) {
if (!desc.matches(WILDCARD)) {
// currently, a 'null' namespace value indicates that the XML artifact in question
// either is namespace-less or is part of the default namespace; in both cases,
// we currently can not perform namespace comparison.
// TODO[wguttmn, 20071205]: add code to handle default namespace declarations rather
// than checking for null
if (namespace == null
|| ResolveHelpers.namespaceEquals(namespace, desc.getNameSpaceURI())) {
return desc;
}
} else {
// -- possible wildcard match check for
// -- exact match (we need to extend the
// -- the descriptor interface to handle this
if (name.equals(desc.getXMLName()))
return desc;
// -- assume wild-card match
result = desc;
}
}
// handle container
if (desc.isContainer()) {
XMLClassDescriptor xcd = (XMLClassDescriptor) desc.getClassDescriptor();
// prevent endless loop
if (xcd != this) {
// is it in this class descriptor?
if (xcd.getFieldDescriptor(name, namespace, NodeType.Element) != null) {
result = desc;
break;
}
}
} // container
}
if (result != null)
return result;
}
// -- handle attributes
if (wild || (nodeType == NodeType.Attribute)) {
if (attributes == null)
attributes = getAttributeArray();
for (int i = 0; i < attributes.length; i++) {
XMLFieldDescriptor desc = attributes[i];
if (desc == null)
continue;
if (desc.matches(name)) {
return desc;
}
}
}
// -- handle namespace node
if (nodeType == NodeType.Namespace) {
if (attributes == null)
attributes = getAttributeArray();
for (int i = 0; i < attributes.length; i++) {
if (attributes[i] == null)
continue;
if (attributes[i].getNodeType() == NodeType.Namespace) {
return attributes[i];
}
}
}
// To handle container object, we need to check if an attribute of a
// container field match this attribute
if (nodeType == NodeType.Attribute) {
if (elements == null)
elements = getElementArray();
for (int i = 0; i < elements.length; i++) {
XMLFieldDescriptor desc = elements[i];
if (desc.isContainer()) {
XMLClassDescriptor xcd = (XMLClassDescriptor) desc.getClassDescriptor();
// prevent endless loop
if (xcd != this) {
// is it in this class descriptor?
XMLFieldDescriptor temp = xcd.getFieldDescriptor(name, namespace, NodeType.Attribute);
if (temp != null) {
return desc;
}
}
}
}
}
return null;
} // -- getFieldDescriptor
/**
* @return the namespace prefix to use when marshalling as XML.
*/
public String getNameSpacePrefix() {
return _nsPrefix;
} // -- getNameSpacePrefix
/**
* @return the namespace URI used when marshalling and unmarshalling as XML.
*/
public String getNameSpaceURI() {
return _nsURI;
} // -- getNameSpaceURI
/**
* Returns a specific validator for the class described by this ClassDescriptor. A null value may
* be returned if no specific validator exists.
*
* @return the type validator for the class described by this ClassDescriptor.
*/
public TypeValidator getValidator() {
if (_validator != null)
return _validator;
return this;
} // -- getValidator
/**
* Returns the XML Name for the Class being described.
*
* @return the XML name.
*/
public String getXMLName() {
return _xmlName;
} // -- getXMLName
/**
* Returns true if XML schema definition of this Class is that of a global element or element with
* anonymous type definition.
*/
public boolean isElementDefinition() {
return _elementDefinition;
} // -- isElementDefinition
/**
* Returns true if this XMLClassDescriptorImpl has any fields which are container objects. A
* container object is a Java object which holds data the should be marshalled, but the object
* itself should not be. So the container object will be "unwrapped" and the fields associated
* with the container will appear as if they were part of this class.
*
* @return true if any of the fields are container fields, otherwise false.
*/
public boolean hasContainerFields() {
return (_containerCount > 0);
} // -- hasContainerFields
/**
* Removes the given XMLFieldDescriptor from the list of descriptors.
*
* @param descriptor the XMLFieldDescriptor to remove
* @return true if the descriptor was removed.
*/
public boolean removeFieldDescriptor(XMLFieldDescriptor descriptor) {
if (descriptor == null)
return false;
boolean removed = false;
NodeType nodeType = descriptor.getNodeType();
switch (nodeType.getType()) {
case NodeType.NAMESPACE:
case NodeType.ATTRIBUTE:
if (_attributes.remove(descriptor)) {
removed = true;
_attArray = null;
}
break;
case NodeType.TEXT:
if (_contentDescriptor == descriptor) {
_contentDescriptor = null;
removed = true;
}
break;
default:
if (_elements.remove(descriptor)) {
_elemArray = null;
removed = true;
if (descriptor.isContainer())
--_containerCount;
}
break;
}
return removed;
} // -- removeFieldDescriptor
/**
* Sets the compositor for the fields of the described class to be ALL.
*/
public void setCompositorAsAll() {
_compositor = ALL;
} // -- setCompositorAsAll
/**
* Sets the compositor for the fields of the described class to be CHOICE.
*/
public void setCompositorAsChoice() {
_compositor = CHOICE;
} // -- setCompositorAsChoice
/**
* Sets the compositor for the fields of the described class to be a Sequence.
*/
public void setCompositorAsSequence() {
_compositor = SEQUENCE;
} // -- setCompositorAsSequence
/**
* Sets the XMLClassDescriptor that this descriptor inherits from
*
* @param classDesc the XMLClassDescriptor that this descriptor extends
*/
public void setExtends(XMLClassDescriptor classDesc) {
FieldDescriptor[] fields = null;
// -- remove reference to previous extended descriptor
if (_extends != null) {
sortDescriptors();
fields = _extends.getFields();
for (int i = 0; i < fields.length; i++) {
removeFieldDescriptor((XMLFieldDescriptor) fields[i]);
}
}
this._extends = classDesc;
// -- flatten out the hierarchy
if (_extends != null) {
fields = classDesc.getFields();
for (int i = 0; i < fields.length; i++) {
addFieldDescriptor((XMLFieldDescriptor) fields[i], false);
}
}
} // -- setExtends
/**
* Sets the Identity FieldDescriptor, if the FieldDescriptor is not already a contained in this
* ClassDescriptor, it will be added
*/
public void setIdentity(XMLFieldDescriptor fieldDesc) {
if (fieldDesc != null) {
if ((!_attributes.contains(fieldDesc)) && (!_elements.contains(fieldDesc))) {
addFieldDescriptor(fieldDesc);
}
}
this._identity = fieldDesc;
} // -- setIdentity
/**
* Sets the namespace prefix used when marshalling as XML.
*
* @param nsPrefix the namespace prefix used when marshalling the "described" object
*/
public void setNameSpacePrefix(String nsPrefix) {
this._nsPrefix = nsPrefix;
} // -- setNameSpacePrefix
/**
* Sets the namespace URI used when marshalling and unmarshalling as XML.
*
* @param nsURI the namespace URI used when marshalling and unmarshalling the "described" Object.
*/
public void setNameSpaceURI(String nsURI) {
this._nsURI = nsURI;
} // -- setNameSpaceURI
/**
* Sets the validator to use for the class described by this ClassDescriptor
*
* @param validator the validator to use when peforming validation of the described class. This
* may be null to signal default validation.
*/
// public void setValidator(TypeValidator validator) {
// this.validator = validator;
// } //-- setValidator
/**
* Sets the XML name for the Class described by this XMLClassDescriptor
*
* @param xmlName the XML name for the Class described by this XMLClassDescriptor
*/
public void setXMLName(String xmlName) {
if (xmlName == null) {
if (_xmlName == null && _class != null) {
_xmlName = _class.getName();
}
} else
this._xmlName = xmlName;
} // -- setXMLName
/**
* Set elementDefinition to true to indicate Class XML schema definition is a global element or
* element with anonymous type.
*
* @param elementDefinition flag to indicate XML definition is global element or element with
* anonymous type
*/
public void setElementDefinition(boolean elementDefinition) {
this._elementDefinition = elementDefinition;
} // -- setElementDefinition
/**
* This method is used to keep the set of descriptors in the proper sorted lists. If you
* dynamically change the NodeType of an XMLFieldDescriptor after adding it the this
* ClassDescriptor, then call this method.
*/
public void sortDescriptors() {
// -- handle attributes
XMLFieldDescriptor[] descriptors = getAttributeArray();
for (int i = 0; i < descriptors.length; i++) {
XMLFieldDescriptor fieldDesc = descriptors[i];
switch (fieldDesc.getNodeType().getType()) {
case NodeType.ELEMENT:
_elements.add(fieldDesc);
_attributes.remove(fieldDesc);
_attArray = null;
break;
case NodeType.TEXT:
_attributes.remove(fieldDesc);
_attArray = null;
break;
default:
break;
}
}
// -- handle elements
descriptors = getElementArray();
for (int i = 0; i < descriptors.length; i++) {
XMLFieldDescriptor fieldDesc = descriptors[i];
switch (fieldDesc.getNodeType().getType()) {
case NodeType.NAMESPACE:
case NodeType.ATTRIBUTE:
_attributes.add(fieldDesc);
_elements.remove(fieldDesc);
_elemArray = null;
break;
case NodeType.TEXT:
_elements.remove(fieldDesc);
_elemArray = null;
break;
default:
break;
}
}
} // -- sortDescriptors
/**
* Returns the String representation of this XMLClassDescriptor
*
* @return the String representation of this XMLClassDescriptor
*/
public String toString() {
String str = super.toString() + "; descriptor for class: ";
// -- add class name
if (_class != null)
str += _class.getName();
else
str += "[null]";
// -- add xml name
str += "; xml name: " + _xmlName;
return str;
} // -- toString
/**
* Validates the given Object
*
* @param object the Object to validate
*/
public void validate(Object object) throws ValidationException {
validate(object, (ValidationContext) null);
} // -- validate
/**
* Validates the given object
*
* @param object the Object to validate
* @param context the ValidationContext
*/
public void validate(Object object, ValidationContext context) throws ValidationException {
if (object == null) {
throw new ValidationException("Cannot validate a null object.");
}
Class a = getJavaClass();
ClassLoader acl = a.getClassLoader();
Class b = object.getClass();
ClassLoader bcl = b.getClassLoader();
if (!getJavaClass().isAssignableFrom(object.getClass())) {
String err =
"The given object is not an instance of the class" + " described by this ClassDecriptor.";
throw new ValidationException(err);
}
// -- DEBUG
// System.out.println("Validating class: " + object.getClass().getName());
// -- /DEBUG
XMLFieldDescriptor[] localElements = getElementArray();
XMLFieldDescriptor[] localAttributes = getAttributeArray();
if (_extends != null) {
// -- cascade call for validation
if (_extends instanceof XMLClassDescriptorImpl) {
((XMLClassDescriptorImpl) _extends).validate(object, context);
} else {
TypeValidator baseValidator = _extends.getValidator();
if (baseValidator != null) {
baseValidator.validate(object, context);
}
}
// -- get local element descriptors by filtering out inherited ones
XMLFieldDescriptor[] inheritedElements = _extends.getElementDescriptors();
XMLFieldDescriptor[] allElements = localElements;
localElements = new XMLFieldDescriptor[allElements.length - inheritedElements.length];
int localIdx = 0;
for (int i = 0; i < allElements.length; i++) {
XMLFieldDescriptor desc = allElements[i];
boolean isInherited = false;
for (int idx = 0; idx < inheritedElements.length; idx++) {
if (inheritedElements[idx].equals(desc)) {
isInherited = true;
break;
}
}
if (!isInherited) {
localElements[localIdx] = desc;
++localIdx;
}
}
// -- get local attribute descriptors by filtering out inherited ones
XMLFieldDescriptor[] inheritedAttributes = _extends.getAttributeDescriptors();
XMLFieldDescriptor[] allAttributes = localAttributes;
localAttributes = new XMLFieldDescriptor[allAttributes.length - inheritedAttributes.length];
localIdx = 0;
for (int i = 0; i < allAttributes.length; i++) {
XMLFieldDescriptor desc = allAttributes[i];
boolean isInherited = false;
for (int idx = 0; idx < inheritedAttributes.length; idx++) {
if (inheritedAttributes[idx].equals(desc)) {
isInherited = true;
break;
}
}
if (!isInherited) {
localAttributes[localIdx] = desc;
++localIdx;
}
}
}
switch (_compositor) {
case CHOICE:
boolean found = false;
boolean hasLocalDescs = (localElements.length > 0);
XMLFieldDescriptor fieldDesc = null;
// -- handle elements, affected by choice
for (int i = 0; i < localElements.length; i++) {
XMLFieldDescriptor desc = localElements[i];
if (desc == null) {
continue;
}
FieldHandler handler = desc.getHandler();
if (handler.getValue(object) != null) {
// Special case if we have a Vector, an ArrayList
// or an Array --> need to check if it is not empty
if (desc.isMultivalued()) {
Object temp = handler.getValue(object);
// -- optimize this?
if (Array.getLength(temp) == 0) {
temp = null;
continue;
}
temp = null;
}
if (found) {
String err = null;
if (desc.isContainer()) {
err = "The group '" + desc.getFieldName();
err += "' cannot exist at the same time that ";
if (fieldDesc.isContainer()) {
err += "the group '" + fieldDesc.getFieldName();
} else {
err += "the element '" + fieldDesc.getXMLName();
}
err += "' also exists.";
} else {
err = "The element '" + desc.getXMLName();
err += "' cannot exist at the same time that ";
err += "element '" + fieldDesc.getXMLName() + "' also exists.";
}
throw new ValidationException(err);
}
found = true;
fieldDesc = desc;
FieldValidator fieldValidator = desc.getValidator();
if (fieldValidator != null) {
fieldValidator.validate(object, context);
}
}
}
// if all elements are mandatory, print the grammar that the choice
// must match.
if ((!found) && (hasLocalDescs)) {
StringBuilder buffer = new StringBuilder(40);
boolean existsOptionalElement = false;
buffer.append('(');
String sep = " | ";
for (int i = 0; i < localElements.length; i++) {
XMLFieldDescriptor desc = localElements[i];
if (desc == null) {
continue;
}
FieldValidator fieldValidator = desc.getValidator();
if (fieldValidator.getMinOccurs() == 0) {
existsOptionalElement = true;
break;
}
buffer.append(sep).append(desc.getXMLName());
}
buffer.append(')');
if (!existsOptionalElement) {
String err = "In the choice contained in <" + this.getXMLName()
+ ">, at least one of these elements must appear:\n" + buffer;
throw new ValidationException(err);
}
}
// -- handle attributes, not affected by choice
for (int i = 0; i < localAttributes.length; i++) {
validateField(object, context, localAttributes[i]);
}
// -- handle content, not affected by choice
if (_contentDescriptor != null) {
validateField(object, context, _contentDescriptor);
}
break;
// -- Currently SEQUENCE is handled the same as all
case SEQUENCE:
// -- ALL
default:
// -- handle elements
for (int i = 0; i < localElements.length; i++) {
final XMLFieldDescriptor fieldDescriptor = localElements[i];
if (fieldDescriptor == null) {
continue;
}
validateField(object, context, fieldDescriptor);
}
// -- handle attributes
// for (int i = 0; i < _attributes.size(); i++) {
for (int i = 0; i < localAttributes.length; i++) {
validateField(object, context, localAttributes[i]);
}
// -- handle content
if (_contentDescriptor != null) {
validateField(object, context, _contentDescriptor);
}
break;
}
} // -- validate
/**
* Validates agiven field of an object, as described by its {@link XMLFieldDescriptor} instance.
*
* @param object The parent object, whose field to validate.
* @param context The current {@link ValidationContext} instance.
* @param fieldDescriptor The {@link XMLFieldDescriptor} instance describing the field to
* validate.
* @throws ValidationException If validation did report a problem.
*/
private void validateField(final Object object, final ValidationContext context,
final XMLFieldDescriptor fieldDescriptor) throws ValidationException {
FieldValidator fieldValidator = fieldDescriptor.getValidator();
if (fieldValidator != null) {
try {
fieldValidator.validate(object, context);
} catch (ValidationException e) {
if (fieldDescriptor.getNodeType() == NodeType.Attribute
|| fieldDescriptor.getNodeType() == NodeType.Element) {
addLocationInformation(fieldDescriptor, e);
}
throw e;
}
}
}
/**
* Adds location information to the {@link ValidationException} instance.
*
* @param localElement The {@link XMLFieldDescriptor} instance whose has been responsible for
* generating the error.
* @param e The {@link ValidationException} to enrich.
*/
private void addLocationInformation(final XMLFieldDescriptor localElement,
final ValidationException e) {
XPathLocation loc = (XPathLocation) e.getLocation();
if (loc == null) {
loc = new XPathLocation();
e.setLocation(loc);
if (localElement.getNodeType() == NodeType.Attribute) {
loc.addAttribute(localElement.getXMLName());
} else {
loc.addChild(localElement.getXMLName());
}
}
}
// -------------------------------------/
// - Implementation of ClassDescriptor -/
// -------------------------------------/
/**
* Returns the Java class represented by this descriptor.
*
* @return The Java class
*/
public Class getJavaClass() {
return _class;
} // -- getJavaClass
/**
* Returns a list of fields represented by this descriptor.
*
* @return A list of fields
*/
public FieldDescriptor[] getFields() {
int size = _attributes.size();
size += _elements.size();
if (_contentDescriptor != null)
++size;
XMLFieldDescriptor[] fields = new XMLFieldDescriptor[size];
_attributes.toArray(fields);
_elements.toArray(fields, _attributes.size());
if (_contentDescriptor != null) {
fields[size - 1] = _contentDescriptor;
}
return fields;
} // -- getFields
/**
* Returns the class descriptor of the class extended by this class.
*
* @return The extended class descriptor
*/
public ClassDescriptor getExtends() {
return _extends;
} // -- getExtends
/**
* Returns the identity field, null if this class has no identity.
*
* @return The identity field
*/
public FieldDescriptor getIdentity() {
return _identity;
} // -- getIdentity
/**
* Returns the access mode specified for this class.
*
* @return The access mode
*/
public AccessMode getAccessMode() {
return _accessMode;
} // -- getAccessMode
/**
* @see org.exolab.castor.xml.XMLClassDescriptor#canAccept(java.lang.String, java.lang.String,
* java.lang.Object)
*/
public boolean canAccept(String name, String namespace, Object object) {
boolean result = false;
boolean hasValue = false;
XMLFieldDescriptor[] fields = null;
int i = 0;
// 1--direct look up for a field
XMLFieldDescriptor fieldDesc = this.getFieldDescriptor(name, namespace, NodeType.Element);
if (fieldDesc == null)
fieldDesc = this.getFieldDescriptor(name, namespace, NodeType.Attribute);
// if the descriptor is still null, the field can't be in stored in this classDescriptor
if (fieldDesc == null)
return false;
Object tempObject;
// 3--The descriptor is multivalued
if (fieldDesc.isMultivalued()) {
// -- check size
FieldValidator validator = fieldDesc.getValidator();
if (validator != null) {
if (validator.getMaxOccurs() < 0) {
result = true;
} else {
// count current objects and add 1
tempObject = fieldDesc.getHandler().getValue(object);
int current = Array.getLength(tempObject);
int newTotal = current + 1;
result = (newTotal <= validator.getMaxOccurs());
}
} else {
// -- not created by source generator...assume unbounded
result = true;
}
}
// 3-- the fieldDescriptor is a container
else if (fieldDesc.isContainer()) {
tempObject = fieldDesc.getHandler().getValue(object);
// if the object is not yet instantiated, we return true
if (tempObject == null)
result = true;
else
result = ((XMLClassDescriptor) fieldDesc.getClassDescriptor()).canAccept(name, namespace,
tempObject);
}
// 4-- Check if the value is set or not
else {
FieldHandler handler = fieldDesc.getHandler();
boolean checkPrimitiveValue = true;
if (handler instanceof AbstractFieldHandler) {
hasValue = ((AbstractFieldHandler) handler).hasValue(object);
// -- poor man's check for a generated handler, since
// -- all generated handlers extend XMLFieldHandler, however
// -- this doesn't guarantee that that handler is indeed
// -- a generated handler, it does however mean that
// -- the handler definately didn't come from the
// -- MappingLoader.
// checkPrimitiveValue = (!(handler instanceof XMLFieldHandler));
} else {
hasValue = (handler.getValue(object) != null);
}
// -- patch for primitives, we should check
// -- for the has-method somehow...but this
// -- is good enough for now. This may break
// -- some non-Castor-generated code with
// -- primitive values that have been set
// -- with the same as the defaults
if (hasValue && checkPrimitiveValue && fieldDesc.getFieldType().isPrimitive()) {
if (isDefaultPrimitiveValue(handler.getValue(object))) {
hasValue = false;
}
}
// -- end patch
result = !hasValue;
}
// 5--if there is no value and the _compositor is CHOICE
// --we have to check to see if another value has not been set
if (result && (_compositor == CHOICE) && (fieldDesc.getNodeType() == NodeType.Element)) {
fields = getElementArray();
i = 0;
while (result && i < fields.length) {
XMLFieldDescriptor desc = fields[i];
if (desc != fieldDesc && (object != null)) {
Object tempObj = desc.getHandler().getValue(object);
hasValue = (tempObj != null);
if (hasValue) {
result = false;
// special case for array
if (tempObj.getClass().isArray()) {
result = Array.getLength(tempObj) == 0;
}
// special case for collection
if (tempObj instanceof Collection) {
result = ((Collection) tempObj).isEmpty();
}
}
}
i++;
} // while
} // CHOICE
return result;
}// --canAccept
// ---------------------/
// - Protected Methods -/
// ---------------------/
/**
* Returns true if the given class should be treated as a primitive type. This method will return
* true for all Java primitive types, the set of primitive object wrappers, as well as Strings.
*
* @return true if the given class should be treated as a primitive type
**/
static boolean isPrimitive(Class type) {
if (type == null)
return false;
// -- java primitive
if (type.isPrimitive())
return true;
// -- primtive wrapper classes
if ((type == Boolean.class) || (type == Character.class))
return true;
return (type.getSuperclass() == Number.class);
} // -- isPrimitive
/**
* Checks to see if the given Object is a java primitive (does not check for primitive wrappers)
* and has a value that is equal to the default value for that primitive. This method will return
* true if the value is a java primitive with a default value.
*
* @return true if the value is a java primitive with a default value
*/
static boolean isDefaultPrimitiveValue(Object value) {
if (value == null)
return false;
Class type = value.getClass();
if (type.isPrimitive()) {
try {
return (value.equals(type.newInstance()));
} catch (java.lang.IllegalAccessException iax) {
// -- Just return false, we should be
// -- able to instantiate primitive types
} catch (java.lang.InstantiationException ix) {
// -- Just return false, we should be
// -- able to instantiate primitive types
}
} else if (type.getSuperclass() == Number.class) {
return ((Number) value).intValue() == 0;
} else if (type == Boolean.class) {
return value.equals(Boolean.FALSE);
} else if (type == Character.class) {
return ((Character) value).charValue() == '\0';
}
return false;
} // -- isDefaultPrimitiveValue
/**
* Sets the Class type being described by this descriptor.
*
* @param type the Class type being described
*/
public void setJavaClass(Class type) {
this._class = type;
} // -- setJavaClass
protected void setExtendsWithoutFlatten(XMLClassDescriptor classDesc) {
this._extends = classDesc;
} // -- setExtendsWithoutFlatten
/**
* Sets a flag to indicate whether or not this XMLClassDescriptorImpl was created via
* introspection
*
* @param introspected a boolean, when true indicated that this XMLClassDescriptor was created via
* introspection
*/
protected void setIntrospected(boolean introspected) {
this._introspected = introspected;
} // -- setIntrospected
// protected String toXMLName(String className) {
// //-- create default XML name
// String name = className;
// int idx = name.lastIndexOf('.');
// if (idx >= 0) name = name.substring(idx+1);
// return _naming.toXMLName(name);
// }
// -------------------/
// - Private Methods -/
// -------------------/
/**
* Adds the given XMLFieldDescriptor to the list of descriptors. The descriptor will be added to
* the appropriate list by calling XMLFieldDescriptor#getNodeType() to determine it's type.
*
* @param descriptor the XMLFieldDescriptor to add
*/
private void addFieldDescriptor(XMLFieldDescriptor descriptor, boolean relink) {
if (descriptor == null)
return;
boolean added = false;
NodeType nodeType = descriptor.getNodeType();
switch (nodeType.getType()) {
case NodeType.NAMESPACE:
case NodeType.ATTRIBUTE:
added = _attributes.add(descriptor);
if (added) {
_attArray = null;
}
break;
case NodeType.TEXT:
_contentDescriptor = descriptor;
added = true;
break;
default:
added = _elements.add(descriptor);
if (added) {
_elemArray = null;
if (descriptor.isContainer())
++_containerCount;
}
break;
}
if ((added) && (relink)) {
descriptor.setContainingClassDescriptor(this);
}
} // -- addFieldDescriptor
private XMLFieldDescriptor[] getAttributeArray() {
// -- create local reference to prevent possible
// -- null pointer (_attArray could be re-set to null)
// -- in multi-threaded environment
XMLFieldDescriptor[] descriptors = _attArray;
if (descriptors == null) {
descriptors = _attributes.toArray();
_attArray = descriptors;
}
return descriptors;
}
private XMLFieldDescriptor[] getElementArray() {
// -- create local reference to prevent possible
// -- null pointer (_elemArray could be re-set to null)
// -- in multi-threaded environment
XMLFieldDescriptor[] descriptors = _elemArray;
if (descriptors == null) {
descriptors = _elements.toArray();
_elemArray = descriptors;
}
return descriptors;
}
/**
* Adds a XMLFieldDescriptor instance to the internally maintained list of sequence elements.
*
* @param element An {@link XMLFieldDescriptor} instance for an element definition.
*/
protected void addSequenceElement(XMLFieldDescriptor element) {
_sequenceOfElements.add(element);
}
public List getSubstitutes() {
return _substitutes;
}
public void setSubstitutes(List substitutes) {
_substitutes = substitutes;
}
public boolean isChoice() {
return this._compositor == CHOICE;
}
/**
* @see org.exolab.castor.builder.info.nature.PropertyHolder# getProperty(java.lang.String)
* @param name of the property
* @return value of the property
*/
public Object getProperty(final String name) {
return _properties.get(name);
}
/**
* @see org.exolab.castor.builder.info.nature.PropertyHolder# setProperty(java.lang.String,
* java.lang.Object)
* @param name of the property
* @param value of the property
*/
public void setProperty(final String name, final Object value) {
_properties.put(name, value);
}
/**
* @see org.exolab.castor.builder.info.nature.NatureExtendable# addNature(java.lang.String)
* @param nature ID of the Nature
*/
public void addNature(final String nature) {
_natures.add(nature);
}
/**
* @see org.exolab.castor.builder.info.nature.NatureExtendable# hasNature(java.lang.String)
* @param nature ID of the Nature
* @return true if the Nature ID was added.
*/
public boolean hasNature(final String nature) {
return _natures.contains(nature);
}
}