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   * $Id$
34   */
35  package org.exolab.castor.xml;
36  
37  import java.beans.PropertyChangeEvent;
38  import java.beans.PropertyChangeListener;
39  import java.io.PrintWriter;
40  import java.lang.reflect.Field;
41  import java.lang.reflect.Method;
42  import java.lang.reflect.Modifier;
43  import java.util.ArrayList;
44  import java.util.Enumeration;
45  import java.util.Hashtable;
46  import java.util.List;
47  import java.util.Vector;
48  
49  import org.castor.xml.InternalContext;
50  import org.castor.xml.JavaNaming;
51  import org.castor.xml.XMLProperties;
52  import org.castor.xml.XMLNaming;
53  import org.exolab.castor.mapping.CollectionHandler;
54  import org.exolab.castor.mapping.FieldHandler;
55  import org.exolab.castor.mapping.FieldHandlerFactory;
56  import org.exolab.castor.mapping.GeneralizedFieldHandler;
57  import org.exolab.castor.mapping.MappingException;
58  import org.exolab.castor.mapping.TypeConvertor;
59  import org.exolab.castor.mapping.loader.CollectionHandlers;
60  import org.exolab.castor.mapping.loader.FieldHandlerImpl;
61  import org.exolab.castor.mapping.loader.TypeInfo;
62  import org.exolab.castor.util.ReflectionUtil;
63  import org.exolab.castor.xml.descriptors.CoreDescriptors;
64  import org.exolab.castor.xml.handlers.ContainerFieldHandler;
65  import org.exolab.castor.xml.handlers.DateFieldHandler;
66  import org.exolab.castor.xml.handlers.DefaultFieldHandlerFactory;
67  import org.exolab.castor.xml.util.ContainerElement;
68  import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
69  import org.exolab.castor.xml.util.XMLFieldDescriptorImpl;
70  
71  /**
72   * A Helper class for the Marshaller and Unmarshaller, basically the common code base between the
73   * two. This class handles the introspection to dynamically create descriptors.
74   *
75   * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
76   * @version $Revision$ $Date: 2006-04-14 04:14:43 -0600 (Fri, 14 Apr 2006) $
77   */
78  public final class Introspector implements PropertyChangeListener {
79  
80    /** The default FieldHandlerFactory. */
81    private static final FieldHandlerFactory DEFAULT_HANDLER_FACTORY =
82        new DefaultFieldHandlerFactory();
83  
84    private static final Class[] EMPTY_CLASS_ARGS = new Class[0];
85  
86    /** Name of the java.util.List collection. */
87    private static final String LIST = "java.util.List";
88  
89    /** Name of the java.util.Map collection. */
90    private static final String MAP = "java.util.Map";
91  
92    /** Name of the java.util.Map collection. */
93    private static final String SET_COLLECTION = "java.util.Set";
94  
95    /** Used as a prefix for the name of a container field. */
96    private static final String COLLECTION_WRAPPER_PREFIX = "##container_for_";
97  
98  
99    /**
100    * The default flag indicating whether or not collections (arrays, vectors, etc) should be wrapped
101    * in a container element.
102    *
103    * @see _wrapCollectionsInContainer
104    */
105   private static final boolean WRAP_COLLECTIONS_DEFAULT = false;
106 
107   /**
108    * The set of available collections to use during introspection. JDK dependant.
109    **/
110   private static final Class[] COLLECTIONS = loadCollections();
111 
112 
113   /**
114    * The default naming conventions.
115    */
116   private static XMLNaming _defaultNaming = null;
117 
118   /**
119    * The naming conventions to use.
120    */
121   private XMLNaming _xmlNaming = null;
122 
123   /**
124    * The NodeType to use for primitives.
125    */
126   private NodeType _primitiveNodeType = null;
127 
128 
129   /**
130    * The variable flag indicating whether or not collections (arrays, vectors, etc) should be
131    * wrapped in a container element. For example:
132    *
133    * <pre>
134    *    &lt;foos&gt;
135    *       &lt;foo&gt;foo1&lt;/foo&gt;
136    *       &lt;foo&gt;foo2&lt;/foo&gt;
137    *    &lt;/foos&gt;
138    *
139    *   instead of the default:
140    *
141    *    &lt;foos&gt;foo1&lt;foos&gt;
142    *    &lt;foos&gt;foo2&lt;/foos&gt;
143    *
144    * </pre>
145    *
146    */
147   private boolean _wrapCollectionsInContainer = WRAP_COLLECTIONS_DEFAULT;
148 
149 
150   /**
151    * The set of registered FieldHandlerFactory instances
152    */
153   private Vector<FieldHandlerFactory> _handlerFactoryList = null;
154 
155   /**
156    * The set of registered FieldHandlerFactory instances associated with their supported types
157    */
158   private Hashtable<Class, FieldHandlerFactory> _handlerFactoryMap = null;
159 
160   /**
161    * A flag indicating that MapKeys should be saved. To remain backward compatible this may be
162    * disable via the castor.properties.
163    */
164   private boolean _saveMapKeys = true;
165 
166   /**
167    * Specifies class loader to be used.
168    */
169   private ClassLoader _classLoader = null;
170 
171   /**
172    * The {@link JavaNaming} to be used.
173    */
174   private JavaNaming _javaNaming;
175 
176   private InternalContext _internalContext;
177 
178   /**
179    * Creates a new instance of the Introspector.
180    */
181   public Introspector() {
182     this(null);
183   } // -- Introspector
184 
185   /**
186    * Creates a new instance of the Introspector.
187    *
188    * @param classLoader
189    */
190   public Introspector(final ClassLoader classLoader) {
191     super();
192     _classLoader = classLoader;
193     init();
194   } // -- Introspector
195 
196   private void init() {
197     if (_internalContext != null) {
198       _javaNaming = _internalContext.getJavaNaming();
199       _xmlNaming = _internalContext.getXMLNaming();
200       setPrimitiveNodeType(_internalContext.getPrimitiveNodeType());
201       _wrapCollectionsInContainer = _internalContext
202           .getBooleanProperty(XMLProperties.WRAP_COLLECTIONS_PROPERTY).booleanValue();
203       _saveMapKeys =
204           _internalContext.getBooleanProperty(XMLProperties.SAVE_MAP_KEYS).booleanValue();
205     }
206   } // -- init
207 
208   public void setInternalContext(InternalContext internalContext) {
209     if (internalContext == null) {
210       if (this._internalContext != null) {
211         this._internalContext.removePropertyChangeListener(this);
212       }
213     } else {
214       if (this._internalContext != internalContext) {
215         if (this._internalContext != null) {
216           this._internalContext.removePropertyChangeListener(this);
217         }
218         internalContext.addPropertyChangeListener(this);
219       }
220     }
221 
222     _internalContext = internalContext;
223     init();
224   }
225 
226   /**
227    * Registers the given "generalized" FieldHandlerFactory with this Introspector.
228    *
229    * @param factory the FieldHandlerFactory to add to this introspector
230    * @throws IllegalArgumentException if the given factory is null
231    */
232   public synchronized void addFieldHandlerFactory(FieldHandlerFactory factory) {
233     if (factory == null) {
234       String err = "The argument 'factory' must not be null.";
235       throw new IllegalArgumentException(err);
236     }
237     if (_handlerFactoryList == null) {
238       _handlerFactoryList = new Vector<>();
239     }
240     _handlerFactoryList.add(factory);
241     registerHandlerFactory(factory);
242   } // -- addFieldHandlerFactory
243 
244   /**
245    * Returns the NodeType for java primitives
246    *
247    * @return the NodeType for java primitives
248    **/
249   public NodeType getPrimitiveNodeType() {
250     return _primitiveNodeType;
251   } // -- getPrimitiveNodeType
252 
253   /**
254    * Creates an XMLClassDescriptor for the given class by using Reflection.
255    * 
256    * @param c the Class to create the XMLClassDescriptor for
257    * @return the new XMLClassDescriptor created for the given class
258    * @exception MarshalException when an error occurs during the creation of the ClassDescriptor.
259    **/
260   public XMLClassDescriptor generateClassDescriptor(Class c) throws MarshalException {
261     return generateClassDescriptor(c, null);
262   } // -- generateClassDescriptor(Class)
263 
264   /**
265    * Creates an XMLClassDescriptor for the given class by using Reflection.
266    * 
267    * @param c the Class to create the XMLClassDescriptor for
268    * @param errorWriter a PrintWriter to print error information to
269    * @return the new XMLClassDescriptor created for the given class
270    * @exception MarshalException when an error occurs during the creation of the ClassDescriptor.
271    **/
272   public XMLClassDescriptor generateClassDescriptor(Class c, PrintWriter errorWriter)
273       throws MarshalException {
274 
275     if (c == null)
276       return null;
277 
278     // -- handle arrays
279     if (c.isArray())
280       return null;
281 
282     // -- handle base objects
283     if ((c == Void.class) || (c == Class.class) || (c == Object.class)) {
284       throw new MarshalException(MarshalException.BASE_CLASS_OR_VOID_ERR);
285     }
286 
287     // -- handle core descriptors
288     XMLClassDescriptor coreDesc = CoreDescriptors.getDescriptor(c);
289     if (coreDesc != null)
290       return coreDesc;
291 
292 
293     // --------------------------/
294     // - handle complex objects -/
295     // --------------------------/
296 
297     XMLClassDescriptorImpl classDesc = new IntrospectedXMLClassDescriptor(c);
298 
299     Method[] methods = c.getMethods();
300     List<XMLFieldDescriptorImpl> dateDescriptors = new ArrayList<>(3);
301     Hashtable<String, MethodSet> methodSets = new Hashtable<>();
302 
303     int methodCount = 0;
304 
305     Class superClass = c.getSuperclass();
306     Class[] interfaces = c.getInterfaces();
307 
308 
309 
310     // -- create method sets
311     for (int i = 0; i < methods.length; i++) {
312       Method method = methods[i];
313 
314       Class owner = method.getDeclaringClass();
315 
316       // -- ignore methods from super-class, that will be
317       // -- introspected separately, if necessary
318       if (owner != c) {
319         // -- if declaring class is anything but
320         // -- an interface, than just continue,
321         // -- the field comes from a super class
322         // -- (e.g. java.lang.Object)
323         if (!owner.isInterface())
324           continue;
325 
326         // -- owner is an interface, is it an
327         // -- interface this class implements
328         // -- or a parent class?
329         if (interfaces.length > 0) {
330           boolean found = false;
331           for (int count = 0; count < interfaces.length; count++) {
332             if (interfaces[count] == owner) {
333               found = true;
334               break;
335             }
336           }
337           if (!found)
338             continue;
339         }
340       } else {
341         // -- look for overloaded methods
342         if (superClass != null) {
343           Class[] args = method.getParameterTypes();
344           String name = method.getName();
345           Method tmpMethod = null;
346           try {
347             tmpMethod = superClass.getMethod(name, args);
348           } catch (NoSuchMethodException nsme) {
349             // -- do nothing
350           }
351           if (tmpMethod != null)
352             continue;
353         }
354       }
355 
356 
357       // -- if method is static...ignore
358       if ((method.getModifiers() & Modifier.STATIC) != 0)
359         continue;
360 
361       String methodName = method.getName();
362 
363       // -- read methods
364       if (methodName.startsWith(JavaNaming.METHOD_PREFIX_GET)) {
365         if (method.getParameterTypes().length != 0) {
366           continue;
367         }
368         // -- disable direct field access
369         ++methodCount;
370         // -- make sure return type is "descriptable"
371         // -- and not null
372         Class type = method.getReturnType();
373         if (type == null)
374           continue;
375         if (!isDescriptable(type))
376           continue;
377 
378         // -- caclulate name from Method name
379         String fieldName = methodName.substring(3);
380         fieldName = _javaNaming.toJavaMemberName(fieldName);
381 
382         MethodSet methodSet = methodSets.get(fieldName);
383         if (methodSet == null) {
384           methodSet = new MethodSet(fieldName);
385           methodSets.put(fieldName, methodSet);
386         }
387         methodSet._get = method;
388       } else if (methodName.startsWith(JavaNaming.METHOD_PREFIX_IS)) {
389         if (method.getParameterTypes().length != 0)
390           continue;
391         // -- make sure type is not null, and a boolean
392         Class type = method.getReturnType();
393         if (type == null)
394           continue;
395         if (type.isPrimitive()) {
396           if (type != Boolean.TYPE)
397             continue;
398         } else {
399           if (type != Boolean.class)
400             continue;
401         }
402         // -- disable direct field access
403         ++methodCount;
404         // -- caclulate name from Method name
405         String fieldName = methodName.substring(JavaNaming.METHOD_PREFIX_IS.length());
406         fieldName = _javaNaming.toJavaMemberName(fieldName);
407 
408         MethodSet methodSet = methodSets.get(fieldName);
409         if (methodSet == null) {
410           methodSet = new MethodSet(fieldName);
411           methodSets.put(fieldName, methodSet);
412         }
413         methodSet._get = method;
414       }
415       // -----------------------------------/
416       // -- write methods (collection item)
417       else if (methodName.startsWith(JavaNaming.METHOD_PREFIX_ADD)) {
418         if (method.getParameterTypes().length != 1)
419           continue;
420         // -- disable direct field access
421         ++methodCount;
422         // -- make sure parameter type is "descriptable"
423         if (!isDescriptable(method.getParameterTypes()[0]))
424           continue;
425         // -- caclulate name from Method name
426         String fieldName = methodName.substring(3);
427         fieldName = _javaNaming.toJavaMemberName(fieldName);
428         MethodSet methodSet = methodSets.get(fieldName);
429         if (methodSet == null) {
430           methodSet = new MethodSet(fieldName);
431           methodSets.put(fieldName, methodSet);
432         }
433         methodSet._add = method;
434       }
435       // -- write method (singleton or collection)
436       else if (methodName.startsWith(JavaNaming.METHOD_PREFIX_SET)) {
437         if (method.getParameterTypes().length != 1) {
438           continue;
439         }
440         // -- disable direct field access
441         ++methodCount;
442         // -- make sure parameter type is "descriptable"
443         if (!isDescriptable(method.getParameterTypes()[0]))
444           continue;
445         // -- caclulate name from Method name
446         String fieldName = methodName.substring(3);
447         fieldName = _javaNaming.toJavaMemberName(fieldName);
448         MethodSet methodSet = methodSets.get(fieldName);
449         if (methodSet == null) {
450           methodSet = new MethodSet(fieldName);
451           methodSets.put(fieldName, methodSet);
452         }
453         methodSet._set = method;
454       } else if (methodName.startsWith(JavaNaming.METHOD_PREFIX_CREATE)) {
455         if (method.getParameterTypes().length != 0)
456           continue;
457         Class type = method.getReturnType();
458         // -- make sure return type is "descriptable"
459         // -- and not null
460         if (!isDescriptable(type))
461           continue;
462         // -- caclulate name from Method name
463         String fieldName = methodName.substring(JavaNaming.METHOD_PREFIX_CREATE.length());
464         fieldName = _javaNaming.toJavaMemberName(fieldName);
465         MethodSet methodSet = methodSets.get(fieldName);
466         if (methodSet == null) {
467           methodSet = new MethodSet(fieldName);
468           methodSets.put(fieldName, methodSet);
469         }
470         methodSet._create = method;
471       }
472     } // -- end create method sets
473 
474 
475     // -- Loop Through MethodSets and create
476     // -- descriptors
477     Enumeration<MethodSet> enumeration = methodSets.elements();
478 
479     while (enumeration.hasMoreElements()) {
480 
481       MethodSet methodSet = enumeration.nextElement();
482 
483       // -- create XMLFieldDescriptor
484       String xmlName = _xmlNaming.toXMLName(methodSet._fieldName);
485 
486       boolean isCollection = false;
487 
488       // -- calculate class type
489       // -- 1st check for add-method, then set or get method
490       Class type = null;
491       if (methodSet._add != null) {
492         type = methodSet._add.getParameterTypes()[0];
493         isCollection = true;
494       }
495 
496       // -- if there was no add method, use get/set methods
497       // -- to calculate type.
498       if (type == null) {
499         if (methodSet._get != null) {
500           type = methodSet._get.getReturnType();
501         } else if (methodSet._set != null) {
502           type = methodSet._set.getParameterTypes()[0];
503         } else {
504           // -- if we make it here, the only method found
505           // -- was a create method, which is useless by itself.
506           continue;
507         }
508       }
509 
510       // -- Handle Collections
511       isCollection = (isCollection || isCollection(type));
512 
513       TypeInfo typeInfo = null;
514       CollectionHandler colHandler = null;
515 
516       // -- If the type is a collection and there is no add method,
517       // -- then we obtain a CollectionHandler
518       if (isCollection && (methodSet._add == null)) {
519 
520         try {
521           colHandler = CollectionHandlers.getHandler(type);
522         } catch (MappingException mx) {
523           // -- No collection handler available,
524           // -- proceed anyway...
525         }
526 
527         // -- Find component type
528         if (type.isArray()) {
529           // -- Byte arrays are handled as a special case
530           // -- so don't use CollectionHandler
531           if (type.getComponentType() == Byte.TYPE) {
532             colHandler = null;
533           } else
534             type = type.getComponentType();
535         }
536       }
537 
538       typeInfo = new TypeInfo(type, null, null, false, null, colHandler);
539 
540       // -- Create FieldHandler first, before the XMLFieldDescriptor
541       // -- in case we need to use a custom handler
542 
543       FieldHandler handler = null;
544       boolean customHandler = false;
545       try {
546         handler = new FieldHandlerImpl(methodSet._fieldName, null, null, methodSet._get,
547             methodSet._set, typeInfo);
548         // -- clean up
549         if (methodSet._add != null)
550           ((FieldHandlerImpl) handler).setAddMethod(methodSet._add);
551 
552         if (methodSet._create != null)
553           ((FieldHandlerImpl) handler).setCreateMethod(methodSet._create);
554 
555         // -- handle Hashtable/Map
556         if (isCollection && _saveMapKeys && isMapCollection(type)) {
557           ((FieldHandlerImpl) handler).setConvertFrom(new IdentityConvertor());
558         }
559 
560         // -- look for GeneralizedFieldHandler
561         FieldHandlerFactory factory = getHandlerFactory(type);
562         if (factory != null) {
563           GeneralizedFieldHandler gfh = factory.createFieldHandler(type);
564           if (gfh != null) {
565             gfh.setFieldHandler(handler);
566             handler = gfh;
567             customHandler = true;
568             // -- swap type with the type specified by the
569             // -- custom field handler
570             if (gfh.getFieldType() != null) {
571               type = gfh.getFieldType();
572             }
573           }
574         }
575 
576       } catch (MappingException mx) {
577         throw new MarshalException(mx);
578       }
579 
580 
581       XMLFieldDescriptorImpl fieldDesc = createFieldDescriptor(type, methodSet._fieldName, xmlName);
582 
583       if (isCollection) {
584         fieldDesc.setMultivalued(true);
585         fieldDesc.setNodeType(NodeType.Element);
586       }
587 
588       // -- check for instances of java.util.Date
589       if (java.util.Date.class.isAssignableFrom(type)) {
590         // handler = new DateFieldHandler(handler);
591         if (!customHandler) {
592           dateDescriptors.add(fieldDesc);
593         }
594       }
595 
596       fieldDesc.setHandler(handler);
597 
598       // -- enable use parent namespace if explicit one doesn't exist
599       fieldDesc.setUseParentsNamespace(true);
600 
601       // -- Wrap collections?
602       if (isCollection && _wrapCollectionsInContainer) {
603         String fieldName = COLLECTION_WRAPPER_PREFIX + methodSet._fieldName;
604         // -- If we have a field 'c' that is a collection and
605         // -- we want to wrap that field in an element <e>, we
606         // -- need to create a field descriptor for
607         // -- an object that represents the element <e> and
608         // -- acts as a go-between from the parent of 'c'
609         // -- denoted as P(c) and 'c' itself
610         //
611         // object model: P(c) -> c
612         // xml : <p><e><c></e><p>
613 
614         // -- Make new class descriptor for the field that
615         // -- will represent the container element <e>
616         Class cType = ContainerElement.class;
617         XMLClassDescriptorImpl containerClassDesc = new XMLClassDescriptorImpl(cType);
618 
619         // -- add the field descriptor to our new class descriptor
620         containerClassDesc.addFieldDescriptor(fieldDesc);
621         // -- nullify xmlName so that auto-naming will be enabled,
622         // -- we can't do this in the constructor because
623         // -- XMLFieldDescriptorImpl will create a default one.
624         fieldDesc.setXMLName(null);
625         fieldDesc.setMatches("*");
626 
627         // -- wrap the field handler in a special container field
628         // -- handler that will actually do the delegation work
629         FieldHandler cHandler = new ContainerFieldHandler(handler);
630         fieldDesc.setHandler(cHandler);
631 
632         fieldDesc = createFieldDescriptor(cType, fieldName, xmlName);
633         fieldDesc.setClassDescriptor(containerClassDesc);
634         fieldDesc.setHandler(cHandler);
635 
636         // -- enable use parent namespace if explicit one doesn't exist
637         fieldDesc.setUseParentsNamespace(true);
638 
639       }
640       // -- add FieldDescriptor to ClassDescriptor
641       classDesc.addFieldDescriptor(fieldDesc);
642 
643 
644     } // -- end of method loop
645 
646     // -- If we didn't find any methods we can try
647     // -- direct field access
648     if (methodCount == 0) {
649 
650       Field[] fields = c.getFields();
651       Hashtable<String, XMLFieldDescriptorImpl> descriptors = new Hashtable<>();
652       for (int i = 0; i < fields.length; i++) {
653         Field field = fields[i];
654 
655         Class owner = field.getDeclaringClass();
656 
657         // -- ignore fields from super-class, that will be
658         // -- introspected separately, if necessary
659         if (owner != c) {
660           // -- if declaring class is anything but
661           // -- an interface, than just continue,
662           // -- the field comes from a super class
663           // -- (e.g. java.lang.Object)
664           if (!owner.isInterface())
665             continue;
666 
667           // -- owner is an interface, is it an
668           // -- interface this class implements
669           // -- or a parent class?
670           if (interfaces.length > 0) {
671             boolean found = false;
672             for (int count = 0; count < interfaces.length; count++) {
673               if (interfaces[count] == owner) {
674                 found = true;
675                 break;
676               }
677             }
678             if (!found)
679               continue;
680           }
681         }
682 
683         // -- make sure field is not transient or static final
684         int modifiers = field.getModifiers();
685         if (Modifier.isTransient(modifiers))
686           continue;
687         if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))
688           continue;
689 
690         Class type = field.getType();
691 
692 
693 
694         if (!isDescriptable(type))
695           continue;
696 
697         // -- Built-in support for JDK 1.1 Collections
698         // -- we need to a pluggable interface for
699         // -- JDK 1.2+
700         boolean isCollection = isCollection(type);
701 
702 
703         TypeInfo typeInfo = null;
704         CollectionHandler colHandler = null;
705 
706         // -- If the type is a collection and there is no add method,
707         // -- then we obtain a CollectionHandler
708         if (isCollection) {
709 
710           try {
711             colHandler = CollectionHandlers.getHandler(type);
712           } catch (MappingException mx) {
713             // -- No CollectionHandler available, continue
714             // -- without one...
715           }
716 
717           // -- Find component type
718           if (type.isArray()) {
719             // -- Byte arrays are handled as a special case
720             // -- so don't use CollectionHandler
721             if (type.getComponentType() == Byte.TYPE) {
722               colHandler = null;
723             } else
724               type = type.getComponentType();
725 
726           }
727         }
728 
729         String fieldName = field.getName();
730         String xmlName = _xmlNaming.toXMLName(fieldName);
731 
732         // -- Create FieldHandler first, before the XMLFieldDescriptor
733         // -- in case we need to use a custom handler
734 
735         typeInfo = new TypeInfo(type, null, null, false, null, colHandler);
736 
737         FieldHandler handler = null;
738         boolean customHandler = false;
739         try {
740           handler = new FieldHandlerImpl(field, typeInfo);
741 
742           // -- handle Hashtable/Map
743           if (isCollection && _saveMapKeys && isMapCollection(type)) {
744             ((FieldHandlerImpl) handler).setConvertFrom(new IdentityConvertor());
745           }
746 
747           // -- look for GeneralizedFieldHandler
748           FieldHandlerFactory factory = getHandlerFactory(type);
749           if (factory != null) {
750             GeneralizedFieldHandler gfh = factory.createFieldHandler(type);
751             if (gfh != null) {
752               gfh.setFieldHandler(handler);
753               handler = gfh;
754               customHandler = true;
755               // -- swap type with the type specified by the
756               // -- custom field handler
757               if (gfh.getFieldType() != null) {
758                 type = gfh.getFieldType();
759               }
760             }
761           }
762         } catch (MappingException mx) {
763           throw new MarshalException(mx);
764         }
765 
766         XMLFieldDescriptorImpl fieldDesc = createFieldDescriptor(type, fieldName, xmlName);
767 
768         if (isCollection) {
769           fieldDesc.setNodeType(NodeType.Element);
770           fieldDesc.setMultivalued(true);
771         }
772         descriptors.put(xmlName, fieldDesc);
773         classDesc.addFieldDescriptor(fieldDesc);
774         fieldDesc.setHandler(handler);
775 
776         // -- enable use parent namespace if explicit one doesn't exist
777         fieldDesc.setUseParentsNamespace(true);
778 
779         // -- check for instances of java.util.Date
780         if (java.util.Date.class.isAssignableFrom(type)) {
781           if (!customHandler) {
782             dateDescriptors.add(fieldDesc);
783           }
784         }
785 
786       }
787     } // -- end of direct field access
788 
789 
790     // -- A temporary fix for java.util.Date
791     if (dateDescriptors != null) {
792       for (XMLFieldDescriptorImpl fieldDesc : dateDescriptors) {
793         FieldHandler handler = fieldDesc.getHandler();
794         fieldDesc.setImmutable(true);
795         DateFieldHandler dfh = new DateFieldHandler(handler);
796 
797         // -- patch for java.sql.Date
798         Class type = fieldDesc.getFieldType();
799         if (java.sql.Date.class.isAssignableFrom(type)) {
800           dfh.setUseSQLDate(true);
801         }
802         fieldDesc.setHandler(dfh);
803       }
804     }
805 
806     // -- Add reference to superclass...if necessary
807     if ((superClass != null) && (superClass != Void.class) && (superClass != Object.class)
808         && (superClass != Class.class)) {
809       try {
810         XMLClassDescriptor parent = generateClassDescriptor(superClass, errorWriter);
811         if (parent != null) {
812           classDesc.setExtends(parent);
813         }
814       } catch (MarshalException mx) {
815         // -- Ignore for now.
816       }
817 
818     }
819 
820     return classDesc;
821   } // -- generateClassDescriptor
822 
823   /**
824    * Removes the given FieldHandlerFactory from this Introspector
825    *
826    * @param factory the FieldHandlerFactory to remove
827    * @return true if the given FieldHandlerFactory was removed, or false otherwise.
828    * @throws IllegalArgumentException if the given factory is null
829    */
830   public synchronized boolean removeFieldHandlerFactory(FieldHandlerFactory factory) {
831     if (factory == null) {
832       String err = "The argument 'factory' must not be null.";
833       throw new IllegalArgumentException(err);
834     }
835 
836     // -- if list is null, just return
837     if (_handlerFactoryList == null)
838       return false;
839 
840     if (_handlerFactoryList.remove(factory)) {
841       // -- re-register remaining handlers
842       _handlerFactoryMap.clear();
843       for (FieldHandlerFactory tmp : _handlerFactoryList) {
844         registerHandlerFactory(tmp);
845       }
846       return true;
847     }
848     return false;
849   } // -- removeFieldHandlerFactory
850 
851 
852   /**
853    * Sets whether or not collections (arrays, vectors, etc) should be wrapped in a container
854    * element. For example:
855    *
856    * <pre>
857    *
858    *    &lt;foos&gt;
859    *       &lt;foo&gt;foo1&lt;/foo&gt;
860    *       &lt;foo&gt;foo2&lt;/foo&gt;
861    *    &lt;/foos&gt;
862    *
863    *   instead of the default:
864    *
865    *    &lt;foos&gt;foo1&lt;foos&gt;
866    *    &lt;foos&gt;foo2&lt;/foos&gt;
867    *
868    * </pre>
869    *
870    * @param wrapCollections a boolean that when true indicates collections should be wrapped in a
871    *        container element.
872    *
873    */
874   public void setWrapCollections(boolean wrapCollections) {
875     _wrapCollectionsInContainer = wrapCollections;
876   } // -- setWrapCollections
877 
878   /**
879    * Returns true if the given XMLClassDescriptor was created via introspection
880    **/
881   public static boolean introspected(XMLClassDescriptor descriptor) {
882     return (descriptor instanceof IntrospectedXMLClassDescriptor);
883   } // -- introspected
884 
885   /**
886    * Returns true if the given Class can be marshalled.
887    *
888    * @param type the Class to check marshallability for.
889    * @return true if the given Class can be marshalled.
890    **/
891   public static boolean marshallable(Class type) {
892 
893     // -- make sure type is not Void, or Class;
894     if (type == Void.class || type == Class.class)
895       return false;
896 
897     if ((!type.isInterface() || (type == Object.class))) {
898 
899       if (!isPrimitive(type)) {
900 
901         // -- make sure type is serializable
902         // if (!Serializable.class.isAssignableFrom( type ))
903         // return false;
904 
905         // -- make sure we can construct the Object
906         if (!type.isArray()) {
907           // -- try to get the default constructor and make
908           // -- sure we are only looking at classes that can
909           // -- be instantiated by calling Class#newInstance
910           try {
911             type.getConstructor(EMPTY_CLASS_ARGS);
912           } catch (NoSuchMethodException e) {
913             // -- Allow any built-in descriptor classes
914             // -- that don't have default constructors
915             // -- such as java.sql.Date, java.sql.Time, etc.
916             return (CoreDescriptors.getDescriptor(type) != null);
917           }
918         }
919       }
920     }
921     return true;
922   } // -- marshallable
923 
924   /**
925    * Sets the Naming conventions to be used by the Introspector
926    *
927    * @param naming the implementation of Naming to use. A value of null, will reset the XMLNaming to
928    *        the default specified in the castor.properties file.
929    **/
930   public void setNaming(XMLNaming naming) {
931     if (naming == null)
932       _xmlNaming = _defaultNaming;
933     else
934       _xmlNaming = naming;
935   } // -- setNaming
936 
937   /**
938    * Sets the NodeType for primitives. If the NodeType is NodeType.Element, all primitives will be
939    * treated as Elements, otherwise all primitives will be treated as Attributes.
940    *
941    * @param nodeType the NodeType to use for primitive values.
942    **/
943   public void setPrimitiveNodeType(NodeType nodeType) {
944     if (nodeType == NodeType.Element)
945       _primitiveNodeType = nodeType;
946     else
947       _primitiveNodeType = NodeType.Attribute;
948   } // -- setPrimitiveNodeType
949 
950   /**
951    * Sets whether or not keys from Hastable / Map instances should be saved in the XML.
952    *
953    * <p>
954    * Note: This is true by default since Castor 0.9.5.3
955    * </p>
956    *
957    * @param saveMapKeys a boolean that when true indicates keys from Hashtable or Map instances
958    *        should be saved. Otherwise only the value object is saved.
959    */
960   public void setSaveMapKeys(boolean saveMapKeys) {
961     _saveMapKeys = saveMapKeys;
962   } // -- setSaveMapKeys
963 
964   /**
965    * Converts the given xml name to a Java name.
966    * 
967    * @param name the name to convert to a Java Name
968    * @param upperFirst a flag to indicate whether or not the the first character should be converted
969    *        to uppercase.
970    **/
971   public static String toJavaName(String name, boolean upperFirst) {
972 
973     int size = name.length();
974     char[] ncChars = name.toCharArray();
975     int next = 0;
976 
977     boolean uppercase = upperFirst;
978 
979     for (int i = 0; i < size; i++) {
980       char ch = ncChars[i];
981 
982       switch (ch) {
983         case ':':
984         case '-':
985           uppercase = true;
986           break;
987         default:
988           if (uppercase == true) {
989             ncChars[next] = Character.toUpperCase(ch);
990             uppercase = false;
991           } else
992             ncChars[next] = ch;
993           ++next;
994           break;
995       }
996     }
997     return new String(ncChars, 0, next);
998   } // -- toJavaName
999 
1000 
1001   // -------------------/
1002   // - Private Methods -/
1003   // -------------------/
1004 
1005   private XMLFieldDescriptorImpl createFieldDescriptor(Class type, String fieldName,
1006       String xmlName) {
1007 
1008     XMLFieldDescriptorImpl fieldDesc = new XMLFieldDescriptorImpl(type, fieldName, xmlName, null);
1009 
1010     if (type.isArray()) {
1011       fieldDesc.setNodeType(NodeType.Element);
1012     }
1013     // -- primitive types are converted to attributes by default
1014     else if (type.isPrimitive()) {
1015       fieldDesc.setNodeType(_primitiveNodeType);
1016     } else {
1017       fieldDesc.setNodeType(NodeType.Element);
1018     }
1019 
1020     // -- wildcard?
1021     if (type == java.lang.Object.class) {
1022       fieldDesc.setMatches(xmlName + " *");
1023     }
1024 
1025     return fieldDesc;
1026   } // -- createFieldDescriptor
1027 
1028 
1029   /**
1030    * Returns the registered FieldHandlerFactory for the given Class type.
1031    *
1032    * @param type the Class type to return the registered FieldHandlerFactory for
1033    */
1034   private FieldHandlerFactory getHandlerFactory(Class type) {
1035     if (_handlerFactoryMap != null) {
1036       Class tmp = type;
1037       while (tmp != null) {
1038         Object obj = _handlerFactoryMap.get(tmp);
1039         if (obj != null) {
1040           return (FieldHandlerFactory) obj;
1041         }
1042         tmp = tmp.getSuperclass();
1043       }
1044     }
1045 
1046     // -- check DefaultFieldHandlerFactory
1047     if (DEFAULT_HANDLER_FACTORY.isSupportedType(type))
1048       return DEFAULT_HANDLER_FACTORY;
1049 
1050     return null;
1051   } // -- getHandlerFactory
1052 
1053   /**
1054    * Registers the supported class types for the given FieldHandlerFactory into the map (for faster
1055    * lookups)
1056    */
1057   private void registerHandlerFactory(FieldHandlerFactory factory) {
1058     if (_handlerFactoryMap == null)
1059       _handlerFactoryMap = new Hashtable<>();
1060 
1061     Class[] types = factory.getSupportedTypes();
1062     for (int i = 0; i < types.length; i++) {
1063       _handlerFactoryMap.put(types[i], factory);
1064     }
1065   } // -- registerHandlerFactory
1066 
1067 
1068   /**
1069    * Returns true if the given Class is an instance of a collection class.
1070    */
1071   public static boolean isCollection(Class clazz) {
1072 
1073     if (clazz.isArray())
1074       return true;
1075 
1076     for (int i = 0; i < COLLECTIONS.length; i++) {
1077       // -- check to see if clazz is either the
1078       // -- same as or a subclass of one of the
1079       // -- available collections. For performance
1080       // -- reasons we first check if class is
1081       // -- directly equal to one of the collections
1082       // -- instead of just calling isAssignableFrom.
1083       if ((clazz == COLLECTIONS[i]) || (COLLECTIONS[i].isAssignableFrom(clazz))) {
1084         return true;
1085       }
1086     }
1087     return false;
1088   } // -- isCollection
1089 
1090 
1091   /**
1092    * Returns true if the given Class is an instance of a collection class.
1093    */
1094   public static boolean isMapCollection(Class clazz) {
1095 
1096     if (clazz.isArray())
1097       return false;
1098 
1099     for (int i = 0; i < COLLECTIONS.length; i++) {
1100       // -- check to see if clazz is either the
1101       // -- same as or a subclass of one of the
1102       // -- available collections. For performance
1103       // -- reasons we first check if class is
1104       // -- directly equal to one of the collections
1105       // -- instead of just calling isAssignableFrom.
1106       if ((clazz == COLLECTIONS[i]) || (COLLECTIONS[i].isAssignableFrom(clazz))) {
1107         if (COLLECTIONS[i] == java.util.Hashtable.class)
1108           return true;
1109         // -- For JDK 1.1 compatibility use string name "java.util.Map"
1110         if (COLLECTIONS[i].getName().equals(MAP))
1111           return true;
1112       }
1113     }
1114     return false;
1115   } // -- isMapCollection
1116 
1117 
1118   /**
1119    * Returns true if we are allowed to create a descriptor for a given class type
1120    * 
1121    * @param type the Class type to test
1122    * @return true if we are allowed to create a descriptor for a given class type
1123    **/
1124   private static boolean isDescriptable(Class type) {
1125     // -- make sure type is not Void, or Class;
1126     if (type == Void.class || type == Class.class)
1127       return false;
1128 
1129     // -- check whether it is a Java 5.0 enum
1130     float javaVersion =
1131         Float.valueOf(System.getProperty("java.specification.version")).floatValue();
1132     if (javaVersion >= 1.5) {
1133       try {
1134         Boolean isEnum = ReflectionUtil.isEnumViaReflection(type);
1135         if (isEnum.booleanValue()) {
1136           return true;
1137         }
1138       } catch (Exception e) {
1139         // nothing to report; implies that there's no such method
1140       }
1141     }
1142 
1143     if ((!type.isInterface()) && (type != Object.class) && (!isPrimitive(type))) {
1144 
1145       // -- make sure type is serializable
1146       // if (!Serializable.class.isAssignableFrom( type ))
1147       // return false;
1148 
1149       // -- make sure we can construct the Object
1150       if (!type.isArray()) {
1151 
1152         // -- try to get the default constructor and make
1153         // -- sure we are only looking at classes that can
1154         // -- be instantiated by calling Class#newInstance
1155         try {
1156           type.getConstructor(EMPTY_CLASS_ARGS);
1157         } catch (NoSuchMethodException e) {
1158 
1159           // -- Allow any built-in descriptor classes
1160           // -- that don't have default constructors
1161           // -- such as java.sql.Date, java.sql.Time, etc.
1162           return (CoreDescriptors.getDescriptor(type) != null);
1163         }
1164       }
1165     }
1166     return true;
1167   } // -- isDescriptable
1168 
1169   /**
1170    * Returns true if the given class should be treated as a primitive type
1171    * 
1172    * @return true if the given class should be treated as a primitive type
1173    **/
1174   private static boolean isPrimitive(Class type) {
1175 
1176     if (type.isPrimitive()) {
1177       return true;
1178     }
1179 
1180     if ((type == Boolean.class) || (type == Character.class)) {
1181       return true;
1182     }
1183 
1184     Class superClass = type.getSuperclass();
1185     if (superClass == Number.class) {
1186       return true;
1187     }
1188 
1189     if (superClass != null) {
1190       return superClass.getName().equals("java.lang.Enum");
1191     } else {
1192       return false;
1193     }
1194 
1195   } // -- isPrimitive
1196 
1197   /**
1198    * Returns an array of collections available during introspection. Allows JDK 1.2+ support without
1199    * breaking JDK 1.1 support.
1200    *
1201    * @return a list of available collections
1202    **/
1203   private static Class[] loadCollections() {
1204 
1205 
1206     List<Class> collections = new ArrayList<>(6);
1207 
1208     // -- JDK 1.1
1209     collections.add(Vector.class);
1210     collections.add(Enumeration.class);
1211     collections.add(Hashtable.class);
1212 
1213     // -- JDK 1.2+
1214     ClassLoader loader = Vector.class.getClassLoader();
1215 
1216 
1217     Class clazz = null;
1218     try {
1219       if (loader != null) {
1220         clazz = loader.loadClass(LIST);
1221       } else
1222         clazz = Class.forName(LIST);
1223     } catch (ClassNotFoundException cnfx) {
1224       // -- just ignore...either JDK 1.1
1225       // -- or some nasty ClassLoader
1226       // -- issue has occurred.
1227     }
1228     if (clazz != null) {
1229       // -- java.util.List found, add to collections,
1230       // -- also add java.util.Map
1231       collections.add(clazz);
1232 
1233       clazz = null;
1234       try {
1235         // -- java.util.Map
1236         if (loader != null) {
1237           clazz = loader.loadClass(MAP);
1238         } else
1239           clazz = Class.forName(MAP);
1240         if (clazz != null) {
1241           collections.add(clazz);
1242         }
1243         // -- java.util.Set
1244         if (loader != null) {
1245           clazz = loader.loadClass(SET_COLLECTION);
1246         } else
1247           clazz = Class.forName(SET_COLLECTION);
1248         if (clazz != null) {
1249           collections.add(clazz);
1250         }
1251 
1252 
1253       } catch (ClassNotFoundException cnfx) {
1254         // -- just ignore...for now
1255         // -- some nasty ClassLoader issue has occurred.
1256       }
1257     }
1258 
1259     return collections.toArray(new Class[collections.size()]);
1260   } // -- loadCollections
1261 
1262 
1263   public void propertyChange(PropertyChangeEvent event) {
1264     if (event.getPropertyName().equals(XMLProperties.PRIMITIVE_NODE_TYPE)) {
1265       if (event.getNewValue() instanceof String) {
1266         this.setPrimitiveNodeType(NodeType.getNodeType((String) event.getNewValue()));
1267       } else {
1268         throw new IllegalArgumentException(
1269             "The value for '" + XMLProperties.PRIMITIVE_NODE_TYPE + "' must be of type String");
1270       }
1271     }
1272   }
1273 
1274 
1275   /**
1276    * A special TypeConvertor that simply returns the object given. This is used for preventing the
1277    * FieldHandlerImpl from using a CollectionHandler when getValue is called.
1278    */
1279   class IdentityConvertor implements TypeConvertor {
1280     public Object convert(final Object object) {
1281       return object;
1282     }
1283   } // -- class: IdentityConvertor
1284 
1285   /**
1286    * A simple struct for holding a set of accessor methods.
1287    */
1288   class MethodSet {
1289     /** A reference to the add method. */
1290     Method _add = null;
1291 
1292     /** A reference to the create method. */
1293     Method _create = null;
1294 
1295     /** A reference to the get method. */
1296     Method _get = null;
1297 
1298     /** A reference to the set method. */
1299     Method _set = null;
1300 
1301     /** The fieldName for the field accessed by the methods in this method set. */
1302     String _fieldName = null;
1303 
1304     MethodSet(String fieldName) {
1305       super();
1306       this._fieldName = fieldName;
1307     }
1308   } // -- inner class: MethodSet
1309 
1310 } // -- Introspector
1311 
1312 
1313 /**
1314  * A simple extension of XMLClassDescriptor so that we can set the "instrospected" flag.
1315  **/
1316 class IntrospectedXMLClassDescriptor extends XMLClassDescriptorImpl {
1317   /**
1318    * Creates an IntrospectedXMLClassDescriptor
1319    * 
1320    * @param type the Class type with which this ClassDescriptor describes.
1321    **/
1322   IntrospectedXMLClassDescriptor(Class type) {
1323     super(type);
1324     setIntrospected(true);
1325   } // -- XMLClassDescriptorImpl
1326 
1327   /**
1328    * Creates an IntrospectedXMLClassDescriptor
1329    * 
1330    * @param type the Class type with which this ClassDescriptor describes.
1331    **/
1332   public IntrospectedXMLClassDescriptor(Class type, String xmlName) {
1333     super(type, xmlName);
1334     setIntrospected(true);
1335   } // -- XMLClassDescriptorImpl
1336 
1337 } // -- IntrospectedClassDescriptor