View Javadoc
1   /**
2    * Redistribution and use of this software and associated documentation ("Software"), with or
3    * without modification, are permitted provided that the following conditions are met:
4    *
5    * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
6    * must also contain a copy of this document.
7    *
8    * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
9    * conditions and the following disclaimer in the documentation and/or other materials provided with
10   * the distribution.
11   *
12   * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
13   * without prior written permission of Intalio, Inc. For written permission, please contact
14   * info@exolab.org.
15   *
16   * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
17   * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
18   * Intalio, Inc.
19   *
20   * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
21   *
22   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
23   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
24   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
25   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
29   * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   *
31   * Copyright 1999-2004(C) Intalio, Inc. All Rights Reserved.
32   *
33   * This file was originally developed by Keith Visco during the course of employment at Intalio Inc.
34   * All portions of this file developed by Keith Visco after Jan 19 2005 are Copyright (C) 2005 Keith
35   * Visco. All Rights Reserved.
36   *
37   * $Id$
38   */
39  package org.exolab.castor.xml;
40  
41  
42  import java.lang.reflect.Constructor;
43  import java.lang.reflect.Method;
44  import java.lang.reflect.Modifier;
45  import java.util.ArrayList;
46  import java.util.List;
47  
48  import org.apache.commons.logging.Log;
49  import org.apache.commons.logging.LogFactory;
50  import org.castor.mapping.BindingType;
51  import org.exolab.castor.mapping.AbstractFieldHandler;
52  import org.exolab.castor.mapping.ClassDescriptor;
53  import org.exolab.castor.mapping.CollectionHandler;
54  import org.exolab.castor.mapping.FieldDescriptor;
55  import org.exolab.castor.mapping.FieldHandler;
56  import org.exolab.castor.mapping.MapItem;
57  import org.exolab.castor.mapping.MappingException;
58  import org.exolab.castor.mapping.TypeConvertor;
59  import org.exolab.castor.mapping.loader.FieldDescriptorImpl;
60  import org.exolab.castor.mapping.loader.AbstractMappingLoader;
61  import org.exolab.castor.mapping.loader.CollectionHandlers;
62  import org.exolab.castor.mapping.loader.FieldHandlerImpl;
63  import org.exolab.castor.mapping.loader.TypeInfo;
64  import org.exolab.castor.mapping.loader.Types;
65  import org.exolab.castor.mapping.xml.BindXml;
66  import org.exolab.castor.mapping.xml.ClassMapping;
67  import org.exolab.castor.mapping.xml.FieldMapping;
68  import org.exolab.castor.mapping.xml.MapTo;
69  import org.exolab.castor.mapping.xml.MappingRoot;
70  import org.exolab.castor.mapping.xml.Property;
71  import org.exolab.castor.mapping.xml.types.BindXmlAutoNamingType;
72  import org.exolab.castor.mapping.xml.types.FieldMappingCollectionType;
73  import org.exolab.castor.xml.handlers.ContainerFieldHandler;
74  import org.exolab.castor.xml.handlers.ToStringFieldHandler;
75  import org.exolab.castor.xml.util.ContainerElement;
76  import org.exolab.castor.xml.util.XMLClassDescriptorAdapter;
77  import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
78  import org.exolab.castor.xml.util.XMLContainerElementFieldDescriptor;
79  import org.exolab.castor.xml.util.XMLFieldDescriptorImpl;
80  import org.exolab.castor.xml.validators.IdRefValidator;
81  import org.exolab.castor.xml.validators.NameValidator;
82  
83  /**
84   * An XML implementation of mapping helper. Creates XML class descriptors from the mapping file.
85   *
86   * @author <a href="keith AT kvisco DOT com">Keith Visco</a>
87   * @author <a href="arkin@intalio.com">Assaf Arkin</a>
88   * @version $Revision$ $Date: 2006-02-23 01:37:50 -0700 (Thu, 23 Feb 2006) $
89   */
90  public final class XMLMappingLoader extends AbstractMappingLoader {
91  
92    /**
93     * {@link Log} instance to be used.
94     */
95    private static final Log LOG = LogFactory.getLog(XMLMappingLoader.class);
96  
97    // -----------------------------------------------------------------------------------
98  
99    /**
100    * The default xml prefix used on certain attributes such as xml:lang, xml:base, etc.
101    */
102   private static final String XML_PREFIX = "xml:";
103 
104   /** Empty array of class types used for reflection. */
105   private static final Class[] EMPTY_ARGS = new Class[0];
106 
107   /** The NCName Schema type. */
108   private static final String NCNAME = "NCName";
109 
110   /** The string argument for the valueOf method, used for introspection. */
111   private static final Class[] STRING_ARG = {String.class};
112 
113   /**
114    * Factory method name for type-safe enumerations. This is primarily for allowing users to map
115    * classes that were created by Castor's SourceGenerator.
116    */
117   private static final String VALUE_OF = "valueOf";
118 
119   /**
120    * Creates a new XMLMappingLoader. Joachim 2007-08-19: called via ClassLoader from
121    * XMLMappingLoaderFactory.getMappingLoader() must not be modified!!!
122    * 
123    * @param loader the class loader to use
124    */
125   public XMLMappingLoader(final ClassLoader loader) {
126     super(loader);
127   }
128 
129   /**
130    * {@inheritDoc}
131    */
132   public BindingType getBindingType() {
133     return BindingType.XML;
134   }
135 
136   /**
137    * {@inheritDoc}
138    */
139   public void loadMapping(final MappingRoot mapping, final Object param) throws MappingException {
140     if (loadMapping()) {
141       createFieldHandlers(mapping);
142       createClassDescriptors(mapping);
143     }
144   }
145 
146   // -----------------------------------------------------------------------------------
147 
148   /**
149    * To create the class descriptor for the given class mapping. Throws IllegalStateException if the
150    * class has no valid internal context.
151    * 
152    * @param classMapping the class mapping information to process
153    * @return the {@link ClassDescriptor} created for the class mapping
154    * @throws MappingException ...
155    */
156   protected ClassDescriptor createClassDescriptor(final ClassMapping classMapping)
157       throws MappingException {
158     // execution makes no sense without a context or without a resolver...
159     if ((getInternalContext() == null)
160         || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
161       String message = "Internal context or class descriptor resolver within are not valid";
162       LOG.warn(message);
163       throw new IllegalStateException(message);
164     }
165     // Create the class descriptor.
166     XMLClassDescriptorAdapter xmlClassDesc = new XMLClassDescriptorAdapter();
167 
168     // Introspection and package level stuff needs to be disabled !!
169     getInternalContext().getXMLClassDescriptorResolver().setUseIntrospection(false);
170     getInternalContext().getXMLClassDescriptorResolver().setLoadPackageMappings(false);
171 
172     try {
173       if (classMapping.getAutoComplete()) {
174         if ((classMapping.getMapTo() == null)
175             && ((classMapping.getClassChoice() == null)
176                 || (classMapping.getClassChoice().getFieldMappingCount() == 0))
177             && (classMapping.getIdentityCount() == 0)) {
178           // If we make it here we simply try to load a compiled mapping
179           try {
180             ClassDescriptor clsDesc = getInternalContext().getXMLClassDescriptorResolver()
181                 .resolve(classMapping.getName());
182             if (clsDesc != null) {
183               return clsDesc;
184             }
185           } catch (ResolverException e) {
186             if (LOG.isDebugEnabled()) {
187               LOG.debug("Ignoring exception: " + e + " at resolving: " + classMapping.getName());
188             }
189           }
190         }
191       }
192 
193       // Obtain the Java class.
194       Class javaClass = resolveType(classMapping.getName());
195       if (classMapping.getVerifyConstructable()) {
196         if (!Types.isConstructable(javaClass, true)) {
197           throw new MappingException("mapping.classNotConstructable", javaClass.getName());
198         }
199       }
200       xmlClassDesc.setJavaClass(javaClass);
201 
202       // Obtain XML name.
203       String xmlName;
204       MapTo mapTo = classMapping.getMapTo();
205       if ((mapTo != null) && (mapTo.getXml() != null)) {
206         xmlName = mapTo.getXml();
207       } else {
208         String clsName = getInternalContext().getJavaNaming().getClassName(javaClass);
209         xmlName = getInternalContext().getXMLNaming().toXMLName(clsName);
210       }
211       xmlClassDesc.setXMLName(xmlName);
212 
213       // If this class extends another class, we need to obtain the extended
214       // class and make sure this class indeed extends it.
215       ClassDescriptor extDesc = getExtended(classMapping, javaClass);
216       xmlClassDesc.setExtends((XMLClassDescriptor) extDesc);
217 
218       // Create all field descriptors.
219       FieldDescriptorImpl[] allFields = createFieldDescriptors(classMapping, javaClass);
220 
221       // Make sure there are no two fields with the same name.
222       checkFieldNameDuplicates(allFields, javaClass);
223 
224       // Identify identity and normal fields. Note that order must be preserved.
225       List<FieldDescriptorImpl> fieldList = new ArrayList<>(allFields.length);
226       List<FieldDescriptor> idList = new ArrayList<>();
227       if (extDesc == null) {
228         // Sort fields into 2 lists based on identity definition of field.
229         for (int i = 0; i < allFields.length; i++) {
230           if (!allFields[i].isIdentity()) {
231             fieldList.add(allFields[i]);
232           } else {
233             idList.add(allFields[i]);
234           }
235         }
236 
237         if (idList.isEmpty()) {
238           // Found no identities based on identity definition of field.
239           // Try to find identities based on identity definition on class.
240           String[] idNames = classMapping.getIdentity();
241 
242           for (int i = 0; i < idNames.length; i++) {
243             FieldDescriptor identity = findIdentityByName(fieldList, idNames[i], javaClass);
244             if (identity != null) {
245               idList.add(identity);
246             } else {
247               throw new MappingException("mapping.identityMissing", idNames[i],
248                   javaClass.getName());
249             }
250           }
251         }
252       } else {
253         // Add all fields of extending class to field list.
254         for (int i = 0; i < allFields.length; i++) {
255           fieldList.add(allFields[i]);
256         }
257 
258         // Add identity of extended class to identity list.
259         if (extDesc.getIdentity() != null) {
260           idList.add(extDesc.getIdentity());
261         }
262 
263         // Search redefined identities in extending class.
264         for (int i = 0; i < idList.size(); i++) {
265           String idname = idList.get(i).getFieldName();
266           FieldDescriptor identity = findIdentityByName(fieldList, idname, javaClass);
267           if (identity != null) {
268             idList.set(i, identity);
269           }
270         }
271       }
272 
273       FieldDescriptor xmlId = null;
274       if (!idList.isEmpty()) {
275         xmlId = (FieldDescriptor) idList.get(0);
276       }
277 
278       if (xmlId != null) {
279         xmlClassDesc.setIdentity((XMLFieldDescriptorImpl) xmlId);
280       }
281       for (FieldDescriptor fieldDesc : fieldList) {
282         if (fieldDesc != null) {
283           xmlClassDesc.addFieldDescriptor((XMLFieldDescriptorImpl) fieldDesc);
284         }
285       }
286 
287       if (classMapping.getAutoComplete()) {
288 
289         XMLClassDescriptor referenceDesc = null;
290 
291         Class type = xmlClassDesc.getJavaClass();
292 
293         // -- check compiled descriptors
294         if ((getInternalContext() == null)
295             || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
296           String message = "Internal context or class descriptor resolver within are not valid";
297           LOG.warn(message);
298           throw new IllegalStateException(message);
299         }
300         try {
301           referenceDesc = (XMLClassDescriptor) getInternalContext().getXMLClassDescriptorResolver()
302               .resolve(type);
303         } catch (ResolverException rx) {
304           throw new MappingException(rx);
305         }
306 
307         if (referenceDesc == null) {
308           Introspector introspector = getInternalContext().getIntrospector();
309           try {
310             referenceDesc = introspector.generateClassDescriptor(type);
311             if (classMapping.getExtends() != null) {
312               // -- clear parent from introspected descriptor since
313               // -- a mapping was provided in the mapping file
314               ((XMLClassDescriptorImpl) referenceDesc).setExtends(null);
315             }
316           } catch (MarshalException mx) {
317             String error =
318                 "unable to introspect class '" + type.getName() + "' for auto-complete: ";
319             throw new MappingException(error + mx.getMessage());
320           }
321         }
322 
323         // -- check for identity
324         String identity = "";
325         if (classMapping.getIdentityCount() > 0) {
326           identity = classMapping.getIdentity(0);
327         }
328 
329 
330         FieldDescriptor[] xmlFields2 = xmlClassDesc.getFields();
331 
332         // Attributes
333         XMLFieldDescriptor[] introFields = referenceDesc.getAttributeDescriptors();
334         for (int i = 0; i < introFields.length; ++i) {
335           if (!isMatchFieldName(xmlFields2, introFields[i].getFieldName())) {
336             // If there is no field with this name, we can add it
337             if (introFields[i].getFieldName().equals(identity)) {
338               xmlClassDesc.setIdentity(introFields[i]);
339             } else {
340               xmlClassDesc.addFieldDescriptor(introFields[i]);
341             }
342           }
343         }
344 
345         // Elements
346         introFields = referenceDesc.getElementDescriptors();
347         for (int i = 0; i < introFields.length; ++i) {
348           if (!isMatchFieldName(xmlFields2, introFields[i].getFieldName())) {
349             // If there is no field with this name, we can add it
350             if (introFields[i].getFieldName().equals(identity)) {
351               xmlClassDesc.setIdentity(introFields[i]);
352             } else {
353               xmlClassDesc.addFieldDescriptor(introFields[i]);
354             }
355           }
356         }
357 
358         // Content
359         XMLFieldDescriptor field = referenceDesc.getContentDescriptor();
360         if (field != null) {
361           if (!isMatchFieldName(xmlFields2, field.getFieldName())) {
362             // If there is no field with this name, we can add
363             xmlClassDesc.addFieldDescriptor(field);
364           }
365         }
366       }
367 
368       // Copy ns-uri + ns-prefix + element-definition
369       if (mapTo != null) {
370         xmlClassDesc.setNameSpacePrefix(mapTo.getNsPrefix());
371         xmlClassDesc.setNameSpaceURI(mapTo.getNsUri());
372         xmlClassDesc.setElementDefinition(mapTo.getElementDefinition());
373       }
374     } finally {
375       getInternalContext().getXMLClassDescriptorResolver().setUseIntrospection(true);
376       getInternalContext().getXMLClassDescriptorResolver().setLoadPackageMappings(true);
377     }
378 
379     return xmlClassDesc;
380   }
381 
382   protected final FieldDescriptor findIdentityByName(final List fldList, final String idName,
383       final Class javaClass) {
384     for (int i = 0; i < fldList.size(); i++) {
385       FieldDescriptor field = (FieldDescriptor) fldList.get(i);
386       if (idName.equals(field.getFieldName())) {
387         fldList.remove(i);
388         return field;
389       }
390     }
391     return null;
392   }
393 
394   protected final void resolveRelations(ClassDescriptor clsDesc) {
395     FieldDescriptor[] fields;
396 
397     fields = clsDesc.getFields();
398     for (int i = 0; i < fields.length; ++i) {
399       if (fields[i].getClassDescriptor() != null)
400         continue;
401       ClassDescriptor relDesc;
402 
403       Class fieldType = fields[i].getFieldType();
404       if (fieldType != null) {
405         relDesc = getDescriptor(fieldType.getName());
406         if (relDesc != null && relDesc instanceof XMLClassDescriptor
407             && fields[i] instanceof XMLFieldDescriptorImpl) {
408           ((XMLFieldDescriptorImpl) fields[i]).setClassDescriptor(relDesc);
409         }
410       }
411     }
412     if (clsDesc instanceof XMLClassDescriptorImpl)
413       ((XMLClassDescriptorImpl) clsDesc).sortDescriptors();
414   }
415 
416   // -----------------------------------------------------------------------------------
417 
418   /**
419    * Match if a field named <code>fieldName</code> is in fields
420    */
421   private boolean isMatchFieldName(FieldDescriptor[] fields, String fieldName) {
422     for (int i = 0; i < fields.length; ++i)
423       if (fields[i].getFieldName().equals(fieldName))
424         return true;
425 
426     return false;
427   } // -- method: isMatchFieldName
428 
429 
430   protected FieldDescriptorImpl createFieldDesc(Class javaClass, FieldMapping fieldMap)
431       throws MappingException {
432 
433     FieldDescriptor fieldDesc;
434     FieldMappingCollectionType colType = fieldMap.getCollection();
435     String xmlName = null;
436     NodeType nodeType = null;
437     String match = null;
438     XMLFieldDescriptorImpl xmlDesc;
439     boolean isReference = false;
440     boolean isXMLTransient = false;
441 
442     // -- handle special case for HashMap/Hashtable
443     if ((fieldMap.getType() == null) && (colType != null)) {
444       if ((colType == FieldMappingCollectionType.HASHTABLE)
445           || (colType == FieldMappingCollectionType.MAP)
446           || (colType == FieldMappingCollectionType.SORTEDMAP)) {
447         fieldMap.setType(MapItem.class.getName());
448       }
449     }
450 
451     // Create an XML field descriptor
452     fieldDesc = super.createFieldDesc(javaClass, fieldMap);
453 
454     BindXml xml = fieldMap.getBindXml();
455 
456     boolean deriveNameByClass = false;
457 
458     if (xml != null) {
459       // -- xml name
460       xmlName = xml.getName();
461 
462       // -- node type
463       if (xml.getNode() != null)
464         nodeType = NodeType.getNodeType(xml.getNode().toString());
465 
466       // -- matches
467       match = xml.getMatches();
468 
469       // -- reference
470       isReference = xml.getReference();
471 
472       // -- XML transient
473       isXMLTransient = xml.getTransient();
474 
475       // -- autonaming
476       BindXmlAutoNamingType autoName = xml.getAutoNaming();
477       if (autoName != null) {
478         deriveNameByClass = (autoName == BindXmlAutoNamingType.DERIVEBYCLASS);
479       }
480 
481     }
482 
483     // -- transient
484     // -- XXXX -> if it's transient we probably shouldn't do all
485     // -- XXXX -> the unecessary work
486     isXMLTransient = isXMLTransient || fieldDesc.isTransient();
487 
488     // --
489 
490     // -- handle QName for xmlName
491     String namespace = null;
492     if ((xmlName != null) && (xmlName.length() > 0)) {
493       if (xmlName.charAt(0) == '{') {
494         int idx = xmlName.indexOf('}');
495         if (idx < 0) {
496           throw new MappingException("Invalid QName: " + xmlName);
497         }
498         namespace = xmlName.substring(1, idx);
499         xmlName = xmlName.substring(idx + 1);
500       } else if (xmlName.startsWith(XML_PREFIX)) {
501         namespace = Namespaces.XML_NAMESPACE;
502         xmlName = xmlName.substring(4);
503       }
504     }
505 
506     if (nodeType == null) {
507       if (isPrimitive(javaClass))
508         nodeType = getInternalContext().getPrimitiveNodeType();
509       else
510         nodeType = NodeType.Element;
511     }
512 
513     // -- Create XML name if necessary. Note if name is to be derived
514     // -- by class..we just make sure we set the name to null...
515     // -- the Marshaller does this during runtime. This allows
516     // -- Collections to be handled properly.
517     if ((!deriveNameByClass) && ((xmlName == null) && (match == null))) {
518       xmlName = getInternalContext().getXMLNaming().toXMLName(fieldDesc.getFieldName());
519       match = xmlName + ' ' + fieldDesc.getFieldName();
520     }
521 
522     xmlDesc = new XMLFieldDescriptorImpl(fieldDesc, xmlName, nodeType,
523         getInternalContext().getPrimitiveNodeType());
524 
525     if (xmlDesc.getHandler() != null && xmlDesc.getHandler() instanceof AbstractFieldHandler) {
526       AbstractFieldHandler handler = (AbstractFieldHandler) xmlDesc.getHandler();
527       handler.setFieldDescriptor(xmlDesc);
528     }
529 
530     // -- transient?
531     xmlDesc.setTransient(isXMLTransient);
532 
533     // --set a default fieldValidator
534     xmlDesc.setValidator(new FieldValidator());
535 
536     // -- enable use parent namespace if explicit one doesn't exist
537     xmlDesc.setUseParentsNamespace(true);
538 
539     // -- If deriveNameByClass we need to reset the name to
540     // -- null because XMLFieldDescriptorImpl tries to be smart
541     // -- and automatically creates the name.
542     if (deriveNameByClass) {
543       xmlDesc.setXMLName(null);
544     }
545 
546     // -- namespace
547     if (namespace != null) {
548       xmlDesc.setNameSpaceURI(namespace);
549     }
550 
551     // -- matches
552     if (match != null) {
553       xmlDesc.setMatches(match);
554       // -- special fix for xml-name since XMLFieldDescriptorImpl
555       // -- will create a default name based off the field name
556       if (xmlName == null)
557         xmlDesc.setXMLName(null);
558     }
559 
560     // -- reference
561     xmlDesc.setReference(isReference);
562     if (isReference) {
563       if (colType == null) {
564         FieldValidator fieldValidator = new FieldValidator();
565         fieldValidator.setValidator(new IdRefValidator());
566         xmlDesc.setValidator(fieldValidator);
567       } else {
568         // TODO handle other cases
569       }
570     }
571 
572     xmlDesc.setContainer(fieldMap.getContainer());
573 
574     xmlDesc.setNillable(fieldMap.isNillable());
575 
576     if (xml != null) {
577 
578       // -- has class descriptor for type specified
579       if (xml.getClassMapping() != null) {
580         ClassDescriptor cd = createClassDescriptor(xml.getClassMapping());
581         xmlDesc.setClassDescriptor(cd);
582       }
583 
584       // -- has location path?
585       if (xml.getLocation() != null) {
586         xmlDesc.setLocationPath(xml.getLocation());
587       }
588       // is the value type needs specific handling
589       // such as QName or NCName support?
590       String xmlType = xml.getType();
591       xmlDesc.setSchemaType(xmlType);
592       xmlDesc.setQNamePrefix(xml.getQNamePrefix());
593       TypeValidator validator = null;
594       if (NCNAME.equals(xmlType)) {
595         validator = new NameValidator(XMLConstants.NAME_TYPE_NCNAME);
596         xmlDesc.setValidator(new FieldValidator(validator));
597       }
598 
599       // -- special properties?
600       Property[] props = xml.getProperty();
601       if ((props != null) && (props.length > 0)) {
602         for (int pIdx = 0; pIdx < props.length; pIdx++) {
603           Property prop = props[pIdx];
604           xmlDesc.setXMLProperty(prop.getName(), prop.getValue());
605         }
606       }
607     }
608 
609     // -- Get collection type
610     if (colType == null) {
611       // -- just in case user forgot to use collection="..."
612       // -- in the mapping file
613       Class type = fieldDesc.getFieldType();
614       if (type != null && CollectionHandlers.hasHandler(type)) {
615         String typeName = CollectionHandlers.getCollectionName(type);
616         colType = FieldMappingCollectionType.fromValue(typeName);
617       }
618     }
619 
620     // -- isMapped item
621     if (colType != null) {
622       if ((colType == FieldMappingCollectionType.HASHTABLE)
623           || (colType == FieldMappingCollectionType.MAP)
624           || (colType == FieldMappingCollectionType.SORTEDMAP)) {
625         // -- Make sure user is not using an addMethod
626         // -- before setting the mapped field to true.
627         String methodName = fieldMap.getSetMethod();
628         if (methodName != null) {
629           if (!methodName.startsWith("add")) {
630             xmlDesc.setMapped(true);
631           }
632         } else
633           xmlDesc.setMapped(true);
634       }
635 
636 
637       // -- special NodeType.Namespace handling
638       // -- prevent FieldHandlerImpl from using CollectionHandler
639       // -- during calls to #getValue
640       if ((nodeType == NodeType.Namespace) || (xmlDesc.isMapped())) {
641         Object handler = xmlDesc.getHandler();
642         if (handler instanceof FieldHandlerImpl) {
643           FieldHandlerImpl handlerImpl = (FieldHandlerImpl) handler;
644           handlerImpl.setConvertFrom(new IdentityConvertor());
645         }
646       }
647       // -- wrap collection in element?
648       if (nodeType == NodeType.Element) {
649         if (fieldMap.hasContainer() && (!fieldMap.getContainer())) {
650           xmlDesc = wrapCollection(xmlDesc);
651         }
652       }
653     }
654 
655     // -- is Type-Safe Enumeration?
656     // -- This is not very clean, we should have a way
657     // -- to specify something is a type-safe enumeration
658     // -- without having to guess.
659     else if ((!isReference) && (!isXMLTransient)) {
660       Class fieldType = xmlDesc.getFieldType();
661       if (!isPrimitive(fieldType)) {
662         // -- make sure no default constructor
663         Constructor cons = null;
664         try {
665           cons = fieldType.getConstructor(EMPTY_ARGS);
666           if (!Modifier.isPublic(cons.getModifiers())) {
667             cons = null;
668           }
669         } catch (NoSuchMethodException nsmx) {
670           // -- Do nothing
671         }
672         try {
673           if (cons == null) {
674             // -- make sure a valueOf factory method
675             // -- exists and no user specified handler exists
676             Method method = fieldType.getMethod(VALUE_OF, STRING_ARG);
677             Class returnType = method.getReturnType();
678             if ((returnType != null) && fieldType.isAssignableFrom(returnType)) {
679               if (fieldMap.getHandler() == null) {
680                 // -- Use EnumFieldHandler
681                 // -- mapping loader now supports a basic EnumFieldHandler
682                 // -- for xml we simply need to make sure the toString()
683                 // -- method is called during getValue()
684                 // FieldHandler handler = xmlDesc.getHandler();
685                 // handler = new EnumFieldHandler(fieldType, handler);
686 
687                 FieldHandler handler = new ToStringFieldHandler(fieldType, xmlDesc.getHandler());
688 
689                 xmlDesc.setHandler(handler);
690                 xmlDesc.setImmutable(true);
691               }
692             }
693           }
694         } catch (NoSuchMethodException nsmx) {
695           // -- Do nothing
696         }
697       }
698     }
699 
700     // -- constructor argument?
701     String setter = fieldMap.getSetMethod();
702     if (setter != null && setter.startsWith("%")) {
703       String parameterNumberAsString = setter.substring(1).trim();
704       int index = Integer.parseInt(parameterNumberAsString);
705       if (index < 1) {
706         throw new MappingException("mapper.invalidParameterIndex", parameterNumberAsString);
707       }
708       // -- adjust index to base zero
709       xmlDesc.setConstructorArgumentIndex(--index);
710     }
711 
712     return xmlDesc;
713   }
714 
715   /**
716    * Sets whether or not to look for and load package specific mapping files (".castor.xml" files).
717    *
718    * @param loadPackageMappings a boolean that enables or disables the loading of package specific
719    *        mapping files
720    */
721   public void setLoadPackageMappings(final boolean loadPackageMappings) {
722     if ((getInternalContext() == null)
723         || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
724       String message = "Internal context or class descriptor resolver within are not valid";
725       LOG.warn(message);
726       throw new IllegalStateException(message);
727     }
728     getInternalContext().getXMLClassDescriptorResolver()
729         .setLoadPackageMappings(loadPackageMappings);
730   } // -- setLoadPackageMappings
731 
732 
733   protected TypeInfo getTypeInfo(Class fieldType, CollectionHandler colHandler,
734       FieldMapping fieldMap) throws MappingException {
735     return new TypeInfo(fieldType, null, null, fieldMap.getRequired(), null, colHandler, false);
736   }
737 
738   /**
739    * This method allows a collection to be treated as a first class object (and not a container) so
740    * that it has an element representation in the marshalled XML.
741    */
742   private XMLFieldDescriptorImpl wrapCollection(XMLFieldDescriptorImpl fieldDesc)
743       throws MappingException {
744     // -- If we have a field 'c' that is a collection and
745     // -- we want to wrap that field in an element <e>, we
746     // -- need to create a field descriptor for
747     // -- an object that represents the element <e> and
748     // -- acts as a go-between from the parent of 'c'
749     // -- denoted as P(c) and 'c' itself
750     //
751     // object model: P(c) -> c
752     // xml : <p><e><c></e><p>
753 
754     // -- Make new class descriptor for the field that
755     // -- will represent the container element <e>
756     Class type = ContainerElement.class;
757     XMLClassDescriptorImpl classDesc = new XMLClassDescriptorImpl(type);
758     // -- make copy of fieldDesc and add it to our new class descriptor
759     XMLFieldDescriptorImpl newFieldDesc =
760         new XMLFieldDescriptorImpl(fieldDesc, fieldDesc.getXMLName(), fieldDesc.getNodeType(),
761             getInternalContext().getPrimitiveNodeType());
762     // -- nullify xmlName so that auto-naming will be enabled,
763     // -- we can't do this in the constructor because
764     // -- XMLFieldDescriptorImpl will create a default one.
765     newFieldDesc.setXMLName(null);
766     newFieldDesc.setMatches("*");
767 
768     // -- add the field descriptor to our new class descriptor
769     classDesc.addFieldDescriptor(newFieldDesc);
770     // -- reassociate the orignal class descriptor (for 'c')
771     // of fieldDesc with our new classDesc
772     fieldDesc.setClassDescriptor(classDesc);
773 
774     // -- wrap the field handler in a special container field
775     // -- handler that will actually do the delgation work
776     FieldHandler handler = new ContainerFieldHandler(fieldDesc.getHandler());
777     newFieldDesc.setHandler(handler);
778     fieldDesc.setHandler(handler);
779 
780     // -- Change fieldType of original field descriptor and
781     // -- return new descriptor
782     return new XMLContainerElementFieldDescriptor(fieldDesc,
783         getInternalContext().getPrimitiveNodeType());
784   } // -- createWrapperDescriptor
785 
786   /**
787    * A special TypeConvertor that simply returns the object given. This is used for preventing the
788    * FieldHandlerImpl from using a CollectionHandler when getValue is called.
789    **/
790   class IdentityConvertor implements TypeConvertor {
791     public Object convert(final Object object) {
792       return object;
793     }
794   } // -- class: IdentityConvertor
795 } // -- class: XMLMappingLoader
796 
797