0001: /**
0002: * Redistribution and use of this software and associated documentation
0003: * ("Software"), with or without modification, are permitted provided
0004: * that the following conditions are met:
0005: *
0006: * 1. Redistributions of source code must retain copyright
0007: * statements and notices. Redistributions must also contain a
0008: * copy of this document.
0009: *
0010: * 2. Redistributions in binary form must reproduce the
0011: * above copyright notice, this list of conditions and the
0012: * following disclaimer in the documentation and/or other
0013: * materials provided with the distribution.
0014: *
0015: * 3. The name "Exolab" must not be used to endorse or promote
0016: * products derived from this Software without prior written
0017: * permission of Intalio, Inc. For written permission,
0018: * please contact info@exolab.org.
0019: *
0020: * 4. Products derived from this Software may not be called "Exolab"
0021: * nor may "Exolab" appear in their names without prior written
0022: * permission of Intalio, Inc. Exolab is a registered
0023: * trademark of Intalio, Inc.
0024: *
0025: * 5. Due credit should be given to the Exolab Project
0026: * (http://www.exolab.org/).
0027: *
0028: * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
0029: * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
0030: * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
0031: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
0032: * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
0033: * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
0034: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
0035: * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
0036: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
0037: * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
0038: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
0039: * OF THE POSSIBILITY OF SUCH DAMAGE.
0040: *
0041: * Copyright 1999-2003 (C) Intalio, Inc. All Rights Reserved.
0042: *
0043: * $Id: Introspector.java 7004 2007-05-28 13:07:06Z wguttmn $
0044: */package org.exolab.castor.xml;
0045:
0046: import java.io.PrintWriter;
0047: import java.lang.reflect.Field;
0048: import java.lang.reflect.Method;
0049: import java.lang.reflect.Modifier;
0050: import java.util.ArrayList;
0051: import java.util.Enumeration;
0052: import java.util.Hashtable;
0053: import java.util.List;
0054: import java.util.Vector;
0055:
0056: import org.exolab.castor.mapping.CollectionHandler;
0057: import org.exolab.castor.mapping.FieldHandler;
0058: import org.exolab.castor.mapping.FieldHandlerFactory;
0059: import org.exolab.castor.mapping.GeneralizedFieldHandler;
0060: import org.exolab.castor.mapping.MappingException;
0061: import org.exolab.castor.mapping.TypeConvertor;
0062: import org.exolab.castor.mapping.loader.CollectionHandlers;
0063: import org.exolab.castor.mapping.loader.FieldHandlerImpl;
0064: import org.exolab.castor.mapping.loader.TypeInfo;
0065: import org.exolab.castor.util.Configuration;
0066: import org.exolab.castor.util.LocalConfiguration;
0067: import org.exolab.castor.util.ReflectionUtil;
0068: import org.exolab.castor.xml.descriptors.CoreDescriptors;
0069: import org.exolab.castor.xml.handlers.ContainerFieldHandler;
0070: import org.exolab.castor.xml.handlers.DateFieldHandler;
0071: import org.exolab.castor.xml.handlers.DefaultFieldHandlerFactory;
0072: import org.exolab.castor.xml.util.ContainerElement;
0073: import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
0074: import org.exolab.castor.xml.util.XMLFieldDescriptorImpl;
0075:
0076: /**
0077: * A Helper class for the Marshaller and Unmarshaller,
0078: * basically the common code base between the two. This
0079: * class handles the introspection to dynamically create
0080: * descriptors.
0081: *
0082: * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
0083: * @version $Revision: 7004 $ $Date: 2006-04-14 04:14:43 -0600 (Fri, 14 Apr 2006) $
0084: */
0085: public final class Introspector {
0086:
0087: /**
0088: * The property name for enabling collection wrapping.
0089: * The property controls whether or not collections
0090: * (arrays, vectors, etc) should be wrapped in a container element.
0091: * For example:
0092: *
0093: * <pre>
0094: * <foos>
0095: * <foo>foo1</foo>
0096: * <foo>foo2</foo>
0097: * </foos>
0098: *
0099: * instead of the default:
0100: *
0101: * <foos>foo1<foos>
0102: * <foos>foo2</foos>
0103: *
0104: * </pre>
0105: *
0106: * Use this property with a value of true or false in the
0107: * castor.properties file
0108: *
0109: * org.exolab.castor.xml.introspector.wrapCollections=true
0110: * -or-
0111: * org.exolab.castor.xml.introspector.wrapCollections=false
0112: *
0113: * This property is false by default.
0114: */
0115: public static final String WRAP_COLLECTIONS_PROPERTY = "org.exolab.castor.xml.introspector.wrapCollections";
0116:
0117: private static final String ADD = "add";
0118: private static final String GET = "get";
0119: private static final String IS = "is";
0120: private static final String SET = "set";
0121: private static final String CREATE = "create";
0122:
0123: /**
0124: * The default FieldHandlerFactory
0125: */
0126: private FieldHandlerFactory DEFAULT_HANDLER_FACTORY = new DefaultFieldHandlerFactory();
0127:
0128: private static final Class[] EMPTY_CLASS_ARGS = new Class[0];
0129:
0130: /**
0131: * Name of the java.util.List collection
0132: */
0133: private static final String LIST = "java.util.List";
0134:
0135: /**
0136: * Name of the java.util.Map collection
0137: */
0138: private static final String MAP = "java.util.Map";
0139:
0140: /**
0141: * Name of the java.util.Map collection
0142: */
0143: private static final String SET_COLLECTION = "java.util.Set";
0144:
0145: /**
0146: * Used as a prefix for the name of a container field
0147: */
0148: private static final String COLLECTION_WRAPPER_PREFIX = "##container_for_";
0149:
0150: /**
0151: * The default flag indicating whether or not collections
0152: * (arrays, vectors, etc) should be wrapped in a container element.
0153: *
0154: * @see _wrapCollectionsInContainer
0155: */
0156: private static final boolean WRAP_COLLECTIONS_DEFAULT = false;
0157:
0158: /**
0159: * The set of available collections to use
0160: * during introspection. JDK dependant.
0161: **/
0162: private static final Class[] _collections = loadCollections();
0163:
0164: /**
0165: * The default naming conventions
0166: **/
0167: private static XMLNaming _defaultNaming = null;
0168:
0169: /**
0170: * The naming conventions to use
0171: **/
0172: private XMLNaming _naming = null;
0173:
0174: /**
0175: * The NodeType to use for primitives
0176: **/
0177: private NodeType _primitiveNodeType = null;
0178:
0179: /**
0180: * The variable flag indicating whether or not collections
0181: * (arrays, vectors, etc) should be wrapped in a container element.
0182: * For example:
0183: *
0184: * <pre>
0185: * <foos>
0186: * <foo>foo1</foo>
0187: * <foo>foo2</foo>
0188: * </foos>
0189: *
0190: * instead of the default:
0191: *
0192: * <foos>foo1<foos>
0193: * <foos>foo2</foos>
0194: *
0195: * </pre>
0196: *
0197: */
0198: private boolean _wrapCollectionsInContainer = WRAP_COLLECTIONS_DEFAULT;
0199:
0200: /**
0201: * The set of registered FieldHandlerFactory instances
0202: */
0203: private Vector _handlerFactoryList = null;
0204:
0205: /**
0206: * The set of registered FieldHandlerFactory instances
0207: * associated with their supported types
0208: */
0209: private Hashtable _handlerFactoryMap = null;
0210:
0211: /**
0212: * A flag indicating that MapKeys should be saved. To remain
0213: * backward compatible this may be disable via the
0214: * castor.properties.
0215: */
0216: private boolean _saveMapKeys = true;
0217:
0218: /**
0219: * Specifies class loader to be used.
0220: */
0221: private ClassLoader _classLoader = null;
0222:
0223: /**
0224: * Creates a new instance of the Introspector.
0225: */
0226: public Introspector() {
0227: this (null);
0228: } //-- Introspector
0229:
0230: /**
0231: * Creates a new instance of the Introspector.
0232: *
0233: * @param classLoader
0234: */
0235: public Introspector(ClassLoader classLoader) {
0236: super ();
0237: _classLoader = classLoader;
0238: init();
0239: } //-- Introspector
0240:
0241: private void init() {
0242:
0243: LocalConfiguration config = LocalConfiguration.getInstance();
0244:
0245: if (_defaultNaming == null) {
0246: _defaultNaming = config.getXMLNaming(_classLoader);
0247: }
0248: _naming = _defaultNaming;
0249: setPrimitiveNodeType(config.getPrimitiveNodeType());
0250:
0251: //-- wrap collections in a container element?
0252: String wrap = config.getProperty(WRAP_COLLECTIONS_PROPERTY,
0253: null);
0254: if (wrap != null) {
0255: _wrapCollectionsInContainer = Boolean.valueOf(wrap)
0256: .booleanValue();
0257: }
0258:
0259: //-- Save Hashtable / Map keys ?
0260: String saveKeys = config.getProperty(
0261: Configuration.Property.SaveMapKeys, null);
0262: if (saveKeys != null) {
0263: if ("false".equals(saveKeys) || "off".equals(saveKeys)) {
0264: _saveMapKeys = false;
0265: }
0266: }
0267:
0268: } //-- init
0269:
0270: /**
0271: * Registers the given "generalized" FieldHandlerFactory with this
0272: * Introspector.
0273: *
0274: * @param factory the FieldHandlerFactory to add to this
0275: * introspector
0276: * @throws IllegalArgumentException if the given factory is null
0277: */
0278: public synchronized void addFieldHandlerFactory(
0279: FieldHandlerFactory factory) {
0280: if (factory == null) {
0281: String err = "The argument 'factory' must not be null.";
0282: throw new IllegalArgumentException(err);
0283: }
0284: if (_handlerFactoryList == null) {
0285: _handlerFactoryList = new Vector();
0286: }
0287: _handlerFactoryList.addElement(factory);
0288: registerHandlerFactory(factory);
0289: } //-- addFieldHandlerFactory
0290:
0291: /**
0292: * Returns the NodeType for java primitives
0293: *
0294: * @return the NodeType for java primitives
0295: **/
0296: public NodeType getPrimitiveNodeType() {
0297: return _primitiveNodeType;
0298: } //-- getPrimitiveNodeType
0299:
0300: /**
0301: * Creates an XMLClassDescriptor for the given class by using Reflection.
0302: * @param c the Class to create the XMLClassDescriptor for
0303: * @return the new XMLClassDescriptor created for the given class
0304: * @exception MarshalException when an error occurs during the creation
0305: * of the ClassDescriptor.
0306: **/
0307: public XMLClassDescriptor generateClassDescriptor(Class c)
0308: throws MarshalException {
0309: return generateClassDescriptor(c, null);
0310: } //-- generateClassDescriptor(Class)
0311:
0312: /**
0313: * Creates an XMLClassDescriptor for the given class by using Reflection.
0314: * @param c the Class to create the XMLClassDescriptor for
0315: * @param errorWriter a PrintWriter to print error information to
0316: * @return the new XMLClassDescriptor created for the given class
0317: * @exception MarshalException when an error occurs during the creation
0318: * of the ClassDescriptor.
0319: **/
0320: public XMLClassDescriptor generateClassDescriptor(Class c,
0321: PrintWriter errorWriter) throws MarshalException {
0322:
0323: if (c == null)
0324: return null;
0325:
0326: //-- handle arrays
0327: if (c.isArray())
0328: return null;
0329:
0330: //-- handle base objects
0331: if ((c == Void.class) || (c == Class.class)
0332: || (c == Object.class)) {
0333: throw new MarshalException(
0334: MarshalException.BASE_CLASS_OR_VOID_ERR);
0335: }
0336:
0337: //-- handle core descriptors
0338: XMLClassDescriptor coreDesc = CoreDescriptors.getDescriptor(c);
0339: if (coreDesc != null)
0340: return coreDesc;
0341:
0342: //--------------------------/
0343: //- handle complex objects -/
0344: //--------------------------/
0345:
0346: XMLClassDescriptorImpl classDesc = new IntrospectedXMLClassDescriptor(
0347: c);
0348:
0349: Method[] methods = c.getMethods();
0350: List dateDescriptors = new ArrayList(3);
0351: Hashtable methodSets = new Hashtable();
0352:
0353: int methodCount = 0;
0354:
0355: Class super Class = c.getSuperclass();
0356: Class[] interfaces = c.getInterfaces();
0357:
0358: //-- create method sets
0359: for (int i = 0; i < methods.length; i++) {
0360: Method method = methods[i];
0361:
0362: Class owner = method.getDeclaringClass();
0363:
0364: //-- ignore methods from super-class, that will be
0365: //-- introspected separately, if necessary
0366: if (owner != c) {
0367: //-- if declaring class is anything but
0368: //-- an interface, than just continue,
0369: //-- the field comes from a super class
0370: //-- (e.g. java.lang.Object)
0371: if (!owner.isInterface())
0372: continue;
0373:
0374: //-- owner is an interface, is it an
0375: //-- interface this class implements
0376: //-- or a parent class?
0377: if (interfaces.length > 0) {
0378: boolean found = false;
0379: for (int count = 0; count < interfaces.length; count++) {
0380: if (interfaces[count] == owner) {
0381: found = true;
0382: break;
0383: }
0384: }
0385: if (!found)
0386: continue;
0387: }
0388: } else {
0389: //-- look for overloaded methods
0390: if (super Class != null) {
0391: Class[] args = method.getParameterTypes();
0392: String name = method.getName();
0393: Method tmpMethod = null;
0394: try {
0395: tmpMethod = super Class.getMethod(name, args);
0396: } catch (NoSuchMethodException nsme) {
0397: //-- do nothing
0398: }
0399: if (tmpMethod != null)
0400: continue;
0401: }
0402: }
0403:
0404: //-- if method is static...ignore
0405: if ((method.getModifiers() & Modifier.STATIC) != 0)
0406: continue;
0407:
0408: String methodName = method.getName();
0409:
0410: //-- read methods
0411: if (methodName.startsWith(GET)) {
0412: if (method.getParameterTypes().length != 0)
0413: continue;
0414: //-- disable direct field access
0415: ++methodCount;
0416: //-- make sure return type is "descriptable"
0417: //-- and not null
0418: Class type = method.getReturnType();
0419: if (type == null)
0420: continue;
0421: if (!isDescriptable(type))
0422: continue;
0423:
0424: //-- caclulate name from Method name
0425: String fieldName = methodName.substring(3);
0426: fieldName = JavaNaming.toJavaMemberName(fieldName);
0427:
0428: MethodSet methodSet = (MethodSet) methodSets
0429: .get(fieldName);
0430: if (methodSet == null) {
0431: methodSet = new MethodSet(fieldName);
0432: methodSets.put(fieldName, methodSet);
0433: }
0434: methodSet.get = method;
0435: } else if (methodName.startsWith(IS)) {
0436: if (method.getParameterTypes().length != 0)
0437: continue;
0438: //-- make sure type is not null, and a boolean
0439: Class type = method.getReturnType();
0440: if (type == null)
0441: continue;
0442: if (type.isPrimitive()) {
0443: if (type != Boolean.TYPE)
0444: continue;
0445: } else {
0446: if (type != Boolean.class)
0447: continue;
0448: }
0449: //-- disable direct field access
0450: ++methodCount;
0451: //-- caclulate name from Method name
0452: String fieldName = methodName.substring(IS.length());
0453: fieldName = JavaNaming.toJavaMemberName(fieldName);
0454:
0455: MethodSet methodSet = (MethodSet) methodSets
0456: .get(fieldName);
0457: if (methodSet == null) {
0458: methodSet = new MethodSet(fieldName);
0459: methodSets.put(fieldName, methodSet);
0460: }
0461: methodSet.get = method;
0462: }
0463: //-----------------------------------/
0464: //-- write methods (collection item)
0465: else if (methodName.startsWith(ADD)) {
0466: if (method.getParameterTypes().length != 1)
0467: continue;
0468: //-- disable direct field access
0469: ++methodCount;
0470: //-- make sure parameter type is "descriptable"
0471: if (!isDescriptable(method.getParameterTypes()[0]))
0472: continue;
0473: //-- caclulate name from Method name
0474: String fieldName = methodName.substring(3);
0475: fieldName = JavaNaming.toJavaMemberName(fieldName);
0476: MethodSet methodSet = (MethodSet) methodSets
0477: .get(fieldName);
0478: if (methodSet == null) {
0479: methodSet = new MethodSet(fieldName);
0480: methodSets.put(fieldName, methodSet);
0481: }
0482: methodSet.add = method;
0483: }
0484: //-- write method (singleton or collection)
0485: else if (methodName.startsWith(SET)) {
0486: if (method.getParameterTypes().length != 1)
0487: continue;
0488: //-- disable direct field access
0489: ++methodCount;
0490: //-- make sure parameter type is "descriptable"
0491: if (!isDescriptable(method.getParameterTypes()[0]))
0492: continue;
0493: //-- caclulate name from Method name
0494: String fieldName = methodName.substring(3);
0495: fieldName = JavaNaming.toJavaMemberName(fieldName);
0496: MethodSet methodSet = (MethodSet) methodSets
0497: .get(fieldName);
0498: if (methodSet == null) {
0499: methodSet = new MethodSet(fieldName);
0500: methodSets.put(fieldName, methodSet);
0501: }
0502: methodSet.set = method;
0503: } else if (methodName.startsWith(CREATE)) {
0504: if (method.getParameterTypes().length != 0)
0505: continue;
0506: Class type = method.getReturnType();
0507: //-- make sure return type is "descriptable"
0508: //-- and not null
0509: if (!isDescriptable(type))
0510: continue;
0511: //-- caclulate name from Method name
0512: String fieldName = methodName
0513: .substring(CREATE.length());
0514: fieldName = JavaNaming.toJavaMemberName(fieldName);
0515: MethodSet methodSet = (MethodSet) methodSets
0516: .get(fieldName);
0517: if (methodSet == null) {
0518: methodSet = new MethodSet(fieldName);
0519: methodSets.put(fieldName, methodSet);
0520: }
0521: methodSet.create = method;
0522: }
0523: } //-- end create method sets
0524:
0525: //-- Loop Through MethodSets and create
0526: //-- descriptors
0527: Enumeration enumeration = methodSets.elements();
0528:
0529: while (enumeration.hasMoreElements()) {
0530:
0531: MethodSet methodSet = (MethodSet) enumeration.nextElement();
0532:
0533: //-- create XMLFieldDescriptor
0534: String xmlName = _naming.toXMLName(methodSet.fieldName);
0535:
0536: boolean isCollection = false;
0537:
0538: //-- calculate class type
0539: //-- 1st check for add-method, then set or get method
0540: Class type = null;
0541: if (methodSet.add != null) {
0542: type = methodSet.add.getParameterTypes()[0];
0543: isCollection = true;
0544: }
0545:
0546: //-- if there was no add method, use get/set methods
0547: //-- to calculate type.
0548: if (type == null) {
0549: if (methodSet.get != null) {
0550: type = methodSet.get.getReturnType();
0551: } else if (methodSet.set != null) {
0552: type = methodSet.set.getParameterTypes()[0];
0553: } else {
0554: //-- if we make it here, the only method found
0555: //-- was a create method, which is useless by itself.
0556: continue;
0557: }
0558: }
0559:
0560: //-- Handle Collections
0561: isCollection = (isCollection || isCollection(type));
0562:
0563: TypeInfo typeInfo = null;
0564: CollectionHandler colHandler = null;
0565:
0566: //-- If the type is a collection and there is no add method,
0567: //-- then we obtain a CollectionHandler
0568: if (isCollection && (methodSet.add == null)) {
0569:
0570: try {
0571: colHandler = CollectionHandlers.getHandler(type);
0572: } catch (MappingException mx) {
0573: //-- No collection handler available,
0574: //-- proceed anyway...
0575: }
0576:
0577: //-- Find component type
0578: if (type.isArray()) {
0579: //-- Byte arrays are handled as a special case
0580: //-- so don't use CollectionHandler
0581: if (type.getComponentType() == Byte.TYPE) {
0582: colHandler = null;
0583: } else
0584: type = type.getComponentType();
0585: }
0586: }
0587:
0588: typeInfo = new TypeInfo(type, null, null, false, null,
0589: colHandler);
0590:
0591: //-- Create FieldHandler first, before the XMLFieldDescriptor
0592: //-- in case we need to use a custom handler
0593:
0594: FieldHandler handler = null;
0595: boolean customHandler = false;
0596: try {
0597: handler = new FieldHandlerImpl(methodSet.fieldName,
0598: null, null, methodSet.get, methodSet.set,
0599: typeInfo);
0600: //-- clean up
0601: if (methodSet.add != null)
0602: ((FieldHandlerImpl) handler)
0603: .setAddMethod(methodSet.add);
0604:
0605: if (methodSet.create != null)
0606: ((FieldHandlerImpl) handler)
0607: .setCreateMethod(methodSet.create);
0608:
0609: //-- handle Hashtable/Map
0610: if (isCollection && _saveMapKeys
0611: && isMapCollection(type)) {
0612: ((FieldHandlerImpl) handler)
0613: .setConvertFrom(new IdentityConvertor());
0614: }
0615:
0616: //-- look for GeneralizedFieldHandler
0617: FieldHandlerFactory factory = getHandlerFactory(type);
0618: if (factory != null) {
0619: GeneralizedFieldHandler gfh = factory
0620: .createFieldHandler(type);
0621: if (gfh != null) {
0622: gfh.setFieldHandler(handler);
0623: handler = gfh;
0624: customHandler = true;
0625: //-- swap type with the type specified by the
0626: //-- custom field handler
0627: if (gfh.getFieldType() != null) {
0628: type = gfh.getFieldType();
0629: }
0630: }
0631: }
0632:
0633: } catch (MappingException mx) {
0634: throw new MarshalException(mx);
0635: }
0636:
0637: XMLFieldDescriptorImpl fieldDesc = createFieldDescriptor(
0638: type, methodSet.fieldName, xmlName);
0639:
0640: if (isCollection) {
0641: fieldDesc.setMultivalued(true);
0642: fieldDesc.setNodeType(NodeType.Element);
0643: }
0644:
0645: //-- check for instances of java.util.Date
0646: if (java.util.Date.class.isAssignableFrom(type)) {
0647: //handler = new DateFieldHandler(handler);
0648: if (!customHandler) {
0649: dateDescriptors.add(fieldDesc);
0650: }
0651: }
0652:
0653: fieldDesc.setHandler(handler);
0654:
0655: //-- Wrap collections?
0656: if (isCollection && _wrapCollectionsInContainer) {
0657: String fieldName = COLLECTION_WRAPPER_PREFIX
0658: + methodSet.fieldName;
0659: //-- If we have a field 'c' that is a collection and
0660: //-- we want to wrap that field in an element <e>, we
0661: //-- need to create a field descriptor for
0662: //-- an object that represents the element <e> and
0663: //-- acts as a go-between from the parent of 'c'
0664: //-- denoted as P(c) and 'c' itself
0665: //
0666: // object model: P(c) -> c
0667: // xml : <p><e><c></e><p>
0668:
0669: //-- Make new class descriptor for the field that
0670: //-- will represent the container element <e>
0671: Class cType = ContainerElement.class;
0672: XMLClassDescriptorImpl containerClassDesc = new XMLClassDescriptorImpl(
0673: cType);
0674:
0675: //-- add the field descriptor to our new class descriptor
0676: containerClassDesc.addFieldDescriptor(fieldDesc);
0677: //-- nullify xmlName so that auto-naming will be enabled,
0678: //-- we can't do this in the constructor because
0679: //-- XMLFieldDescriptorImpl will create a default one.
0680: fieldDesc.setXMLName(null);
0681: fieldDesc.setMatches("*");
0682:
0683: //-- wrap the field handler in a special container field
0684: //-- handler that will actually do the delgation work
0685: FieldHandler cHandler = new ContainerFieldHandler(
0686: handler);
0687: fieldDesc.setHandler(cHandler);
0688:
0689: fieldDesc = createFieldDescriptor(cType, fieldName,
0690: xmlName);
0691: fieldDesc.setClassDescriptor(containerClassDesc);
0692: fieldDesc.setHandler(cHandler);
0693: }
0694: //-- add FieldDescriptor to ClassDescriptor
0695: classDesc.addFieldDescriptor(fieldDesc);
0696:
0697: } //-- end of method loop
0698:
0699: //-- If we didn't find any methods we can try
0700: //-- direct field access
0701: if (methodCount == 0) {
0702:
0703: Field[] fields = c.getFields();
0704: Hashtable descriptors = new Hashtable();
0705: for (int i = 0; i < fields.length; i++) {
0706: Field field = fields[i];
0707:
0708: Class owner = field.getDeclaringClass();
0709:
0710: //-- ignore fields from super-class, that will be
0711: //-- introspected separately, if necessary
0712: if (owner != c) {
0713: //-- if declaring class is anything but
0714: //-- an interface, than just continue,
0715: //-- the field comes from a super class
0716: //-- (e.g. java.lang.Object)
0717: if (!owner.isInterface())
0718: continue;
0719:
0720: //-- owner is an interface, is it an
0721: //-- interface this class implements
0722: //-- or a parent class?
0723: if (interfaces.length > 0) {
0724: boolean found = false;
0725: for (int count = 0; count < interfaces.length; count++) {
0726: if (interfaces[count] == owner) {
0727: found = true;
0728: break;
0729: }
0730: }
0731: if (!found)
0732: continue;
0733: }
0734: }
0735:
0736: //-- make sure field is not transient or static final
0737: int modifiers = field.getModifiers();
0738: if (Modifier.isTransient(modifiers))
0739: continue;
0740: if (Modifier.isFinal(modifiers)
0741: && Modifier.isStatic(modifiers))
0742: continue;
0743:
0744: Class type = field.getType();
0745:
0746: if (!isDescriptable(type))
0747: continue;
0748:
0749: //-- Built-in support for JDK 1.1 Collections
0750: //-- we need to a pluggable interface for
0751: //-- JDK 1.2+
0752: boolean isCollection = isCollection(type);
0753:
0754: TypeInfo typeInfo = null;
0755: CollectionHandler colHandler = null;
0756:
0757: //-- If the type is a collection and there is no add method,
0758: //-- then we obtain a CollectionHandler
0759: if (isCollection) {
0760:
0761: try {
0762: colHandler = CollectionHandlers
0763: .getHandler(type);
0764: } catch (MappingException mx) {
0765: //-- No CollectionHandler available, continue
0766: //-- without one...
0767: }
0768:
0769: //-- Find component type
0770: if (type.isArray()) {
0771: //-- Byte arrays are handled as a special case
0772: //-- so don't use CollectionHandler
0773: if (type.getComponentType() == Byte.TYPE) {
0774: colHandler = null;
0775: } else
0776: type = type.getComponentType();
0777:
0778: }
0779: }
0780:
0781: String fieldName = field.getName();
0782: String xmlName = _naming.toXMLName(fieldName);
0783:
0784: //-- Create FieldHandler first, before the XMLFieldDescriptor
0785: //-- in case we need to use a custom handler
0786:
0787: typeInfo = new TypeInfo(type, null, null, false, null,
0788: colHandler);
0789:
0790: FieldHandler handler = null;
0791: boolean customHandler = false;
0792: try {
0793: handler = new FieldHandlerImpl(field, typeInfo);
0794:
0795: //-- handle Hashtable/Map
0796: if (isCollection && _saveMapKeys
0797: && isMapCollection(type)) {
0798: ((FieldHandlerImpl) handler)
0799: .setConvertFrom(new IdentityConvertor());
0800: }
0801:
0802: //-- look for GeneralizedFieldHandler
0803: FieldHandlerFactory factory = getHandlerFactory(type);
0804: if (factory != null) {
0805: GeneralizedFieldHandler gfh = factory
0806: .createFieldHandler(type);
0807: if (gfh != null) {
0808: gfh.setFieldHandler(handler);
0809: handler = gfh;
0810: customHandler = true;
0811: //-- swap type with the type specified by the
0812: //-- custom field handler
0813: if (gfh.getFieldType() != null) {
0814: type = gfh.getFieldType();
0815: }
0816: }
0817: }
0818: } catch (MappingException mx) {
0819: throw new MarshalException(mx);
0820: }
0821:
0822: XMLFieldDescriptorImpl fieldDesc = createFieldDescriptor(
0823: type, fieldName, xmlName);
0824:
0825: if (isCollection) {
0826: fieldDesc.setNodeType(NodeType.Element);
0827: fieldDesc.setMultivalued(true);
0828: }
0829: descriptors.put(xmlName, fieldDesc);
0830: classDesc.addFieldDescriptor(fieldDesc);
0831: fieldDesc.setHandler(handler);
0832:
0833: //-- check for instances of java.util.Date
0834: if (java.util.Date.class.isAssignableFrom(type)) {
0835: if (!customHandler) {
0836: dateDescriptors.add(fieldDesc);
0837: }
0838: }
0839:
0840: }
0841: } //-- end of direct field access
0842:
0843: //-- A temporary fix for java.util.Date
0844: if (dateDescriptors != null) {
0845: for (int i = 0; i < dateDescriptors.size(); i++) {
0846: XMLFieldDescriptorImpl fieldDesc = (XMLFieldDescriptorImpl) dateDescriptors
0847: .get(i);
0848: FieldHandler handler = fieldDesc.getHandler();
0849: fieldDesc.setImmutable(true);
0850: DateFieldHandler dfh = new DateFieldHandler(handler);
0851:
0852: //-- patch for java.sql.Date
0853: Class type = fieldDesc.getFieldType();
0854: if (java.sql.Date.class.isAssignableFrom(type)) {
0855: dfh.setUseSQLDate(true);
0856: }
0857: fieldDesc.setHandler(dfh);
0858: }
0859: }
0860:
0861: //-- Add reference to superclass...if necessary
0862: if ((super Class != null) && (super Class != Void.class)
0863: && (super Class != Object.class)
0864: && (super Class != Class.class)) {
0865: try {
0866: XMLClassDescriptor parent = generateClassDescriptor(
0867: super Class, errorWriter);
0868: if (parent != null) {
0869: classDesc.setExtends(parent);
0870: }
0871: } catch (MarshalException mx) {
0872: //-- Ignore for now.
0873: }
0874:
0875: }
0876:
0877: return classDesc;
0878: } //-- generateClassDescriptor
0879:
0880: /**
0881: * Removes the given FieldHandlerFactory from this Introspector
0882: *
0883: * @param factory the FieldHandlerFactory to remove
0884: * @return true if the given FieldHandlerFactory was removed, or
0885: * false otherwise.
0886: * @throws IllegalArgumentException if the given factory is null
0887: */
0888: public synchronized boolean removeFieldHandlerFactory(
0889: FieldHandlerFactory factory) {
0890: if (factory == null) {
0891: String err = "The argument 'factory' must not be null.";
0892: throw new IllegalArgumentException(err);
0893: }
0894:
0895: //-- if list is null, just return
0896: if (_handlerFactoryList == null)
0897: return false;
0898:
0899: if (_handlerFactoryList.removeElement(factory)) {
0900: //-- re-register remaining handlers
0901: _handlerFactoryMap.clear();
0902: for (int i = 0; i < _handlerFactoryList.size(); i++) {
0903: FieldHandlerFactory tmp = (FieldHandlerFactory) _handlerFactoryList
0904: .elementAt(i);
0905: registerHandlerFactory(tmp);
0906: }
0907: return true;
0908: }
0909: return false;
0910: } //-- removeFieldHandlerFactory
0911:
0912: /**
0913: * Sets whether or not collections (arrays, vectors, etc)
0914: * should be wrapped in a container element. For example:
0915: *
0916: * <pre>
0917: *
0918: * <foos>
0919: * <foo>foo1</foo>
0920: * <foo>foo2</foo>
0921: * </foos>
0922: *
0923: * instead of the default:
0924: *
0925: * <foos>foo1<foos>
0926: * <foos>foo2</foos>
0927: *
0928: * </pre>
0929: *
0930: * @param wrapCollections a boolean that when true indicates
0931: * collections should be wrapped in a container element.
0932: *
0933: */
0934: public void setWrapCollections(boolean wrapCollections) {
0935: _wrapCollectionsInContainer = wrapCollections;
0936: } //-- setWrapCollections
0937:
0938: /**
0939: * Returns true if the given XMLClassDescriptor was created via
0940: * introspection
0941: **/
0942: public static boolean introspected(XMLClassDescriptor descriptor) {
0943: return (descriptor instanceof IntrospectedXMLClassDescriptor);
0944: } //-- introspected
0945:
0946: /**
0947: * Returns true if the given Class can be marshalled.
0948: *
0949: * @param type the Class to check marshallability for.
0950: * @return true if the given Class can be marshalled.
0951: **/
0952: public static boolean marshallable(Class type) {
0953:
0954: //-- make sure type is not Void, or Class;
0955: if (type == Void.class || type == Class.class)
0956: return false;
0957:
0958: if ((!type.isInterface() || (type == Object.class))) {
0959:
0960: if (!isPrimitive(type)) {
0961:
0962: //-- make sure type is serializable
0963: // if (!Serializable.class.isAssignableFrom( type ))
0964: // return false;
0965:
0966: //-- make sure we can construct the Object
0967: if (!type.isArray()) {
0968: //-- try to get the default constructor and make
0969: //-- sure we are only looking at classes that can
0970: //-- be instantiated by calling Class#newInstance
0971: try {
0972: type.getConstructor(EMPTY_CLASS_ARGS);
0973: } catch (NoSuchMethodException e) {
0974: //-- Allow any built-in descriptor classes
0975: //-- that don't have default constructors
0976: //-- such as java.sql.Date, java.sql.Time, etc.
0977: return (CoreDescriptors.getDescriptor(type) != null);
0978: }
0979: }
0980: }
0981: }
0982: return true;
0983: } //-- marshallable
0984:
0985: /**
0986: * Sets the Naming conventions to be used by the Introspector
0987: *
0988: * @param naming the implementation of Naming to use. A
0989: * value of null, will reset the XMLNaming to the
0990: * default specified in the castor.properties file.
0991: **/
0992: public void setNaming(XMLNaming naming) {
0993: if (naming == null)
0994: _naming = _defaultNaming;
0995: else
0996: _naming = naming;
0997: } //-- setNaming
0998:
0999: /**
1000: * Sets the NodeType for primitives. If the
1001: * NodeType is NodeType.Element, all primitives will
1002: * be treated as Elements, otherwise all primitives
1003: * will be treated as Attributes.
1004: *
1005: * @param nodeType the NodeType to use for primitive values.
1006: **/
1007: public void setPrimitiveNodeType(NodeType nodeType) {
1008: if (nodeType == NodeType.Element)
1009: _primitiveNodeType = nodeType;
1010: else
1011: _primitiveNodeType = NodeType.Attribute;
1012: } //-- setPrimitiveNodeType
1013:
1014: /**
1015: * Sets whether or not keys from Hastable / Map instances
1016: * should be saved in the XML.
1017: *
1018: * <p>Note: This is true by default since Castor 0.9.5.3</p>
1019: *
1020: * @param saveMapKeys a boolean that when true indicates keys
1021: * from Hashtable or Map instances should be saved. Otherwise
1022: * only the value object is saved.
1023: */
1024: public void setSaveMapKeys(boolean saveMapKeys) {
1025: _saveMapKeys = saveMapKeys;
1026: } //-- setSaveMapKeys
1027:
1028: /**
1029: * Converts the given xml name to a Java name.
1030: * @param name the name to convert to a Java Name
1031: * @param upperFirst a flag to indicate whether or not the
1032: * the first character should be converted to uppercase.
1033: **/
1034: public static String toJavaName(String name, boolean upperFirst) {
1035:
1036: int size = name.length();
1037: char[] ncChars = name.toCharArray();
1038: int next = 0;
1039:
1040: boolean uppercase = upperFirst;
1041:
1042: for (int i = 0; i < size; i++) {
1043: char ch = ncChars[i];
1044:
1045: switch (ch) {
1046: case ':':
1047: case '-':
1048: uppercase = true;
1049: break;
1050: default:
1051: if (uppercase == true) {
1052: ncChars[next] = Character.toUpperCase(ch);
1053: uppercase = false;
1054: } else
1055: ncChars[next] = ch;
1056: ++next;
1057: break;
1058: }
1059: }
1060: return new String(ncChars, 0, next);
1061: } //-- toJavaName
1062:
1063: //-------------------/
1064: //- Private Methods -/
1065: //-------------------/
1066:
1067: private XMLFieldDescriptorImpl createFieldDescriptor(Class type,
1068: String fieldName, String xmlName) {
1069:
1070: XMLFieldDescriptorImpl fieldDesc = new XMLFieldDescriptorImpl(
1071: type, fieldName, xmlName, null);
1072:
1073: if (type.isArray()) {
1074: fieldDesc.setNodeType(NodeType.Element);
1075: }
1076: //-- primitive types are converted to attributes by default
1077: else if (type.isPrimitive()) {
1078: fieldDesc.setNodeType(_primitiveNodeType);
1079: } else {
1080: fieldDesc.setNodeType(NodeType.Element);
1081: }
1082:
1083: //-- wildcard?
1084: if (type == java.lang.Object.class) {
1085: fieldDesc.setMatches(xmlName + " *");
1086: }
1087:
1088: return fieldDesc;
1089: } //-- createFieldDescriptor
1090:
1091: /**
1092: * Returns the registered FieldHandlerFactory for the
1093: * given Class type.
1094: *
1095: * @param type the Class type to return the registered
1096: * FieldHandlerFactory for
1097: */
1098: private FieldHandlerFactory getHandlerFactory(Class type) {
1099: if (_handlerFactoryMap != null) {
1100: Class tmp = type;
1101: while (tmp != null) {
1102: Object obj = _handlerFactoryMap.get(tmp);
1103: if (obj != null) {
1104: return (FieldHandlerFactory) obj;
1105: }
1106: tmp = tmp.getSuperclass();
1107: }
1108: }
1109:
1110: //-- check DefaultFieldHandlerFactory
1111: if (DEFAULT_HANDLER_FACTORY.isSupportedType(type))
1112: return DEFAULT_HANDLER_FACTORY;
1113:
1114: return null;
1115: } //-- getHandlerFactory
1116:
1117: /**
1118: * Registers the supported class types for the given
1119: * FieldHandlerFactory into the map (for faster lookups)
1120: */
1121: private void registerHandlerFactory(FieldHandlerFactory factory) {
1122: if (_handlerFactoryMap == null)
1123: _handlerFactoryMap = new Hashtable();
1124:
1125: Class[] types = factory.getSupportedTypes();
1126: for (int i = 0; i < types.length; i++) {
1127: _handlerFactoryMap.put(types[i], factory);
1128: }
1129: } //-- registerHandlerFactory
1130:
1131: /**
1132: * Returns true if the given Class is an instance of a
1133: * collection class.
1134: */
1135: public static boolean isCollection(Class clazz) {
1136:
1137: if (clazz.isArray())
1138: return true;
1139:
1140: for (int i = 0; i < _collections.length; i++) {
1141: //-- check to see if clazz is either the
1142: //-- same as or a subclass of one of the
1143: //-- available collections. For performance
1144: //-- reasons we first check if class is
1145: //-- directly equal to one of the collections
1146: //-- instead of just calling isAssignableFrom.
1147: if ((clazz == _collections[i])
1148: || (_collections[i].isAssignableFrom(clazz))) {
1149: return true;
1150: }
1151: }
1152: return false;
1153: } //-- isCollection
1154:
1155: /**
1156: * Returns true if the given Class is an instance of a
1157: * collection class.
1158: */
1159: public static boolean isMapCollection(Class clazz) {
1160:
1161: if (clazz.isArray())
1162: return false;
1163:
1164: for (int i = 0; i < _collections.length; i++) {
1165: //-- check to see if clazz is either the
1166: //-- same as or a subclass of one of the
1167: //-- available collections. For performance
1168: //-- reasons we first check if class is
1169: //-- directly equal to one of the collections
1170: //-- instead of just calling isAssignableFrom.
1171: if ((clazz == _collections[i])
1172: || (_collections[i].isAssignableFrom(clazz))) {
1173: if (_collections[i] == java.util.Hashtable.class)
1174: return true;
1175: //-- For JDK 1.1 compatibility use string name "java.util.Map"
1176: if (_collections[i].getName().equals(MAP))
1177: return true;
1178: }
1179: }
1180: return false;
1181: } //-- isMapCollection
1182:
1183: /**
1184: * Returns true if we are allowed to create a descriptor
1185: * for a given class type
1186: * @param type the Class type to test
1187: * @return true if we are allowed to create a descriptor
1188: * for a given class type
1189: **/
1190: private static boolean isDescriptable(Class type) {
1191: //-- make sure type is not Void, or Class;
1192: if (type == Void.class || type == Class.class)
1193: return false;
1194:
1195: //-- check whether it is a Java 5.0 enum
1196: float javaVersion = Float.valueOf(
1197: System.getProperty("java.specification.version"))
1198: .floatValue();
1199: if (javaVersion >= 1.5) {
1200: try {
1201: Boolean isEnum = ReflectionUtil
1202: .isEnumViaReflection(type);
1203: if (isEnum.booleanValue()) {
1204: return true;
1205: }
1206: } catch (Exception e) {
1207: // nothing to report; implies that there's no such method
1208: }
1209: }
1210:
1211: if ((!type.isInterface()) && (type != Object.class)
1212: && (!isPrimitive(type))) {
1213:
1214: //-- make sure type is serializable
1215: //if (!Serializable.class.isAssignableFrom( type ))
1216: // return false;
1217:
1218: //-- make sure we can construct the Object
1219: if (!type.isArray()) {
1220:
1221: //-- try to get the default constructor and make
1222: //-- sure we are only looking at classes that can
1223: //-- be instantiated by calling Class#newInstance
1224: try {
1225: type.getConstructor(EMPTY_CLASS_ARGS);
1226: } catch (NoSuchMethodException e) {
1227:
1228: //-- Allow any built-in descriptor classes
1229: //-- that don't have default constructors
1230: //-- such as java.sql.Date, java.sql.Time, etc.
1231: return (CoreDescriptors.getDescriptor(type) != null);
1232: }
1233: }
1234: }
1235: return true;
1236: } //-- isDescriptable
1237:
1238: /**
1239: * Returns true if the given class should be treated as a primitive
1240: * type
1241: * @return true if the given class should be treated as a primitive
1242: * type
1243: **/
1244: private static boolean isPrimitive(Class type) {
1245:
1246: if (type.isPrimitive()) {
1247: return true;
1248: }
1249:
1250: if ((type == Boolean.class) || (type == Character.class)) {
1251: return true;
1252: }
1253:
1254: Class super Class = type.getSuperclass();
1255: if (super Class == Number.class) {
1256: return true;
1257: }
1258:
1259: if (super Class != null) {
1260: return super Class.getName().equals("java.lang.Enum");
1261: } else {
1262: return false;
1263: }
1264:
1265: } //-- isPrimitive
1266:
1267: /**
1268: * Returns an array of collections available during
1269: * introspection. Allows JDK 1.2+ support without
1270: * breaking JDK 1.1 support.
1271: *
1272: * @return a list of available collections
1273: **/
1274: private static Class[] loadCollections() {
1275:
1276: Vector collections = new Vector(6);
1277:
1278: //-- JDK 1.1
1279: collections.addElement(Vector.class);
1280: collections.addElement(Enumeration.class);
1281: collections.addElement(Hashtable.class);
1282:
1283: //-- JDK 1.2+
1284: ClassLoader loader = Vector.class.getClassLoader();
1285:
1286: Class clazz = null;
1287: try {
1288: if (loader != null) {
1289: clazz = loader.loadClass(LIST);
1290: } else
1291: clazz = Class.forName(LIST);
1292: } catch (ClassNotFoundException cnfx) {
1293: //-- just ignore...either JDK 1.1
1294: //-- or some nasty ClassLoader
1295: //-- issue has occurred.
1296: }
1297: if (clazz != null) {
1298: //-- java.util.List found, add to collections,
1299: //-- also add java.util.Map
1300: collections.addElement(clazz);
1301:
1302: clazz = null;
1303: try {
1304: //-- java.util.Map
1305: if (loader != null) {
1306: clazz = loader.loadClass(MAP);
1307: } else
1308: clazz = Class.forName(MAP);
1309: if (clazz != null) {
1310: collections.addElement(clazz);
1311: }
1312: //-- java.util.Set
1313: if (loader != null) {
1314: clazz = loader.loadClass(SET_COLLECTION);
1315: } else
1316: clazz = Class.forName(SET_COLLECTION);
1317: if (clazz != null) {
1318: collections.addElement(clazz);
1319: }
1320:
1321: } catch (ClassNotFoundException cnfx) {
1322: //-- just ignore...for now
1323: //-- some nasty ClassLoader issue has occurred.
1324: }
1325: }
1326:
1327: Class[] classes = new Class[collections.size()];
1328: collections.copyInto(classes);
1329:
1330: return classes;
1331: } //-- loadCollections
1332:
1333: /**
1334: * A special TypeConvertor that simply returns the object
1335: * given. This is used for preventing the FieldHandlerImpl
1336: * from using a CollectionHandler when getValue is called.
1337: */
1338: class IdentityConvertor implements TypeConvertor {
1339: public Object convert(Object object, String param)
1340: throws ClassCastException {
1341: return object;
1342: }
1343: } //-- class: IdentityConvertor
1344:
1345: /**
1346: * A simple struct for holding a set of accessor methods
1347: **/
1348: class MethodSet {
1349:
1350: /**
1351: * A reference to the add method.
1352: **/
1353: Method add = null;
1354:
1355: /**
1356: * A reference to the create method.
1357: **/
1358: Method create = null;
1359:
1360: /**
1361: * A reference to the get method.
1362: **/
1363: Method get = null;
1364:
1365: /**
1366: * A reference to the set method.
1367: **/
1368: Method set = null;
1369:
1370: /**
1371: * The fieldName for the field accessed by the methods in
1372: * this method set.
1373: **/
1374: String fieldName = null;
1375:
1376: MethodSet(String fieldName) {
1377: super ();
1378: this .fieldName = fieldName;
1379: }
1380: } //-- inner class: MethodSet
1381:
1382: } //-- Introspector
1383:
1384: /**
1385: * A simple extension of XMLClassDescriptor
1386: * so that we can set the "instrospected" flag.
1387: **/
1388: class IntrospectedXMLClassDescriptor extends XMLClassDescriptorImpl {
1389: /**
1390: * Creates an IntrospectedXMLClassDescriptor
1391: * @param type the Class type with which this
1392: * ClassDescriptor describes.
1393: **/
1394: IntrospectedXMLClassDescriptor(Class type) {
1395: super (type);
1396: setIntrospected(true);
1397: } //-- XMLClassDescriptorImpl
1398:
1399: /**
1400: * Creates an IntrospectedXMLClassDescriptor
1401: * @param type the Class type with which this
1402: * ClassDescriptor describes.
1403: **/
1404: public IntrospectedXMLClassDescriptor(Class type, String xmlName) {
1405: super (type, xmlName);
1406: setIntrospected(true);
1407: } //-- XMLClassDescriptorImpl
1408:
1409: } //-- IntrospectedClassDescriptor
|