001: /*
002: * Copyright 2006 Keith Visco, Ralf Joachim
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.exolab.castor.xml.util;
017:
018: import org.exolab.castor.mapping.ClassDescriptor;
019: import org.exolab.castor.mapping.FieldDescriptor;
020: import org.exolab.castor.mapping.FieldHandler;
021: import org.exolab.castor.mapping.MapItem;
022: import org.exolab.castor.mapping.MappingException;
023: import org.exolab.castor.mapping.loader.AbstractFieldDescriptor;
024: import org.exolab.castor.mapping.loader.FieldHandlerImpl;
025: import org.exolab.castor.xml.FieldValidator;
026: import org.exolab.castor.xml.NodeType;
027: import org.exolab.castor.xml.XMLClassDescriptor;
028: import org.exolab.castor.xml.XMLFieldDescriptor;
029: import org.exolab.castor.xml.descriptors.CoreDescriptors;
030: import org.exolab.castor.xml.handlers.DateFieldHandler;
031:
032: import java.util.ArrayList;
033: import java.util.List;
034: import java.util.StringTokenizer;
035: import java.util.Properties;
036:
037: /**
038: * XML field descriptor. Wraps {@link FieldDescriptor} and adds XML-related
039: * information, type conversion, etc.
040: * <p>
041: * Note: When using a GeneralizedFieldHandler the getFieldType() methods of
042: * handler and descriptor need to return the same result.
043: *
044: * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
045: * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a>
046: * @version $Revision: 6907 $ $Date: 2006-04-13 06:47:36 -0600 (Thu, 13 Apr 2006) $
047: */
048: public class XMLFieldDescriptorImpl extends AbstractFieldDescriptor
049: implements XMLFieldDescriptor {
050: private static final String WILD_CARD = "*";
051:
052: private static final String NULL_CLASS_ERR = "The 'type' argument passed to the constructor of "
053: + "XMLFieldDescriptorImpl may not be null.";
054:
055: private static final String NULL_NAME_ERR = "The 'fieldName' argument passed to the constructor of "
056: + "XMLFieldDescriptorImpl may not be null.";
057:
058: /** The index of this field within the constructor arguments. Note: This
059: * field is only applicable if the field is an attribute field and it's
060: * supposed to be set via the constructor. A value less than zero indicates
061: * that this field is not part of the constructor arguments.
062: */
063: private int _argIndex = -1;
064:
065: /** True if the field is a container field. */
066: private boolean _container = false;
067:
068: /** Flag to indicate that objects should be added to their as soon as they
069: * are created, but before they are finished being populated.
070: */
071: private boolean _incremental = false;
072:
073: /** True if the field is a reference to another Object in the hierarchy. */
074: public boolean _isReference = false;
075:
076: private boolean _isWild = false;
077:
078: /** True if the field type is mapped in a Hashtable or Map. */
079: private boolean _mapped = false;
080:
081: private String[] _matches = null;
082:
083: /** True if the field is allowed to have nil content. */
084: private boolean _nillable = false;
085:
086: /** The node type (attribute, element, text). */
087: private NodeType _nodeType = null;
088:
089: /** The namespace prefix that is to be used when marshaling */
090: private String _nsPrefix = null;
091:
092: /** The namespace URI used for both marshaling and unmarshaling. */
093: private String _nsURI = null;
094:
095: /** The "user-set" properties of this XMLFieldDescriptor. */
096: private Properties _properties = null;
097:
098: /** The XML Schema type of this field value. */
099: private String _schemaType = null;
100:
101: /** The prefix used in case the value of the field described is of type QName. */
102: private String _qNamePrefix = null;
103:
104: /** A flag which indicates the parent class' namespace should be used by default. */
105: private boolean _useParentClassNamespace = false;
106:
107: private FieldValidator _validator = null;
108:
109: /** The XML name of the field, this is only the local name. */
110: private String _xmlName = null;
111:
112: /** The relative XML path used when wrapping in nested elements, does not
113: * include the name of the field itself.
114: */
115: private String _xmlPath = null;
116:
117: private List _substitutes;
118:
119: //----------------/
120: //- Constructors -/
121: //----------------/
122:
123: public XMLFieldDescriptorImpl(final Class fieldType,
124: final String fieldName, final String xmlName,
125: final NodeType nodeType) {
126: _matches = new String[0];
127:
128: if (fieldName == null) {
129: throw new IllegalArgumentException(NULL_NAME_ERR);
130: }
131: if (fieldType == null) {
132: throw new IllegalArgumentException(NULL_CLASS_ERR);
133: }
134:
135: setFieldName(fieldName);
136:
137: if (fieldType == org.exolab.castor.types.AnyNode.class) {
138: // if the field type is an AnyNode Castor must treat it as
139: // an object to avoid changes in the marshaling framework
140: setFieldType(java.lang.Object.class);
141: } else {
142: setFieldType(fieldType);
143: }
144:
145: _nodeType = ((nodeType == null) ? NodeType.Attribute : nodeType);
146:
147: //-- call the setXMLName method to handle checking for full path
148: setXMLName(xmlName);
149: }
150:
151: /**
152: * Construct a new field descriptor for the specified field. This is an XML
153: * field descriptor wrapping a field descriptor and adding XML related
154: * properties and methods.
155: *
156: * @param fieldDesc The field descriptor
157: * @param xmlName The XML name of the field
158: * @param nodeType The node type of this field
159: * @param primitiveNodeType
160: * @throws MappingException Invalid mapping information
161: */
162: public XMLFieldDescriptorImpl(final FieldDescriptor fieldDesc,
163: final String xmlName, final NodeType nodeType,
164: final NodeType primitiveNodeType) throws MappingException {
165: _matches = new String[0];
166:
167: if (fieldDesc instanceof XMLFieldDescriptor) {
168: setContainingClassDescriptor(fieldDesc
169: .getContainingClassDescriptor());
170: }
171:
172: setFieldName(fieldDesc.getFieldName());
173:
174: if (fieldDesc.getFieldType() == org.exolab.castor.types.AnyNode.class) {
175: // if the field type is an AnyNode Castor must treat it as
176: // an object to avoid changes in the marshaling framework
177: setFieldType(java.lang.Object.class);
178: } else {
179: setFieldType(fieldDesc.getFieldType());
180: }
181:
182: ClassDescriptor cd = fieldDesc.getClassDescriptor();
183: if (cd != null) {
184: if (cd instanceof XMLClassDescriptor) {
185: setClassDescriptor(cd);
186: } else {
187: setClassDescriptor(new XMLClassDescriptorAdapter(cd,
188: null, primitiveNodeType));
189: }
190: }
191:
192: setHandler(fieldDesc.getHandler());
193: // Check for instances of java.util.Date. This logic really doesn't belong here,
194: // as it can interfere with user specified handlers. Instead it should go into
195: // XMLMappingLoader.
196: if (getFieldType() != null) {
197: if (java.util.Date.class.isAssignableFrom(getFieldType())) {
198: if (getHandler() instanceof FieldHandlerImpl) {
199: setHandler(new DateFieldHandler(getHandler()));
200: }
201: }
202: }
203:
204: setTransient(fieldDesc.isTransient());
205: setImmutable(fieldDesc.isImmutable());
206: setRequired(fieldDesc.isRequired());
207: setMultivalued(fieldDesc.isMultivalued());
208:
209: //-- handle xml name
210: if (xmlName == null) {
211: setXMLName(getFieldName());
212: } else {
213: setXMLName(xmlName);
214: }
215:
216: if (nodeType == null) {
217: if (isMultivalued()) {
218: _nodeType = NodeType.Element;
219: } else {
220: _nodeType = NodeType.Attribute;
221: }
222: } else {
223: _nodeType = nodeType;
224: }
225:
226: if (isRequired()) {
227: _validator = new FieldValidator();
228: _validator.setMinOccurs(1);
229: _validator.setDescriptor(this );
230: }
231: }
232:
233: //------------------/
234: //- Public Methods -/
235: //------------------/
236:
237: /**
238: * Sets whether or not the value of the field represented by this
239: * FieldDescriptor should be set via the constructor of the containing
240: * ClassDescriptor. The index value greater than 0 specifies the index
241: * within the argument array that the value of this field should be.
242: * <p>
243: * Note: This only applies to attribute mapped fields at this time.
244: *
245: * @param index the index within the argument array. A value less than zero
246: * indicates that this field should not be part of the constructor
247: * arguments.
248: */
249: public void setConstructorArgumentIndex(final int index) {
250: if (_nodeType != NodeType.Attribute) {
251: String err = "constructor arguments only valid for attribute mapped fields.";
252: throw new IllegalStateException(err);
253: }
254: _argIndex = index;
255: }
256:
257: /**
258: * @see org.exolab.castor.xml.XMLFieldDescriptor#getConstructorArgumentIndex()
259: * {@inheritDoc}
260: */
261: public int getConstructorArgumentIndex() {
262: return _argIndex;
263: }
264:
265: /**
266: * @see org.exolab.castor.xml.XMLFieldDescriptor#isConstructorArgument()
267: * {@inheritDoc}
268: */
269: public boolean isConstructorArgument() {
270: return (_argIndex >= 0);
271: }
272:
273: /**
274: * Sets the location path for the field being described.
275: * <p>
276: * In most cases, this isn't needed. However sometimes a field may be mapped
277: * to a nested element. In which case the value of the location path should
278: * be the nested element name. If more than one level of nesting is needed
279: * each nested element name should be separated by a path separator (forward
280: * slash '/').
281: * <p>
282: * The location path name is "relative" to the parent Class. The name of the
283: * parent should not be included in the path.
284: * <p>
285: * For example, give the following two classes: <code>
286: * class Root {
287: * Bar bar;
288: * }
289: *
290: * class Bar {
291: * String value;
292: * }
293: * </code>
294: *
295: * And the following XML:
296: *
297: * <code>
298: * <root>
299: * <foo>
300: * <bar> value of bar </bar>
301: * </foo>
302: * </root>
303: * </code>
304: *
305: * Since foo has no associated class, the path for 'bar' would be: "foo"
306: *
307: * @param path the "relative" location path for the field.
308: * @see #getLocationPath
309: */
310: public void setLocationPath(final String path) {
311: //-- need to add some validation to the path at some point.
312: _xmlPath = path;
313: }
314:
315: /**
316: * @see org.exolab.castor.xml.XMLFieldDescriptor#getLocationPath()
317: * {@inheritDoc}
318: */
319: public String getLocationPath() {
320: return _xmlPath;
321: }
322:
323: /**
324: * Sets the namespace prefix used when marshaling as XML.
325: *
326: * @param nsPrefix The namespace prefix used when marshaling the
327: * "described" object.
328: */
329: public void setNameSpacePrefix(final String nsPrefix) {
330: _nsPrefix = nsPrefix;
331: }
332:
333: /**
334: * @see org.exolab.castor.xml.XMLFieldDescriptor#getNameSpacePrefix()
335: * {@inheritDoc}
336: */
337: public String getNameSpacePrefix() {
338: return _nsPrefix;
339: }
340:
341: /**
342: * Sets whether or not the namespace for the parent "containing" class
343: * should be used during marshaling/unmarshaling when no specific
344: * namespace URI has been set for this field.
345: */
346: public void setUseParentsNamespace(final boolean useParentsNamespace) {
347: _useParentClassNamespace = useParentsNamespace;
348: }
349:
350: /**
351: * Sets the namespace URI used when marshaling and unmarshaling as XML.
352: *
353: * @param nsURI The namespace URI used when marshaling and unmarshaling the
354: * "described" Object.
355: */
356: public void setNameSpaceURI(final String nsURI) {
357: _nsURI = nsURI;
358: }
359:
360: /**
361: * @see org.exolab.castor.xml.XMLFieldDescriptor#getNameSpaceURI()
362: * {@inheritDoc}
363: */
364: public String getNameSpaceURI() {
365: ClassDescriptor parent = getContainingClassDescriptor();
366: if ((_nsURI == null) && (parent != null)
367: && _useParentClassNamespace) {
368: Class type = getFieldType();
369: boolean test = isPrimitive(type) || isBuiltInType(type)
370: || isMappedItem(type);
371: if ((_nodeType == NodeType.Element) && test) {
372: if (parent instanceof XMLClassDescriptor) {
373: return ((XMLClassDescriptor) parent)
374: .getNameSpaceURI();
375: }
376: }
377: }
378: return _nsURI;
379: }
380:
381: /**
382: * Sets the XML node type for the described field.
383: *
384: * @param nodeType the NodeType for the described field.
385: */
386: public void setNodeType(final NodeType nodeType) {
387: _nodeType = ((nodeType == null) ? NodeType.Attribute : nodeType);
388: }
389:
390: /**
391: * @see org.exolab.castor.xml.XMLFieldDescriptor#getNodeType()
392: * {@inheritDoc}
393: */
394: public NodeType getNodeType() {
395: return _nodeType;
396: }
397:
398: /**
399: * Sets the value property with the given name.
400: *
401: * @param propertyName The name of the property to set the value of.
402: * @param value The value of the property.
403: * @see #getProperty
404: */
405: public void setProperty(final String propertyName,
406: final String value) {
407: if (propertyName == null) {
408: String err = "The argument 'propertyName' must not be null.";
409: throw new IllegalArgumentException(err);
410: }
411:
412: if (_properties == null) {
413: _properties = new Properties();
414: }
415:
416: if (value == null) {
417: _properties.remove(propertyName);
418: } else {
419: _properties.put(propertyName, value);
420: }
421: }
422:
423: /**
424: * @see org.exolab.castor.xml.XMLFieldDescriptor#getProperty(java.lang.String)
425: * {@inheritDoc}
426: */
427: public String getProperty(final String propertyName) {
428: if ((_properties == null) || (propertyName == null)) {
429: return null;
430: }
431: return _properties.getProperty(propertyName);
432: }
433:
434: /**
435: * Sets the type of the XML Schema type of the value for the field being
436: * described.
437: *
438: * @param schemaType The value type.
439: */
440: public void setSchemaType(final String schemaType) {
441: _schemaType = schemaType;
442: }
443:
444: /**
445: * @see org.exolab.castor.xml.XMLFieldDescriptor#getSchemaType()
446: * {@inheritDoc}
447: */
448: public String getSchemaType() {
449: return _schemaType;
450: }
451:
452: public void setValidator(final FieldValidator validator) {
453: if (_validator != null) {
454: _validator.setDescriptor(null);
455: }
456: _validator = validator;
457: if (_validator != null) {
458: _validator.setDescriptor(this );
459: }
460: }
461:
462: /**
463: * @see org.exolab.castor.xml.XMLFieldDescriptor#getValidator()
464: * {@inheritDoc}
465: */
466: public FieldValidator getValidator() {
467: return _validator;
468: }
469:
470: /**
471: * Sets the xml name for the described field.
472: *
473: * @param xmlName the XML name for the described field.
474: */
475: public void setXMLName(final String xmlName) {
476: _xmlName = xmlName;
477: }
478:
479: /**
480: * @see org.exolab.castor.xml.XMLFieldDescriptor#getXMLName()
481: * {@inheritDoc}
482: */
483: public String getXMLName() {
484: return _xmlName;
485: }
486:
487: /**
488: * Set if the field is a container field or not.
489: *
490: * @param isContainer a boolean indicating whether or not the field is a
491: * container field.
492: */
493: public void setContainer(final boolean isContainer) {
494: _container = isContainer;
495: }
496:
497: /**
498: * @see org.exolab.castor.xml.XMLFieldDescriptor#isContainer()
499: * {@inheritDoc}
500: */
501: public boolean isContainer() {
502: return _container;
503: }
504:
505: /**
506: * Sets the incremental flag which indicates whether this member can be
507: * added before the unmarshaler is finished unmarshaling it.
508: *
509: * @param incremental the boolean which if true indicated that this member
510: * can safely be added before the unmarshaler is finished
511: * unmarshaling it.
512: */
513: public void setIncremental(final boolean incremental) {
514: _incremental = incremental;
515: }
516:
517: /**
518: * @see org.exolab.castor.xml.XMLFieldDescriptor#isIncremental()
519: * {@inheritDoc}
520: */
521: public boolean isIncremental() {
522: return _incremental;
523: }
524:
525: /**
526: * Sets whether or not this field has been mapped in a Map or Hashtable.
527: *
528: * @param mapped a boolean that when true indicates this field is a
529: * Hashtable or Map.
530: */
531: public void setMapped(final boolean mapped) {
532: _mapped = mapped;
533: }
534:
535: /**
536: * @see org.exolab.castor.xml.XMLFieldDescriptor#isMapped()
537: * {@inheritDoc}
538: */
539: public boolean isMapped() {
540: return _mapped;
541: }
542:
543: /**
544: * Sets whether or not the described field is allowed to be nil. A nillable
545: * field can have empty content (text or element content), but may have
546: * attribute values, and still be considered value, even if the child
547: * elements are required.
548: *
549: * @param nillable a boolean indicating whether or not the described field
550: * may be nillable.
551: */
552: public void setNillable(final boolean nillable) {
553: _nillable = nillable;
554: }
555:
556: /**
557: * @see org.exolab.castor.xml.XMLFieldDescriptor#isNillable()
558: * {@inheritDoc}
559: */
560: public boolean isNillable() {
561: return _nillable;
562: }
563:
564: /**
565: * Sets the flag indicating that the field described by this descriptor is a
566: * reference to another field in the object model.
567: *
568: * @param isReference true if the field is a reference to another field.
569: */
570: public void setReference(final boolean isReference) {
571: _isReference = isReference;
572: }
573:
574: /**
575: * @see org.exolab.castor.xml.XMLFieldDescriptor#isReference()
576: * {@inheritDoc}
577: */
578: public boolean isReference() {
579: return _isReference;
580: }
581:
582: /**
583: * Sets the prefix used in case the value of the field described by this
584: * descriptor is of type QName.
585: *
586: * @param qNamePrefix
587: */
588: public void setQNamePrefix(final String qNamePrefix) {
589: _qNamePrefix = qNamePrefix;
590: }
591:
592: /**
593: * Returns the prefix used in case the value of the field described by this
594: * descriptor is of type QName. This is helpful for the marshaler but not
595: * mandatory.
596: *
597: * @return the prefix used in the QName value.
598: */
599: public String getQNamePrefix() {
600: return _qNamePrefix;
601: }
602:
603: /**
604: * This is a space separated list of xml names that this Field descriptor
605: * matches. A '*' is wild.
606: *
607: * @param matchExpr the space separated list of xml names, matched by this
608: * descriptor.
609: */
610: public void setMatches(String matchExpr) {
611: _isWild = false;
612:
613: if ((matchExpr == null) || (matchExpr.length() == 0)) {
614: return;
615: }
616:
617: StringTokenizer st = new StringTokenizer(matchExpr);
618: ArrayList names = new ArrayList();
619: while (st.hasMoreTokens()) {
620: String token = st.nextToken();
621: if (WILD_CARD.equals(token)) {
622: _isWild = true;
623: break;
624: }
625: names.add(token);
626: }
627: _matches = new String[names.size()];
628: names.toArray(_matches);
629: }
630:
631: /**
632: * @see org.exolab.castor.xml.XMLFieldDescriptor#matches(java.lang.String)
633: * {@inheritDoc}
634: */
635: public boolean matches(final String xmlName) {
636: if (xmlName != null) {
637: if (_isWild) {
638: return true;
639: } else if (_matches.length > 0) {
640: for (int i = 0; i < _matches.length; i++) {
641: if (xmlName.equals(_matches[i])) {
642: return true;
643: }
644: }
645: } else {
646: return xmlName.equals(_xmlName);
647: }
648: }
649: return false;
650: }
651:
652: /**
653: * @see org.exolab.castor.xml.XMLFieldDescriptor#matches(java.lang.String, java.lang.String)
654: * {@inheritDoc}
655: */
656: public boolean matches(final String xmlName, final String namespace) {
657: // compare namespaces
658: if (namespace == null) {
659: if ((_nsURI != null) && (_nsURI.length() > 0)) {
660: return false;
661: }
662: } else if (_nsURI == null) {
663: if ((namespace.length() > 0) && (!_isWild)) {
664: return false;
665: }
666: } else if (!_nsURI.equals(namespace)) {
667: return false;
668: }
669:
670: // if we make this far the namespaces match, now compare names
671: return matches(xmlName);
672: }
673:
674: /**
675: * Returns true if two XMLFieldDescriptors should be treated as equal. Any
676: * XMLFieldDescriptor that handles the same field is considered equal.
677: * @param obj The object to compare to <code>this</code>
678: *
679: * @return true if two XMLFieldDescriptors should be treated as equal.
680: */
681: public boolean equals(final Object obj) {
682: if (obj == this ) {
683: return true;
684: }
685:
686: if ((obj == null) || (!(obj instanceof XMLFieldDescriptor))) {
687: return false;
688: }
689:
690: XMLFieldDescriptor descriptor = (XMLFieldDescriptor) obj;
691:
692: // check field names
693: if (!getFieldName().equals(descriptor.getFieldName())) {
694: return false;
695: }
696:
697: // check field types
698: if (!getFieldType().equals(descriptor.getFieldType())) {
699: return false;
700: }
701:
702: // check field handler
703: FieldHandler tmpHandler = descriptor.getHandler();
704: if (getHandler() == null) {
705: return (tmpHandler == null);
706: } else if (tmpHandler == null) {
707: return false;
708: }
709:
710: // The following line causes some issues when used against a FieldHandlerImpl
711: // because the equals method for FieldHandlerImpl is the default. Temporarily
712: // replace it with a slightly more generic comparison but this should probably
713: // change in the future. (kv)
714: // return (_handler.equals(tmpHandler));
715: return (getHandler().getClass().isInstance(tmpHandler));
716: }
717:
718: /**
719: * Returns the hashCode for this XMLFieldDescriptor
720: * @return the hashCode for this XMLFieldDescriptor
721: */
722: public int hashCode() {
723: int hash = 17;
724: hash = 17 * getFieldName().hashCode();
725: hash = hash * 17 * getFieldType().hashCode();
726: if (getHandler() != null) {
727: hash = hash * 17 * getHandler().hashCode();
728: }
729: return hash;
730: }
731:
732: public String toString() {
733: return "XMLFieldDesciptor: " + getFieldName() + " AS "
734: + _xmlName;
735: }
736:
737: //-------------------/
738: //- Private Methods -/
739: //-------------------/
740:
741: /**
742: * Returns true if the given class should be treated as a primitive type.
743: * This method will return true for all Java primitive types, the set of
744: * primitive object wrappers, as well as Strings.
745: *
746: * @return true If the given class should be treated as a primitive type.
747: */
748: private static boolean isPrimitive(final Class type) {
749: if (type == null) {
750: return false;
751: }
752: if (type.isPrimitive()) {
753: return true;
754: }
755: if (type == String.class) {
756: return true;
757: }
758: if ((type == Boolean.class) || (type == Character.class)) {
759: return true;
760: }
761:
762: // Any class which extends Number should be treated as a primitive.
763: return (type.getSuperclass() == Number.class);
764: }
765:
766: /**
767: * Return true if the given class is a "built-in" type. A built-in type is
768: * one in which Castor provides the default descriptor for.
769: *
770: * @param type The class to check.
771: * @return true If the given class is a built-in type.
772: */
773: private static boolean isBuiltInType(final Class type) {
774: if (type == null) {
775: return false;
776: }
777: //-- All built-in Java types, such as java.util.Date,
778: //-- java.sql.Date, various Collection classes, etc.
779: return (CoreDescriptors.getDescriptor(type) != null);
780: }
781:
782: private static boolean isMappedItem(final Class fieldType) {
783: return (fieldType == MapItem.class);
784: }
785:
786: /**
787: * Returns the possible substitution groups for this class.
788: * @return the possible substitution groups for this class.
789: */
790: public List getSubstitutes() {
791: return _substitutes;
792: }
793:
794: /**
795: * Sets the possible substitution groups for this class.
796: * @param substitutes Possible substitution groups for this class.
797: */
798: public void setSubstitutes(List substitutes) {
799: _substitutes = substitutes;
800: }
801:
802: }
|