/** * 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. * * $Id$ */ package org.exolab.castor.xml; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Vector; import org.castor.xml.BackwardCompatibilityContext; import org.castor.xml.InternalContext; import org.castor.xml.AbstractInternalContext; import org.castor.xml.JavaNaming; import org.exolab.castor.mapping.ClassDescriptor; import org.exolab.castor.mapping.CollectionHandler; import org.exolab.castor.mapping.FieldDescriptor; import org.exolab.castor.mapping.MappingException; import org.exolab.castor.mapping.loader.CollectionHandlers; import org.exolab.castor.util.ReflectionUtil; /** * A core class for common code shared throughout the * Marshalling Framework * * @author <a href="mailto:kvisco-at-intalio.com">Keith Visco</a> * @version $Revision$ $Date: 2005-12-13 14:58:48 -0700 (Tue, 13 Dec 2005) $ */ abstract class MarshalFramework { //--------------------------/ //- Public class variables -/ //--------------------------/ /** * JDK version 1.5 . */ private static final double JDK_VERSION_1_5 = 1.5; /** * The XSI Namespace URI. **/ public static final String XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance"; /** * The name of the Schema location attribute. **/ public static final String XSI_SCHEMA_LOCATION = "schemaLocation"; /** * The name of the no namespace schema location attribute. **/ public static final String XSI_NO_NAMESPACE_SCHEMA_LOCATION = "noNamespaceSchemaLocation"; /** * The xml:lang attribute name. */ public static final String XML_LANG_ATTR = "xml:lang"; /** * The xml:lang attribute, without the "xml:" prefix. */ public static final String LANG_ATTR = "lang"; /** * The xsi:nil attribute, without the "xsi:" prefix. */ public static final String NIL_ATTR = "nil"; /** * The xsi:nil attribute. */ public static final String XSI_NIL_ATTR = "xsi:nil"; /** * The xml:space attribute name. */ public static final String XML_SPACE_ATTR = "xml:space"; /** * The xml:space attribute name, without the "xml:" prefix. */ public static final String SPACE_ATTR = "space"; /** * The xsi:type attribute name, without the "xsi:" prefix. */ public static final String TYPE_ATTR = "type"; /** * The value of 'true'. */ public static final String TRUE_VALUE = "true"; //-----------------------------/ //- Protected class variables -/ //-----------------------------/ /** * A constant to indicate a wrong name without setting null. */ static final String INTERNAL_XML_NAME = "-error-if-this-is-used-"; /** * The default prefix used for specifying the * xsi:type as a classname instead of a schema name. * This is a Castor specific hack. */ static final String JAVA_PREFIX = "java:"; /** * The name of the QName type. */ static final String QNAME_NAME = "QName"; /** * An empty array of field descriptors. */ static final XMLFieldDescriptor[] NO_FIELD_DESCRIPTORS = new XMLFieldDescriptor[0]; //-----------------------------/ //- Private variables -/ //-----------------------------/ /** * The {@link AbstractInternalContext} to use at all un-marshal actions. * @since 1.1.3 */ private InternalContext _internalContext; //-----------------------------/ //- Public methods -/ //-----------------------------/ /** * We need some stuff initialized here. MarshalFramework requires internally * an {@link InternalContext}, so either one is given or {@link BackwardCompatibilityContext} * is instantiated! Mind that instantiating {@link BackwardCompatibilityContext} * means to (re-)read configuration files. * @param internalContext either an {@link InternalContext} comes from outside * or {@link BackwardCompatibilityContext} is instantiated */ public MarshalFramework(final InternalContext internalContext) { if (internalContext == null) { _internalContext = new BackwardCompatibilityContext(); } else { _internalContext = internalContext; } } /** * To get the {@link JavaNaming} instance to be used. * @return the JavaNaming to be used */ public JavaNaming getJavaNaming() { return _internalContext.getJavaNaming(); } /** * To set the {@link JavaNaming} instance to be used. * @param javaNaming the JavaNaming to be used * TODO: joachim remove me if possible! */ private void setJavaNaming(final JavaNaming javaNaming) { _internalContext.setJavaNaming(javaNaming); } /** * To get the {@link AbstractInternalContext} to use. * @return the {@link AbstractInternalContext} to use */ public InternalContext getInternalContext() { return _internalContext; } /** * To set the {@link AbstractInternalContext} to use. * @param internalContext the {@link AbstractInternalContext} to use */ public void setInternalContext(final InternalContext internalContext) { _internalContext = internalContext; } /** * Returns true if the given Class is a considered a * collection by the marshalling framework. * @param clazz the Class to check * @return true if the given Class is considered a collection. * TODO: joachim: this code exists somewhere else too!! */ public static boolean isCollection(final Class clazz) { return CollectionHandlers.hasHandler(clazz); } //-- isCollection /** * Returns the CollectionHandler associated with the * given collection, or null if no such handler exists. * @param clazz the Class to check * @return the CollectionHandler for the associated type. */ public CollectionHandler getCollectionHandler(final Class clazz) { CollectionHandler handler = null; try { handler = CollectionHandlers.getHandler(clazz); } catch (MappingException mx) { //-- Not a collection, or no handler exists, return null. } return handler; } //-- getCollectionHandler /** * 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. * @param type the Class to check * @return true if the given class should be treated as a primitive type * TODO: joachim: this code exists somewhere else too!! **/ static boolean isPrimitive(final Class type) { if (type == null) { return false; } //-- java primitive if (type.isPrimitive()) { return true; } //-- we treat strings as primitives if (type == String.class) { return true; } //-- primtive wrapper classes if ((type == Boolean.class) || (type == Character.class)) { return true; } Class superClass = type.getSuperclass(); if (superClass == Number.class) { return true; } if (superClass != null) { return superClass.getName().equals("java.lang.Enum"); } return false; } //-- isPrimitive /** * Returns true if the given class should be treated as an enum type. This method * will return true for all Java 5 (or later) enums, and for enum-style * classes. * @param type the Class to check * @return true if the given class should be treated as an enum **/ static boolean isEnum(final Class type) { if (type == null) { return false; } float javaVersion = Float.valueOf(System.getProperty("java.specification.version")).floatValue(); if (javaVersion >= JDK_VERSION_1_5) { try { Boolean isEnum = ReflectionUtil.isEnumViaReflection(type); return isEnum.booleanValue(); } catch (Exception e) { // nothing to report; implies that there's no such method } } // TODO: add code to cover 1.4 enum-stype classes as well. return false; } //-- isPrimitive // /** // * Calls isEnum() method on target class vi areflection to find out // * whether the given type is a Java 5 enumeration. // * @param type The type to analyze. // * @return True if the type given is a Java 5.0 enum. // * @throws NoSuchMethodException If the method can not be found. // * @throws IllegalAccessException If access to this method is illegal // * @throws InvocationTargetException If the target method can not be invoked. // */ // private static Boolean isEnumViaReflection(Class type) // throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { // Method isEnumMethod = type.getClass().getMethod("isEnum", (Class[]) null); // return (Boolean) isEnumMethod.invoke(type, (Object[]) null); // } // /** * Returns true if any of the fields associated with the given * XMLClassDescriptor are located at, or beneath, the given location. * * @param location the location to compare against * @param classDesc the XMLClassDescriptor in which to check the field * locations * @return true if any of the fields has a location associated */ static final boolean hasFieldsAtLocation (final String location, final XMLClassDescriptor classDesc) { //-- check elements XMLFieldDescriptor[] descriptors = classDesc.getElementDescriptors(); for (int i = 0; i < descriptors.length; i++) { if (descriptors[i] == null) { continue; } String tmpLocation = descriptors[i].getLocationPath(); if ((tmpLocation != null) && (tmpLocation.startsWith(location))) { return true; } } //-- check attributes descriptors = classDesc.getAttributeDescriptors(); for (int i = 0; i < descriptors.length; i++) { if (descriptors[i] == null) { continue; } String tmpLocation = descriptors[i].getLocationPath(); if ((tmpLocation != null) && (tmpLocation.startsWith(location))) { return true; } } //-- check content/text XMLFieldDescriptor content = classDesc.getContentDescriptor(); if (content != null) { String tmpLocation = content.getLocationPath(); if ((tmpLocation != null) && (tmpLocation.startsWith(location))) { return true; } } return false; } //-- hasFieldsAtLocation /** * Compares the given namespaces (as strings) for equality. * null and empty values are considered equal. * * @param ns1 the namespace to compare to argument ns2 * @param ns2 the namespace to compare to argument ns1 * @return true if the namespaces are considert equal * TODO: joachim put it into XMLNaming! */ public static boolean namespaceEquals(final String ns1, final String ns2) { if (ns1 == null) { return ((ns2 == null) || (ns2.length() == 0)); } if (ns2 == null) { return (ns1.length() == 0); } return ns1.equals(ns2); } //-- namespaceEquals /** * Returns true if the given classes are both the same * primitive or primitive wrapper class. For exmaple, if * class "a" is an int (Integer.TYPE) and class "b" is * either an int or Integer.class then true will be * returned, otherwise false. * @param a compare a with b * @param b compare a with b * @return true if both a and b are considered equal */ static boolean primitiveOrWrapperEquals(final Class a, final Class b) { if (!isPrimitive(a)) { return false; } if (!isPrimitive(b)) { return false; } if (a == b) { return true; } //-- Boolean/boolean if ((a == Boolean.class) || (a == Boolean.TYPE)) { return ((b == Boolean.class) || (b == Boolean.TYPE)); } //-- Byte/byte else if ((a == Byte.class) || (a == Byte.TYPE)) { return ((b == Byte.class) || (b == Byte.TYPE)); } //-- Character/char else if ((a == Character.class) || (a == Character.TYPE)) { return ((b == Character.class) || (b == Character.TYPE)); } //-- Double/double else if ((a == Double.class) || (a == Double.TYPE)) { return ((b == Double.class) || (b == Double.TYPE)); } else if ((a == Float.class) || (a == Float.TYPE)) { return ((b == Float.class) || (b == Float.TYPE)); } //-- Integer/int else if ((a == Integer.class) || (a == Integer.TYPE)) { return ((b == Integer.class) || (b == Integer.TYPE)); } //-- Long/long else if ((a == Long.class) || (a == Long.TYPE)) { return ((b == Long.class) || (b == Long.TYPE)); } //-- Short/short else if ((a == Short.class) || (a == Short.TYPE)) { return ((b == Short.class) || (b == Short.TYPE)); } return false; } //-- primitiveOrWrapperEquals /** * */ private static final InheritanceMatch[] NO_MATCH_ARRAY = new InheritanceMatch[0]; /** * Search there is a field descriptor which can accept one of the class * descriptor which match the given name and namespace. * * @param name XML name of the field * @param namespace namespace of the field * @param classDesc the class descriptor to match against * @param cdResolver the class descriptor resolver to use * @return An array of InheritanceMatch. * @throws MarshalException if the resolver called fails fatally */ protected InheritanceMatch[] searchInheritance(final String name, final String namespace, final XMLClassDescriptor classDesc) throws MarshalException { Iterator classDescriptorIterator = null; try { //-- A little required logic for finding Not-Yet-Loaded //-- descriptors String className = getJavaNaming().toJavaClassName(name); //-- should use namespace-to-prefix mappings, but //-- just create package for now. Class clazz = classDesc.getJavaClass(); String pkg = null; if (clazz != null) { while (clazz.getDeclaringClass() != null) { clazz = clazz.getDeclaringClass(); } pkg = clazz.getName(); int idx = pkg.lastIndexOf('.'); if (idx >= 0) { pkg = pkg.substring(0, idx + 1); className = pkg + className; } } getInternalContext().getXMLClassDescriptorResolver().resolve( className, classDesc.getClass().getClassLoader()); //-- end Not-Yet-Loaded descriptor logic //-- resolve all by XML name + namespace URI classDescriptorIterator = getInternalContext().getXMLClassDescriptorResolver().resolveAllByXMLName( name, namespace, null); } catch (ResolverException rx) { Throwable actual = rx.getCause(); if (actual instanceof MarshalException) { throw (MarshalException) actual; } if (actual != null) { throw new MarshalException(actual); } throw new MarshalException(rx); } Vector inheritanceList = null; XMLFieldDescriptor descriptor = null; XMLFieldDescriptor[] descriptors = classDesc.getElementDescriptors(); XMLClassDescriptor cdInherited = null; if (classDescriptorIterator.hasNext()) { while (classDescriptorIterator.hasNext() && (descriptor == null)) { cdInherited = (XMLClassDescriptor) classDescriptorIterator.next(); Class subclass = cdInherited.getJavaClass(); for (int i = 0; i < descriptors.length; i++) { if (descriptors[i] == null) { continue; } //-- skip descriptors with special internal name if (INTERNAL_XML_NAME.equals(descriptors[i].getXMLName())) { continue; } //-- check for inheritence Class superclass = descriptors[i].getFieldType(); // It is possible that the superclass is of type object if we use any node. if (superclass.isAssignableFrom(subclass) && (superclass != Object.class)) { descriptor = descriptors[i]; if (inheritanceList == null) { inheritanceList = new Vector(3); } inheritanceList.addElement(new InheritanceMatch(descriptor, cdInherited)); } } } //-- reset inherited class descriptor, if necessary if (descriptor == null) { cdInherited = null; } } if (inheritanceList != null) { InheritanceMatch[] result = new InheritanceMatch[inheritanceList.size()]; inheritanceList.toArray(result); return result; } return NO_MATCH_ARRAY; } /** * Used to store the information when we find a possible inheritance. It * store the XMLClassDescriptor of the object to instantiate and the * XMLFieldDescriptor of the parent, where the instance of the * XMLClassDescriptor will be put. */ public static class InheritanceMatch { /** * The field descriptor of the parent. */ public XMLFieldDescriptor parentFieldDesc; /** * The class descriptor to instantiate. */ public XMLClassDescriptor inheritedClassDesc; public InheritanceMatch(XMLFieldDescriptor fieldDesc, XMLClassDescriptor classDesc) { parentFieldDesc = fieldDesc; inheritedClassDesc = classDesc; } } /** * An internal implementation of XMLClassDescriptor used by * the Unmarshaller and Marshaller... */ class InternalXMLClassDescriptor implements XMLClassDescriptor { private XMLClassDescriptor _classDesc = null; /** * Cached arrays. */ private XMLFieldDescriptor[] _attributes = null; /** * Cached arrays. */ private XMLFieldDescriptor[] _elements = null; /** * Cached arrays. */ private FieldDescriptor[] _fields = null; /** * Map holding the properties set and read by Natures. */ private Map _properties = new HashMap(); /** * Map holding the available natures. */ private Set _natures = new HashSet(); /** * Creates a new InternalXMLClassDescriptor for the given * XMLClassDescriptor. */ protected InternalXMLClassDescriptor(XMLClassDescriptor classDesc) { if (classDesc == null) { String err = "The argument 'classDesc' must not be null."; throw new IllegalArgumentException(err); } //-- prevent wrapping another InternalXMLClassDescriptor, while (classDesc instanceof InternalXMLClassDescriptor) { classDesc = ((InternalXMLClassDescriptor) classDesc).getClassDescriptor(); } _classDesc = classDesc; } /** * Returns the XMLClassDescriptor that this InternalXMLClassDescriptor * wraps. * * @return the XMLClassDescriptor */ public XMLClassDescriptor getClassDescriptor() { return _classDesc; } //-- getClassDescriptor /** * Returns the set of XMLFieldDescriptors for all members * that should be marshalled as XML attributes. This * includes namespace nodes. * * @return an array of XMLFieldDescriptors for all members * that should be marshalled as XML attributes. */ public XMLFieldDescriptor[] getAttributeDescriptors() { if (_attributes == null) { _attributes = _classDesc.getAttributeDescriptors(); } return _attributes; } //-- 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 _classDesc.getContentDescriptor(); } //-- getContentDescriptor /** * 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. * * @param name the xml name to match against * @param namespace the xml namespace to match * @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 (final String name, final String namespace, final NodeType nodeType) { return _classDesc.getFieldDescriptor(name, namespace, nodeType); } //-- getFieldDescriptor /** * 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() { if (_elements == null) { _elements = _classDesc.getElementDescriptors(); } return _elements; } //-- getElementDescriptors /** * @return the namespace prefix to use when marshalling as XML. */ public String getNameSpacePrefix() { return _classDesc.getNameSpacePrefix(); } //-- getNameSpacePrefix /** * @return the namespace URI used when marshalling and unmarshalling as XML. */ public String getNameSpaceURI() { return _classDesc.getNameSpaceURI(); } //-- 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() { return _classDesc.getValidator(); } //-- getValidator /** * Returns the XML Name for the Class being described. * * @return the XML name. */ public String getXMLName() { return _classDesc.getXMLName(); } //-- getXMLName /** * Returns true if the wrapped ClassDescriptor was created * by introspection. * * @return true if the wrapped ClassDescriptor was created * by introspection. */ public boolean introspected() { return Introspector.introspected(_classDesc); } //-- introspected /** * @see org.exolab.castor.xml.XMLClassDescriptor#canAccept( * java.lang.String, java.lang.String, java.lang.Object) */ public boolean canAccept(final String name, final String namespace, final Object object) { return _classDesc.canAccept(name, namespace, object); } //-- canAccept /** * {@inheritDoc} * * @see org.exolab.castor.xml.XMLClassDescriptor#checkDescriptorForCorrectOrderWithinSequence(org.exolab.castor.xml.XMLFieldDescriptor, org.exolab.castor.xml.UnmarshalState, java.lang.String) */ public void checkDescriptorForCorrectOrderWithinSequence( final XMLFieldDescriptor elementDescriptor, final UnmarshalState parentState, final String xmlName) throws ValidationException { _classDesc.checkDescriptorForCorrectOrderWithinSequence(elementDescriptor, parentState, xmlName); } //-------------------------------------/ //- Implementation of ClassDescriptor -/ //-------------------------------------/ /** * Returns the Java class represented by this descriptor. * * @return The Java class */ public Class getJavaClass() { return _classDesc.getJavaClass(); } //-- getJavaClass /** * Returns a list of fields represented by this descriptor. * * @return A list of fields */ public FieldDescriptor[] getFields() { if (_fields == null) { _fields = _classDesc.getFields(); } return _fields; } //-- getFields /** * Returns the class descriptor of the class extended by this class. * * @return The extended class descriptor */ public ClassDescriptor getExtends() { return _classDesc.getExtends(); } //-- getExtends /** * Returns the identity field, null if this class has no identity. * * @return The identity field */ public FieldDescriptor getIdentity() { return _classDesc.getIdentity(); } //-- getIdentity /** * {@inheritDoc} * * @see org.exolab.castor.xml.XMLClassDescriptor#isChoice() */ public boolean isChoice() { return false; } /** * @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); } } //-- InternalXMLClassDescriptor } //-- MarshalFramework