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 * <foos>
149 * <foo>foo1</foo>
150 * <foo>foo2</foo>
151 * </foos>
152 *
153 * instead of the default:
154 *
155 * <foos>foo1<foos>
156 * <foos>foo2</foos>
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 * <foos>
889 * <foo>foo1</foo>
890 * <foo>foo2</foo>
891 * </foos>
892 *
893 * instead of the default:
894 *
895 * <foos>foo1<foos>
896 * <foos>foo2</foos>
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