View Javadoc
1   /*
2    * Redistribution and use of this software and associated documentation ("Software"), with or
3    * without modification, are permitted provided that the following conditions are met:
4    *
5    * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
6    * must also contain a copy of this document.
7    *
8    * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
9    * conditions and the following disclaimer in the documentation and/or other materials provided with
10   * the distribution.
11   *
12   * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
13   * without prior written permission of Intalio, Inc. For written permission, please contact
14   * info@exolab.org.
15   *
16   * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
17   * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
18   * Intalio, Inc.
19   *
20   * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
21   *
22   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
23   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
24   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
25   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
29   * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   *
31   * Copyright 1999-2003 (C) Intalio, Inc. All Rights Reserved.
32   *
33   * Portions of this file developed by Keith Visco after Jan 19 2005 are Copyright (C) 2005 Keith
34   * Visco. All Rights Reserverd.
35   *
36   * $Id$
37   */
38  package org.exolab.castor.mapping.loader;
39  
40  import java.io.Serializable;
41  import java.lang.reflect.Array;
42  import java.lang.reflect.Constructor;
43  import java.lang.reflect.Field;
44  import java.lang.reflect.Method;
45  import java.lang.reflect.Modifier;
46  import java.util.ArrayList;
47  import java.util.HashMap;
48  import java.util.Iterator;
49  import java.util.List;
50  import java.util.Enumeration;
51  import java.util.Map;
52  import java.util.Properties;
53  
54  import org.castor.xml.InternalContext;
55  import org.castor.xml.AbstractInternalContext;
56  import org.castor.core.util.Messages;
57  import org.exolab.castor.mapping.ClassDescriptor;
58  import org.exolab.castor.mapping.ClonableFieldHandler;
59  import org.exolab.castor.mapping.ClonableFieldHandlerMarker;
60  import org.exolab.castor.mapping.CollectionHandler;
61  import org.exolab.castor.mapping.ConfigurableFieldHandler;
62  import org.exolab.castor.mapping.ExtendedFieldHandler;
63  import org.exolab.castor.mapping.FieldDescriptor;
64  import org.exolab.castor.mapping.FieldHandler;
65  import org.exolab.castor.mapping.GeneralizedFieldHandler;
66  import org.exolab.castor.mapping.MapItem;
67  import org.exolab.castor.mapping.MappingException;
68  import org.exolab.castor.mapping.ValidityException;
69  import org.exolab.castor.mapping.handlers.EnumFieldHandler;
70  import org.exolab.castor.mapping.handlers.TransientFieldHandler;
71  import org.exolab.castor.mapping.xml.ClassChoice;
72  import org.exolab.castor.mapping.xml.FieldHandlerDef;
73  import org.exolab.castor.mapping.xml.MappingRoot;
74  import org.exolab.castor.mapping.xml.ClassMapping;
75  import org.exolab.castor.mapping.xml.FieldMapping;
76  import org.exolab.castor.mapping.xml.Param;
77  
78  /**
79   * Assists in the construction of descriptors. Can be used as a mapping resolver to the engine.
80   * Engines will implement their own mapping scheme typically by extending this class.
81   *
82   * @author <a href="arkin@intalio.com">Assaf Arkin</a>
83   * @author <a href="keith AT kvisco DOT com">Keith Visco</a>
84   * @version $Revision$ $Date: 2006-04-10 16:39:24 -0600 (Mon, 10 Apr 2006) $
85   */
86  public abstract class AbstractMappingLoader extends AbstractMappingLoader2 {
87  
88    /** The prefix for the "add" method. */
89    private static final String ADD_METHOD_PREFIX = "add";
90  
91    /** The prefix for an enumeration method. */
92    private static final String ENUM_METHOD_PREFIX = "enum";
93  
94    /** The prefix for an enumeration method. */
95    private static final String ITER_METHOD_PREFIX = "iterate";
96  
97    /** The standard prefix for the getter method. */
98    private static final String GET_METHOD_PREFIX = "get";
99  
100   /** The prefix for the "is" method for booleans. */
101   private static final String IS_METHOD_PREFIX = "is";
102 
103   /** The standard prefix for the setter method. */
104   private static final String SET_METHOD_PREFIX = "set";
105 
106   /** The prefix for the "create" method. */
107   private static final String CREATE_METHOD_PREFIX = "create";
108 
109   /** The prefix for the "has" method. */
110   private static final String HAS_METHOD_PREFIX = "has";
111 
112   /** The prefix for the "delete" method. */
113   private static final String DELETE_METHOD_PREFIX = "delete";
114 
115   /** Empty array of class types used for reflection. */
116   protected static final Class<?>[] EMPTY_ARGS = new Class[0];
117 
118   /**
119    * The string argument for the valueOf method, used for introspection when searching for type-safe
120    * enumeration style classes.
121    */
122   protected static final Class<?>[] STRING_ARG = {String.class};
123 
124   /** Factory method name for type-safe enumerations. */
125   protected static final String VALUE_OF = "valueOf";
126 
127   /** Method name to get string value of a type-safe enumerations. */
128   protected static final String NAME = "name";
129 
130   /**
131    * The {@link AbstractInternalContext} is the centerpiece providing runtime configuration and
132    * state information.
133    */
134   private InternalContext _internalContext;
135 
136   /** Map of field handlers associated by their name. */
137   private final Map<String, FieldHandler> _fieldHandlers = new HashMap<String, FieldHandler>();
138 
139   /**
140    * Constructs a new mapping helper. This constructor is used by a derived class.
141    *
142    * @param loader The class loader to use, null for the default
143    */
144   protected AbstractMappingLoader(final ClassLoader loader) {
145     super(loader);
146   }
147 
148   /**
149    * {@inheritDoc}
150    */
151   public final String getSourceType() {
152     return "CastorXmlMapping";
153   }
154 
155   /**
156    * Loads the mapping from the specified mapping object if not loaded previously.
157    *
158    * @param mapping The mapping information.
159    * @param param Arbitrary parameter that can be used by subclasses.
160    * @throws MappingException The mapping file is invalid.
161    */
162   public abstract void loadMapping(final MappingRoot mapping, final Object param)
163       throws MappingException;
164 
165   /**
166    * Load field handler definitions, check for duplicate definitions and instantiate the appropriate
167    * FieldHandler implementations.
168    * 
169    * @param mapping Mapping to load field handler definitions from.
170    * @throws MappingException If mapping contains more then one field handler definition with same
171    *         name.
172    */
173   protected void createFieldHandlers(final MappingRoot mapping) throws MappingException {
174     Enumeration<? extends FieldHandlerDef> enumeration = mapping.enumerateFieldHandlerDef();
175     while (enumeration.hasMoreElements()) {
176       FieldHandlerDef def = enumeration.nextElement();
177 
178       String name = def.getName();
179 
180       if (_fieldHandlers.containsKey(name)) {
181         throw new MappingException(Messages.format("mapping.dupFieldHandler", name));
182       }
183 
184 
185       Class<?> clazz = resolveType(def.getClazz());
186       FieldHandler fieldHandler = null;
187       try {
188         if (!FieldHandler.class.isAssignableFrom(clazz)) {
189           throw new MappingException(
190               Messages.format("mapping.classNotFieldHandler", name, def.getClazz()));
191         }
192         fieldHandler = (FieldHandler) clazz.newInstance();
193         _fieldHandlers.put(name, fieldHandler);
194       } catch (InstantiationException e) {
195         throw new MappingException(e);
196       } catch (IllegalAccessException e) {
197         throw new MappingException(e);
198       }
199 
200       // Add configuration data, if there is any
201       configureFieldHandler(def, fieldHandler);
202 
203     }
204   }
205 
206   /*
207    * Checks if the field handler is configurable, and adds the configuration data to the field
208    * handler if this is the case.
209    */
210   private void configureFieldHandler(final FieldHandlerDef def, final FieldHandler fieldHandler)
211       throws MappingException {
212 
213     // Gather the configuration data (parameters).
214     Properties params = new Properties();
215     Enumeration<? extends Param> enumerateParam = def.enumerateParam();
216     while (enumerateParam.hasMoreElements()) {
217       Param par = enumerateParam.nextElement();
218       params.put(par.getName(), par.getValue());
219     }
220 
221     // If there is configuration data, make sure that the field handler class
222     // supports it.
223     if (!params.isEmpty()) {
224       if (!ConfigurableFieldHandler.class.isAssignableFrom(fieldHandler.getClass())) {
225         throw new MappingException(Messages.format("mapping.classNotConfigurableFieldHandler",
226             def.getName(), def.getClazz()));
227       }
228 
229       // Pass the configuration data to the field handler.
230       try {
231         ((ConfigurableFieldHandler) fieldHandler).setConfiguration(params);
232       } catch (ValidityException e) {
233         throw new MappingException(
234             Messages.format("mapping.invalidFieldHandlerConfig", def.getName(), e.getMessage()), e);
235       }
236     }
237   }
238 
239   protected final void createClassDescriptors(final MappingRoot mapping) throws MappingException {
240     // Load the mapping for all the classes. This is always returned
241     // in the same order as it appeared in the mapping file.
242     Enumeration<? extends ClassMapping> enumeration = mapping.enumerateClassMapping();
243 
244     List<ClassMapping> retryList = new ArrayList<ClassMapping>();
245     while (enumeration.hasMoreElements()) {
246       ClassMapping clsMap = enumeration.nextElement();
247       try {
248         ClassDescriptor clsDesc = createClassDescriptor(clsMap);
249         if (clsDesc != null) {
250           addDescriptor(clsDesc);
251         }
252       } catch (MappingException mx) {
253         // save for later for possible out-of-order mapping files...
254         retryList.add(clsMap);
255         continue;
256       }
257     }
258 
259     // handle possible retries, for now we only loop once on the retries, but we
260     // should change this to keep looping until we have no more success rate.
261     for (ClassMapping classMapping : retryList) {
262       ClassDescriptor clsDesc = createClassDescriptor(classMapping);
263       if (clsDesc != null) {
264         addDescriptor(clsDesc);
265       }
266     }
267 
268     // iterate over all class descriptors and resolve relations between them
269     for (ClassDescriptor classDescriptor : getDescriptors()) {
270       resolveRelations(classDescriptor);
271     }
272   }
273 
274   protected abstract ClassDescriptor createClassDescriptor(final ClassMapping clsMap)
275       throws MappingException;
276 
277   /**
278    * Gets the ClassDescriptor the given <code>classMapping</code> extends.
279    *
280    * @param clsMap The ClassMapping to find the required descriptor for.
281    * @param javaClass The name of the class that is checked (this is used for generating the
282    *        exception).
283    * @return The ClassDescriptor the given ClassMapping extends or <code>null</code> if the given
284    *         ClassMapping does not extend any.
285    * @throws MappingException If the given ClassMapping extends another ClassMapping but its
286    *         descriptor could not be found.
287    */
288   protected final ClassDescriptor getExtended(final ClassMapping clsMap, final Class<?> javaClass)
289       throws MappingException {
290     if (clsMap.getExtends() == null) {
291       return null;
292     }
293 
294     ClassMapping mapping = (ClassMapping) clsMap.getExtends();
295     Class<?> type = resolveType(mapping.getName());
296     ClassDescriptor result = getDescriptor(type.getName());
297 
298     if (result == null) {
299       throw new MappingException("mapping.extendsMissing", mapping, javaClass.getName());
300     }
301 
302     if (!result.getJavaClass().isAssignableFrom(javaClass)) {
303       throw new MappingException("mapping.classDoesNotExtend", javaClass.getName(),
304           result.getJavaClass().getName());
305     }
306 
307     return result;
308   }
309 
310   /**
311    * Gets the ClassDescriptor the given <code>classMapping</code> depends on.
312    *
313    * @param clsMap The ClassMapping to find the required ClassDescriptor for.
314    * @param javaClass The name of the class that is checked (this is used for generating the
315    *        exception).
316    * @return The ClassDescriptor the given ClassMapping depends on or <code>null</code> if the given
317    *         ClassMapping does not depend on any.
318    * @throws MappingException If the given ClassMapping depends on another ClassMapping but its
319    *         descriptor could not be found.
320    */
321   protected final ClassDescriptor getDepended(final ClassMapping clsMap, final Class<?> javaClass)
322       throws MappingException {
323     if (clsMap.getDepends() == null) {
324       return null;
325     }
326 
327     ClassMapping mapping = (ClassMapping) clsMap.getDepends();
328     Class<?> type = resolveType(mapping.getName());
329     ClassDescriptor result = getDescriptor(type.getName());
330 
331     if (result == null) {
332       throw new MappingException("Depends not found: " + mapping + " " + javaClass.getName());
333     }
334 
335     return result;
336   }
337 
338   /**
339    * Checks all given fields for name equality and throws a MappingException if at least two fields
340    * have the same name.
341    *
342    * @param fields The fields to be checked.
343    * @param cls Class that is checked (this is used for generating the exception).
344    * @throws MappingException If at least two fields have the same name.
345    */
346   protected final void checkFieldNameDuplicates(final FieldDescriptor[] fields, final Class<?> cls)
347       throws MappingException {
348     for (int i = 0; i < fields.length - 1; i++) {
349       String fieldName = fields[i].getFieldName();
350       for (int j = i + 1; j < fields.length; j++) {
351         if (fieldName.equals(fields[j].getFieldName())) {
352           throw new MappingException(
353               "The field " + fieldName + " appears twice in the descriptor for " + cls.getName());
354         }
355       }
356     }
357   }
358 
359   protected abstract void resolveRelations(final ClassDescriptor clsDesc);
360 
361   // --------------------------------------------------------------------------
362 
363   /**
364    * Returns the Java class for the named type. The type name can be one of the accepted short names
365    * (e.g. <tt>integer</tt>) or the full Java class name (e.g. <tt>java.lang.Integer</tt>). If the
366    * short name is used, the primitive type might be returned.
367    */
368   protected final Class<?> resolveType(final String typeName) throws MappingException {
369     try {
370       return Types.typeFromName(getClassLoader(), typeName);
371     } catch (ClassNotFoundException ex) {
372       throw new MappingException("mapping.classNotFound", typeName);
373     }
374   }
375 
376   /**
377    * Create field descriptors. The class mapping information is used to create descriptors for all
378    * the fields in the class, except for container fields. Implementations may extend this method to
379    * create more suitable descriptors, or create descriptors only for a subset of the fields.
380    *
381    * @param clsMap The class to which the fields belong.
382    * @param javaClass The field mappings.
383    * @throws MappingException An exception indicating why mapping for the class cannot be created.
384    */
385   protected final FieldDescriptorImpl[] createFieldDescriptors(final ClassMapping clsMap,
386       final Class<?> javaClass) throws MappingException {
387     FieldMapping[] fldMap = null;
388 
389     if (clsMap.getClassChoice() != null) {
390       fldMap = clsMap.getClassChoice().getFieldMapping();
391     }
392 
393     if ((fldMap == null) || (fldMap.length == 0)) {
394       return new FieldDescriptorImpl[0];
395     }
396 
397     FieldDescriptorImpl[] fields = new FieldDescriptorImpl[fldMap.length];
398     for (int i = 0; i < fldMap.length; i++) {
399       fields[i] = createFieldDesc(javaClass, fldMap[i]);
400 
401       // set identity flag
402       fields[i].setIdentity(fldMap[i].getIdentity());
403     }
404 
405     return fields;
406   }
407 
408   /**
409    * Gets the top-most (i.e. without any further 'extends') extends of the given
410    * <code>classMapping</code>.
411    *
412    * @param clsMap The ClassMapping to get the origin for.
413    * @return The top-most extends of the given ClassMapping or the ClassMapping itself if it does
414    *         not extend any other ClassMapping.
415    */
416   protected final ClassMapping getOrigin(final ClassMapping clsMap) {
417     ClassMapping result = clsMap;
418 
419     while (result.getExtends() != null) {
420       result = (ClassMapping) result.getExtends();
421     }
422 
423     return result;
424   }
425 
426   protected final FieldDescriptor[] divideFieldDescriptors(final FieldDescriptor[] fields,
427       final String[] ids, final FieldDescriptor[] identities) {
428     List<FieldDescriptor> fieldList = new ArrayList<FieldDescriptor>(fields.length);
429 
430     for (int i = 0; i < fields.length; i++) {
431       FieldDescriptor field = fields[i];
432       final int index = getIdColumnIndex(field, ids);
433       if (index == -1) {
434         // copy non identity field from list of fields.
435         fieldList.add(field);
436       } else {
437         if (field instanceof FieldDescriptorImpl) {
438           ((FieldDescriptorImpl) field).setRequired(true);
439         }
440         if (field.getHandler() instanceof FieldHandlerImpl) {
441           ((FieldHandlerImpl) field.getHandler()).setRequired(true);
442         }
443 
444         identities[index] = field;
445       }
446     }
447 
448     // convert regularFieldList into array
449     FieldDescriptor[] result = new FieldDescriptor[fieldList.size()];
450     return fieldList.toArray(result);
451   }
452 
453   /**
454    * Finds the index in the given <code>idColumnNames</code> that has the same name as the given
455    * <code>field</code>.
456    *
457    * @param field The FieldDescriptor to find the column index for.
458    * @param ids The id columnNames available.
459    * @return The index of the id column name that matches the given field's name or <code>-1</code>
460    *         if no such id column name exists.
461    */
462   protected int getIdColumnIndex(FieldDescriptor field, String[] ids) {
463     for (int i = 0; i < ids.length; i++) {
464       if (field.getFieldName().equals(ids[i])) {
465         return i;
466       }
467     }
468     return -1;
469   }
470 
471   /**
472    * Creates a single field descriptor. The field mapping is used to create a new stock
473    * {@link FieldDescriptor}. Implementations may extend this class to create a more suitable
474    * descriptor.
475    *
476    * @param javaClass The class to which the field belongs.
477    * @param fieldMap The field mapping information.
478    * @return The field descriptor.
479    * @throws MappingException The field or its accessor methods are not found, not accessible, not
480    *         of the specified type, etc.
481    */
482   protected FieldDescriptorImpl createFieldDesc(final Class<?> javaClass,
483       final FieldMapping fieldMap) throws MappingException {
484     String fieldName = fieldMap.getName();
485 
486     // If the field type is supplied, grab it and use it to locate the field/accessor.
487     Class<?> fieldType = null;
488     if (fieldMap.getType() != null) {
489       fieldType = resolveType(fieldMap.getType());
490     }
491 
492     // If the field is declared as a collection, grab the collection type as
493     // well and use it to locate the field/accessor.
494     CollectionHandler collectionHandler = null;
495     if (fieldMap.getCollection() != null) {
496       String colTypeName = fieldMap.getCollection().toString();
497       Class<?> collectionType = CollectionHandlers.getCollectionType(colTypeName);
498       collectionHandler = CollectionHandlers.getHandler(collectionType);
499     }
500 
501     TypeInfo typeInfo = getTypeInfo(fieldType, collectionHandler, fieldMap);
502 
503     ExtendedFieldHandler exfHandler = null;
504     FieldHandler handler = null;
505 
506     // Check for user supplied FieldHandler
507     if (fieldMap.getHandler() != null) {
508       handler = getFieldHandler(fieldMap);
509 
510       // ExtendedFieldHandler?
511       if (handler instanceof ExtendedFieldHandler) {
512         exfHandler = (ExtendedFieldHandler) handler;
513       }
514 
515       // Fix for CastorJDO from Steve Vaughan, CastorJDO requires FieldHandlerImpl
516       // or a ClassCastException will be thrown... [KV 20030131 - also make sure
517       // this new handler doesn't use it's own CollectionHandler otherwise it'll
518       // cause unwanted calls to the getValue method during unmarshalling]
519       collectionHandler = typeInfo.getCollectionHandler();
520       typeInfo.setCollectionHandler(null);
521       handler = new FieldHandlerImpl(handler, typeInfo);
522       typeInfo.setCollectionHandler(collectionHandler);
523       // End Castor JDO fix
524     }
525 
526     boolean generalized = (exfHandler instanceof GeneralizedFieldHandler);
527 
528     // If generalized we need to change the fieldType to whatever is specified in the
529     // GeneralizedFieldHandler so that the correct getter/setter methods can be found
530     if (generalized) {
531       fieldType = ((GeneralizedFieldHandler) exfHandler).getFieldType();
532     }
533 
534     if (generalized || (handler == null)) {
535       // Create TypeInfoRef to get new TypeInfo from call to createFieldHandler
536       FieldHandler custom = handler;
537       TypeInfoReference typeInfoRef = new TypeInfoReference();
538       typeInfoRef.typeInfo = typeInfo;
539       handler = createFieldHandler(javaClass, fieldType, fieldMap, typeInfoRef);
540       if (custom != null) {
541         ((GeneralizedFieldHandler) exfHandler).setFieldHandler(handler);
542         handler = custom;
543       } else {
544         boolean isTypeSafeEnum = false;
545         // Check for type-safe enum style classes
546         if ((fieldType != null) && !isPrimitive(fieldType)) {
547           if (!hasPublicDefaultConstructor(fieldType)) {
548             Method method = getStaticValueOfMethod(fieldType);
549             if (method != null) {
550               handler = new EnumFieldHandler(fieldType, handler, method);
551               typeInfo.setImmutable(true);
552               isTypeSafeEnum = true;
553             }
554           }
555         }
556         // Reset proper TypeInfo
557         if (!isTypeSafeEnum) {
558           typeInfo = typeInfoRef.typeInfo;
559         }
560       }
561     }
562 
563     FieldDescriptorImpl fieldDesc =
564         new FieldDescriptorImpl(fieldName, typeInfo, handler, fieldMap.getTransient());
565 
566     fieldDesc.setRequired(fieldMap.getRequired());
567 
568     // set collection type as specified in mapping file
569     fieldDesc.setCollection(fieldMap.getCollection());
570 
571     // set various method informations
572     fieldDesc.setComparator(fieldMap.getComparator());
573     fieldDesc.setCreateMethod(fieldMap.getCreateMethod());
574     fieldDesc.setGetMethod(fieldMap.getGetMethod());
575     fieldDesc.setSetMethod(fieldMap.getSetMethod());
576 
577     // set direct access (if defined)
578     fieldDesc.setDirect(fieldMap.getDirect());
579 
580     // extract values for 'laziness' from field mapping
581     fieldDesc.setLazy(fieldMap.isLazy());
582 
583     // If we're using an ExtendedFieldHandler we need to set the FieldDescriptor
584     if (exfHandler != null) {
585       ((FieldHandlerFriend) exfHandler).setFieldDescriptor(fieldDesc);
586     }
587 
588     return fieldDesc;
589   }
590 
591   private FieldHandler<?> getFieldHandler(final FieldMapping fieldMap) throws MappingException {
592 
593     // If there is a custom field handler present in the mapping, that one
594     // is returned.
595     FieldHandler<?> handler = _fieldHandlers.get(fieldMap.getHandler());
596     if (handler != null) {
597 
598       if (!(handler instanceof ClonableFieldHandler
599           || handler instanceof ClonableFieldHandlerMarker)) {
600         return handler;
601       }
602 
603       String methodName = "copyFieldHandler";
604       if (handler instanceof ClonableFieldHandler) {
605         methodName = "copyInstance";
606       }
607 
608       FieldHandler<?> clonedHandler = handler;
609       Class<?> classToClone = handler.getClass();
610       try {
611         Method method;
612         method = classToClone.getMethod(methodName, (Class[]) null);
613         clonedHandler = (FieldHandler<?>) method.invoke(handler, (Object[]) null);
614         return clonedHandler;
615       } catch (Exception e) {
616         String err = "The class '" + classToClone.getName()
617             + "' must implement the ClonableFieldHandlerMarker interface.";
618         throw new MappingException(err, e);
619       }
620     }
621 
622     Class<?> handlerClass = null;
623     handlerClass = resolveType(fieldMap.getHandler());
624 
625     if (!FieldHandler.class.isAssignableFrom(handlerClass)) {
626       String err = "The class '" + fieldMap.getHandler() + "' must implement "
627           + FieldHandler.class.getName();
628       throw new MappingException(err);
629     }
630 
631     // Get default constructor to invoke. We can't use the newInstance method
632     // unfortunately because FieldHandler overloads this method
633     try {
634       Constructor<?> constructor = handlerClass.getConstructor(new Class[0]);
635       return (FieldHandler<?>) constructor.newInstance(new Object[0]);
636     } catch (Exception ex) {
637       String err =
638           "The class '" + handlerClass.getName() + "' must have a default public constructor.";
639       throw new MappingException(err);
640     }
641   }
642 
643   /**
644    * Does the given class has a public default constructor?
645    *
646    * @param type Class to check for a public default constructor.
647    * @return <code>true</code> if class has a public default constructor.
648    */
649   private boolean hasPublicDefaultConstructor(final Class<?> type) {
650     try {
651       Constructor<?> cons = type.getConstructor(EMPTY_ARGS);
652       return Modifier.isPublic(cons.getModifiers());
653     } catch (NoSuchMethodException ex) {
654       return false;
655     }
656   }
657 
658   /**
659    * Get static valueOf(String) factory method of given class.
660    *
661    * @param type Class to check for a static valueOf(String) factory method.
662    * @return Static valueOf(String) factory method or <code>null</code> if none could be found.
663    */
664   private Method getStaticValueOfMethod(final Class<?> type) {
665     try {
666       Method method = type.getMethod(VALUE_OF, STRING_ARG);
667       Class<?> returnType = method.getReturnType();
668       if (returnType == null) {
669         return null;
670       }
671       if (!type.isAssignableFrom(returnType)) {
672         return null;
673       }
674       if (!Modifier.isStatic(method.getModifiers())) {
675         return null;
676       }
677       return method;
678     } catch (NoSuchMethodException ex) {
679       return null;
680     }
681   }
682 
683   /**
684    * Creates the FieldHandler for the given FieldMapping.
685    *
686    * @param javaClass the class type of the parent of the field.
687    * @param fldType the Java class type for the field.
688    * @param fldMap the field mapping.
689    * @return the newly created FieldHandler.
690    */
691   protected final FieldHandler createFieldHandler(Class<?> javaClass, Class<?> fldType,
692       final FieldMapping fldMap, final TypeInfoReference typeInfoRef) throws MappingException {
693     // Prevent introspection of transient fields
694     if (fldMap.getTransient()) {
695       return new TransientFieldHandler();
696     }
697 
698     Class<?> collectionType = null;
699     CollectionHandler collectionHandler = null;
700     boolean colRequireGetSet = true;
701 
702     String fieldName = fldMap.getName();
703 
704     // If the field is declared as a collection, grab the collection type as
705     // well and use it to locate the field/accessor.
706     if (fldMap.getCollection() != null) {
707       String colTypeName = fldMap.getCollection().toString();
708       collectionType = CollectionHandlers.getCollectionType(colTypeName);
709       collectionHandler = CollectionHandlers.getHandler(collectionType);
710       colRequireGetSet = CollectionHandlers.isGetSetCollection(collectionType);
711       if (collectionType == Object[].class) {
712         if (fldType == null) {
713           String msg = "'type' is a required attribute for field that are " + "array collections: "
714               + fieldName;
715           throw new MappingException(msg);
716         }
717         Object obj = Array.newInstance(fldType, 0);
718         collectionType = obj.getClass();
719       }
720     }
721 
722 
723     FieldHandlerImpl handler = null;
724 
725     // If get/set methods not specified, use field names to determine them.
726     if (fldMap.getDirect()) {
727       // No accessor, map field directly.
728       Field field =
729           findField(javaClass, fieldName, (collectionType == null ? fldType : collectionType));
730       if (field == null) {
731         throw new MappingException("mapping.fieldNotAccessible", fieldName, javaClass.getName());
732       }
733       if (fldType == null) {
734         fldType = field.getType();
735       }
736 
737       typeInfoRef.typeInfo = getTypeInfo(fldType, collectionHandler, fldMap);
738 
739       handler = new FieldHandlerImpl(field, typeInfoRef.typeInfo);
740     } else if ((fldMap.getGetMethod() == null) && (fldMap.getSetMethod() == null)) {
741       // If both methods (get/set) are not specified, determine them automatically
742       if (fieldName == null) {
743         throw new MappingException("mapping.missingFieldName", javaClass.getName());
744       }
745 
746       List<Method> getSequence = new ArrayList<Method>();
747       List<Method> setSequence = new ArrayList<Method>();
748       Method getMethod = null;
749       Method setMethod = null;
750 
751       // Get method normally starts with "get", but may start with "is"
752       // if it's a boolean.
753       try {
754         // Handle nested fields
755         while (true) {
756           int point = fieldName.indexOf('.');
757           if (point < 0) {
758             break;
759           }
760 
761           String parentField = fieldName.substring(0, point);
762 
763           // Getter method for parent field
764           String methodName = GET_METHOD_PREFIX + capitalize(parentField);
765           Method method = javaClass.getMethod(methodName, (Class[]) null);
766           if (isAbstractOrStatic(method)) {
767             throw new MappingException("mapping.accessorNotAccessible", methodName,
768                 javaClass.getName());
769           }
770           getSequence.add(method);
771 
772           Class<?> nextClass = method.getReturnType();
773 
774           // Setter method for parent field
775           try {
776             methodName = SET_METHOD_PREFIX + capitalize(parentField);
777             Class<?>[] types = new Class[] {nextClass};
778             method = javaClass.getMethod(methodName, types);
779             if (isAbstractOrStatic(method)) {
780               method = null;
781             }
782           } catch (Exception ex) {
783             method = null;
784           }
785           setSequence.add(method);
786 
787           javaClass = nextClass;
788           fieldName = fieldName.substring(point + 1);
789         }
790 
791         // Find getter method for actual field
792         String methodName = GET_METHOD_PREFIX + capitalize(fieldName);
793         Class<?> returnType = (collectionType == null) ? fldType : collectionType;
794         getMethod = findAccessor(javaClass, methodName, returnType, true);
795 
796         // If getMethod is null, check for boolean type method prefix
797         if (getMethod == null) {
798           if ((fldType == Boolean.class) || (fldType == Boolean.TYPE)) {
799             methodName = IS_METHOD_PREFIX + capitalize(fieldName);
800             getMethod = findAccessor(javaClass, methodName, returnType, true);
801           }
802         }
803       } catch (MappingException ex) {
804         throw ex;
805       } catch (Exception ex) {
806         // LOG.warn("Unexpected exception", ex);
807       }
808 
809       if (getMethod == null) {
810         String getAccessor = GET_METHOD_PREFIX + capitalize(fieldName);
811         String isAccessor = IS_METHOD_PREFIX + capitalize(fieldName);
812         throw new MappingException("mapping.accessorNotFound", getAccessor + "/" + isAccessor,
813             (collectionType == null ? fldType : collectionType), javaClass.getName());
814       }
815 
816       if ((fldType == null) && (collectionType == null)) {
817         fldType = getMethod.getReturnType();
818       }
819 
820       // We try to locate a set method anyway but complain only if we need one
821       String methodName = SET_METHOD_PREFIX + capitalize(fieldName);
822       setMethod = findAccessor(javaClass, methodName,
823           (collectionType == null ? fldType : collectionType), false);
824 
825       // If we have a collection that need both set and get but we don't have a
826       // set method, we fail
827       if ((setMethod == null) && (collectionType != null) && colRequireGetSet) {
828         throw new MappingException("mapping.accessorNotFound", methodName,
829             (collectionType == null ? fldType : collectionType), javaClass.getName());
830       }
831 
832       typeInfoRef.typeInfo = getTypeInfo(fldType, collectionHandler, fldMap);
833 
834       fieldName = fldMap.getName();
835       if (fieldName == null) {
836         if (getMethod == null) {
837           fieldName = setMethod.getName();
838         } else {
839           fieldName = getMethod.getName();
840         }
841       }
842 
843       // Convert method call sequence for nested fields to arrays
844       Method[] getArray = null;
845       Method[] setArray = null;
846       if (!getSequence.isEmpty()) {
847         getArray = getSequence.toArray(new Method[getSequence.size()]);
848         setArray = setSequence.toArray(new Method[setSequence.size()]);
849       }
850 
851       // Create handler
852       handler = new FieldHandlerImpl(fieldName, getArray, setArray, getMethod, setMethod,
853           typeInfoRef.typeInfo);
854 
855       if (setMethod != null) {
856         if (setMethod.getName().startsWith(ADD_METHOD_PREFIX)) {
857           handler.setAddMethod(setMethod);
858         }
859       }
860     } else {
861       Method getMethod = null;
862       Method setMethod = null;
863 
864       // First look up the get accessors
865       if (fldMap.getGetMethod() != null) {
866         Class<?> rtype = fldType;
867         if (collectionType != null) {
868           String methodName = fldMap.getGetMethod();
869           if (methodName.startsWith(ENUM_METHOD_PREFIX)) {
870             // An enumeration method must really return a enumeration.
871             rtype = Enumeration.class;
872           } else if (methodName.startsWith(ITER_METHOD_PREFIX)) {
873             // An iterator method must really return a iterator.
874             rtype = Iterator.class;
875           } else {
876             rtype = collectionType;
877           }
878         }
879 
880         getMethod = findAccessor(javaClass, fldMap.getGetMethod(), rtype, true);
881         if (getMethod == null) {
882           throw new MappingException("mapping.accessorNotFound", fldMap.getGetMethod(), rtype,
883               javaClass.getName());
884         }
885 
886         if (fldType == null && collectionType == null) {
887           fldType = getMethod.getReturnType();
888         }
889       }
890 
891       // Second look up the set/add accessor
892       if (fldMap.getSetMethod() != null) {
893         String methodName = fldMap.getSetMethod();
894         Class<?> type = fldType;
895         if (collectionType != null) {
896           if (!methodName.startsWith(ADD_METHOD_PREFIX)) {
897             type = collectionType;
898           }
899         }
900 
901         // Set via constructor?
902         if (methodName.startsWith("%")) {
903           // Validate index value
904           int index = 0;
905 
906           String temp = methodName.substring(1);
907           try {
908             index = Integer.parseInt(temp);
909           } catch (NumberFormatException ex) {
910             throw new MappingException("mapping.invalidParameterIndex", temp);
911           }
912 
913           if ((index < 1) || (index > 9)) {
914             throw new MappingException("mapping.invalidParameterIndex", temp);
915           }
916         } else {
917           setMethod = findAccessor(javaClass, methodName, type, false);
918           if (setMethod == null) {
919             throw new MappingException("mapping.accessorNotFound", methodName, type,
920                 javaClass.getName());
921           }
922 
923           if (fldType == null) {
924             fldType = setMethod.getParameterTypes()[0];
925           }
926         }
927       }
928 
929       typeInfoRef.typeInfo = getTypeInfo(fldType, collectionHandler, fldMap);
930 
931       fieldName = fldMap.getName();
932       if (fieldName == null) {
933         if (getMethod == null) {
934           fieldName = setMethod.getName();
935         } else {
936           fieldName = getMethod.getName();
937         }
938       }
939 
940       // Create handler
941       handler =
942           new FieldHandlerImpl(fieldName, null, null, getMethod, setMethod, typeInfoRef.typeInfo);
943 
944       if (setMethod != null) {
945         if (setMethod.getName().startsWith(ADD_METHOD_PREFIX)) {
946           handler.setAddMethod(setMethod);
947         }
948       }
949     }
950 
951     // If there is a create method, add it to the field handler
952     String methodName = fldMap.getCreateMethod();
953     if (methodName != null) {
954       try {
955         Method method = javaClass.getMethod(methodName, (Class[]) null);
956         handler.setCreateMethod(method);
957       } catch (Exception ex) {
958         throw new MappingException("mapping.createMethodNotFound", methodName, javaClass.getName());
959       }
960     } else if ((fieldName != null) && !Types.isSimpleType(fldType)) {
961       try {
962         methodName = CREATE_METHOD_PREFIX + capitalize(fieldName);
963         Method method = javaClass.getMethod(methodName, (Class[]) null);
964         handler.setCreateMethod(method);
965       } catch (Exception ex) {
966         // LOG.warn ("Unexpected exception", ex);
967       }
968     }
969 
970     // If there is an has/delete method, add them to field handler
971     if (fieldName != null) {
972       try {
973         methodName = fldMap.getHasMethod();
974         if (methodName == null) {
975           methodName = HAS_METHOD_PREFIX + capitalize(fieldName);
976         }
977         Method hasMethod = javaClass.getMethod(methodName, (Class[]) null);
978 
979         if ((hasMethod.getModifiers() & Modifier.STATIC) != 0) {
980           hasMethod = null;
981         }
982 
983         Method deleteMethod = null;
984         try {
985           methodName = DELETE_METHOD_PREFIX + capitalize(fieldName);
986           deleteMethod = javaClass.getMethod(methodName, (Class[]) null);
987           if ((deleteMethod.getModifiers() & Modifier.STATIC) != 0) {
988             deleteMethod = null;
989           }
990         } catch (Exception ex) {
991           // Purposely Ignore exception we're just seeing if the method exists
992         }
993 
994         handler.setHasDeleteMethod(hasMethod, deleteMethod);
995       } catch (Exception ex) {
996         // LOG.warn("Unexpected exception", ex);
997       }
998     }
999 
1000     return handler;
1001   }
1002 
1003   private static boolean isAbstract(final Class<?> cls) {
1004     return ((cls.getModifiers() & Modifier.ABSTRACT) != 0);
1005   }
1006 
1007   private static boolean isAbstractOrStatic(final Method method) {
1008     return ((method.getModifiers() & Modifier.ABSTRACT) != 0)
1009         || ((method.getModifiers() & Modifier.STATIC) != 0);
1010   }
1011 
1012   protected TypeInfo getTypeInfo(final Class fieldType, final CollectionHandler colHandler,
1013       final FieldMapping fieldMap) throws MappingException {
1014     return new TypeInfo(Types.typeFromPrimitive(fieldType), null, null, fieldMap.getRequired(),
1015         null, colHandler, false);
1016   }
1017 
1018   /**
1019    * Returns the named field. Uses reflection to return the named field and check the field type, if
1020    * specified.
1021    *
1022    * @param javaClass The class to which the field belongs.
1023    * @param fieldName The name of the field.
1024    * @param fieldType The type of the field if known, or null.
1025    * @return The field, null if not found.
1026    * @throws MappingException The field is not accessible or is not of the specified type.
1027    */
1028   private final Field findField(final Class<?> javaClass, final String fieldName,
1029       Class<?> fieldType) throws MappingException {
1030     try {
1031       // Look up the field based on its name, make sure it's only modifier
1032       // is public. If a type was specified, match the field type.
1033       Field field = javaClass.getField(fieldName);
1034       if ((field.getModifiers() != Modifier.PUBLIC)
1035           && (field.getModifiers() != (Modifier.PUBLIC | Modifier.VOLATILE))) {
1036         throw new MappingException("mapping.fieldNotAccessible", fieldName, javaClass.getName());
1037       }
1038 
1039       if (fieldType == null) {
1040         fieldType = Types.typeFromPrimitive(field.getType());
1041       } else {
1042         Class<?> ft1 = Types.typeFromPrimitive(fieldType);
1043         Class<?> ft2 = Types.typeFromPrimitive(field.getType());
1044         if ((ft1 != ft2) && (fieldType != Serializable.class)) {
1045           throw new MappingException("mapping.fieldTypeMismatch", field, fieldType.getName());
1046         }
1047       }
1048       return field;
1049     } catch (NoSuchFieldException ex) {
1050       return null;
1051     } catch (SecurityException ex) {
1052       return null;
1053     }
1054   }
1055 
1056   /**
1057    * Returns the named accessor. Uses reflection to return the named accessor and check the return
1058    * value or parameter type, if specified.
1059    *
1060    * @param javaClass The class to which the field belongs.
1061    * @param methodName The name of the accessor method.
1062    * @param fieldType The type of the field if known, or null.
1063    * @param getMethod True if get method, false if set method.
1064    * @return The method, null if not found.
1065    * @throws MappingException The method is not accessible or is not of the specified type.
1066    */
1067   public static final Method findAccessor(final Class<?> javaClass, final String methodName,
1068       Class<?> fieldType, final boolean getMethod) throws MappingException {
1069     try {
1070       Method method = null;
1071 
1072       if (getMethod) {
1073         // Get method: look for the named method or prepend get to the method
1074         // name. Look up the field and potentially check the return type.
1075         method = javaClass.getMethod(methodName, new Class[0]);
1076 
1077         // The MapItem is used to handle the contents of maps. Since the MapItem
1078         // has to use Object for its methods we cannot (but also don't have to)
1079         // check for correct types.
1080         if (javaClass == MapItem.class) {
1081           if (methodName.equals("getKey")) {
1082             return method;
1083           }
1084           if (methodName.equals("getValue")) {
1085             return method;
1086           }
1087         }
1088 
1089         if (fieldType == null) {
1090           fieldType = Types.typeFromPrimitive(method.getReturnType());
1091         } else {
1092           fieldType = Types.typeFromPrimitive(fieldType);
1093           Class<?> returnType = Types.typeFromPrimitive(method.getReturnType());
1094 
1095           // -- First check against whether the declared type is
1096           // -- an interface or abstract class. We also check
1097           // -- type as Serializable for CMP 1.1 compatibility.
1098           if (fieldType.isInterface() || ((fieldType.getModifiers() & Modifier.ABSTRACT) != 0)
1099               || (fieldType == java.io.Serializable.class)) {
1100 
1101             if (!fieldType.isAssignableFrom(returnType)) {
1102               throw new MappingException("mapping.accessorReturnTypeMismatch", method,
1103                   fieldType.getName());
1104             }
1105           } else {
1106             if (!returnType.isAssignableFrom(fieldType)) {
1107               throw new MappingException("mapping.accessorReturnTypeMismatch", method,
1108                   fieldType.getName());
1109             }
1110           }
1111         }
1112       } else {
1113         // Set method: look for the named method or prepend set to the method
1114         // name. If the field type is know, look up a suitable method. If the
1115         // field type is unknown, lookup the first method with that name and
1116         // one parameter.
1117         Class<?> fieldTypePrimitive = null;
1118         if (fieldType != null) {
1119           fieldTypePrimitive = Types.typeFromPrimitive(fieldType);
1120           try {
1121             method = javaClass.getMethod(methodName, new Class[] {fieldType});
1122           } catch (Exception ex) {
1123             try {
1124               method = javaClass.getMethod(methodName, new Class[] {fieldTypePrimitive});
1125             } catch (Exception ex2) {
1126               // LOG.warn("Unexpected exception", ex2);
1127             }
1128           }
1129 
1130           /*
1131            * Replace above try catch block with the following one to resolve CASTOR-1141 for jdo
1132            * part. After this change you can use this method also in FieldMolder and remove its
1133            * findAccessor() method to omit code duplication. Having said that this introduces a
1134            * problem with xmlctf that have to resolved first. // first check for setter with
1135            * reference type (e.g. setXxx(Integer)) try { method = javaClass.getMethod(methodName,
1136            * new Class[] {fieldTypePrimitive}); } catch (Exception ex) { // if setter for reference
1137            * type could not be found // try to find one for primitive type (e.g. setXxx(int)) try {
1138            * method = javaClass.getMethod(methodName, new Class[] {fieldType}); } catch (Exception
1139            * ex2) { // LOG.warn("Unexpected exception", ex2); } }
1140            */
1141         }
1142 
1143         if (method == null) {
1144           Method[] methods = javaClass.getMethods();
1145           for (int i = 0; i < methods.length; ++i) {
1146             if (methods[i].getName().equals(methodName)) {
1147               Class<?>[] paramTypes = methods[i].getParameterTypes();
1148               if (paramTypes.length != 1) {
1149                 continue;
1150               }
1151 
1152               Class<?> paramType = Types.typeFromPrimitive(paramTypes[0]);
1153 
1154               if (fieldType == null) {
1155                 method = methods[i];
1156                 break;
1157               } else if (paramType.isAssignableFrom(fieldTypePrimitive)) {
1158                 method = methods[i];
1159                 break;
1160               } else if (fieldType.isInterface() || isAbstract(fieldType)) {
1161                 if (fieldTypePrimitive.isAssignableFrom(paramType)) {
1162                   method = methods[i];
1163                   break;
1164                 }
1165               }
1166             }
1167           }
1168 
1169           if (method == null) {
1170             return null;
1171           }
1172         }
1173       }
1174 
1175       // Make sure method is public and not static.
1176       // (note: Class.getMethod() returns only public methods).
1177       if ((method.getModifiers() & Modifier.STATIC) != 0) {
1178         throw new MappingException("mapping.accessorNotAccessible", methodName,
1179             javaClass.getName());
1180       }
1181       return method;
1182     } catch (MappingException ex) {
1183       throw ex;
1184     } catch (Exception ex) {
1185       return null;
1186     }
1187   }
1188 
1189   private static final String capitalize(final String name) {
1190     char first = name.charAt(0);
1191     if (Character.isUpperCase(first)) {
1192       return name;
1193     }
1194     return Character.toUpperCase(first) + name.substring(1);
1195   }
1196 
1197   /**
1198    * Returns a list of column names that are part of the identity.
1199    *
1200    * @param ids Known identity names.
1201    * @param clsMap The class mapping.
1202    * @return List of identity column names.
1203    */
1204   public static final String[] getIdentityColumnNames(final String[] ids,
1205       final ClassMapping clsMap) {
1206 
1207     String[] idNames = ids;
1208 
1209     if ((ids == null) || (ids.length == 0)) {
1210       ClassChoice classChoice = clsMap.getClassChoice();
1211       if (classChoice == null) {
1212         classChoice = new ClassChoice();
1213       }
1214 
1215       FieldMapping[] fieldMappings = classChoice.getFieldMapping();
1216 
1217       List<String> idNamesList = new ArrayList<String>();
1218       for (FieldMapping fieldMapping : fieldMappings) {
1219         if (fieldMapping.getIdentity() == true) {
1220           idNamesList.add(fieldMapping.getName());
1221         }
1222       }
1223 
1224       if (!idNamesList.isEmpty()) {
1225         idNames = new String[idNamesList.size()];
1226         idNames = idNamesList.toArray(idNames);
1227       }
1228     }
1229 
1230     return idNames;
1231   }
1232 
1233   /**
1234    * Returns true if the given class should be treated as a primitive type
1235    * 
1236    * @return true if the given class should be treated as a primitive type
1237    */
1238   protected static final boolean isPrimitive(final Class<?> type) {
1239     if (type.isPrimitive()) {
1240       return true;
1241     }
1242     if ((type == Boolean.class) || (type == Character.class)) {
1243       return true;
1244     }
1245     return (type.getSuperclass() == Number.class);
1246   }
1247 
1248   /**
1249    * A class used to by the createFieldHandler method in order to save the reference of the TypeInfo
1250    * that was used.
1251    */
1252   public class TypeInfoReference {
1253     public TypeInfo typeInfo = null;
1254   }
1255 
1256   public void setInternalContext(final InternalContext internalContext) {
1257     _internalContext = internalContext;
1258   }
1259 
1260   public InternalContext getInternalContext() {
1261     return _internalContext;
1262   }
1263 
1264 }