org.exolab.castor.xml.Introspector 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-2003 (C) Intalio, Inc. All Rights Reserved.
*
* $Id$
*/
package org.exolab.castor.xml;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import org.castor.xml.InternalContext;
import org.castor.xml.JavaNaming;
import org.castor.xml.XMLProperties;
import org.castor.xml.XMLNaming;
import org.exolab.castor.mapping.CollectionHandler;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.mapping.FieldHandlerFactory;
import org.exolab.castor.mapping.GeneralizedFieldHandler;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.TypeConvertor;
import org.exolab.castor.mapping.loader.CollectionHandlers;
import org.exolab.castor.mapping.loader.FieldHandlerImpl;
import org.exolab.castor.mapping.loader.TypeInfo;
import org.exolab.castor.util.ReflectionUtil;
import org.exolab.castor.xml.descriptors.CoreDescriptors;
import org.exolab.castor.xml.handlers.ContainerFieldHandler;
import org.exolab.castor.xml.handlers.DateFieldHandler;
import org.exolab.castor.xml.handlers.DefaultFieldHandlerFactory;
import org.exolab.castor.xml.util.ContainerElement;
import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
import org.exolab.castor.xml.util.XMLFieldDescriptorImpl;
/**
* A Helper class for the Marshaller and Unmarshaller, basically the common code base between the
* two. This class handles the introspection to dynamically create descriptors.
*
* @author Keith Visco
* @version $Revision$ $Date: 2006-04-14 04:14:43 -0600 (Fri, 14 Apr 2006) $
*/
public final class Introspector implements PropertyChangeListener {
/** The default FieldHandlerFactory. */
private static final FieldHandlerFactory DEFAULT_HANDLER_FACTORY =
new DefaultFieldHandlerFactory();
private static final Class[] EMPTY_CLASS_ARGS = new Class[0];
/** Name of the java.util.List collection. */
private static final String LIST = "java.util.List";
/** Name of the java.util.Map collection. */
private static final String MAP = "java.util.Map";
/** Name of the java.util.Map collection. */
private static final String SET_COLLECTION = "java.util.Set";
/** Used as a prefix for the name of a container field. */
private static final String COLLECTION_WRAPPER_PREFIX = "##container_for_";
/**
* The default flag indicating whether or not collections (arrays, vectors, etc) should be wrapped
* in a container element.
*
* @see _wrapCollectionsInContainer
*/
private static final boolean WRAP_COLLECTIONS_DEFAULT = false;
/**
* The set of available collections to use during introspection. JDK dependant.
**/
private static final Class[] COLLECTIONS = loadCollections();
/**
* The default naming conventions.
*/
private static XMLNaming _defaultNaming = null;
/**
* The naming conventions to use.
*/
private XMLNaming _xmlNaming = null;
/**
* The NodeType to use for primitives.
*/
private NodeType _primitiveNodeType = null;
/**
* The variable flag indicating whether or not collections (arrays, vectors, etc) should be
* wrapped in a container element. For example:
*
*
* <foos>
* <foo>foo1</foo>
* <foo>foo2</foo>
* </foos>
*
* instead of the default:
*
* <foos>foo1<foos>
* <foos>foo2</foos>
*
*
*
*/
private boolean _wrapCollectionsInContainer = WRAP_COLLECTIONS_DEFAULT;
/**
* The set of registered FieldHandlerFactory instances
*/
private Vector _handlerFactoryList = null;
/**
* The set of registered FieldHandlerFactory instances associated with their supported types
*/
private Hashtable _handlerFactoryMap = null;
/**
* A flag indicating that MapKeys should be saved. To remain backward compatible this may be
* disable via the castor.properties.
*/
private boolean _saveMapKeys = true;
/**
* Specifies class loader to be used.
*/
private ClassLoader _classLoader = null;
/**
* The {@link JavaNaming} to be used.
*/
private JavaNaming _javaNaming;
private InternalContext _internalContext;
/**
* Creates a new instance of the Introspector.
*/
public Introspector() {
this(null);
} // -- Introspector
/**
* Creates a new instance of the Introspector.
*
* @param classLoader
*/
public Introspector(final ClassLoader classLoader) {
super();
_classLoader = classLoader;
init();
} // -- Introspector
private void init() {
if (_internalContext != null) {
_javaNaming = _internalContext.getJavaNaming();
_xmlNaming = _internalContext.getXMLNaming();
setPrimitiveNodeType(_internalContext.getPrimitiveNodeType());
_wrapCollectionsInContainer = _internalContext
.getBooleanProperty(XMLProperties.WRAP_COLLECTIONS_PROPERTY).booleanValue();
_saveMapKeys =
_internalContext.getBooleanProperty(XMLProperties.SAVE_MAP_KEYS).booleanValue();
}
} // -- init
public void setInternalContext(InternalContext internalContext) {
if (internalContext == null) {
if (this._internalContext != null) {
this._internalContext.removePropertyChangeListener(this);
}
} else {
if (this._internalContext != internalContext) {
if (this._internalContext != null) {
this._internalContext.removePropertyChangeListener(this);
}
internalContext.addPropertyChangeListener(this);
}
}
_internalContext = internalContext;
init();
}
/**
* Registers the given "generalized" FieldHandlerFactory with this Introspector.
*
* @param factory the FieldHandlerFactory to add to this introspector
* @throws IllegalArgumentException if the given factory is null
*/
public synchronized void addFieldHandlerFactory(FieldHandlerFactory factory) {
if (factory == null) {
String err = "The argument 'factory' must not be null.";
throw new IllegalArgumentException(err);
}
if (_handlerFactoryList == null) {
_handlerFactoryList = new Vector<>();
}
_handlerFactoryList.add(factory);
registerHandlerFactory(factory);
} // -- addFieldHandlerFactory
/**
* Returns the NodeType for java primitives
*
* @return the NodeType for java primitives
**/
public NodeType getPrimitiveNodeType() {
return _primitiveNodeType;
} // -- getPrimitiveNodeType
/**
* Creates an XMLClassDescriptor for the given class by using Reflection.
*
* @param c the Class to create the XMLClassDescriptor for
* @return the new XMLClassDescriptor created for the given class
* @exception MarshalException when an error occurs during the creation of the ClassDescriptor.
**/
public XMLClassDescriptor generateClassDescriptor(Class c) throws MarshalException {
return generateClassDescriptor(c, null);
} // -- generateClassDescriptor(Class)
/**
* Creates an XMLClassDescriptor for the given class by using Reflection.
*
* @param c the Class to create the XMLClassDescriptor for
* @param errorWriter a PrintWriter to print error information to
* @return the new XMLClassDescriptor created for the given class
* @exception MarshalException when an error occurs during the creation of the ClassDescriptor.
**/
public XMLClassDescriptor generateClassDescriptor(Class c, PrintWriter errorWriter)
throws MarshalException {
if (c == null)
return null;
// -- handle arrays
if (c.isArray())
return null;
// -- handle base objects
if ((c == Void.class) || (c == Class.class) || (c == Object.class)) {
throw new MarshalException(MarshalException.BASE_CLASS_OR_VOID_ERR);
}
// -- handle core descriptors
XMLClassDescriptor coreDesc = CoreDescriptors.getDescriptor(c);
if (coreDesc != null)
return coreDesc;
// --------------------------/
// - handle complex objects -/
// --------------------------/
XMLClassDescriptorImpl classDesc = new IntrospectedXMLClassDescriptor(c);
Method[] methods = c.getMethods();
List dateDescriptors = new ArrayList<>(3);
Hashtable methodSets = new Hashtable<>();
int methodCount = 0;
Class superClass = c.getSuperclass();
Class[] interfaces = c.getInterfaces();
// -- create method sets
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
Class owner = method.getDeclaringClass();
// -- ignore methods from super-class, that will be
// -- introspected separately, if necessary
if (owner != c) {
// -- if declaring class is anything but
// -- an interface, than just continue,
// -- the field comes from a super class
// -- (e.g. java.lang.Object)
if (!owner.isInterface())
continue;
// -- owner is an interface, is it an
// -- interface this class implements
// -- or a parent class?
if (interfaces.length > 0) {
boolean found = false;
for (int count = 0; count < interfaces.length; count++) {
if (interfaces[count] == owner) {
found = true;
break;
}
}
if (!found)
continue;
}
} else {
// -- look for overloaded methods
if (superClass != null) {
Class[] args = method.getParameterTypes();
String name = method.getName();
Method tmpMethod = null;
try {
tmpMethod = superClass.getMethod(name, args);
} catch (NoSuchMethodException nsme) {
// -- do nothing
}
if (tmpMethod != null)
continue;
}
}
// -- if method is static...ignore
if ((method.getModifiers() & Modifier.STATIC) != 0)
continue;
String methodName = method.getName();
// -- read methods
if (methodName.startsWith(JavaNaming.METHOD_PREFIX_GET)) {
if (method.getParameterTypes().length != 0) {
continue;
}
// -- disable direct field access
++methodCount;
// -- make sure return type is "descriptable"
// -- and not null
Class type = method.getReturnType();
if (type == null)
continue;
if (!isDescriptable(type))
continue;
// -- caclulate name from Method name
String fieldName = methodName.substring(3);
fieldName = _javaNaming.toJavaMemberName(fieldName);
MethodSet methodSet = methodSets.get(fieldName);
if (methodSet == null) {
methodSet = new MethodSet(fieldName);
methodSets.put(fieldName, methodSet);
}
methodSet._get = method;
} else if (methodName.startsWith(JavaNaming.METHOD_PREFIX_IS)) {
if (method.getParameterTypes().length != 0)
continue;
// -- make sure type is not null, and a boolean
Class type = method.getReturnType();
if (type == null)
continue;
if (type.isPrimitive()) {
if (type != Boolean.TYPE)
continue;
} else {
if (type != Boolean.class)
continue;
}
// -- disable direct field access
++methodCount;
// -- caclulate name from Method name
String fieldName = methodName.substring(JavaNaming.METHOD_PREFIX_IS.length());
fieldName = _javaNaming.toJavaMemberName(fieldName);
MethodSet methodSet = methodSets.get(fieldName);
if (methodSet == null) {
methodSet = new MethodSet(fieldName);
methodSets.put(fieldName, methodSet);
}
methodSet._get = method;
}
// -----------------------------------/
// -- write methods (collection item)
else if (methodName.startsWith(JavaNaming.METHOD_PREFIX_ADD)) {
if (method.getParameterTypes().length != 1)
continue;
// -- disable direct field access
++methodCount;
// -- make sure parameter type is "descriptable"
if (!isDescriptable(method.getParameterTypes()[0]))
continue;
// -- caclulate name from Method name
String fieldName = methodName.substring(3);
fieldName = _javaNaming.toJavaMemberName(fieldName);
MethodSet methodSet = methodSets.get(fieldName);
if (methodSet == null) {
methodSet = new MethodSet(fieldName);
methodSets.put(fieldName, methodSet);
}
methodSet._add = method;
}
// -- write method (singleton or collection)
else if (methodName.startsWith(JavaNaming.METHOD_PREFIX_SET)) {
if (method.getParameterTypes().length != 1) {
continue;
}
// -- disable direct field access
++methodCount;
// -- make sure parameter type is "descriptable"
if (!isDescriptable(method.getParameterTypes()[0]))
continue;
// -- caclulate name from Method name
String fieldName = methodName.substring(3);
fieldName = _javaNaming.toJavaMemberName(fieldName);
MethodSet methodSet = methodSets.get(fieldName);
if (methodSet == null) {
methodSet = new MethodSet(fieldName);
methodSets.put(fieldName, methodSet);
}
methodSet._set = method;
} else if (methodName.startsWith(JavaNaming.METHOD_PREFIX_CREATE)) {
if (method.getParameterTypes().length != 0)
continue;
Class type = method.getReturnType();
// -- make sure return type is "descriptable"
// -- and not null
if (!isDescriptable(type))
continue;
// -- caclulate name from Method name
String fieldName = methodName.substring(JavaNaming.METHOD_PREFIX_CREATE.length());
fieldName = _javaNaming.toJavaMemberName(fieldName);
MethodSet methodSet = methodSets.get(fieldName);
if (methodSet == null) {
methodSet = new MethodSet(fieldName);
methodSets.put(fieldName, methodSet);
}
methodSet._create = method;
}
} // -- end create method sets
// -- Loop Through MethodSets and create
// -- descriptors
Enumeration enumeration = methodSets.elements();
while (enumeration.hasMoreElements()) {
MethodSet methodSet = enumeration.nextElement();
// -- create XMLFieldDescriptor
String xmlName = _xmlNaming.toXMLName(methodSet._fieldName);
boolean isCollection = false;
// -- calculate class type
// -- 1st check for add-method, then set or get method
Class type = null;
if (methodSet._add != null) {
type = methodSet._add.getParameterTypes()[0];
isCollection = true;
}
// -- if there was no add method, use get/set methods
// -- to calculate type.
if (type == null) {
if (methodSet._get != null) {
type = methodSet._get.getReturnType();
} else if (methodSet._set != null) {
type = methodSet._set.getParameterTypes()[0];
} else {
// -- if we make it here, the only method found
// -- was a create method, which is useless by itself.
continue;
}
}
// -- Handle Collections
isCollection = (isCollection || isCollection(type));
TypeInfo typeInfo = null;
CollectionHandler colHandler = null;
// -- If the type is a collection and there is no add method,
// -- then we obtain a CollectionHandler
if (isCollection && (methodSet._add == null)) {
try {
colHandler = CollectionHandlers.getHandler(type);
} catch (MappingException mx) {
// -- No collection handler available,
// -- proceed anyway...
}
// -- Find component type
if (type.isArray()) {
// -- Byte arrays are handled as a special case
// -- so don't use CollectionHandler
if (type.getComponentType() == Byte.TYPE) {
colHandler = null;
} else
type = type.getComponentType();
}
}
typeInfo = new TypeInfo(type, null, null, false, null, colHandler);
// -- Create FieldHandler first, before the XMLFieldDescriptor
// -- in case we need to use a custom handler
FieldHandler handler = null;
boolean customHandler = false;
try {
handler = new FieldHandlerImpl(methodSet._fieldName, null, null, methodSet._get,
methodSet._set, typeInfo);
// -- clean up
if (methodSet._add != null)
((FieldHandlerImpl) handler).setAddMethod(methodSet._add);
if (methodSet._create != null)
((FieldHandlerImpl) handler).setCreateMethod(methodSet._create);
// -- handle Hashtable/Map
if (isCollection && _saveMapKeys && isMapCollection(type)) {
((FieldHandlerImpl) handler).setConvertFrom(new IdentityConvertor());
}
// -- look for GeneralizedFieldHandler
FieldHandlerFactory factory = getHandlerFactory(type);
if (factory != null) {
GeneralizedFieldHandler gfh = factory.createFieldHandler(type);
if (gfh != null) {
gfh.setFieldHandler(handler);
handler = gfh;
customHandler = true;
// -- swap type with the type specified by the
// -- custom field handler
if (gfh.getFieldType() != null) {
type = gfh.getFieldType();
}
}
}
} catch (MappingException mx) {
throw new MarshalException(mx);
}
XMLFieldDescriptorImpl fieldDesc = createFieldDescriptor(type, methodSet._fieldName, xmlName);
if (isCollection) {
fieldDesc.setMultivalued(true);
fieldDesc.setNodeType(NodeType.Element);
}
// -- check for instances of java.util.Date
if (java.util.Date.class.isAssignableFrom(type)) {
// handler = new DateFieldHandler(handler);
if (!customHandler) {
dateDescriptors.add(fieldDesc);
}
}
fieldDesc.setHandler(handler);
// -- enable use parent namespace if explicit one doesn't exist
fieldDesc.setUseParentsNamespace(true);
// -- Wrap collections?
if (isCollection && _wrapCollectionsInContainer) {
String fieldName = COLLECTION_WRAPPER_PREFIX + methodSet._fieldName;
// -- If we have a field 'c' that is a collection and
// -- we want to wrap that field in an element , we
// -- need to create a field descriptor for
// -- an object that represents the element and
// -- acts as a go-between from the parent of 'c'
// -- denoted as P(c) and 'c' itself
//
// object model: P(c) -> c
// xml :
// -- Make new class descriptor for the field that
// -- will represent the container element
Class cType = ContainerElement.class;
XMLClassDescriptorImpl containerClassDesc = new XMLClassDescriptorImpl(cType);
// -- add the field descriptor to our new class descriptor
containerClassDesc.addFieldDescriptor(fieldDesc);
// -- nullify xmlName so that auto-naming will be enabled,
// -- we can't do this in the constructor because
// -- XMLFieldDescriptorImpl will create a default one.
fieldDesc.setXMLName(null);
fieldDesc.setMatches("*");
// -- wrap the field handler in a special container field
// -- handler that will actually do the delegation work
FieldHandler cHandler = new ContainerFieldHandler(handler);
fieldDesc.setHandler(cHandler);
fieldDesc = createFieldDescriptor(cType, fieldName, xmlName);
fieldDesc.setClassDescriptor(containerClassDesc);
fieldDesc.setHandler(cHandler);
// -- enable use parent namespace if explicit one doesn't exist
fieldDesc.setUseParentsNamespace(true);
}
// -- add FieldDescriptor to ClassDescriptor
classDesc.addFieldDescriptor(fieldDesc);
} // -- end of method loop
// -- If we didn't find any methods we can try
// -- direct field access
if (methodCount == 0) {
Field[] fields = c.getFields();
Hashtable descriptors = new Hashtable<>();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
Class owner = field.getDeclaringClass();
// -- ignore fields from super-class, that will be
// -- introspected separately, if necessary
if (owner != c) {
// -- if declaring class is anything but
// -- an interface, than just continue,
// -- the field comes from a super class
// -- (e.g. java.lang.Object)
if (!owner.isInterface())
continue;
// -- owner is an interface, is it an
// -- interface this class implements
// -- or a parent class?
if (interfaces.length > 0) {
boolean found = false;
for (int count = 0; count < interfaces.length; count++) {
if (interfaces[count] == owner) {
found = true;
break;
}
}
if (!found)
continue;
}
}
// -- make sure field is not transient or static final
int modifiers = field.getModifiers();
if (Modifier.isTransient(modifiers))
continue;
if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))
continue;
Class type = field.getType();
if (!isDescriptable(type))
continue;
// -- Built-in support for JDK 1.1 Collections
// -- we need to a pluggable interface for
// -- JDK 1.2+
boolean isCollection = isCollection(type);
TypeInfo typeInfo = null;
CollectionHandler colHandler = null;
// -- If the type is a collection and there is no add method,
// -- then we obtain a CollectionHandler
if (isCollection) {
try {
colHandler = CollectionHandlers.getHandler(type);
} catch (MappingException mx) {
// -- No CollectionHandler available, continue
// -- without one...
}
// -- Find component type
if (type.isArray()) {
// -- Byte arrays are handled as a special case
// -- so don't use CollectionHandler
if (type.getComponentType() == Byte.TYPE) {
colHandler = null;
} else
type = type.getComponentType();
}
}
String fieldName = field.getName();
String xmlName = _xmlNaming.toXMLName(fieldName);
// -- Create FieldHandler first, before the XMLFieldDescriptor
// -- in case we need to use a custom handler
typeInfo = new TypeInfo(type, null, null, false, null, colHandler);
FieldHandler handler = null;
boolean customHandler = false;
try {
handler = new FieldHandlerImpl(field, typeInfo);
// -- handle Hashtable/Map
if (isCollection && _saveMapKeys && isMapCollection(type)) {
((FieldHandlerImpl) handler).setConvertFrom(new IdentityConvertor());
}
// -- look for GeneralizedFieldHandler
FieldHandlerFactory factory = getHandlerFactory(type);
if (factory != null) {
GeneralizedFieldHandler gfh = factory.createFieldHandler(type);
if (gfh != null) {
gfh.setFieldHandler(handler);
handler = gfh;
customHandler = true;
// -- swap type with the type specified by the
// -- custom field handler
if (gfh.getFieldType() != null) {
type = gfh.getFieldType();
}
}
}
} catch (MappingException mx) {
throw new MarshalException(mx);
}
XMLFieldDescriptorImpl fieldDesc = createFieldDescriptor(type, fieldName, xmlName);
if (isCollection) {
fieldDesc.setNodeType(NodeType.Element);
fieldDesc.setMultivalued(true);
}
descriptors.put(xmlName, fieldDesc);
classDesc.addFieldDescriptor(fieldDesc);
fieldDesc.setHandler(handler);
// -- enable use parent namespace if explicit one doesn't exist
fieldDesc.setUseParentsNamespace(true);
// -- check for instances of java.util.Date
if (java.util.Date.class.isAssignableFrom(type)) {
if (!customHandler) {
dateDescriptors.add(fieldDesc);
}
}
}
} // -- end of direct field access
// -- A temporary fix for java.util.Date
if (dateDescriptors != null) {
for (XMLFieldDescriptorImpl fieldDesc : dateDescriptors) {
FieldHandler handler = fieldDesc.getHandler();
fieldDesc.setImmutable(true);
DateFieldHandler dfh = new DateFieldHandler(handler);
// -- patch for java.sql.Date
Class type = fieldDesc.getFieldType();
if (java.sql.Date.class.isAssignableFrom(type)) {
dfh.setUseSQLDate(true);
}
fieldDesc.setHandler(dfh);
}
}
// -- Add reference to superclass...if necessary
if ((superClass != null) && (superClass != Void.class) && (superClass != Object.class)
&& (superClass != Class.class)) {
try {
XMLClassDescriptor parent = generateClassDescriptor(superClass, errorWriter);
if (parent != null) {
classDesc.setExtends(parent);
}
} catch (MarshalException mx) {
// -- Ignore for now.
}
}
return classDesc;
} // -- generateClassDescriptor
/**
* Removes the given FieldHandlerFactory from this Introspector
*
* @param factory the FieldHandlerFactory to remove
* @return true if the given FieldHandlerFactory was removed, or false otherwise.
* @throws IllegalArgumentException if the given factory is null
*/
public synchronized boolean removeFieldHandlerFactory(FieldHandlerFactory factory) {
if (factory == null) {
String err = "The argument 'factory' must not be null.";
throw new IllegalArgumentException(err);
}
// -- if list is null, just return
if (_handlerFactoryList == null)
return false;
if (_handlerFactoryList.remove(factory)) {
// -- re-register remaining handlers
_handlerFactoryMap.clear();
for (FieldHandlerFactory tmp : _handlerFactoryList) {
registerHandlerFactory(tmp);
}
return true;
}
return false;
} // -- removeFieldHandlerFactory
/**
* Sets whether or not collections (arrays, vectors, etc) should be wrapped in a container
* element. For example:
*
*
*
* <foos>
* <foo>foo1</foo>
* <foo>foo2</foo>
* </foos>
*
* instead of the default:
*
* <foos>foo1<foos>
* <foos>foo2</foos>
*
*
*
* @param wrapCollections a boolean that when true indicates collections should be wrapped in a
* container element.
*
*/
public void setWrapCollections(boolean wrapCollections) {
_wrapCollectionsInContainer = wrapCollections;
} // -- setWrapCollections
/**
* Returns true if the given XMLClassDescriptor was created via introspection
**/
public static boolean introspected(XMLClassDescriptor descriptor) {
return (descriptor instanceof IntrospectedXMLClassDescriptor);
} // -- introspected
/**
* Returns true if the given Class can be marshalled.
*
* @param type the Class to check marshallability for.
* @return true if the given Class can be marshalled.
**/
public static boolean marshallable(Class type) {
// -- make sure type is not Void, or Class;
if (type == Void.class || type == Class.class)
return false;
if ((!type.isInterface() || (type == Object.class))) {
if (!isPrimitive(type)) {
// -- make sure type is serializable
// if (!Serializable.class.isAssignableFrom( type ))
// return false;
// -- make sure we can construct the Object
if (!type.isArray()) {
// -- try to get the default constructor and make
// -- sure we are only looking at classes that can
// -- be instantiated by calling Class#newInstance
try {
type.getConstructor(EMPTY_CLASS_ARGS);
} catch (NoSuchMethodException e) {
// -- Allow any built-in descriptor classes
// -- that don't have default constructors
// -- such as java.sql.Date, java.sql.Time, etc.
return (CoreDescriptors.getDescriptor(type) != null);
}
}
}
}
return true;
} // -- marshallable
/**
* Sets the Naming conventions to be used by the Introspector
*
* @param naming the implementation of Naming to use. A value of null, will reset the XMLNaming to
* the default specified in the castor.properties file.
**/
public void setNaming(XMLNaming naming) {
if (naming == null)
_xmlNaming = _defaultNaming;
else
_xmlNaming = naming;
} // -- setNaming
/**
* Sets the NodeType for primitives. If the NodeType is NodeType.Element, all primitives will be
* treated as Elements, otherwise all primitives will be treated as Attributes.
*
* @param nodeType the NodeType to use for primitive values.
**/
public void setPrimitiveNodeType(NodeType nodeType) {
if (nodeType == NodeType.Element)
_primitiveNodeType = nodeType;
else
_primitiveNodeType = NodeType.Attribute;
} // -- setPrimitiveNodeType
/**
* Sets whether or not keys from Hastable / Map instances should be saved in the XML.
*
*
* Note: This is true by default since Castor 0.9.5.3
*
*
* @param saveMapKeys a boolean that when true indicates keys from Hashtable or Map instances
* should be saved. Otherwise only the value object is saved.
*/
public void setSaveMapKeys(boolean saveMapKeys) {
_saveMapKeys = saveMapKeys;
} // -- setSaveMapKeys
/**
* Converts the given xml name to a Java name.
*
* @param name the name to convert to a Java Name
* @param upperFirst a flag to indicate whether or not the the first character should be converted
* to uppercase.
**/
public static String toJavaName(String name, boolean upperFirst) {
int size = name.length();
char[] ncChars = name.toCharArray();
int next = 0;
boolean uppercase = upperFirst;
for (int i = 0; i < size; i++) {
char ch = ncChars[i];
switch (ch) {
case ':':
case '-':
uppercase = true;
break;
default:
if (uppercase == true) {
ncChars[next] = Character.toUpperCase(ch);
uppercase = false;
} else
ncChars[next] = ch;
++next;
break;
}
}
return new String(ncChars, 0, next);
} // -- toJavaName
// -------------------/
// - Private Methods -/
// -------------------/
private XMLFieldDescriptorImpl createFieldDescriptor(Class type, String fieldName,
String xmlName) {
XMLFieldDescriptorImpl fieldDesc = new XMLFieldDescriptorImpl(type, fieldName, xmlName, null);
if (type.isArray()) {
fieldDesc.setNodeType(NodeType.Element);
}
// -- primitive types are converted to attributes by default
else if (type.isPrimitive()) {
fieldDesc.setNodeType(_primitiveNodeType);
} else {
fieldDesc.setNodeType(NodeType.Element);
}
// -- wildcard?
if (type == java.lang.Object.class) {
fieldDesc.setMatches(xmlName + " *");
}
return fieldDesc;
} // -- createFieldDescriptor
/**
* Returns the registered FieldHandlerFactory for the given Class type.
*
* @param type the Class type to return the registered FieldHandlerFactory for
*/
private FieldHandlerFactory getHandlerFactory(Class type) {
if (_handlerFactoryMap != null) {
Class tmp = type;
while (tmp != null) {
Object obj = _handlerFactoryMap.get(tmp);
if (obj != null) {
return (FieldHandlerFactory) obj;
}
tmp = tmp.getSuperclass();
}
}
// -- check DefaultFieldHandlerFactory
if (DEFAULT_HANDLER_FACTORY.isSupportedType(type))
return DEFAULT_HANDLER_FACTORY;
return null;
} // -- getHandlerFactory
/**
* Registers the supported class types for the given FieldHandlerFactory into the map (for faster
* lookups)
*/
private void registerHandlerFactory(FieldHandlerFactory factory) {
if (_handlerFactoryMap == null)
_handlerFactoryMap = new Hashtable<>();
Class[] types = factory.getSupportedTypes();
for (int i = 0; i < types.length; i++) {
_handlerFactoryMap.put(types[i], factory);
}
} // -- registerHandlerFactory
/**
* Returns true if the given Class is an instance of a collection class.
*/
public static boolean isCollection(Class clazz) {
if (clazz.isArray())
return true;
for (int i = 0; i < COLLECTIONS.length; i++) {
// -- check to see if clazz is either the
// -- same as or a subclass of one of the
// -- available collections. For performance
// -- reasons we first check if class is
// -- directly equal to one of the collections
// -- instead of just calling isAssignableFrom.
if ((clazz == COLLECTIONS[i]) || (COLLECTIONS[i].isAssignableFrom(clazz))) {
return true;
}
}
return false;
} // -- isCollection
/**
* Returns true if the given Class is an instance of a collection class.
*/
public static boolean isMapCollection(Class clazz) {
if (clazz.isArray())
return false;
for (int i = 0; i < COLLECTIONS.length; i++) {
// -- check to see if clazz is either the
// -- same as or a subclass of one of the
// -- available collections. For performance
// -- reasons we first check if class is
// -- directly equal to one of the collections
// -- instead of just calling isAssignableFrom.
if ((clazz == COLLECTIONS[i]) || (COLLECTIONS[i].isAssignableFrom(clazz))) {
if (COLLECTIONS[i] == java.util.Hashtable.class)
return true;
// -- For JDK 1.1 compatibility use string name "java.util.Map"
if (COLLECTIONS[i].getName().equals(MAP))
return true;
}
}
return false;
} // -- isMapCollection
/**
* Returns true if we are allowed to create a descriptor for a given class type
*
* @param type the Class type to test
* @return true if we are allowed to create a descriptor for a given class type
**/
private static boolean isDescriptable(Class type) {
// -- make sure type is not Void, or Class;
if (type == Void.class || type == Class.class)
return false;
// -- check whether it is a Java 5.0 enum
float javaVersion =
Float.valueOf(System.getProperty("java.specification.version")).floatValue();
if (javaVersion >= 1.5) {
try {
Boolean isEnum = ReflectionUtil.isEnumViaReflection(type);
if (isEnum.booleanValue()) {
return true;
}
} catch (Exception e) {
// nothing to report; implies that there's no such method
}
}
if ((!type.isInterface()) && (type != Object.class) && (!isPrimitive(type))) {
// -- make sure type is serializable
// if (!Serializable.class.isAssignableFrom( type ))
// return false;
// -- make sure we can construct the Object
if (!type.isArray()) {
// -- try to get the default constructor and make
// -- sure we are only looking at classes that can
// -- be instantiated by calling Class#newInstance
try {
type.getConstructor(EMPTY_CLASS_ARGS);
} catch (NoSuchMethodException e) {
// -- Allow any built-in descriptor classes
// -- that don't have default constructors
// -- such as java.sql.Date, java.sql.Time, etc.
return (CoreDescriptors.getDescriptor(type) != null);
}
}
}
return true;
} // -- isDescriptable
/**
* Returns true if the given class should be treated as a primitive type
*
* @return true if the given class should be treated as a primitive type
**/
private static boolean isPrimitive(Class type) {
if (type.isPrimitive()) {
return true;
}
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");
} else {
return false;
}
} // -- isPrimitive
/**
* Returns an array of collections available during introspection. Allows JDK 1.2+ support without
* breaking JDK 1.1 support.
*
* @return a list of available collections
**/
private static Class[] loadCollections() {
List collections = new ArrayList<>(6);
// -- JDK 1.1
collections.add(Vector.class);
collections.add(Enumeration.class);
collections.add(Hashtable.class);
// -- JDK 1.2+
ClassLoader loader = Vector.class.getClassLoader();
Class clazz = null;
try {
if (loader != null) {
clazz = loader.loadClass(LIST);
} else
clazz = Class.forName(LIST);
} catch (ClassNotFoundException cnfx) {
// -- just ignore...either JDK 1.1
// -- or some nasty ClassLoader
// -- issue has occurred.
}
if (clazz != null) {
// -- java.util.List found, add to collections,
// -- also add java.util.Map
collections.add(clazz);
clazz = null;
try {
// -- java.util.Map
if (loader != null) {
clazz = loader.loadClass(MAP);
} else
clazz = Class.forName(MAP);
if (clazz != null) {
collections.add(clazz);
}
// -- java.util.Set
if (loader != null) {
clazz = loader.loadClass(SET_COLLECTION);
} else
clazz = Class.forName(SET_COLLECTION);
if (clazz != null) {
collections.add(clazz);
}
} catch (ClassNotFoundException cnfx) {
// -- just ignore...for now
// -- some nasty ClassLoader issue has occurred.
}
}
return collections.toArray(new Class[collections.size()]);
} // -- loadCollections
public void propertyChange(PropertyChangeEvent event) {
if (event.getPropertyName().equals(XMLProperties.PRIMITIVE_NODE_TYPE)) {
if (event.getNewValue() instanceof String) {
this.setPrimitiveNodeType(NodeType.getNodeType((String) event.getNewValue()));
} else {
throw new IllegalArgumentException(
"The value for '" + XMLProperties.PRIMITIVE_NODE_TYPE + "' must be of type String");
}
}
}
/**
* A special TypeConvertor that simply returns the object given. This is used for preventing the
* FieldHandlerImpl from using a CollectionHandler when getValue is called.
*/
class IdentityConvertor implements TypeConvertor {
public Object convert(final Object object) {
return object;
}
} // -- class: IdentityConvertor
/**
* A simple struct for holding a set of accessor methods.
*/
class MethodSet {
/** A reference to the add method. */
Method _add = null;
/** A reference to the create method. */
Method _create = null;
/** A reference to the get method. */
Method _get = null;
/** A reference to the set method. */
Method _set = null;
/** The fieldName for the field accessed by the methods in this method set. */
String _fieldName = null;
MethodSet(String fieldName) {
super();
this._fieldName = fieldName;
}
} // -- inner class: MethodSet
} // -- Introspector
/**
* A simple extension of XMLClassDescriptor so that we can set the "instrospected" flag.
**/
class IntrospectedXMLClassDescriptor extends XMLClassDescriptorImpl {
/**
* Creates an IntrospectedXMLClassDescriptor
*
* @param type the Class type with which this ClassDescriptor describes.
**/
IntrospectedXMLClassDescriptor(Class type) {
super(type);
setIntrospected(true);
} // -- XMLClassDescriptorImpl
/**
* Creates an IntrospectedXMLClassDescriptor
*
* @param type the Class type with which this ClassDescriptor describes.
**/
public IntrospectedXMLClassDescriptor(Class type, String xmlName) {
super(type, xmlName);
setIntrospected(true);
} // -- XMLClassDescriptorImpl
} // -- IntrospectedClassDescriptor