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