001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.betwixt;
018:
019: import java.util.ArrayList;
020: import java.util.List;
021:
022: import org.apache.commons.betwixt.expression.Expression;
023:
024: /** <p><code>ElementDescriptor</code> describes the XML elements
025: * to be created for a bean instance.</p>
026: *
027: * <p> It contains <code>AttributeDescriptor</code>'s for all it's attributes
028: * and <code>ElementDescriptor</code>'s for it's child elements.
029: *
030: * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
031: * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
032: */
033: public class ElementDescriptor extends NodeDescriptor {
034:
035: /**
036: * Descriptors for attributes this element contains.
037: * <strong>Note:</strong> Constructed lazily on demand from a List.
038: * {@link #getAttributeDescriptor()} should be called rather than accessing this
039: * field directly.
040: */
041: private AttributeDescriptor[] attributeDescriptors;
042: /**
043: * Descriptors for child elements.
044: * <strong>Note:</strong> Constructed lazily on demand from a List.
045: * {@link #getElementDescriptor()} should be called rather than accessing this
046: * field directly.
047: */
048: private ElementDescriptor[] elementDescriptors;
049:
050: /**
051: * Descriptors for child content.
052: * <strong>Note:</strong> Constructed lazily on demand from a List.
053: * {@link #getContentDescriptor()} should be called rather than accessing this
054: * field directly.
055: */
056: private Descriptor[] contentDescriptors;
057:
058: /**
059: * The List used on construction. It will be GC'd
060: * after initilization and the array is lazily constructed
061: */
062: private List attributeList;
063:
064: /**
065: * The List used on construction. It will be GC'd
066: * after initilization and the array is lazily constructed
067: */
068: private List elementList;
069:
070: /**
071: * The list used o construct array. It will be GC'd after
072: * initialization when the array is lazily constructed.
073: */
074: private List contentList;
075:
076: /** the expression used to evaluate the new context of this node
077: * or null if the same context is to be used */
078: private Expression contextExpression;
079:
080: /** Whether this element refers to a primitive type (or property of a parent object) */
081: private boolean primitiveType;
082: /** Is this a collective type? */
083: private boolean isCollectiveType;
084:
085: /**
086: * Is this element hollow?
087: * In other words, is this descriptor a place holder indicating the name
088: * and update for a root ElementDescriptor for this type obtained by introspection
089: * TODO: this would probably be better modeled as a separate subclass
090: */
091: private boolean isHollow = false;
092:
093: /**
094: * Whether this collection element can be used
095: * as a collection element. Defaults to true
096: */
097: private boolean wrapCollectionsInElement = true;
098:
099: /** specifies a separate implementation class that should be instantiated
100: * when reading beans
101: * or null if there is no separate implementation */
102: private Class implementationClass = null;
103:
104: /**
105: * Should the bind time type determine the mapping?
106: * (As opposed to the introspection time type.)
107: * Note that this attribute is write once, read many (WORM).
108: */
109: private Boolean useBindTimeTypeForMapping = null;
110:
111: /**
112: * Constructs an <code>ElementDescriptor</code> that refers to a primitive type.
113: */
114: public ElementDescriptor() {
115: }
116:
117: /**
118: * Base constructor.
119: * @param primitiveType if true, this element refers to a primitive type
120: * @deprecated 0.6 PrimitiveType property has been removed
121: */
122: public ElementDescriptor(boolean primitiveType) {
123: this .primitiveType = primitiveType;
124: }
125:
126: /**
127: * Creates a ElementDescriptor with no namespace URI or prefix.
128: *
129: * @param localName the (xml) local name of this node.
130: * This will be used to set both qualified and local name for this name.
131: */
132: public ElementDescriptor(String localName) {
133: super (localName);
134: }
135:
136: /**
137: * Creates a <code>ElementDescriptor</code> with namespace URI and qualified name
138: * @param localName the (xml) local name of this node
139: * @param qualifiedName the (xml) qualified name of this node
140: * @param uri the (xml) namespace prefix of this node
141: */
142: public ElementDescriptor(String localName, String qualifiedName,
143: String uri) {
144: super (localName, qualifiedName, uri);
145: }
146:
147: /**
148: * Returns true if this element has child <code>ElementDescriptors</code>
149: * @return true if this element has child elements
150: * @see #getElementDescriptors
151: */
152: public boolean hasChildren() {
153: return getElementDescriptors().length > 0;
154: }
155:
156: /**
157: * Returns true if this element has <code>AttributeDescriptors</code>
158: * @return true if this element has attributes
159: * @see #getAttributeDescriptors
160: */
161: public boolean hasAttributes() {
162: return getAttributeDescriptors().length > 0;
163: }
164:
165: /**
166: * Returns true if this element has child content.
167: * @return true if this element has either child mixed content or child elements
168: * @see #getContentDescriptors
169: * @since 0.5
170: */
171: public boolean hasContent() {
172: return getContentDescriptors().length > 0;
173: }
174:
175: /**
176: * <p>Is this a simple element?</p>
177: * <p>
178: * A simple element is one without child elements or attributes.
179: * This corresponds to the simple type concept used in XML Schema.
180: * TODO: need to consider whether it's sufficient to calculate
181: * which are simple types (and so don't get IDs assigned etc).
182: * </p>
183: * @return true if it is a <code>SimpleType</code> element
184: */
185: public boolean isSimple() {
186: return !(hasAttributes()) && !(hasChildren());
187: }
188:
189: /**
190: * Sets whether <code>Collection</code> bean properties should wrap items in a parent element.
191: * In other words, should the mapping for bean properties which are <code>Collection</code>s
192: * enclosed the item elements within a parent element.
193: * Normally only used when this describes a collection bean property.
194: *
195: * @param wrapCollectionsInElement true if the elements for the items in the collection
196: * should be contained in a parent element
197: * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
198: * be done during introspection
199: */
200: public void setWrapCollectionsInElement(
201: boolean wrapCollectionsInElement) {
202: this .wrapCollectionsInElement = wrapCollectionsInElement;
203: }
204:
205: /**
206: * Returns true if collective bean properties should wrap the items in a parent element.
207: * In other words, should the mapping for bean properties which are <code>Collection</code>s
208: * enclosed the item elements within a parent element.
209: * Normally only used when this describes a collection bean property.
210: *
211: * @return true if the elements for the items in the collection should be contained
212: * in a parent element
213: * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
214: * be done during introspection
215: */
216: public boolean isWrapCollectionsInElement() {
217: return this .wrapCollectionsInElement;
218: }
219:
220: /**
221: * Adds an attribute to the element this <code>ElementDescriptor</code> describes
222: * @param descriptor the <code>AttributeDescriptor</code> that will be added to the
223: * attributes associated with element this <code>ElementDescriptor</code> describes
224: */
225: public void addAttributeDescriptor(AttributeDescriptor descriptor) {
226: if (attributeList == null) {
227: attributeList = new ArrayList();
228: }
229: getAttributeList().add(descriptor);
230: attributeDescriptors = null;
231: }
232:
233: /**
234: * Removes an attribute descriptor from this element descriptor.
235: * @param descriptor the <code>AttributeDescriptor</code> to be removed, not null
236: * @since 0.8
237: */
238: public void removeAttributeDescriptor(AttributeDescriptor descriptor) {
239: getAttributeList().remove(descriptor);
240: }
241:
242: /**
243: * Returns the attribute descriptors for this element
244: *
245: * @return descriptors for the attributes of the element that this
246: * <code>ElementDescriptor</code> describes
247: */
248: public AttributeDescriptor[] getAttributeDescriptors() {
249: if (attributeDescriptors == null) {
250: if (attributeList == null) {
251: attributeDescriptors = new AttributeDescriptor[0];
252: } else {
253: attributeDescriptors = new AttributeDescriptor[attributeList
254: .size()];
255: attributeList.toArray(attributeDescriptors);
256:
257: // allow GC of List when initialized
258: attributeList = null;
259: }
260: }
261: return attributeDescriptors;
262: }
263:
264: /**
265: * Returns an attribute descriptor with a given name or null.
266: *
267: * @param name to search for; will be checked against the attributes' qualified name.
268: * @return <code>AttributeDescriptor</code> with the given name,
269: * or null if no descriptor has that name
270: * @since 0.8
271: */
272: public AttributeDescriptor getAttributeDescriptor(final String name) {
273: for (int i = 0, size = attributeDescriptors.length; i < size; i++) {
274: AttributeDescriptor descr = attributeDescriptors[i];
275: if (descr.getQualifiedName().equals(name)) {
276: return descr;
277: }
278: }
279:
280: return null;
281: }
282:
283: /**
284: * Sets the <code>AttributesDescriptors</code> for this element.
285: * This sets descriptors for the attributes of the element describe by the
286: * <code>ElementDescriptor</code>.
287: *
288: * @param attributeDescriptors the <code>AttributeDescriptor</code> describe the attributes
289: * of the element described by this <code>ElementDescriptor</code>
290: */
291: public void setAttributeDescriptors(
292: AttributeDescriptor[] attributeDescriptors) {
293: this .attributeDescriptors = attributeDescriptors;
294: this .attributeList = null;
295: }
296:
297: /**
298: * Adds a descriptor for a child element.
299: *
300: * @param descriptor the <code>ElementDescriptor</code> describing the child element to add
301: */
302: public void addElementDescriptor(ElementDescriptor descriptor) {
303: if (elementList == null) {
304: elementList = new ArrayList();
305: }
306: getElementList().add(descriptor);
307: elementDescriptors = null;
308: addContentDescriptor(descriptor);
309: }
310:
311: /**
312: * Removes an element descriptor from this element descriptor.
313: * @param descriptor the <code>ElementDescriptor</code> that will be removed.
314: * @since 0.8
315: */
316: public void removeElementDescriptor(ElementDescriptor descriptor) {
317: getElementList().remove(descriptor);
318: getContentList().remove(descriptor);
319: }
320:
321: /**
322: * Returns descriptors for the child elements of the element this describes.
323: * @return the <code>ElementDescriptor</code> describing the child elements
324: * of the element that this <code>ElementDescriptor</code> describes
325: */
326: public ElementDescriptor[] getElementDescriptors() {
327: if (elementDescriptors == null) {
328: if (elementList == null) {
329: elementDescriptors = new ElementDescriptor[0];
330: } else {
331: elementDescriptors = new ElementDescriptor[elementList
332: .size()];
333: elementList.toArray(elementDescriptors);
334:
335: // allow GC of List when initialized
336: elementList = null;
337: }
338: }
339: return elementDescriptors;
340: }
341:
342: /**
343: * Gets a child ElementDescriptor matching the given name if one exists.
344: * Note that (so long as there are no better matches), a null name
345: * acts as a wildcard. In other words, an
346: * <code>ElementDescriptor</code> the first descriptor
347: * with a null name will match any name
348: * passed in, unless some other matches the name exactly.
349: *
350: * @param name the localname to be matched, not null
351: * @return the child ElementDescriptor with the given name if one exists,
352: * otherwise null
353: */
354: public ElementDescriptor getElementDescriptor(String name) {
355:
356: ElementDescriptor elementDescriptor = null;
357: ElementDescriptor descriptorWithNullName = null;
358: ElementDescriptor firstPolymorphic = null;
359: ElementDescriptor[] elementDescriptors = getElementDescriptors();
360: for (int i = 0, size = elementDescriptors.length; i < size; i++) {
361: if (firstPolymorphic == null
362: && elementDescriptors[i].isPolymorphic()) {
363: firstPolymorphic = elementDescriptors[i];
364: }
365: String elementName = elementDescriptors[i]
366: .getQualifiedName();
367: if (name.equals(elementName)) {
368: elementDescriptor = elementDescriptors[i];
369: break;
370: }
371: if (descriptorWithNullName == null && elementName == null) {
372: descriptorWithNullName = elementDescriptors[i];
373: }
374: }
375: if (elementDescriptor == null) {
376: elementDescriptor = firstPolymorphic;
377: }
378: if (elementDescriptor == null) {
379: elementDescriptor = descriptorWithNullName;
380: }
381: return elementDescriptor;
382: }
383:
384: /**
385: * Sets the descriptors for the child element of the element this describes.
386: * Also sets the child content descriptors for this element
387: *
388: * @param elementDescriptors the <code>ElementDescriptor</code>s of the element
389: * that this describes
390: */
391: public void setElementDescriptors(
392: ElementDescriptor[] elementDescriptors) {
393: this .elementDescriptors = elementDescriptors;
394: this .elementList = null;
395: setContentDescriptors(elementDescriptors);
396: }
397:
398: /**
399: * Adds a descriptor for child content.
400: *
401: * @param descriptor the <code>Descriptor</code> describing the child content to add
402: * @since 0.5
403: */
404: public void addContentDescriptor(Descriptor descriptor) {
405: if (contentList == null) {
406: contentList = new ArrayList();
407: }
408: getContentList().add(descriptor);
409: contentDescriptors = null;
410: }
411:
412: /**
413: * Returns descriptors for the child content of the element this describes.
414: * @return the <code>Descriptor</code> describing the child elements
415: * of the element that this <code>ElementDescriptor</code> describes
416: * @since 0.5
417: */
418: public Descriptor[] getContentDescriptors() {
419: if (contentDescriptors == null) {
420: if (contentList == null) {
421: contentDescriptors = new Descriptor[0];
422: } else {
423: contentDescriptors = new Descriptor[contentList.size()];
424: contentList.toArray(contentDescriptors);
425:
426: // allow GC of List when initialized
427: contentList = null;
428: }
429: }
430: return contentDescriptors;
431: }
432:
433: /**
434: * <p>Gets the primary descriptor for body text of this element.
435: * Betwixt collects all body text for any element together.
436: * This makes it rounds tripping difficult for beans that write more than one
437: * mixed content property.
438: * </p><p>
439: * The algorithm used in the default implementation is that the first TextDescriptor
440: * found amongst the descriptors is returned.
441: *
442: * @return the primary descriptor or null if this element has no mixed body content
443: * @since 0.5
444: */
445: public TextDescriptor getPrimaryBodyTextDescriptor() {
446: // todo: this probably isn't the most efficent algorithm
447: // but should avoid premature optimization
448: Descriptor[] descriptors = getContentDescriptors();
449: for (int i = 0, size = descriptors.length; i < size; i++) {
450: if (descriptors[i] instanceof TextDescriptor) {
451: return (TextDescriptor) descriptors[i];
452: }
453: }
454: // if we haven't found anything, return null.
455: return null;
456: }
457:
458: /**
459: * Sets the descriptors for the child content of the element this describes.
460: * @param contentDescriptors the <code>Descriptor</code>s of the element
461: * that this describes
462: * @since 0.5
463: */
464: public void setContentDescriptors(Descriptor[] contentDescriptors) {
465: this .contentDescriptors = contentDescriptors;
466: this .contentList = null;
467: }
468:
469: /**
470: * Returns the expression used to evaluate the new context of this element.
471: * @return the expression used to evaluate the new context of this element
472: */
473: public Expression getContextExpression() {
474: return contextExpression;
475: }
476:
477: /**
478: * Sets the expression used to evaluate the new context of this element
479: * @param contextExpression the expression used to evaluate the new context of this element
480: */
481: public void setContextExpression(Expression contextExpression) {
482: this .contextExpression = contextExpression;
483: }
484:
485: /**
486: * Returns true if this element refers to a primitive type property
487: * @return whether this element refers to a primitive type (or property of a parent object)
488: * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
489: * be done during introspection
490: */
491: public boolean isPrimitiveType() {
492: return primitiveType;
493: }
494:
495: /**
496: * Sets whether this element refers to a primitive type (or property of a parent object)
497: * @param primitiveType true if this element refers to a primitive type
498: * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
499: * be done during introspection
500: */
501: public void setPrimitiveType(boolean primitiveType) {
502: this .primitiveType = primitiveType;
503: }
504:
505: // Implementation methods
506: //-------------------------------------------------------------------------
507:
508: /**
509: * Lazily creates the mutable List.
510: * This nullifies the attributeDescriptors array so that
511: * as items are added to the list the Array is ignored until it is
512: * explicitly asked for.
513: *
514: * @return list of <code>AttributeDescriptors</code>'s describing the attributes
515: * of the element that this <code>ElementDescriptor</code> describes
516: */
517: protected List getAttributeList() {
518: if (attributeList == null) {
519: if (attributeDescriptors != null) {
520: int size = attributeDescriptors.length;
521: attributeList = new ArrayList(size);
522: for (int i = 0; i < size; i++) {
523: attributeList.add(attributeDescriptors[i]);
524: }
525: // force lazy recreation later
526: attributeDescriptors = null;
527: } else {
528: attributeList = new ArrayList();
529: }
530: }
531: return attributeList;
532: }
533:
534: /**
535: * Lazily creates the mutable List of child elements.
536: * This nullifies the elementDescriptors array so that
537: * as items are added to the list the Array is ignored until it is
538: * explicitly asked for.
539: *
540: * @return list of <code>ElementDescriptor</code>'s describe the child elements of
541: * the element that this <code>ElementDescriptor</code> describes
542: */
543: protected List getElementList() {
544: if (elementList == null) {
545: if (elementDescriptors != null) {
546: int size = elementDescriptors.length;
547: elementList = new ArrayList(size);
548: for (int i = 0; i < size; i++) {
549: elementList.add(elementDescriptors[i]);
550: }
551: // force lazy recreation later
552: elementDescriptors = null;
553: } else {
554: elementList = new ArrayList();
555: }
556: }
557: return elementList;
558: }
559:
560: /**
561: * Lazily creates the mutable List of child content descriptors.
562: * This nullifies the contentDescriptors array so that
563: * as items are added to the list the Array is ignored until it is
564: * explicitly asked for.
565: *
566: * @return list of <code>Descriptor</code>'s describe the child content of
567: * the element that this <code>Descriptor</code> describes
568: * @since 0.5
569: */
570: protected List getContentList() {
571: if (contentList == null) {
572: if (contentDescriptors != null) {
573: int size = contentDescriptors.length;
574: contentList = new ArrayList(size);
575: for (int i = 0; i < size; i++) {
576: contentList.add(contentDescriptors[i]);
577: }
578: // force lazy recreation later
579: contentDescriptors = null;
580: } else {
581: contentList = new ArrayList();
582: }
583: }
584: return contentList;
585: }
586:
587: /**
588: * Gets the class which should be used for instantiation.
589: * @return the class which should be used for instantiation of beans
590: * mapped from this element, null if the standard class should be used
591: */
592: public Class getImplementationClass() {
593: return implementationClass;
594: }
595:
596: /**
597: * Sets the class which should be used for instantiation.
598: * @param implementationClass the class which should be used for instantiation
599: * or null to use the mapped type
600: * @since 0.5
601: */
602: public void setImplementationClass(Class implementationClass) {
603: this .implementationClass = implementationClass;
604: }
605:
606: /**
607: * Does this describe a collective?
608: */
609: public boolean isCollective() {
610: // TODO is this implementation correct?
611: // maybe this method is unnecessary
612: return isCollectiveType;
613: }
614:
615: /**
616: * Sets whether the element described is a collective.
617: * @since 0.7
618: * @param isCollectiveType
619: */
620: public void setCollective(boolean isCollectiveType) {
621: this .isCollectiveType = isCollectiveType;
622: }
623:
624: /**
625: * Finds the parent of the given descriptor.
626: * @param elementDescriptor <code>ElementDescriptor</code>
627: * @return <code>ElementDescriptor</code>, not null
628: */
629: public ElementDescriptor findParent(
630: ElementDescriptor elementDescriptor) {
631: //TODO: is this really a good design?
632: ElementDescriptor result = null;
633: ElementDescriptor[] elementDescriptors = getElementDescriptors();
634: for (int i = 0, size = elementDescriptors.length; i < size; i++) {
635: if (elementDescriptors[i].equals(elementDescriptor)) {
636: result = this ;
637: break;
638: } else {
639: result = elementDescriptors[i]
640: .findParent(elementDescriptor);
641: if (result != null) {
642: break;
643: }
644: }
645: }
646: return result;
647: }
648:
649: /**
650: * Returns something useful for logging.
651: *
652: * @return a string useful for logging
653: */
654: public String toString() {
655: return "ElementDescriptor[qname=" + getQualifiedName()
656: + ",pname=" + getPropertyName() + ",class="
657: + getPropertyType() + ",singular="
658: + getSingularPropertyType() + ",updater="
659: + getUpdater() + ",wrap="
660: + isWrapCollectionsInElement() + "]";
661: }
662:
663: /**
664: * <p>Is this decriptor hollow?</p>
665: * <p>
666: * A hollow descriptor is one which gives only the class that the subgraph
667: * is mapped to rather than describing the entire subgraph.
668: * A new <code>XMLBeanInfo</code> should be introspected
669: * and that used to describe the subgraph.
670: * A hollow descriptor should not have any child descriptors.
671: * TODO: consider whether a subclass would be better
672: * </p>
673: * @return true if this is hollow
674: */
675: public boolean isHollow() {
676: return isHollow;
677: }
678:
679: /**
680: * Sets whether this descriptor is hollow.
681: * A hollow descriptor is one which gives only the class that the subgraph
682: * is mapped to rather than describing the entire subgraph.
683: * A new <code>XMLBeanInfo</code> should be introspected
684: * and that used to describe the subgraph.
685: * A hollow descriptor should not have any child descriptors.
686: * TODO: consider whether a subclass would be better
687: * @param isHollow true if this is hollow
688: */
689: public void setHollow(boolean isHollow) {
690: this .isHollow = isHollow;
691: }
692:
693: /**
694: * <p>Is the bind time type to be used to determine the mapping?</p>
695: * <p>
696: * The mapping for an object property value can either be the
697: * introspection time type (based on the logical type of the property)
698: * or the bind time type (based on the type of the actual instance).
699: * </p>
700: * @since 0.7
701: * @return true if the bind time type is to be used to determine the mapping,
702: * false if the introspection time type is to be used
703: */
704: public boolean isUseBindTimeTypeForMapping() {
705: boolean result = true;
706: if (this .useBindTimeTypeForMapping != null) {
707: result = this .useBindTimeTypeForMapping.booleanValue();
708: }
709: return result;
710: }
711:
712: /**
713: * <p>Sets whether the bind time type to be used to determine the mapping.
714: * The mapping for an object property value can either be the
715: * introspection time type (based on the logical type of the property)
716: * or the bind time type (based on the type of the actual instance).
717: * </p><p>
718: * <strong>Note:</strong> this property is write once, read many.
719: * So, the first time that this method is called the value will be set
720: * but subsequent calls will be ignored.
721: * </p>
722: * @since 0.7
723: * @param useBindTimeTypeForMapping true if the bind time type is to be used to
724: * determine the mapping, false if the introspection time type is to be used
725: */
726: public void setUseBindTimeTypeForMapping(
727: boolean useBindTimeTypeForMapping) {
728: if (this .useBindTimeTypeForMapping == null) {
729: this .useBindTimeTypeForMapping = new Boolean(
730: useBindTimeTypeForMapping);
731: }
732: }
733:
734: /**
735: * <p>Is this a polymorphic element?</p>
736: * <p>
737: * A polymorphic element's name is not fixed at
738: * introspection time and it's resolution is postponed to bind time.
739: * </p>
740: * @since 0.7
741: * @return true if {@link #getQualifiedName} is null,
742: * false otherwise
743: */
744: public boolean isPolymorphic() {
745: return (getQualifiedName() == null);
746: }
747: }
|