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: * Portions of this file developed by Keith Visco after Jan 19 2005 are
0044: * Copyright (C) 2005 Keith Visco. All Rights Reserverd.
0045: *
0046: * $Id: AbstractMappingLoader.java 6784 2007-01-29 03:29:17Z ekuns $
0047: */
0048: package org.exolab.castor.mapping.loader;
0049:
0050: import java.io.Serializable;
0051: import java.lang.reflect.Array;
0052: import java.lang.reflect.Constructor;
0053: import java.lang.reflect.Field;
0054: import java.lang.reflect.Method;
0055: import java.lang.reflect.Modifier;
0056: import java.util.ArrayList;
0057: import java.util.Iterator;
0058: import java.util.List;
0059: import java.util.Enumeration;
0060:
0061: import org.exolab.castor.mapping.ClassDescriptor;
0062: import org.exolab.castor.mapping.CollectionHandler;
0063: import org.exolab.castor.mapping.ExtendedFieldHandler;
0064: import org.exolab.castor.mapping.FieldDescriptor;
0065: import org.exolab.castor.mapping.FieldHandler;
0066: import org.exolab.castor.mapping.GeneralizedFieldHandler;
0067: import org.exolab.castor.mapping.MapItem;
0068: import org.exolab.castor.mapping.MappingException;
0069: import org.exolab.castor.mapping.handlers.EnumFieldHandler;
0070: import org.exolab.castor.mapping.handlers.TransientFieldHandler;
0071: import org.exolab.castor.mapping.xml.ClassChoice;
0072: import org.exolab.castor.mapping.xml.MappingRoot;
0073: import org.exolab.castor.mapping.xml.ClassMapping;
0074: import org.exolab.castor.mapping.xml.FieldMapping;
0075:
0076: /**
0077: * Assists in the construction of descriptors. Can be used as a mapping
0078: * resolver to the engine. Engines will implement their own mapping
0079: * scheme typically by extending this class.
0080: *
0081: * @author <a href="arkin@intalio.com">Assaf Arkin</a>
0082: * @author <a href="keith AT kvisco DOT com">Keith Visco</a>
0083: * @version $Revision: 6784 $ $Date: 2006-04-10 16:39:24 -0600 (Mon, 10 Apr 2006) $
0084: */
0085: public abstract class AbstractMappingLoader extends
0086: AbstractMappingLoader2 {
0087: //--------------------------------------------------------------------------
0088:
0089: /** The prefix for the "add" method. */
0090: private static final String ADD_METHOD_PREFIX = "add";
0091:
0092: /** The prefix for an enumeration method. */
0093: private static final String ENUM_METHOD_PREFIX = "enum";
0094:
0095: /** The prefix for an enumeration method. */
0096: private static final String ITER_METHOD_PREFIX = "iterate";
0097:
0098: /** The standard prefix for the getter method. */
0099: private static final String GET_METHOD_PREFIX = "get";
0100:
0101: /** The prefix for the "is" method for booleans. */
0102: private static final String IS_METHOD_PREFIX = "is";
0103:
0104: /** The standard prefix for the setter method. */
0105: private static final String SET_METHOD_PREFIX = "set";
0106:
0107: /** The prefix for the "create" method. */
0108: private static final String CREATE_METHOD_PREFIX = "create";
0109:
0110: /** The prefix for the "has" method. */
0111: private static final String HAS_METHOD_PREFIX = "has";
0112:
0113: /** The prefix for the "delete" method. */
0114: private static final String DELETE_METHOD_PREFIX = "delete";
0115:
0116: /** Empty array of class types used for reflection. */
0117: protected static final Class[] EMPTY_ARGS = new Class[0];
0118:
0119: /** The string argument for the valueOf method, used for introspection when searching for
0120: * type-safe enumeration style classes. */
0121: protected static final Class[] STRING_ARG = { String.class };
0122:
0123: /** Factory method name for type-safe enumerations. */
0124: protected static final String VALUE_OF = "valueOf";
0125:
0126: //--------------------------------------------------------------------------
0127:
0128: /**
0129: * Constructs a new mapping helper. This constructor is used by a derived class.
0130: *
0131: * @param loader The class loader to use, null for the default
0132: */
0133: protected AbstractMappingLoader(final ClassLoader loader) {
0134: super (loader);
0135: }
0136:
0137: //--------------------------------------------------------------------------
0138:
0139: /**
0140: * {@inheritDoc}
0141: */
0142: public final String getSourceType() {
0143: return "CastorXmlMapping";
0144: }
0145:
0146: //--------------------------------------------------------------------------
0147:
0148: /**
0149: * Loads the mapping from the specified mapping object if not loaded previously.
0150: *
0151: * @param mapping The mapping information.
0152: * @param param Arbitrary parameter that can be used by subclasses.
0153: * @throws MappingException The mapping file is invalid.
0154: */
0155: public abstract void loadMapping(final MappingRoot mapping,
0156: final Object param) throws MappingException;
0157:
0158: protected final void createClassDescriptors(
0159: final MappingRoot mapping) throws MappingException {
0160: // Load the mapping for all the classes. This is always returned
0161: // in the same order as it appeared in the mapping file.
0162: Enumeration enumeration = mapping.enumerateClassMapping();
0163:
0164: List retryList = new ArrayList();
0165: while (enumeration.hasMoreElements()) {
0166: ClassMapping clsMap = (ClassMapping) enumeration
0167: .nextElement();
0168: try {
0169: ClassDescriptor clsDesc = createClassDescriptor(clsMap);
0170: if (clsDesc != null) {
0171: addDescriptor(clsDesc);
0172: }
0173: } catch (MappingException mx) {
0174: // save for later for possible out-of-order mapping files...
0175: retryList.add(clsMap);
0176: continue;
0177: }
0178: }
0179:
0180: // handle possible retries, for now we only loop once on the retries, but we
0181: // should change this to keep looping until we have no more success rate.
0182: for (Iterator i = retryList.iterator(); i.hasNext();) {
0183: ClassMapping clsMap = (ClassMapping) i.next();
0184: ClassDescriptor clsDesc = createClassDescriptor(clsMap);
0185: if (clsDesc != null) {
0186: addDescriptor(clsDesc);
0187: }
0188: }
0189:
0190: // iterate over all class descriptors and resolve relations between them
0191: for (Iterator i = descriptorIterator(); i.hasNext();) {
0192: resolveRelations((ClassDescriptor) i.next());
0193: }
0194: }
0195:
0196: protected abstract ClassDescriptor createClassDescriptor(
0197: final ClassMapping clsMap) throws MappingException;
0198:
0199: /**
0200: * Gets the ClassDescriptor the given <code>classMapping</code> extends.
0201: *
0202: * @param clsMap The ClassMapping to find the required descriptor for.
0203: * @param javaClass The name of the class that is checked (this is used for
0204: * generating the exception).
0205: * @return The ClassDescriptor the given ClassMapping extends or
0206: * <code>null</code> if the given ClassMapping does not extend
0207: * any.
0208: * @throws MappingException If the given ClassMapping extends another
0209: * ClassMapping but its descriptor could not be found.
0210: */
0211: protected final ClassDescriptor getExtended(
0212: final ClassMapping clsMap, final Class javaClass)
0213: throws MappingException {
0214: if (clsMap.getExtends() == null) {
0215: return null;
0216: }
0217:
0218: ClassMapping mapping = (ClassMapping) clsMap.getExtends();
0219: Class type = resolveType(mapping.getName());
0220: ClassDescriptor result = getDescriptor(type.getName());
0221:
0222: if (result == null) {
0223: throw new MappingException("mapping.extendsMissing",
0224: mapping, javaClass.getName());
0225: }
0226:
0227: if (!result.getJavaClass().isAssignableFrom(javaClass)) {
0228: throw new MappingException("mapping.classDoesNotExtend",
0229: javaClass.getName(), result.getJavaClass()
0230: .getName());
0231: }
0232:
0233: return result;
0234: }
0235:
0236: /**
0237: * Gets the ClassDescriptor the given <code>classMapping</code> depends
0238: * on.
0239: *
0240: * @param clsMap The ClassMapping to find the required ClassDescriptor for.
0241: * @param javaClass The name of the class that is checked (this is used for
0242: * generating the exception).
0243: * @return The ClassDescriptor the given ClassMapping depends on or
0244: * <code>null</code> if the given ClassMapping does not depend on
0245: * any.
0246: * @throws MappingException If the given ClassMapping depends on another
0247: * ClassMapping but its descriptor could not be found.
0248: */
0249: protected final ClassDescriptor getDepended(
0250: final ClassMapping clsMap, final Class javaClass)
0251: throws MappingException {
0252: if (clsMap.getDepends() == null) {
0253: return null;
0254: }
0255:
0256: ClassMapping mapping = (ClassMapping) clsMap.getDepends();
0257: Class type = resolveType(mapping.getName());
0258: ClassDescriptor result = getDescriptor(type.getName());
0259:
0260: if (result == null) {
0261: throw new MappingException("Depends not found: " + mapping
0262: + " " + javaClass.getName());
0263: }
0264:
0265: return result;
0266: }
0267:
0268: /**
0269: * Checks all given fields for name equality and throws a MappingException if at
0270: * least two fields have the same name.
0271: *
0272: * @param fields The fields to be checked.
0273: * @param cls Class that is checked (this is used for generating the exception).
0274: * @throws MappingException If at least two fields have the same name.
0275: */
0276: protected final void checkFieldNameDuplicates(
0277: final FieldDescriptor[] fields, final Class cls)
0278: throws MappingException {
0279: for (int i = 0; i < fields.length - 1; i++) {
0280: String fieldName = fields[i].getFieldName();
0281: for (int j = i + 1; j < fields.length; j++) {
0282: if (fieldName.equals(fields[j].getFieldName())) {
0283: throw new MappingException("The field " + fieldName
0284: + " appears twice in the descriptor for "
0285: + cls.getName());
0286: }
0287: }
0288: }
0289: }
0290:
0291: protected abstract void resolveRelations(
0292: final ClassDescriptor clsDesc);
0293:
0294: //--------------------------------------------------------------------------
0295:
0296: /**
0297: * Returns the Java class for the named type. The type name can be one of the
0298: * accepted short names (e.g. <tt>integer</tt>) or the full Java class name (e.g.
0299: * <tt>java.lang.Integer</tt>). If the short name is used, the primitive type might
0300: * be returned.
0301: */
0302: protected final Class resolveType(final String typeName)
0303: throws MappingException {
0304: try {
0305: return Types.typeFromName(getClassLoader(), typeName);
0306: } catch (ClassNotFoundException ex) {
0307: throw new MappingException("mapping.classNotFound",
0308: typeName);
0309: }
0310: }
0311:
0312: /**
0313: * Create field descriptors. The class mapping information is used to create
0314: * descriptors for all the fields in the class, except for container fields.
0315: * Implementations may extend this method to create more suitable descriptors, or
0316: * create descriptors only for a subset of the fields.
0317: *
0318: * @param clsMap The class to which the fields belong.
0319: * @param javaClass The field mappings.
0320: * @throws MappingException An exception indicating why mapping for the class cannot
0321: * be created.
0322: */
0323: protected final AbstractFieldDescriptor[] createFieldDescriptors(
0324: final ClassMapping clsMap, final Class javaClass)
0325: throws MappingException {
0326: FieldMapping[] fldMap = null;
0327:
0328: if (clsMap.getClassChoice() != null) {
0329: fldMap = clsMap.getClassChoice().getFieldMapping();
0330: }
0331:
0332: if ((fldMap == null) || (fldMap.length == 0)) {
0333: return new AbstractFieldDescriptor[0];
0334: }
0335:
0336: AbstractFieldDescriptor[] fields = new AbstractFieldDescriptor[fldMap.length];
0337: for (int i = 0; i < fldMap.length; i++) {
0338: fields[i] = createFieldDesc(javaClass, fldMap[i]);
0339:
0340: // set identity flag
0341: fields[i].setIdentity(fldMap[i].getIdentity());
0342: }
0343:
0344: return fields;
0345: }
0346:
0347: /**
0348: * Gets the top-most (i.e. without any further 'extends') extends of the given
0349: * <code>classMapping</code>.
0350: *
0351: * @param clsMap The ClassMapping to get the origin for.
0352: * @return The top-most extends of the given ClassMapping or the ClassMapping itself
0353: * if it does not extend any other ClassMapping.
0354: */
0355: protected final ClassMapping getOrigin(final ClassMapping clsMap) {
0356: ClassMapping result = clsMap;
0357:
0358: while (result.getExtends() != null) {
0359: result = (ClassMapping) result.getExtends();
0360: }
0361:
0362: return result;
0363: }
0364:
0365: protected final FieldDescriptor[] divideFieldDescriptors(
0366: final FieldDescriptor[] fields, final String[] ids,
0367: final FieldDescriptor[] identities) {
0368: List fieldList = new ArrayList(fields.length);
0369:
0370: for (int i = 0; i < fields.length; i++) {
0371: FieldDescriptor field = fields[i];
0372: final int index = getIdColumnIndex(field, ids);
0373: if (index == -1) {
0374: // copy non identity field from list of fields.
0375: fieldList.add(field);
0376: } else {
0377: if (field instanceof FieldDescriptorImpl) {
0378: ((FieldDescriptorImpl) field).setRequired(true);
0379: }
0380: if (field.getHandler() instanceof FieldHandlerImpl) {
0381: ((FieldHandlerImpl) field.getHandler())
0382: .setRequired(true);
0383: }
0384:
0385: identities[index] = field;
0386: }
0387: }
0388:
0389: // convert regularFieldList into array
0390: FieldDescriptor[] result = new FieldDescriptor[fieldList.size()];
0391: return (FieldDescriptor[]) fieldList.toArray(result);
0392: }
0393:
0394: /**
0395: * Finds the index in the given <code>idColumnNames</code> that has the same name as
0396: * the given <code>field</code>.
0397: *
0398: * @param field The FieldDescriptor to find the column index for.
0399: * @param ids The id columnNames available.
0400: * @return The index of the id column name that matches the given field's name or
0401: * <code>-1</code> if no such id column name exists.
0402: */
0403: protected int getIdColumnIndex(FieldDescriptor field, String[] ids) {
0404: for (int i = 0; i < ids.length; i++) {
0405: if (field.getFieldName().equals(ids[i])) {
0406: return i;
0407: }
0408: }
0409: return -1;
0410: }
0411:
0412: /**
0413: * Creates a single field descriptor. The field mapping is used to create a new stock
0414: * {@link FieldDescriptor}. Implementations may extend this class to create a more
0415: * suitable descriptor.
0416: *
0417: * @param javaClass The class to which the field belongs.
0418: * @param fieldMap The field mapping information.
0419: * @return The field descriptor.
0420: * @throws MappingException The field or its accessor methods are not
0421: * found, not accessible, not of the specified type, etc.
0422: */
0423: protected AbstractFieldDescriptor createFieldDesc(
0424: final Class javaClass, final FieldMapping fieldMap)
0425: throws MappingException {
0426: String fieldName = fieldMap.getName();
0427:
0428: // If the field type is supplied, grab it and use it to locate the field/accessor.
0429: Class fieldType = null;
0430: if (fieldMap.getType() != null) {
0431: fieldType = resolveType(fieldMap.getType());
0432: }
0433:
0434: // If the field is declared as a collection, grab the collection type as
0435: // well and use it to locate the field/accessor.
0436: CollectionHandler colHandler = null;
0437: if (fieldMap.getCollection() != null) {
0438: String colTypeName = fieldMap.getCollection().toString();
0439: Class colType = CollectionHandlers
0440: .getCollectionType(colTypeName);
0441: colHandler = CollectionHandlers.getHandler(colType);
0442: }
0443:
0444: TypeInfo typeInfo = getTypeInfo(fieldType, colHandler, fieldMap);
0445:
0446: ExtendedFieldHandler exfHandler = null;
0447: FieldHandler handler = null;
0448:
0449: // Check for user supplied FieldHandler
0450: if (fieldMap.getHandler() != null) {
0451: Class handlerClass = null;
0452: handlerClass = resolveType(fieldMap.getHandler());
0453:
0454: if (!FieldHandler.class.isAssignableFrom(handlerClass)) {
0455: String err = "The class '" + fieldMap.getHandler()
0456: + "' must implement "
0457: + FieldHandler.class.getName();
0458: throw new MappingException(err);
0459: }
0460:
0461: // Get default constructor to invoke. We can't use the newInstance method
0462: // unfortunately becaue FieldHandler overloads this method
0463: try {
0464: Constructor constructor = handlerClass
0465: .getConstructor(new Class[0]);
0466: handler = (FieldHandler) constructor
0467: .newInstance(new Object[0]);
0468: } catch (Exception ex) {
0469: String err = "The class '" + handlerClass.getName()
0470: + "' must have a default public constructor.";
0471: throw new MappingException(err);
0472: }
0473:
0474: // ExtendedFieldHandler?
0475: if (handler instanceof ExtendedFieldHandler) {
0476: exfHandler = (ExtendedFieldHandler) handler;
0477: }
0478:
0479: // Fix for CastorJDO from Steve Vaughan, CastorJDO requires FieldHandlerImpl
0480: // or a ClassCastException will be thrown... [KV 20030131 - also make sure
0481: // this new handler doesn't use it's own CollectionHandler otherwise it'll
0482: // cause unwanted calls to the getValue method during unmarshalling]
0483: colHandler = typeInfo.getCollectionHandler();
0484: typeInfo.setCollectionHandler(null);
0485: handler = new FieldHandlerImpl(handler, typeInfo);
0486: typeInfo.setCollectionHandler(colHandler);
0487: // End Castor JDO fix
0488: }
0489:
0490: boolean generalized = (exfHandler instanceof GeneralizedFieldHandler);
0491:
0492: // If generalized we need to change the fieldType to whatever is specified in the
0493: // GeneralizedFieldHandler so that the correct getter/setter methods can be found
0494: if (generalized) {
0495: fieldType = ((GeneralizedFieldHandler) exfHandler)
0496: .getFieldType();
0497: }
0498:
0499: if (generalized || (handler == null)) {
0500: // Create TypeInfoRef to get new TypeInfo from call to createFieldHandler
0501: FieldHandler custom = handler;
0502: TypeInfoReference typeInfoRef = new TypeInfoReference();
0503: typeInfoRef.typeInfo = typeInfo;
0504: handler = createFieldHandler(javaClass, fieldType,
0505: fieldMap, typeInfoRef);
0506: if (custom != null) {
0507: ((GeneralizedFieldHandler) exfHandler)
0508: .setFieldHandler(handler);
0509: handler = custom;
0510: } else {
0511: boolean isTypeSafeEnum = false;
0512: // Check for type-safe enum style classes
0513: if ((fieldType != null) && !isPrimitive(fieldType)) {
0514: if (!hasPublicDefaultConstructor(fieldType)) {
0515: Method method = getStaticValueOfMethod(fieldType);
0516: if (method != null) {
0517: handler = new EnumFieldHandler(fieldType,
0518: handler, method);
0519: typeInfo.setImmutable(true);
0520: isTypeSafeEnum = true;
0521: }
0522: }
0523: }
0524: // Reset proper TypeInfo
0525: if (!isTypeSafeEnum) {
0526: typeInfo = typeInfoRef.typeInfo;
0527: }
0528: }
0529: }
0530:
0531: FieldDescriptorImpl fieldDesc = new FieldDescriptorImpl(
0532: fieldName, typeInfo, handler, fieldMap.getTransient());
0533:
0534: fieldDesc.setRequired(fieldMap.getRequired());
0535:
0536: // If we're using an ExtendedFieldHandler we need to set the FieldDescriptor
0537: if (exfHandler != null) {
0538: ((FieldHandlerFriend) exfHandler)
0539: .setFieldDescriptor(fieldDesc);
0540: }
0541:
0542: return fieldDesc;
0543: }
0544:
0545: /**
0546: * Does the given class has a public default constructor?
0547: *
0548: * @param type Class to check for a public default constructor.
0549: * @return <code>true</code> if class has a public default constructor.
0550: */
0551: private boolean hasPublicDefaultConstructor(final Class type) {
0552: try {
0553: Constructor cons = type.getConstructor(EMPTY_ARGS);
0554: return Modifier.isPublic(cons.getModifiers());
0555: } catch (NoSuchMethodException ex) {
0556: return false;
0557: }
0558: }
0559:
0560: /**
0561: * Get static valueOf(String) factory method of given class.
0562: *
0563: * @param type Class to check for a static valueOf(String) factory method.
0564: * @return Static valueOf(String) factory method or <code>null</code> if none could
0565: * be found.
0566: */
0567: private Method getStaticValueOfMethod(final Class type) {
0568: try {
0569: Method method = type.getMethod(VALUE_OF, STRING_ARG);
0570: Class returnType = method.getReturnType();
0571: if (returnType == null) {
0572: return null;
0573: }
0574: if (!type.isAssignableFrom(returnType)) {
0575: return null;
0576: }
0577: if (!Modifier.isStatic(method.getModifiers())) {
0578: return null;
0579: }
0580: return method;
0581: } catch (NoSuchMethodException ex) {
0582: return null;
0583: }
0584: }
0585:
0586: /**
0587: * Creates the FieldHandler for the given FieldMapping.
0588: *
0589: * @param javaClass the class type of the parent of the field.
0590: * @param fldType the Java class type for the field.
0591: * @param fldMap the field mapping.
0592: * @return the newly created FieldHandler.
0593: */
0594: protected final FieldHandler createFieldHandler(Class javaClass,
0595: Class fldType, final FieldMapping fldMap,
0596: final TypeInfoReference typeInfoRef)
0597: throws MappingException {
0598: // Prevent introspection of transient fields
0599: if (fldMap.getTransient()) {
0600: return new TransientFieldHandler();
0601: }
0602:
0603: Class colType = null;
0604: CollectionHandler colHandler = null;
0605: boolean colRequireGetSet = true;
0606:
0607: String fieldName = fldMap.getName();
0608:
0609: // If the field is declared as a collection, grab the collection type as
0610: // well and use it to locate the field/accessor.
0611: if (fldMap.getCollection() != null) {
0612: String colTypeName = fldMap.getCollection().toString();
0613: colType = CollectionHandlers.getCollectionType(colTypeName);
0614: colHandler = CollectionHandlers.getHandler(colType);
0615: colRequireGetSet = CollectionHandlers
0616: .isGetSetCollection(colType);
0617: if (colType == Object[].class) {
0618: if (fldType == null) {
0619: String msg = "'type' is a required attribute for field that are "
0620: + "array collections: " + fieldName;
0621: throw new MappingException(msg);
0622: }
0623: Object obj = Array.newInstance(fldType, 0);
0624: colType = obj.getClass();
0625: }
0626: }
0627:
0628: FieldHandlerImpl handler = null;
0629:
0630: // If get/set methods not specified, use field names to determine them.
0631: if (fldMap.getDirect()) {
0632: // No accessor, map field directly.
0633: Field field = findField(javaClass, fieldName,
0634: (colType == null ? fldType : colType));
0635: if (field == null) {
0636: throw new MappingException(
0637: "mapping.fieldNotAccessible", fieldName,
0638: javaClass.getName());
0639: }
0640: if (fldType == null) {
0641: fldType = field.getType();
0642: }
0643:
0644: typeInfoRef.typeInfo = getTypeInfo(fldType, colHandler,
0645: fldMap);
0646:
0647: handler = new FieldHandlerImpl(field, typeInfoRef.typeInfo);
0648: } else if ((fldMap.getGetMethod() == null)
0649: && (fldMap.getSetMethod() == null)) {
0650: // If both methods (get/set) are not specified, determine them automatically
0651: if (fieldName == null) {
0652: throw new MappingException("mapping.missingFieldName",
0653: javaClass.getName());
0654: }
0655:
0656: List getSequence = new ArrayList();
0657: List setSequence = new ArrayList();
0658: Method getMethod = null;
0659: Method setMethod = null;
0660:
0661: // Get method normally starts with "get", but may start with "is"
0662: // if it's a boolean.
0663: try {
0664: // Handle nested fields
0665: while (true) {
0666: int point = fieldName.indexOf('.');
0667: if (point < 0) {
0668: break;
0669: }
0670:
0671: String parentField = fieldName.substring(0, point);
0672:
0673: // Getter method for parent field
0674: String methodName = GET_METHOD_PREFIX
0675: + capitalize(parentField);
0676: Method method = javaClass.getMethod(methodName,
0677: (Class[]) null);
0678: if (isAbstractOrStatic(method)) {
0679: throw new MappingException(
0680: "mapping.accessorNotAccessible",
0681: methodName, javaClass.getName());
0682: }
0683: getSequence.add(method);
0684:
0685: Class nextClass = method.getReturnType();
0686:
0687: // Setter method for parent field
0688: try {
0689: methodName = SET_METHOD_PREFIX
0690: + capitalize(parentField);
0691: Class[] types = new Class[] { nextClass };
0692: method = javaClass.getMethod(methodName, types);
0693: if (isAbstractOrStatic(method)) {
0694: method = null;
0695: }
0696: } catch (Exception ex) {
0697: method = null;
0698: }
0699: setSequence.add(method);
0700:
0701: javaClass = nextClass;
0702: fieldName = fieldName.substring(point + 1);
0703: }
0704:
0705: // Find getter method for actual field
0706: String methodName = GET_METHOD_PREFIX
0707: + capitalize(fieldName);
0708: Class returnType = (colType == null) ? fldType
0709: : colType;
0710: getMethod = findAccessor(javaClass, methodName,
0711: returnType, true);
0712:
0713: // If getMethod is null, check for boolean type method prefix
0714: if (getMethod == null) {
0715: if ((fldType == Boolean.class)
0716: || (fldType == Boolean.TYPE)) {
0717: methodName = IS_METHOD_PREFIX
0718: + capitalize(fieldName);
0719: getMethod = findAccessor(javaClass, methodName,
0720: returnType, true);
0721: }
0722: }
0723: } catch (MappingException ex) {
0724: throw ex;
0725: } catch (Exception ex) {
0726: // LOG.warn("Unexpected exception", ex);
0727: }
0728:
0729: if (getMethod == null) {
0730: String getAccessor = GET_METHOD_PREFIX
0731: + capitalize(fieldName);
0732: String isAccessor = IS_METHOD_PREFIX
0733: + capitalize(fieldName);
0734: throw new MappingException("mapping.accessorNotFound",
0735: getAccessor + "/" + isAccessor,
0736: (colType == null ? fldType : colType),
0737: javaClass.getName());
0738: }
0739:
0740: if ((fldType == null) && (colType == null)) {
0741: fldType = getMethod.getReturnType();
0742: }
0743:
0744: // We try to locate a set method anyway but complain only if we need one
0745: String methodName = SET_METHOD_PREFIX
0746: + capitalize(fieldName);
0747: setMethod = findAccessor(javaClass, methodName,
0748: (colType == null ? fldType : colType), false);
0749:
0750: // If we have a collection that need both set and get but we don't have a
0751: // set method, we fail
0752: if ((setMethod == null) && (colType != null)
0753: && colRequireGetSet) {
0754: throw new MappingException("mapping.accessorNotFound",
0755: methodName, (colType == null ? fldType
0756: : colType), javaClass.getName());
0757: }
0758:
0759: typeInfoRef.typeInfo = getTypeInfo(fldType, colHandler,
0760: fldMap);
0761:
0762: fieldName = fldMap.getName();
0763: if (fieldName == null) {
0764: if (getMethod == null) {
0765: fieldName = setMethod.getName();
0766: } else {
0767: fieldName = getMethod.getName();
0768: }
0769: }
0770:
0771: // Convert method call sequence for nested fields to arrays
0772: Method[] getArray = null;
0773: Method[] setArray = null;
0774: if (getSequence.size() > 0) {
0775: getArray = new Method[getSequence.size()];
0776: getArray = (Method[]) getSequence.toArray(getArray);
0777: setArray = new Method[setSequence.size()];
0778: setArray = (Method[]) setSequence.toArray(setArray);
0779: }
0780:
0781: // Create handler
0782: handler = new FieldHandlerImpl(fieldName, getArray,
0783: setArray, getMethod, setMethod,
0784: typeInfoRef.typeInfo);
0785:
0786: if (setMethod != null) {
0787: if (setMethod.getName().startsWith(ADD_METHOD_PREFIX)) {
0788: handler.setAddMethod(setMethod);
0789: }
0790: }
0791: } else {
0792: Method getMethod = null;
0793: Method setMethod = null;
0794:
0795: // First look up the get accessors
0796: if (fldMap.getGetMethod() != null) {
0797: Class rtype = fldType;
0798: if (colType != null) {
0799: String methodName = fldMap.getGetMethod();
0800: if (methodName.startsWith(ENUM_METHOD_PREFIX)) {
0801: // An enumeration method must really return a enumeration.
0802: rtype = Enumeration.class;
0803: } else if (methodName
0804: .startsWith(ITER_METHOD_PREFIX)) {
0805: // An iterator method must really return a iterator.
0806: rtype = Iterator.class;
0807: } else {
0808: rtype = colType;
0809: }
0810: }
0811:
0812: getMethod = findAccessor(javaClass, fldMap
0813: .getGetMethod(), rtype, true);
0814: if (getMethod == null) {
0815: throw new MappingException(
0816: "mapping.accessorNotFound", fldMap
0817: .getGetMethod(), rtype, javaClass
0818: .getName());
0819: }
0820:
0821: if ((fldType == null) && (colType == null)) {
0822: fldType = getMethod.getReturnType();
0823: }
0824: }
0825:
0826: // Second look up the set/add accessor
0827: if (fldMap.getSetMethod() != null) {
0828: String methodName = fldMap.getSetMethod();
0829: Class type = fldType;
0830: if (colType != null) {
0831: if (!methodName.startsWith(ADD_METHOD_PREFIX)) {
0832: type = colType;
0833: }
0834: }
0835:
0836: // Set via constructor?
0837: if (methodName.startsWith("%")) {
0838: // Validate index value
0839: int index = 0;
0840:
0841: String temp = methodName.substring(1);
0842: try {
0843: index = Integer.parseInt(temp);
0844: } catch (NumberFormatException ex) {
0845: throw new MappingException(
0846: "mapping.invalidParameterIndex", temp);
0847: }
0848:
0849: if ((index < 1) || (index > 9)) {
0850: throw new MappingException(
0851: "mapping.invalidParameterIndex", temp);
0852: }
0853: } else {
0854: setMethod = findAccessor(javaClass, methodName,
0855: type, false);
0856: if (setMethod == null) {
0857: throw new MappingException(
0858: "mapping.accessorNotFound", methodName,
0859: type, javaClass.getName());
0860: }
0861:
0862: if (fldType == null) {
0863: fldType = setMethod.getParameterTypes()[0];
0864: }
0865: }
0866: }
0867:
0868: typeInfoRef.typeInfo = getTypeInfo(fldType, colHandler,
0869: fldMap);
0870:
0871: fieldName = fldMap.getName();
0872: if (fieldName == null) {
0873: if (getMethod == null) {
0874: fieldName = setMethod.getName();
0875: } else {
0876: fieldName = getMethod.getName();
0877: }
0878: }
0879:
0880: // Create handler
0881: handler = new FieldHandlerImpl(fieldName, null, null,
0882: getMethod, setMethod, typeInfoRef.typeInfo);
0883:
0884: if (setMethod != null) {
0885: if (setMethod.getName().startsWith(ADD_METHOD_PREFIX)) {
0886: handler.setAddMethod(setMethod);
0887: }
0888: }
0889: }
0890:
0891: // If there is a create method, add it to the field handler
0892: String methodName = fldMap.getCreateMethod();
0893: if (methodName != null) {
0894: try {
0895: Method method = javaClass.getMethod(methodName,
0896: (Class[]) null);
0897: handler.setCreateMethod(method);
0898: } catch (Exception ex) {
0899: throw new MappingException(
0900: "mapping.createMethodNotFound", methodName,
0901: javaClass.getName());
0902: }
0903: } else if ((fieldName != null) && !Types.isSimpleType(fldType)) {
0904: try {
0905: methodName = CREATE_METHOD_PREFIX
0906: + capitalize(fieldName);
0907: Method method = javaClass.getMethod(methodName,
0908: (Class[]) null);
0909: handler.setCreateMethod(method);
0910: } catch (Exception ex) {
0911: // LOG.warn ("Unexpected exception", ex);
0912: }
0913: }
0914:
0915: // If there is an has/delete method, add them to field handler
0916: if (fieldName != null) {
0917: try {
0918: methodName = fldMap.getHasMethod();
0919: if (methodName == null) {
0920: methodName = HAS_METHOD_PREFIX
0921: + capitalize(fieldName);
0922: }
0923: Method hasMethod = javaClass.getMethod(methodName,
0924: (Class[]) null);
0925:
0926: if ((hasMethod.getModifiers() & Modifier.STATIC) != 0) {
0927: hasMethod = null;
0928: }
0929:
0930: Method deleteMethod = null;
0931: try {
0932: methodName = DELETE_METHOD_PREFIX
0933: + capitalize(fieldName);
0934: deleteMethod = javaClass.getMethod(methodName,
0935: (Class[]) null);
0936: if ((deleteMethod.getModifiers() & Modifier.STATIC) != 0) {
0937: deleteMethod = null;
0938: }
0939: } catch (Exception ex) {
0940: // Purposely Ignore exception we're just seeing if the method exists
0941: }
0942:
0943: handler.setHasDeleteMethod(hasMethod, deleteMethod);
0944: } catch (Exception ex) {
0945: // LOG.warn("Unexpected exception", ex);
0946: }
0947: }
0948:
0949: return handler;
0950: }
0951:
0952: private static boolean isAbstract(final Class cls) {
0953: return ((cls.getModifiers() & Modifier.ABSTRACT) != 0);
0954: }
0955:
0956: private static boolean isAbstractOrStatic(final Method method) {
0957: return ((method.getModifiers() & Modifier.ABSTRACT) != 0)
0958: || ((method.getModifiers() & Modifier.STATIC) != 0);
0959: }
0960:
0961: protected TypeInfo getTypeInfo(final Class fieldType,
0962: final CollectionHandler colHandler,
0963: final FieldMapping fieldMap) throws MappingException {
0964: return new TypeInfo(Types.typeFromPrimitive(fieldType), null,
0965: null, null, fieldMap.getRequired(), null, colHandler,
0966: false);
0967: }
0968:
0969: /**
0970: * Returns the named field. Uses reflection to return the named field and check the
0971: * field type, if specified.
0972: *
0973: * @param javaClass The class to which the field belongs.
0974: * @param fieldName The name of the field.
0975: * @param fieldType The type of the field if known, or null.
0976: * @return The field, null if not found.
0977: * @throws MappingException The field is not accessible or is not of the
0978: * specified type.
0979: */
0980: private final Field findField(final Class javaClass,
0981: final String fieldName, Class fieldType)
0982: throws MappingException {
0983: try {
0984: // Look up the field based on its name, make sure it's only modifier
0985: // is public. If a type was specified, match the field type.
0986: Field field = javaClass.getField(fieldName);
0987: if ((field.getModifiers() != Modifier.PUBLIC)
0988: && (field.getModifiers() != (Modifier.PUBLIC | Modifier.VOLATILE))) {
0989: throw new MappingException(
0990: "mapping.fieldNotAccessible", fieldName,
0991: javaClass.getName());
0992: }
0993:
0994: if (fieldType == null) {
0995: fieldType = Types.typeFromPrimitive(field.getType());
0996: } else {
0997: Class ft1 = Types.typeFromPrimitive(fieldType);
0998: Class ft2 = Types.typeFromPrimitive(field.getType());
0999: if ((ft1 != ft2) && (fieldType != Serializable.class)) {
1000: throw new MappingException(
1001: "mapping.fieldTypeMismatch", field,
1002: fieldType.getName());
1003: }
1004: }
1005: return field;
1006: } catch (NoSuchFieldException ex) {
1007: return null;
1008: } catch (SecurityException ex) {
1009: return null;
1010: }
1011: }
1012:
1013: /**
1014: * Returns the named accessor. Uses reflection to return the named accessor and
1015: * check the return value or parameter type, if specified.
1016: *
1017: * @param javaClass The class to which the field belongs.
1018: * @param methodName The name of the accessor method.
1019: * @param fieldType The type of the field if known, or null.
1020: * @param getMethod True if get method, false if set method.
1021: * @return The method, null if not found.
1022: * @throws MappingException The method is not accessible or is not of the
1023: * specified type.
1024: */
1025: public static final Method findAccessor(final Class javaClass,
1026: final String methodName, Class fieldType,
1027: final boolean getMethod) throws MappingException {
1028: try {
1029: Method method = null;
1030:
1031: if (getMethod) {
1032: // Get method: look for the named method or prepend get to the method
1033: // name. Look up the field and potentially check the return type.
1034: method = javaClass.getMethod(methodName, new Class[0]);
1035:
1036: // The MapItem is used to handle the contents of maps. Since the MapItem
1037: // has to use Object for its methods we cannot (but also don't have to)
1038: // check for correct types.
1039: if (javaClass == MapItem.class) {
1040: if (methodName.equals("getKey")) {
1041: return method;
1042: }
1043: if (methodName.equals("getValue")) {
1044: return method;
1045: }
1046: }
1047:
1048: if (fieldType == null) {
1049: fieldType = Types.typeFromPrimitive(method
1050: .getReturnType());
1051: } else {
1052: fieldType = Types.typeFromPrimitive(fieldType);
1053: Class returnType = Types.typeFromPrimitive(method
1054: .getReturnType());
1055:
1056: //-- First check against whether the declared type is
1057: //-- an interface or abstract class. We also check
1058: //-- type as Serializable for CMP 1.1 compatibility.
1059: if (fieldType.isInterface()
1060: || ((fieldType.getModifiers() & Modifier.ABSTRACT) != 0)
1061: || (fieldType == java.io.Serializable.class)) {
1062:
1063: if (!fieldType.isAssignableFrom(returnType)) {
1064: throw new MappingException(
1065: "mapping.accessorReturnTypeMismatch",
1066: method, fieldType.getName());
1067: }
1068: } else {
1069: if (!returnType.isAssignableFrom(fieldType)) {
1070: throw new MappingException(
1071: "mapping.accessorReturnTypeMismatch",
1072: method, fieldType.getName());
1073: }
1074: }
1075: }
1076: } else {
1077: // Set method: look for the named method or prepend set to the method
1078: // name. If the field type is know, look up a suitable method. If the
1079: // field type is unknown, lookup the first method with that name and
1080: // one parameter.
1081: Class fieldTypePrimitive = null;
1082: if (fieldType != null) {
1083: fieldTypePrimitive = Types
1084: .typeFromPrimitive(fieldType);
1085: try {
1086: method = javaClass.getMethod(methodName,
1087: new Class[] { fieldType });
1088: } catch (Exception ex) {
1089: try {
1090: method = javaClass.getMethod(methodName,
1091: new Class[] { fieldTypePrimitive });
1092: } catch (Exception ex2) {
1093: // LOG.warn("Unexpected exception", ex2);
1094: }
1095: }
1096: }
1097:
1098: if (method == null) {
1099: Method[] methods = javaClass.getMethods();
1100: for (int i = 0; i < methods.length; ++i) {
1101: if (methods[i].getName().equals(methodName)) {
1102: Class[] paramTypes = methods[i]
1103: .getParameterTypes();
1104: if (paramTypes.length != 1) {
1105: continue;
1106: }
1107:
1108: Class paramType = Types
1109: .typeFromPrimitive(paramTypes[0]);
1110:
1111: if (fieldType == null) {
1112: method = methods[i];
1113: break;
1114: } else if (paramType
1115: .isAssignableFrom(fieldTypePrimitive)) {
1116: method = methods[i];
1117: break;
1118: } else if (fieldType.isInterface()
1119: || isAbstract(fieldType)) {
1120: if (fieldTypePrimitive
1121: .isAssignableFrom(paramType)) {
1122: method = methods[i];
1123: break;
1124: }
1125: }
1126: }
1127: }
1128:
1129: if (method == null) {
1130: return null;
1131: }
1132: }
1133: }
1134:
1135: // Make sure method is public and not static.
1136: // (note: Class.getMethod() returns only public methods).
1137: if ((method.getModifiers() & Modifier.STATIC) != 0) {
1138: throw new MappingException(
1139: "mapping.accessorNotAccessible", methodName,
1140: javaClass.getName());
1141: }
1142: return method;
1143: } catch (MappingException ex) {
1144: throw ex;
1145: } catch (Exception ex) {
1146: return null;
1147: }
1148: }
1149:
1150: private static final String capitalize(final String name) {
1151: char first = name.charAt(0);
1152: if (Character.isUpperCase(first)) {
1153: return name;
1154: }
1155: return Character.toUpperCase(first) + name.substring(1);
1156: }
1157:
1158: /**
1159: * Returns a list of column names that are part of the identity.
1160: *
1161: * @param ids Known identity names.
1162: * @param clsMap Class mapping.
1163: * @return List of identity column names.
1164: */
1165: public static final String[] getIdentityColumnNames(
1166: final String[] ids, final ClassMapping clsMap) {
1167:
1168: String[] idNames = ids;
1169:
1170: if ((ids == null) || (ids.length == 0)) {
1171: ClassChoice classChoice = clsMap.getClassChoice();
1172: if (classChoice == null) {
1173: classChoice = new ClassChoice();
1174: }
1175:
1176: FieldMapping[] fieldMappings = classChoice
1177: .getFieldMapping();
1178:
1179: List idNamesList = new ArrayList();
1180: for (int i = 0; i < fieldMappings.length; i++) {
1181: if (fieldMappings[i].getIdentity() == true) {
1182: idNamesList.add(fieldMappings[i].getName());
1183: }
1184: }
1185:
1186: if (idNamesList.size() > 0) {
1187: idNames = new String[idNamesList.size()];
1188: idNames = (String[]) idNamesList.toArray(idNames);
1189: }
1190: }
1191:
1192: return idNames;
1193: }
1194:
1195: /**
1196: * Returns true if the given class should be treated as a primitive
1197: * type
1198: * @return true if the given class should be treated as a primitive
1199: * type
1200: */
1201: protected static final boolean isPrimitive(final Class type) {
1202: if (type.isPrimitive()) {
1203: return true;
1204: }
1205: if ((type == Boolean.class) || (type == Character.class)) {
1206: return true;
1207: }
1208: return (type.getSuperclass() == Number.class);
1209: }
1210:
1211: /**
1212: * A class used to by the createFieldHandler method in order to
1213: * save the reference of the TypeInfo that was used.
1214: */
1215: public class TypeInfoReference {
1216: public TypeInfo typeInfo = null;
1217: }
1218:
1219: }
|