001: /*
002: Copyright (c) 2004-2007, Dennis M. Sosnoski
003: All rights reserved.
004:
005: Redistribution and use in source and binary forms, with or without modification,
006: are permitted provided that the following conditions are met:
007:
008: * Redistributions of source code must retain the above copyright notice, this
009: list of conditions and the following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice,
011: this list of conditions and the following disclaimer in the documentation
012: and/or other materials provided with the distribution.
013: * Neither the name of JiBX nor the names of its contributors may be used
014: to endorse or promote products derived from this software without specific
015: prior written permission.
016:
017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: */
028:
029: package org.jibx.binding.model;
030:
031: import java.util.ArrayList;
032: import java.util.HashMap;
033: import java.util.Iterator;
034:
035: import org.jibx.binding.util.StringArray;
036: import org.jibx.runtime.IUnmarshallingContext;
037: import org.jibx.runtime.JiBXException;
038:
039: /**
040: * Model component for <b>collection</b> element of binding definition.
041: *
042: * @author Dennis M. Sosnoski
043: * @version 1.0
044: */
045:
046: public class CollectionElement extends StructureElementBase {
047: /** Enumeration of allowed attribute names */
048: public static final StringArray s_allowedAttributes = new StringArray(
049: new String[] { "add-method", "item-type", "iter-method",
050: "load-method", "size-method", "store-method" },
051: StructureElementBase.s_allowedAttributes);
052:
053: /** Load method name. */
054: private String m_loadMethodName;
055:
056: /** Size method name. */
057: private String m_sizeMethodName;
058:
059: /** Store method name. */
060: private String m_storeMethodName;
061:
062: /** Add method name. */
063: private String m_addMethodName;
064:
065: /** Iterator method name. */
066: private String m_iterMethodName;
067:
068: /** Item type name. */
069: private String m_itemTypeName;
070:
071: /** Load method information. */
072: private IClassItem m_loadMethodItem;
073:
074: /** Size method information. */
075: private IClassItem m_sizeMethodItem;
076:
077: /** Store method information. */
078: private IClassItem m_storeMethodItem;
079:
080: /** Add method information. */
081: private IClassItem m_addMethodItem;
082:
083: /** Iterator method information. */
084: private IClassItem m_iterMethodItem;
085:
086: /** Item type information. */
087: private IClass m_itemTypeClass;
088:
089: /**
090: * Default constructor.
091: */
092: public CollectionElement() {
093: super (COLLECTION_ELEMENT);
094: }
095:
096: /**
097: * Get item type name.
098: *
099: * @return item type name (or <code>null</code> if none)
100: */
101: public String getItemTypeName() {
102: return m_itemTypeName;
103: }
104:
105: /**
106: * Set item type name.
107: *
108: * @param type item type name (or <code>null</code> if none)
109: */
110: public void setItemTypeName(String type) {
111: m_itemTypeName = type;
112: }
113:
114: /**
115: * Get item type information. This call is only meaningful after
116: * validation.
117: *
118: * @return item type information
119: */
120: public IClass getItemTypeClass() {
121: return m_itemTypeClass;
122: }
123:
124: /**
125: * Get add method name.
126: *
127: * @return add method name (or <code>null</code> if none)
128: */
129: public String getAddMethodName() {
130: return m_addMethodName;
131: }
132:
133: /**
134: * Set add method name.
135: *
136: * @param name add method name (or <code>null</code> if none)
137: */
138: public void setAddMethodName(String name) {
139: m_addMethodName = name;
140: }
141:
142: /**
143: * Get add method information. This call is only meaningful after
144: * validation.
145: *
146: * @return add method information (or <code>null</code> if none)
147: */
148: public IClassItem getAddMethodItem() {
149: return m_addMethodItem;
150: }
151:
152: /**
153: * Get iterator method name.
154: *
155: * @return iterator method name (or <code>null</code> if none)
156: */
157: public String getIterMethodName() {
158: return m_iterMethodName;
159: }
160:
161: /**
162: * Set iterator method name.
163: *
164: * @param name iterator method name (or <code>null</code> if none)
165: */
166: public void setIterMethodName(String name) {
167: m_iterMethodName = name;
168: }
169:
170: /**
171: * Get iterator method information. This call is only meaningful after
172: * validation.
173: *
174: * @return iterator method information (or <code>null</code> if none)
175: */
176: public IClassItem getIterMethodItem() {
177: return m_iterMethodItem;
178: }
179:
180: /**
181: * Get load method name.
182: *
183: * @return load method name (or <code>null</code> if none)
184: */
185: public String getLoadMethodName() {
186: return m_loadMethodName;
187: }
188:
189: /**
190: * Set load method name.
191: *
192: * @param name load method name (or <code>null</code> if none)
193: */
194: public void setLoadMethodName(String name) {
195: m_loadMethodName = name;
196: }
197:
198: /**
199: * Get load method information. This call is only meaningful after
200: * validation.
201: *
202: * @return load method information (or <code>null</code> if none)
203: */
204: public IClassItem getLoadMethodItem() {
205: return m_loadMethodItem;
206: }
207:
208: /**
209: * Get size method name.
210: *
211: * @return size method name (or <code>null</code> if none)
212: */
213: public String getSizeMethodName() {
214: return m_sizeMethodName;
215: }
216:
217: /**
218: * Set size method name.
219: *
220: * @param name size method name (or <code>null</code> if none)
221: */
222: public void setSizeMethodName(String name) {
223: m_sizeMethodName = name;
224: }
225:
226: /**
227: * Get size method information. This call is only meaningful after
228: * validation.
229: *
230: * @return size method information (or <code>null</code> if none)
231: */
232: public IClassItem getSizeMethodItem() {
233: return m_sizeMethodItem;
234: }
235:
236: /**
237: * Get store method name.
238: *
239: * @return store method name (or <code>null</code> if none)
240: */
241: public String getStoreMethodName() {
242: return m_storeMethodName;
243: }
244:
245: /**
246: * Set store method name.
247: *
248: * @param name store method name (or <code>null</code> if none)
249: */
250: public void setStoreMethodName(String name) {
251: m_storeMethodName = name;
252: }
253:
254: /**
255: * Get store method information. This call is only meaningful after
256: * validation.
257: *
258: * @return store method information (or <code>null</code> if none)
259: */
260: public IClassItem getStoreMethodItem() {
261: return m_storeMethodItem;
262: }
263:
264: //
265: // Overrides of base class methods
266:
267: /**
268: * Set ID property. This is never supported for an object coming from a
269: * collection.
270: *
271: * @param child child defining the ID property
272: * @return <code>true</code> if successful, <code>false</code> if ID
273: * already defined
274: */
275: public boolean setIdChild(IComponent child) {
276: throw new IllegalStateException(
277: "Internal error: method should never be called");
278: }
279:
280: /**
281: * Check for object present. Always <code>true</code> for collection.
282: *
283: * @return <code>true</code>
284: */
285: public boolean hasObject() {
286: return true;
287: }
288:
289: /**
290: * Check for attribute definition. Always <code>false</code> for collection.
291: *
292: * @return <code>false</code>
293: */
294: public boolean hasAttribute() {
295: return false;
296: }
297:
298: /**
299: * Check for content definition. Always <code>true</code> for collection.
300: *
301: * @return <code>true</code>
302: */
303: public boolean hasContent() {
304: return true;
305: }
306:
307: /* (non-Javadoc)
308: * @see org.jibx.binding.model.ContainerElementBase#getChildObjectType()
309: */
310: public IClass getChildObjectType() {
311: return getItemTypeClass();
312: }
313:
314: //
315: // Validation methods
316:
317: /**
318: * Make sure all attributes are defined.
319: *
320: * @param uctx unmarshalling context
321: * @exception JiBXException on unmarshalling error
322: */
323: private void preSet(IUnmarshallingContext uctx)
324: throws JiBXException {
325: validateAttributes(uctx, s_allowedAttributes);
326: }
327:
328: /* (non-Javadoc)
329: * @see org.jibx.binding.model.ElementBase#prevalidate(org.jibx.binding.model.ValidationContext)
330: */
331: public void prevalidate(ValidationContext vctx) {
332:
333: // first process attributes and check for errors
334: super .prevalidate(vctx);
335: if (!vctx.isSkipped(this )) {
336:
337: // check for ignored attributes
338: if (isAllowRepeats()) {
339: vctx
340: .addWarning("'allow-repeats' attribute ignored on collection");
341: }
342: if (isChoice()) {
343: vctx
344: .addWarning("'choice' attribute ignored on collection");
345: }
346:
347: // get the actual collection type and item type
348: IClass clas = getType();
349: if (clas == null) {
350: clas = vctx.getContextObject().getObjectType();
351: }
352: String tname = m_itemTypeName;
353: if (tname == null) {
354: String ctype = clas.getName();
355: if (ctype.endsWith("[]")) {
356: tname = ctype.substring(0, ctype.length() - 2);
357: } else {
358: tname = "java.lang.Object";
359: }
360: }
361: m_itemTypeClass = vctx.getClassInfo(tname);
362: if (m_itemTypeClass == null) {
363: vctx.addFatal("Can't find class " + tname);
364: }
365:
366: // handle input and output bindings separately
367: if (vctx.isInBinding()) {
368:
369: // check store techniques
370: String sname = m_storeMethodName;
371: String aname = m_addMethodName;
372: if (sname != null && aname != null) {
373: vctx.addWarning("Both store-method and add-method "
374: + "supplied; using add-method");
375: sname = null;
376: }
377:
378: // set defaults based on collection type if needed
379: if (sname == null && aname == null) {
380: if (clas.isSuperclass("java.util.ArrayList")
381: || clas.isSuperclass("java.util.Vector")
382: || clas
383: .isImplements("Ljava/util/Collection;")) {
384: aname = "add";
385: } else if (!clas.getName().endsWith("[]")) {
386: vctx
387: .addError("Need store-method or add-method for "
388: + "input binding");
389: }
390: }
391:
392: // find the actual method information
393: if (sname != null) {
394: m_storeMethodItem = clas.getBestMethod(sname, null,
395: new String[] { "int", tname });
396: if (m_storeMethodItem == null) {
397: vctx.addError("store-method " + sname
398: + " not found in class "
399: + clas.getName());
400: }
401: }
402: if (aname != null) {
403: m_addMethodItem = clas.getBestMethod(aname, null,
404: new String[] { tname });
405: if (m_addMethodItem == null) {
406: vctx.addError("add-method " + aname
407: + " not found in class "
408: + clas.getName());
409: }
410: }
411:
412: }
413: if (vctx.isOutBinding()) {
414:
415: // precheck load techniques
416: String lname = m_loadMethodName;
417: String sname = m_sizeMethodName;
418: String iname = m_iterMethodName;
419: if (lname == null) {
420: if (sname != null) {
421: vctx
422: .addWarning("size-method requires load-method; "
423: + "ignoring supplied size-method");
424: sname = null;
425: }
426: } else {
427: if (sname == null) {
428: vctx
429: .addWarning("load-method requires "
430: + "size-method; ignoring supplied load-method");
431: lname = null;
432: } else {
433: if (iname != null) {
434: vctx
435: .addWarning("Both load-method and "
436: + "iter-method supplied; using load-method");
437: iname = null;
438: }
439: }
440: }
441:
442: // set defaults based on collection type if needed
443: if (lname == null && iname == null) {
444: if (clas.isSuperclass("java.util.ArrayList")
445: || clas.isSuperclass("java.util.Vector")) {
446: lname = "get";
447: sname = "size";
448: } else if (clas
449: .isImplements("Ljava/util/Collection;")) {
450: iname = "iterator";
451: }
452: }
453:
454: // postcheck load techniques with defaults set
455: if (lname == null) {
456: if (iname == null && !clas.getName().endsWith("[]")) {
457: vctx
458: .addError("Need load-method and size-method, or "
459: + "iter-method, for output binding");
460: }
461: } else {
462: if (sname == null && iname == null) {
463: vctx
464: .addError("Need load-method and size-method,"
465: + " or iter-method, for output binding");
466: }
467: }
468:
469: // find the actual method information
470: if (lname != null) {
471: m_loadMethodItem = clas.getBestMethod(lname, tname,
472: new String[] { "int" });
473: if (m_loadMethodItem == null) {
474: vctx.addError("load-method " + lname
475: + " not found in class "
476: + clas.getName());
477: }
478: }
479: if (iname != null) {
480: m_iterMethodItem = clas.getBestMethod(iname,
481: "java.util.Iterator", new String[0]);
482: if (m_iterMethodItem == null) {
483: vctx.addError("iter-method " + iname
484: + " not found in class "
485: + clas.getName());
486: }
487: }
488: }
489: }
490: }
491:
492: /**
493: * Check that child components are of types compatible with the collection
494: * item-type. This method may call itself recursively to process the
495: * children of child components which do not themselves set a type. The
496: * result is used for recursive checking to detect conditions where an
497: * inner structure defines a type but an outer one does not (which causes
498: * errors in the current code generation).
499: *
500: * @param vctx validation context
501: * @param type collection item type
502: * @param children list of child components to be checked
503: * @return <code>true</code> if only child is a <value> element with
504: * type, <code>false</code> if not
505: */
506: private boolean checkCollectionChildren(ValidationContext vctx,
507: IClass type, ArrayList children) {
508: boolean valonly = children.size() == 1;
509: for (int i = 0; i < children.size(); i++) {
510: ElementBase child = (ElementBase) children.get(i);
511: if (!vctx.isSkipped(child)) {
512:
513: // track whether children of this child must be type-checked
514: boolean expand = true;
515: valonly = valonly && (child instanceof ValueElement);
516: if (child instanceof IComponent) {
517:
518: // first make sure a name is present
519: IComponent comp = (IComponent) child;
520: if (vctx.isInBinding() && !comp.hasName()
521: && comp instanceof ValueElement) {
522: vctx
523: .addFatal(
524: "<value> elements within a collection "
525: + "must define element name for unmarshalling",
526: comp);
527: }
528:
529: // find the type associated with this component (if any)
530: IClass ctype = comp.getType();
531: expand = false;
532: if (comp instanceof ContainerElementBase) {
533: ContainerElementBase contain = (ContainerElementBase) comp;
534: if (contain.hasObject()) {
535: ctype = contain.getObjectType();
536: } else {
537: expand = true;
538: }
539: }
540:
541: // see if a type was found (no need to look at children)
542: if (!expand) {
543:
544: // verify that type is compatible with collection
545: if (!ctype.isAssignable(type)) {
546: vctx.addFatal(
547: "References to collection items must "
548: + "use compatible types: "
549: + ctype.getName()
550: + " cannot be used as "
551: + type.getName(), child);
552: }
553: }
554: }
555:
556: // recurse on children if necessary for type-checking
557: if (expand && child instanceof NestingElementBase) {
558: NestingElementBase nest = (NestingElementBase) child;
559: if (nest.children().size() > 0) {
560:
561: // type check child definitions
562: boolean valchild = checkCollectionChildren(
563: vctx, type,
564: ((NestingElementBase) child).children());
565:
566: // now check for structure element with no type
567: if (!valchild
568: && child instanceof StructureElement) {
569: vctx
570: .addError(
571: "Type must be specified on outermost <structure> element within collection",
572: child);
573: }
574: }
575: }
576: }
577: }
578: return valonly;
579: }
580:
581: /**
582: * Check children of unordered collection for consistency. In an input
583: * binding each child element must define a unique qualified name. In an
584: * output binding each child element must define a unique type or supply
585: * a test method to allow checking when that element should be generated for
586: * an object.
587: *
588: * @param vctx validation context
589: * @param children list of child components
590: */
591: private void checkUnorderedChildren(ValidationContext vctx,
592: ArrayList children) {
593: HashMap typemap = new HashMap();
594: HashMap namemap = new HashMap();
595: for (int i = 0; i < children.size(); i++) {
596: ElementBase child = (ElementBase) children.get(i);
597: if (child instanceof IComponent && !vctx.isSkipped(child)) {
598: IComponent comp = (IComponent) child;
599: if (vctx.isInBinding()) {
600:
601: // names must be distinct in input binding
602: String name = comp.getName();
603: String uri = comp.getUri();
604: if (uri == null) {
605: uri = "";
606: }
607: Object value = namemap.get(name);
608: if (value == null) {
609:
610: // first instance of name, store directly
611: namemap.put(name, comp);
612:
613: } else if (value instanceof HashMap) {
614:
615: // multiple instances already found, match on URI
616: HashMap urimap = (HashMap) value;
617: if (urimap.get(uri) != null) {
618: vctx
619: .addError(
620: "Duplicate names are not "
621: + "allowed in unordered collection",
622: comp);
623: } else {
624: urimap.put(uri, comp);
625: }
626:
627: } else {
628:
629: // duplicate name, check URI
630: IComponent match = (IComponent) value;
631: if ((uri == null && match.getUri() == null)
632: || (uri != null && uri.equals(match
633: .getUri()))) {
634: vctx
635: .addError(
636: "Duplicate names are not "
637: + "allowed in unordered collection",
638: comp);
639: } else {
640:
641: // multiple namespaces for same name, use map
642: HashMap urimap = new HashMap();
643: urimap.put(uri, comp);
644: String muri = match.getUri();
645: if (muri == null) {
646: muri = "";
647: }
648: urimap.put(muri, match);
649: namemap.put(name, urimap);
650: }
651:
652: }
653: }
654: if (vctx.isOutBinding()) {
655:
656: // just accumulate lists of each type in this loop
657: String type = comp.getType().getName();
658: Object value = typemap.get(type);
659: if (value == null) {
660: typemap.put(type, comp);
661: } else if (value instanceof ArrayList) {
662: ArrayList types = (ArrayList) value;
663: types.add(comp);
664: } else {
665: ArrayList types = new ArrayList();
666: types.add(value);
667: types.add(comp);
668: typemap.put(type, types);
669: }
670: }
671: }
672: }
673:
674: // check for duplicate type usage in output binding
675: for (Iterator iter = typemap.values().iterator(); iter
676: .hasNext();) {
677: Object value = iter.next();
678: if (value instanceof ArrayList) {
679:
680: // multiple instances of type, make sure we can distinguish
681: ArrayList types = (ArrayList) value;
682: for (int i = 0; i < types.size(); i++) {
683: Object child = types.get(i);
684: if (child instanceof ValueElement) {
685: ValueElement vel = (ValueElement) child;
686: if (vel.getTest() == null) {
687: vctx
688: .addError(
689: "test-method needed for "
690: + "multiple instances of same type in "
691: + "unordered collection",
692: vel);
693: }
694: } else if (child instanceof StructureElementBase) {
695: StructureElementBase sel = (StructureElementBase) child;
696: if (sel.getTest() == null) {
697: vctx
698: .addError(
699: "test-method needed for "
700: + "multiple instances of same type in "
701: + "unordered collection",
702: sel);
703: }
704: }
705: }
706: }
707: }
708: }
709:
710: /**
711: * Check children of ordered collection for consistency. In an input binding
712: * each child element must use a different qualified name from the preceding
713: * child element. In an output binding each child element must define a
714: * different type from the preceding child element, or the preceding child
715: * element must supply a test method to allow checking when that element
716: * should be generated for an object.
717: *
718: * @param vctx validation context
719: * @param children list of child components
720: */
721: private void checkOrderedChildren(ValidationContext vctx,
722: ArrayList children) {
723: IComponent prior = null;
724: for (int i = 0; i < children.size(); i++) {
725: ElementBase child = (ElementBase) children.get(i);
726: if (child instanceof IComponent && !vctx.isSkipped(child)) {
727: IComponent comp = (IComponent) child;
728: if (prior == null) {
729: prior = comp;
730: } else {
731: if (vctx.isInBinding()) {
732:
733: // make sure names are different
734: String uri = comp.getUri();
735: String cname = comp.getName();
736: String pname = prior.getName();
737: if (cname != null
738: && pname != null
739: && cname.equals(pname)
740: && ((uri == null && prior.getUri() == null) || (uri != null && uri
741: .equals(prior.getUri())))) {
742: vctx
743: .addError(
744: "Successive elements of collection "
745: + "cannot use duplicate names for unmarshalling",
746: comp);
747: }
748: }
749: if (vctx.isOutBinding()) {
750:
751: // make sure types differ or test method supplied
752: IClass type = comp.getType();
753: if (type.isAssignable(prior.getType())) {
754: IClassItem test = null;
755: if (prior instanceof ValueElement) {
756: test = ((ValueElement) prior).getTest();
757: } else if (prior instanceof StructureElementBase) {
758: test = ((StructureElementBase) prior)
759: .getTest();
760: }
761: if (test == null) {
762: vctx
763: .addError(
764: "Collection component must "
765: + "specify a test-method to distinguish "
766: + "from next component of compatible type "
767: + "for marshalling",
768: prior);
769: }
770: }
771: }
772: }
773: }
774: }
775: }
776:
777: /* (non-Javadoc)
778: * @see org.jibx.binding.model.ElementBase#validate(org.jibx.binding.model.ValidationContext)
779: */
780: public void validate(ValidationContext vctx) {
781:
782: // call base class method first
783: super .validate(vctx);
784:
785: // check for way to determine if named collection present on output
786: if (vctx.isOutBinding()
787: && hasName()
788: && !hasProperty()
789: && isOptional()
790: && getTest() == null
791: && !(vctx.getParentContainer() instanceof CollectionElement)) {
792: vctx
793: .addError("Need test method for optional collection output element");
794: }
795:
796: // make sure only element components are present
797: ArrayList children = children();
798: for (int i = 0; i < children.size(); i++) {
799: IComponent child = (IComponent) children.get(i);
800: if (child.hasAttribute()) {
801: vctx
802: .addFatal(
803: "Attributes not allowed as child components of collection",
804: child);
805: }
806: }
807:
808: // check each child component
809: checkCollectionChildren(vctx, m_itemTypeClass, children);
810:
811: // make sure child components can be distinguished
812: if (children.size() > 1) {
813: if (isOrdered()) {
814: checkOrderedChildren(vctx, children);
815: } else {
816: checkUnorderedChildren(vctx, children);
817: }
818: }
819: }
820: }
|