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