/** * 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-2003 (C) Intalio, Inc. All Rights Reserved. * * $Id$ */ package org.exolab.castor.xml; 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 <a href="mailto:kvisco@intalio.com">Keith Visco</a> * @version $Revision$ $Date: 2006-04-14 04:14:43 -0600 (Fri, 14 Apr 2006) $ */ public final class Introspector { /** 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: * * <pre> * <foos> * <foo>foo1</foo> * <foo>foo2</foo> * </foos> * * instead of the default: * * <foos>foo1<foos> * <foos>foo2</foos> * * </pre> * */ 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) { _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.addElement(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 = (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 = (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 = (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 = (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 = (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 = (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 <e>, we //-- need to create a field descriptor for //-- an object that represents the element <e> and //-- acts as a go-between from the parent of 'c' //-- denoted as P(c) and 'c' itself // // object model: P(c) -> c // xml : <p><e><c></e><p> //-- Make new class descriptor for the field that //-- will represent the container element <e> 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 (int i = 0; i < dateDescriptors.size(); i++) { XMLFieldDescriptorImpl fieldDesc = (XMLFieldDescriptorImpl) dateDescriptors.get(i); 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.removeElement(factory)) { //-- re-register remaining handlers _handlerFactoryMap.clear(); for (int i = 0; i < _handlerFactoryList.size(); i++) { FieldHandlerFactory tmp = (FieldHandlerFactory)_handlerFactoryList.elementAt(i); registerHandlerFactory(tmp); } return true; } return false; } //-- removeFieldHandlerFactory /** * Sets whether or not collections (arrays, vectors, etc) * should be wrapped in a container element. For example: * * <pre> * * <foos> * <foo>foo1</foo> * <foo>foo2</foo> * </foos> * * instead of the default: * * <foos>foo1<foos> * <foos>foo2</foos> * * </pre> * * @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. * * <p>Note: This is true by default since Castor 0.9.5.3</p> * * @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() { Vector collections = new Vector(6); //-- JDK 1.1 collections.addElement(Vector.class); collections.addElement(Enumeration.class); collections.addElement(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.addElement(clazz); clazz = null; try { //-- java.util.Map if (loader != null) { clazz = loader.loadClass(MAP); } else clazz = Class.forName(MAP); if (clazz != null) { collections.addElement(clazz); } //-- java.util.Set if (loader != null) { clazz = loader.loadClass(SET_COLLECTION); } else clazz = Class.forName(SET_COLLECTION); if (clazz != null) { collections.addElement(clazz); } } catch(ClassNotFoundException cnfx) { //-- just ignore...for now //-- some nasty ClassLoader issue has occurred. } } Class[] classes = new Class[collections.size()]; collections.copyInto(classes); return classes; } //-- loadCollections /** * 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