/* * 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. * * Portions of this file developed by Keith Visco after Jan 19 2005 are * Copyright (C) 2005 Keith Visco. All Rights Reserverd. * * $Id$ */ package org.exolab.castor.mapping.loader; import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Enumeration; import java.util.Map; import java.util.Properties; import org.castor.xml.InternalContext; import org.castor.xml.AbstractInternalContext; import org.castor.core.util.Messages; import org.exolab.castor.mapping.ClassDescriptor; import org.exolab.castor.mapping.CollectionHandler; import org.exolab.castor.mapping.ConfigurableFieldHandler; import org.exolab.castor.mapping.ExtendedFieldHandler; import org.exolab.castor.mapping.FieldDescriptor; import org.exolab.castor.mapping.FieldHandler; import org.exolab.castor.mapping.GeneralizedFieldHandler; import org.exolab.castor.mapping.MapItem; import org.exolab.castor.mapping.MappingException; import org.exolab.castor.mapping.ValidityException; import org.exolab.castor.mapping.handlers.EnumFieldHandler; import org.exolab.castor.mapping.handlers.TransientFieldHandler; import org.exolab.castor.mapping.xml.ClassChoice; import org.exolab.castor.mapping.xml.FieldHandlerDef; import org.exolab.castor.mapping.xml.MappingRoot; import org.exolab.castor.mapping.xml.ClassMapping; import org.exolab.castor.mapping.xml.FieldMapping; import org.exolab.castor.mapping.xml.Param; /** * Assists in the construction of descriptors. Can be used as a mapping * resolver to the engine. Engines will implement their own mapping * scheme typically by extending this class. * * @author <a href="arkin@intalio.com">Assaf Arkin</a> * @author <a href="keith AT kvisco DOT com">Keith Visco</a> * @version $Revision$ $Date: 2006-04-10 16:39:24 -0600 (Mon, 10 Apr 2006) $ */ public abstract class AbstractMappingLoader extends AbstractMappingLoader2 { /** The prefix for the "add" method. */ private static final String ADD_METHOD_PREFIX = "add"; /** The prefix for an enumeration method. */ private static final String ENUM_METHOD_PREFIX = "enum"; /** The prefix for an enumeration method. */ private static final String ITER_METHOD_PREFIX = "iterate"; /** The standard prefix for the getter method. */ private static final String GET_METHOD_PREFIX = "get"; /** The prefix for the "is" method for booleans. */ private static final String IS_METHOD_PREFIX = "is"; /** The standard prefix for the setter method. */ private static final String SET_METHOD_PREFIX = "set"; /** The prefix for the "create" method. */ private static final String CREATE_METHOD_PREFIX = "create"; /** The prefix for the "has" method. */ private static final String HAS_METHOD_PREFIX = "has"; /** The prefix for the "delete" method. */ private static final String DELETE_METHOD_PREFIX = "delete"; /** Empty array of class types used for reflection. */ protected static final Class<?>[] EMPTY_ARGS = new Class[0]; /** The string argument for the valueOf method, used for introspection when searching for * type-safe enumeration style classes. */ protected static final Class<?>[] STRING_ARG = {String.class}; /** Factory method name for type-safe enumerations. */ protected static final String VALUE_OF = "valueOf"; /** Method name to get string value of a type-safe enumerations. */ protected static final String NAME = "name"; /** * The {@link AbstractInternalContext} is the centerpiece providing runtime configuration * and state information. */ private InternalContext _internalContext; /** Map of field handlers associated by their name. */ private final Map<String, FieldHandler> _fieldHandlers = new HashMap<String, FieldHandler>(); /** * Constructs a new mapping helper. This constructor is used by a derived class. * * @param loader The class loader to use, null for the default */ protected AbstractMappingLoader(final ClassLoader loader) { super(loader); } /** * {@inheritDoc} */ public final String getSourceType() { return "CastorXmlMapping"; } /** * Loads the mapping from the specified mapping object if not loaded previously. * * @param mapping The mapping information. * @param param Arbitrary parameter that can be used by subclasses. * @throws MappingException The mapping file is invalid. */ public abstract void loadMapping(final MappingRoot mapping, final Object param) throws MappingException; /** * Load field handler definitions, check for duplicate definitions and * instantiate the appropriate FieldHandler implementations. * * @param mapping Mapping to load field handler definitions from. * @throws MappingException If mapping contains more then one field handler * definition with same name. */ protected void createFieldHandlers(final MappingRoot mapping) throws MappingException { Enumeration<? extends FieldHandlerDef> enumeration = mapping.enumerateFieldHandlerDef(); while (enumeration.hasMoreElements()) { FieldHandlerDef def = enumeration.nextElement(); String name = def.getName(); if (_fieldHandlers.containsKey(name)) { throw new MappingException(Messages.format("mapping.dupFieldHandler", name)); } Class<?> clazz = resolveType(def.getClazz()); FieldHandler fieldHandler = null; try { if (!FieldHandler.class.isAssignableFrom(clazz)) { throw new MappingException(Messages.format("mapping.classNotFieldHandler", name, def.getClazz())); } fieldHandler = (FieldHandler) clazz.newInstance(); _fieldHandlers.put(name, fieldHandler); } catch (InstantiationException e) { throw new MappingException(e); } catch (IllegalAccessException e) { throw new MappingException(e); } // Add configuration data, if there is any configureFieldHandler(def, fieldHandler); } } /* * Checks if the field handler is configurable, and adds the configuration * data to the field handler if this is the case. */ private void configureFieldHandler(final FieldHandlerDef def, final FieldHandler fieldHandler) throws MappingException { // Gather the configuration data (parameters). Properties params = new Properties(); Enumeration<? extends Param> enumerateParam = def.enumerateParam(); while (enumerateParam.hasMoreElements()) { Param par = enumerateParam.nextElement(); params.put(par.getName(), par.getValue()); } // If there is configuration data, make sure that the field handler class // supports it. if (params.size() > 0) { if (!ConfigurableFieldHandler.class.isAssignableFrom(fieldHandler.getClass())) { throw new MappingException(Messages.format("mapping.classNotConfigurableFieldHandler", def.getName(), def.getClazz())); } // Pass the configuration data to the field handler. try { ((ConfigurableFieldHandler)fieldHandler).setConfiguration(params); } catch (ValidityException e) { throw new MappingException(Messages.format("mapping.invalidFieldHandlerConfig", def.getName(), e.getMessage()), e); } } } protected final void createClassDescriptors(final MappingRoot mapping) throws MappingException { // Load the mapping for all the classes. This is always returned // in the same order as it appeared in the mapping file. Enumeration<? extends ClassMapping> enumeration = mapping.enumerateClassMapping(); List<ClassMapping> retryList = new ArrayList<ClassMapping>(); while (enumeration.hasMoreElements()) { ClassMapping clsMap = enumeration.nextElement(); try { ClassDescriptor clsDesc = createClassDescriptor(clsMap); if (clsDesc != null) { addDescriptor(clsDesc); } } catch (MappingException mx) { // save for later for possible out-of-order mapping files... retryList.add(clsMap); continue; } } // handle possible retries, for now we only loop once on the retries, but we // should change this to keep looping until we have no more success rate. for (ClassMapping classMapping : retryList) { ClassDescriptor clsDesc = createClassDescriptor(classMapping); if (clsDesc != null) { addDescriptor(clsDesc); } } // iterate over all class descriptors and resolve relations between them for (ClassDescriptor classDescriptor : getDescriptors()) { resolveRelations(classDescriptor); } } protected abstract ClassDescriptor createClassDescriptor(final ClassMapping clsMap) throws MappingException; /** * Gets the ClassDescriptor the given <code>classMapping</code> extends. * * @param clsMap The ClassMapping to find the required descriptor for. * @param javaClass The name of the class that is checked (this is used for * generating the exception). * @return The ClassDescriptor the given ClassMapping extends or * <code>null</code> if the given ClassMapping does not extend * any. * @throws MappingException If the given ClassMapping extends another * ClassMapping but its descriptor could not be found. */ protected final ClassDescriptor getExtended(final ClassMapping clsMap, final Class<?> javaClass) throws MappingException { if (clsMap.getExtends() == null) { return null; } ClassMapping mapping = (ClassMapping) clsMap.getExtends(); Class<?> type = resolveType(mapping.getName()); ClassDescriptor result = getDescriptor(type.getName()); if (result == null) { throw new MappingException( "mapping.extendsMissing", mapping, javaClass.getName()); } if (!result.getJavaClass().isAssignableFrom(javaClass)) { throw new MappingException("mapping.classDoesNotExtend", javaClass.getName(), result.getJavaClass().getName()); } return result; } /** * Gets the ClassDescriptor the given <code>classMapping</code> depends * on. * * @param clsMap The ClassMapping to find the required ClassDescriptor for. * @param javaClass The name of the class that is checked (this is used for * generating the exception). * @return The ClassDescriptor the given ClassMapping depends on or * <code>null</code> if the given ClassMapping does not depend on * any. * @throws MappingException If the given ClassMapping depends on another * ClassMapping but its descriptor could not be found. */ protected final ClassDescriptor getDepended(final ClassMapping clsMap, final Class<?> javaClass) throws MappingException { if (clsMap.getDepends() == null) { return null; } ClassMapping mapping = (ClassMapping) clsMap.getDepends(); Class<?> type = resolveType(mapping.getName()); ClassDescriptor result = getDescriptor(type.getName()); if (result == null) { throw new MappingException( "Depends not found: " + mapping + " " + javaClass.getName()); } return result; } /** * Checks all given fields for name equality and throws a MappingException if at * least two fields have the same name. * * @param fields The fields to be checked. * @param cls Class that is checked (this is used for generating the exception). * @throws MappingException If at least two fields have the same name. */ protected final void checkFieldNameDuplicates(final FieldDescriptor[] fields, final Class<?> cls) throws MappingException { for (int i = 0; i < fields.length - 1; i++) { String fieldName = fields[i].getFieldName(); for (int j = i + 1; j < fields.length; j++) { if (fieldName.equals(fields[j].getFieldName())) { throw new MappingException("The field " + fieldName + " appears twice in the descriptor for " + cls.getName()); } } } } protected abstract void resolveRelations(final ClassDescriptor clsDesc); //-------------------------------------------------------------------------- /** * Returns the Java class for the named type. The type name can be one of the * accepted short names (e.g. <tt>integer</tt>) or the full Java class name (e.g. * <tt>java.lang.Integer</tt>). If the short name is used, the primitive type might * be returned. */ protected final Class<?> resolveType(final String typeName) throws MappingException { try { return Types.typeFromName(getClassLoader(), typeName); } catch (ClassNotFoundException ex) { throw new MappingException("mapping.classNotFound", typeName); } } /** * Create field descriptors. The class mapping information is used to create * descriptors for all the fields in the class, except for container fields. * Implementations may extend this method to create more suitable descriptors, or * create descriptors only for a subset of the fields. * * @param clsMap The class to which the fields belong. * @param javaClass The field mappings. * @throws MappingException An exception indicating why mapping for the class cannot * be created. */ protected final FieldDescriptorImpl[] createFieldDescriptors(final ClassMapping clsMap, final Class<?> javaClass) throws MappingException { FieldMapping[] fldMap = null; if (clsMap.getClassChoice() != null) { fldMap = clsMap.getClassChoice().getFieldMapping(); } if ((fldMap == null) || (fldMap.length == 0)) { return new FieldDescriptorImpl[0]; } FieldDescriptorImpl[] fields = new FieldDescriptorImpl[fldMap.length]; for (int i = 0; i < fldMap.length; i++) { fields[i] = createFieldDesc(javaClass, fldMap[i]); // set identity flag fields[i].setIdentity(fldMap[i].getIdentity()); } return fields; } /** * Gets the top-most (i.e. without any further 'extends') extends of the given * <code>classMapping</code>. * * @param clsMap The ClassMapping to get the origin for. * @return The top-most extends of the given ClassMapping or the ClassMapping itself * if it does not extend any other ClassMapping. */ protected final ClassMapping getOrigin(final ClassMapping clsMap) { ClassMapping result = clsMap; while (result.getExtends() != null) { result = (ClassMapping) result.getExtends(); } return result; } protected final FieldDescriptor[] divideFieldDescriptors(final FieldDescriptor[] fields, final String[] ids, final FieldDescriptor[] identities) { List<FieldDescriptor> fieldList = new ArrayList<FieldDescriptor>(fields.length); for (int i = 0; i < fields.length; i++) { FieldDescriptor field = fields[i]; final int index = getIdColumnIndex(field, ids); if (index == -1) { // copy non identity field from list of fields. fieldList.add(field); } else { if (field instanceof FieldDescriptorImpl) { ((FieldDescriptorImpl) field).setRequired(true); } if (field.getHandler() instanceof FieldHandlerImpl) { ((FieldHandlerImpl) field.getHandler()).setRequired(true); } identities[index] = field; } } // convert regularFieldList into array FieldDescriptor[] result = new FieldDescriptor[fieldList.size()]; return fieldList.toArray(result); } /** * Finds the index in the given <code>idColumnNames</code> that has the same name as * the given <code>field</code>. * * @param field The FieldDescriptor to find the column index for. * @param ids The id columnNames available. * @return The index of the id column name that matches the given field's name or * <code>-1</code> if no such id column name exists. */ protected int getIdColumnIndex(FieldDescriptor field, String[] ids) { for (int i = 0; i < ids.length; i++) { if (field.getFieldName().equals(ids[i])) { return i; } } return -1; } /** * Creates a single field descriptor. The field mapping is used to create a new stock * {@link FieldDescriptor}. Implementations may extend this class to create a more * suitable descriptor. * * @param javaClass The class to which the field belongs. * @param fieldMap The field mapping information. * @return The field descriptor. * @throws MappingException The field or its accessor methods are not * found, not accessible, not of the specified type, etc. */ protected FieldDescriptorImpl createFieldDesc(final Class<?> javaClass, final FieldMapping fieldMap) throws MappingException { String fieldName = fieldMap.getName(); // If the field type is supplied, grab it and use it to locate the field/accessor. Class<?> fieldType = null; if (fieldMap.getType() != null) { fieldType = resolveType(fieldMap.getType()); } // If the field is declared as a collection, grab the collection type as // well and use it to locate the field/accessor. CollectionHandler collectionHandler = null; if (fieldMap.getCollection() != null) { String colTypeName = fieldMap.getCollection().toString(); Class<?> collectionType = CollectionHandlers.getCollectionType(colTypeName); collectionHandler = CollectionHandlers.getHandler(collectionType); } TypeInfo typeInfo = getTypeInfo(fieldType, collectionHandler, fieldMap); ExtendedFieldHandler exfHandler = null; FieldHandler handler = null; // Check for user supplied FieldHandler if (fieldMap.getHandler() != null) { handler = getFieldHandler(fieldMap); // ExtendedFieldHandler? if (handler instanceof ExtendedFieldHandler) { exfHandler = (ExtendedFieldHandler) handler; } // Fix for CastorJDO from Steve Vaughan, CastorJDO requires FieldHandlerImpl // or a ClassCastException will be thrown... [KV 20030131 - also make sure // this new handler doesn't use it's own CollectionHandler otherwise it'll // cause unwanted calls to the getValue method during unmarshalling] collectionHandler = typeInfo.getCollectionHandler(); typeInfo.setCollectionHandler(null); handler = new FieldHandlerImpl(handler, typeInfo); typeInfo.setCollectionHandler(collectionHandler); // End Castor JDO fix } boolean generalized = (exfHandler instanceof GeneralizedFieldHandler); // If generalized we need to change the fieldType to whatever is specified in the // GeneralizedFieldHandler so that the correct getter/setter methods can be found if (generalized) { fieldType = ((GeneralizedFieldHandler)exfHandler).getFieldType(); } if (generalized || (handler == null)) { // Create TypeInfoRef to get new TypeInfo from call to createFieldHandler FieldHandler custom = handler; TypeInfoReference typeInfoRef = new TypeInfoReference(); typeInfoRef.typeInfo = typeInfo; handler = createFieldHandler(javaClass, fieldType, fieldMap, typeInfoRef); if (custom != null) { ((GeneralizedFieldHandler) exfHandler).setFieldHandler(handler); handler = custom; } else { boolean isTypeSafeEnum = false; // Check for type-safe enum style classes if ((fieldType != null) && !isPrimitive(fieldType)) { if (!hasPublicDefaultConstructor(fieldType)) { Method method = getStaticValueOfMethod(fieldType); if (method != null) { handler = new EnumFieldHandler(fieldType, handler, method); typeInfo.setImmutable(true); isTypeSafeEnum = true; } } } // Reset proper TypeInfo if (!isTypeSafeEnum) { typeInfo = typeInfoRef.typeInfo; } } } FieldDescriptorImpl fieldDesc = new FieldDescriptorImpl( fieldName, typeInfo, handler, fieldMap.getTransient()); fieldDesc.setRequired(fieldMap.getRequired()); // set collection type as specified in mapping file fieldDesc.setCollection(fieldMap.getCollection()); // set various method informations fieldDesc.setComparator(fieldMap.getComparator()); fieldDesc.setCreateMethod(fieldMap.getCreateMethod()); fieldDesc.setGetMethod(fieldMap.getGetMethod()); fieldDesc.setSetMethod(fieldMap.getSetMethod()); // set direct access (if defined) fieldDesc.setDirect(fieldMap.getDirect()); // extract values for 'laziness' from field mapping fieldDesc.setLazy(fieldMap.isLazy()); // If we're using an ExtendedFieldHandler we need to set the FieldDescriptor if (exfHandler != null) { ((FieldHandlerFriend) exfHandler).setFieldDescriptor(fieldDesc); } return fieldDesc; } private FieldHandler getFieldHandler(final FieldMapping fieldMap) throws MappingException { // If there is a custom field handler present in the mapping, that one // is returned. FieldHandler handler = _fieldHandlers.get(fieldMap.getHandler()); if (handler != null) { return handler; } Class<?> handlerClass = null; handlerClass = resolveType(fieldMap.getHandler()); if (!FieldHandler.class.isAssignableFrom(handlerClass)) { String err = "The class '" + fieldMap.getHandler() + "' must implement " + FieldHandler.class.getName(); throw new MappingException(err); } // Get default constructor to invoke. We can't use the newInstance method // unfortunately becaue FieldHandler overloads this method try { Constructor<?> constructor = handlerClass.getConstructor(new Class[0]); return (FieldHandler) constructor.newInstance(new Object[0]); } catch (Exception ex) { String err = "The class '" + handlerClass.getName() + "' must have a default public constructor."; throw new MappingException(err); } } /** * Does the given class has a public default constructor? * * @param type Class to check for a public default constructor. * @return <code>true</code> if class has a public default constructor. */ private boolean hasPublicDefaultConstructor(final Class<?> type) { try { Constructor<?> cons = type.getConstructor(EMPTY_ARGS); return Modifier.isPublic(cons.getModifiers()); } catch (NoSuchMethodException ex) { return false; } } /** * Get static valueOf(String) factory method of given class. * * @param type Class to check for a static valueOf(String) factory method. * @return Static valueOf(String) factory method or <code>null</code> if none could * be found. */ private Method getStaticValueOfMethod(final Class<?> type) { try { Method method = type.getMethod(VALUE_OF, STRING_ARG); Class<?> returnType = method.getReturnType(); if (returnType == null) { return null; } if (!type.isAssignableFrom(returnType)) { return null; } if (!Modifier.isStatic(method.getModifiers())) { return null; } return method; } catch (NoSuchMethodException ex) { return null; } } /** * Creates the FieldHandler for the given FieldMapping. * * @param javaClass the class type of the parent of the field. * @param fldType the Java class type for the field. * @param fldMap the field mapping. * @return the newly created FieldHandler. */ protected final FieldHandler createFieldHandler(Class<?> javaClass, Class<?> fldType, final FieldMapping fldMap, final TypeInfoReference typeInfoRef) throws MappingException { // Prevent introspection of transient fields if (fldMap.getTransient()) { return new TransientFieldHandler(); } Class<?> collectionType = null; CollectionHandler collectionHandler = null; boolean colRequireGetSet = true; String fieldName = fldMap.getName(); // If the field is declared as a collection, grab the collection type as // well and use it to locate the field/accessor. if (fldMap.getCollection() != null) { String colTypeName = fldMap.getCollection().toString(); collectionType = CollectionHandlers.getCollectionType(colTypeName); collectionHandler = CollectionHandlers.getHandler(collectionType); colRequireGetSet = CollectionHandlers.isGetSetCollection(collectionType); if (collectionType == Object[].class) { if (fldType == null) { String msg = "'type' is a required attribute for field that are " + "array collections: " + fieldName; throw new MappingException(msg); } Object obj = Array.newInstance(fldType, 0); collectionType = obj.getClass(); } } FieldHandlerImpl handler = null; // If get/set methods not specified, use field names to determine them. if (fldMap.getDirect()) { // No accessor, map field directly. Field field = findField(javaClass, fieldName, (collectionType == null ? fldType : collectionType)); if (field == null) { throw new MappingException( "mapping.fieldNotAccessible", fieldName, javaClass.getName()); } if (fldType == null) { fldType = field.getType(); } typeInfoRef.typeInfo = getTypeInfo(fldType, collectionHandler, fldMap); handler = new FieldHandlerImpl(field, typeInfoRef.typeInfo); } else if ((fldMap.getGetMethod() == null) && (fldMap.getSetMethod() == null)) { // If both methods (get/set) are not specified, determine them automatically if (fieldName == null) { throw new MappingException( "mapping.missingFieldName", javaClass.getName()); } List<Method> getSequence = new ArrayList<Method>(); List<Method> setSequence = new ArrayList<Method>(); Method getMethod = null; Method setMethod = null; // Get method normally starts with "get", but may start with "is" // if it's a boolean. try { // Handle nested fields while (true) { int point = fieldName.indexOf('.'); if (point < 0) { break; } String parentField = fieldName.substring(0, point); // Getter method for parent field String methodName = GET_METHOD_PREFIX + capitalize(parentField); Method method = javaClass.getMethod(methodName, (Class[]) null); if (isAbstractOrStatic(method)) { throw new MappingException("mapping.accessorNotAccessible", methodName, javaClass.getName()); } getSequence.add(method); Class<?> nextClass = method.getReturnType(); // Setter method for parent field try { methodName = SET_METHOD_PREFIX + capitalize(parentField); Class<?>[] types = new Class[] {nextClass}; method = javaClass.getMethod(methodName, types); if (isAbstractOrStatic(method)) { method = null; } } catch (Exception ex) { method = null; } setSequence.add(method); javaClass = nextClass; fieldName = fieldName.substring(point + 1); } // Find getter method for actual field String methodName = GET_METHOD_PREFIX + capitalize( fieldName ); Class<?> returnType = (collectionType == null) ? fldType : collectionType; getMethod = findAccessor(javaClass, methodName, returnType, true); // If getMethod is null, check for boolean type method prefix if (getMethod == null) { if ((fldType == Boolean.class) || (fldType == Boolean.TYPE)) { methodName = IS_METHOD_PREFIX + capitalize(fieldName); getMethod = findAccessor(javaClass, methodName, returnType, true); } } } catch (MappingException ex) { throw ex; } catch (Exception ex) { // LOG.warn("Unexpected exception", ex); } if (getMethod == null) { String getAccessor = GET_METHOD_PREFIX + capitalize(fieldName); String isAccessor = IS_METHOD_PREFIX + capitalize(fieldName); throw new MappingException("mapping.accessorNotFound", getAccessor + "/" + isAccessor, (collectionType == null ? fldType : collectionType), javaClass.getName()); } if ((fldType == null) && (collectionType == null)) { fldType = getMethod.getReturnType(); } // We try to locate a set method anyway but complain only if we need one String methodName = SET_METHOD_PREFIX + capitalize(fieldName); setMethod = findAccessor(javaClass, methodName, (collectionType == null ? fldType : collectionType), false); // If we have a collection that need both set and get but we don't have a // set method, we fail if ((setMethod == null) && (collectionType != null) && colRequireGetSet) { throw new MappingException("mapping.accessorNotFound", methodName, (collectionType == null ? fldType : collectionType), javaClass.getName()); } typeInfoRef.typeInfo = getTypeInfo(fldType, collectionHandler, fldMap); fieldName = fldMap.getName(); if (fieldName == null) { if (getMethod == null) { fieldName = setMethod.getName(); } else { fieldName = getMethod.getName(); } } // Convert method call sequence for nested fields to arrays Method[] getArray = null; Method[] setArray = null; if (getSequence.size() > 0) { getArray = new Method[getSequence.size()]; getArray = getSequence.toArray(getArray); setArray = new Method[setSequence.size()]; setArray = setSequence.toArray(setArray); } // Create handler handler = new FieldHandlerImpl(fieldName, getArray, setArray, getMethod, setMethod, typeInfoRef.typeInfo); if (setMethod != null) { if (setMethod.getName().startsWith(ADD_METHOD_PREFIX)) { handler.setAddMethod(setMethod); } } } else { Method getMethod = null; Method setMethod = null; // First look up the get accessors if (fldMap.getGetMethod() != null) { Class<?> rtype = fldType; if (collectionType != null) { String methodName = fldMap.getGetMethod(); if (methodName.startsWith(ENUM_METHOD_PREFIX)) { // An enumeration method must really return a enumeration. rtype = Enumeration.class; } else if (methodName.startsWith(ITER_METHOD_PREFIX)) { // An iterator method must really return a iterator. rtype = Iterator.class; } else { rtype = collectionType; } } getMethod = findAccessor(javaClass, fldMap.getGetMethod(), rtype, true); if (getMethod == null) { throw new MappingException("mapping.accessorNotFound", fldMap.getGetMethod(), rtype, javaClass.getName()); } if ((fldType == null) && (collectionType == null)) { fldType = getMethod.getReturnType(); } } // Second look up the set/add accessor if (fldMap.getSetMethod() != null) { String methodName = fldMap.getSetMethod(); Class<?> type = fldType; if (collectionType != null) { if (!methodName.startsWith(ADD_METHOD_PREFIX)) { type = collectionType; } } // Set via constructor? if (methodName.startsWith("%")) { // Validate index value int index = 0; String temp = methodName.substring(1); try { index = Integer.parseInt(temp); } catch(NumberFormatException ex) { throw new MappingException("mapping.invalidParameterIndex", temp); } if ((index < 1) || (index > 9)) { throw new MappingException("mapping.invalidParameterIndex", temp); } } else { setMethod = findAccessor(javaClass, methodName, type , false); if (setMethod == null) { throw new MappingException("mapping.accessorNotFound", methodName, type, javaClass.getName()); } if (fldType == null) { fldType = setMethod.getParameterTypes()[0]; } } } typeInfoRef.typeInfo = getTypeInfo(fldType, collectionHandler, fldMap); fieldName = fldMap.getName(); if (fieldName == null) { if (getMethod == null) { fieldName = setMethod.getName(); } else { fieldName = getMethod.getName(); } } // Create handler handler = new FieldHandlerImpl(fieldName, null, null, getMethod, setMethod, typeInfoRef.typeInfo); if (setMethod != null) { if (setMethod.getName().startsWith(ADD_METHOD_PREFIX)) { handler.setAddMethod(setMethod); } } } // If there is a create method, add it to the field handler String methodName = fldMap.getCreateMethod(); if (methodName != null) { try { Method method = javaClass.getMethod(methodName, (Class[]) null); handler.setCreateMethod(method); } catch (Exception ex) { throw new MappingException("mapping.createMethodNotFound", methodName, javaClass.getName()); } } else if ((fieldName != null) && !Types.isSimpleType(fldType)) { try { methodName = CREATE_METHOD_PREFIX + capitalize(fieldName); Method method = javaClass.getMethod(methodName, (Class[]) null); handler.setCreateMethod(method); } catch (Exception ex) { // LOG.warn ("Unexpected exception", ex); } } // If there is an has/delete method, add them to field handler if (fieldName != null) { try { methodName = fldMap.getHasMethod(); if (methodName == null) { methodName = HAS_METHOD_PREFIX + capitalize(fieldName); } Method hasMethod = javaClass.getMethod(methodName, (Class[]) null); if ((hasMethod.getModifiers() & Modifier.STATIC) != 0) { hasMethod = null; } Method deleteMethod = null; try { methodName = DELETE_METHOD_PREFIX + capitalize(fieldName); deleteMethod = javaClass.getMethod(methodName, (Class[]) null ); if ((deleteMethod.getModifiers() & Modifier.STATIC) != 0) { deleteMethod = null; } } catch (Exception ex) { // Purposely Ignore exception we're just seeing if the method exists } handler.setHasDeleteMethod(hasMethod, deleteMethod); } catch (Exception ex) { // LOG.warn("Unexpected exception", ex); } } return handler; } private static boolean isAbstract(final Class<?> cls) { return ((cls.getModifiers() & Modifier.ABSTRACT) != 0); } private static boolean isAbstractOrStatic(final Method method) { return ((method.getModifiers() & Modifier.ABSTRACT) != 0) || ((method.getModifiers() & Modifier.STATIC) != 0); } protected TypeInfo getTypeInfo(final Class<?> fieldType, final CollectionHandler colHandler, final FieldMapping fieldMap) throws MappingException { return new TypeInfo(Types.typeFromPrimitive(fieldType), null, null, fieldMap.getRequired(), null, colHandler, false); } /** * Returns the named field. Uses reflection to return the named field and check the * field type, if specified. * * @param javaClass The class to which the field belongs. * @param fieldName The name of the field. * @param fieldType The type of the field if known, or null. * @return The field, null if not found. * @throws MappingException The field is not accessible or is not of the * specified type. */ private final Field findField(final Class<?> javaClass, final String fieldName, Class<?> fieldType) throws MappingException { try { // Look up the field based on its name, make sure it's only modifier // is public. If a type was specified, match the field type. Field field = javaClass.getField(fieldName); if ((field.getModifiers() != Modifier.PUBLIC) && (field.getModifiers() != (Modifier.PUBLIC | Modifier.VOLATILE))) { throw new MappingException( "mapping.fieldNotAccessible", fieldName, javaClass.getName()); } if (fieldType == null) { fieldType = Types.typeFromPrimitive(field.getType()); } else { Class<?> ft1 = Types.typeFromPrimitive(fieldType); Class<?> ft2 = Types.typeFromPrimitive(field.getType()); if ((ft1 != ft2) && (fieldType != Serializable.class)) { throw new MappingException( "mapping.fieldTypeMismatch", field, fieldType.getName()); } } return field; } catch (NoSuchFieldException ex) { return null; } catch (SecurityException ex) { return null; } } /** * Returns the named accessor. Uses reflection to return the named accessor and * check the return value or parameter type, if specified. * * @param javaClass The class to which the field belongs. * @param methodName The name of the accessor method. * @param fieldType The type of the field if known, or null. * @param getMethod True if get method, false if set method. * @return The method, null if not found. * @throws MappingException The method is not accessible or is not of the * specified type. */ public static final Method findAccessor(final Class<?> javaClass, final String methodName, Class<?> fieldType, final boolean getMethod) throws MappingException { try { Method method = null; if (getMethod) { // Get method: look for the named method or prepend get to the method // name. Look up the field and potentially check the return type. method = javaClass.getMethod(methodName, new Class[0]); // The MapItem is used to handle the contents of maps. Since the MapItem // has to use Object for its methods we cannot (but also don't have to) // check for correct types. if (javaClass == MapItem.class) { if (methodName.equals("getKey")) { return method; } if (methodName.equals("getValue")) { return method; } } if (fieldType == null) { fieldType = Types.typeFromPrimitive(method.getReturnType()); } else { fieldType = Types.typeFromPrimitive(fieldType); Class<?> returnType = Types.typeFromPrimitive(method.getReturnType()); //-- First check against whether the declared type is //-- an interface or abstract class. We also check //-- type as Serializable for CMP 1.1 compatibility. if (fieldType.isInterface() || ((fieldType.getModifiers() & Modifier.ABSTRACT) != 0) || (fieldType == java.io.Serializable.class)) { if (!fieldType.isAssignableFrom(returnType)) { throw new MappingException( "mapping.accessorReturnTypeMismatch", method, fieldType.getName()); } } else { if (!returnType.isAssignableFrom(fieldType)) { throw new MappingException( "mapping.accessorReturnTypeMismatch", method, fieldType.getName()); } } } } else { // Set method: look for the named method or prepend set to the method // name. If the field type is know, look up a suitable method. If the // field type is unknown, lookup the first method with that name and // one parameter. Class<?> fieldTypePrimitive = null; if (fieldType != null) { fieldTypePrimitive = Types.typeFromPrimitive(fieldType); try { method = javaClass.getMethod(methodName, new Class[] {fieldType}); } catch (Exception ex) { try { method = javaClass.getMethod( methodName, new Class[] {fieldTypePrimitive}); } catch (Exception ex2) { // LOG.warn("Unexpected exception", ex2); } } /* Replace above try catch block with the following one to resolve * CASTOR-1141 for jdo part. After this change you can use this method * also in FieldMolder and remove its findAccessor() method to omit * code duplication. Having said that this introduces a problem * with xmlctf that have to resolved first. // first check for setter with reference type (e.g. setXxx(Integer)) try { method = javaClass.getMethod(methodName, new Class[] {fieldTypePrimitive}); } catch (Exception ex) { // if setter for reference type could not be found // try to find one for primitive type (e.g. setXxx(int)) try { method = javaClass.getMethod(methodName, new Class[] {fieldType}); } catch (Exception ex2) { // LOG.warn("Unexpected exception", ex2); } } */ } if (method == null) { Method[] methods = javaClass.getMethods(); for (int i = 0; i < methods.length; ++i) { if (methods[i].getName().equals(methodName)) { Class<?>[] paramTypes = methods[i].getParameterTypes(); if (paramTypes.length != 1) { continue; } Class<?> paramType = Types.typeFromPrimitive(paramTypes[0]); if (fieldType == null) { method = methods[i]; break; } else if (paramType.isAssignableFrom(fieldTypePrimitive)) { method = methods[i]; break; } else if (fieldType.isInterface() || isAbstract(fieldType)) { if (fieldTypePrimitive.isAssignableFrom(paramType)) { method = methods[i]; break; } } } } if (method == null) { return null; } } } // Make sure method is public and not static. // (note: Class.getMethod() returns only public methods). if ((method.getModifiers() & Modifier.STATIC) != 0) { throw new MappingException( "mapping.accessorNotAccessible", methodName, javaClass.getName()); } return method; } catch (MappingException ex) { throw ex; } catch (Exception ex) { return null; } } private static final String capitalize(final String name) { char first = name.charAt(0); if (Character.isUpperCase(first)) { return name; } return Character.toUpperCase(first) + name.substring(1); } /** * Returns a list of column names that are part of the identity. * * @param ids Known identity names. * @param clsMap The class mapping. * @return List of identity column names. */ public static final String[] getIdentityColumnNames(final String[] ids, final ClassMapping clsMap) { String[] idNames = ids; if ((ids == null) || (ids.length == 0)) { ClassChoice classChoice = clsMap.getClassChoice(); if (classChoice == null) { classChoice = new ClassChoice(); } FieldMapping[] fieldMappings = classChoice.getFieldMapping(); List<String> idNamesList = new ArrayList<String>(); for (FieldMapping fieldMapping : fieldMappings) { if (fieldMapping.getIdentity() == true) { idNamesList.add(fieldMapping.getName()); } } if (!idNamesList.isEmpty()) { idNames = new String[idNamesList.size()]; idNames = idNamesList.toArray(idNames); } } return idNames; } /** * 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 */ protected static final boolean isPrimitive(final Class<?> type) { if (type.isPrimitive()) { return true; } if ((type == Boolean.class) || (type == Character.class)) { return true; } return (type.getSuperclass() == Number.class); } /** * A class used to by the createFieldHandler method in order to * save the reference of the TypeInfo that was used. */ public class TypeInfoReference { public TypeInfo typeInfo = null; } public void setInternalContext(final InternalContext internalContext) { _internalContext = internalContext; } public InternalContext getInternalContext() { return _internalContext; } }