001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.xml.handlers;
017:
018: import java.net.URI;
019: import java.util.HashMap;
020: import java.util.LinkedList;
021: import java.util.List;
022: import java.util.Map;
023:
024: import javax.naming.OperationNotSupportedException;
025:
026: import org.geotools.xml.DocumentFactory;
027: import org.geotools.xml.XMLElementHandler;
028: import org.geotools.xml.schema.All;
029: import org.geotools.xml.schema.Any;
030: import org.geotools.xml.schema.Choice;
031: import org.geotools.xml.schema.ComplexType;
032: import org.geotools.xml.schema.Element;
033: import org.geotools.xml.schema.ElementGrouping;
034: import org.geotools.xml.schema.ElementValue;
035: import org.geotools.xml.schema.Group;
036: import org.geotools.xml.schema.Sequence;
037: import org.xml.sax.Attributes;
038: import org.xml.sax.SAXException;
039: import org.xml.sax.helpers.AttributesImpl;
040:
041: /**
042: * <p>
043: * This class is intended to handle parsing an xml element from an instance
044: * document for elements who's type is both known and complex. This handler is
045: * used within the XMLSAXHandler to handle sax events generated by the SAX
046: * parser.
047: * </p>
048: *
049: * @author dzwiers www.refractions.net
050: *
051: * @see ComplexType
052: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/xml/src/main/java/org/geotools/xml/handlers/ComplexElementHandler.java $
053: */
054: public class ComplexElementHandler extends XMLElementHandler {
055:
056: /** <code>serialVersionUID</code> field */
057: private static final long serialVersionUID = ComplexElementHandler.class
058: .hashCode();
059:
060: private ComplexType type; // saves casting all over
061: private Element elem;
062: private String text;
063: private Attributes attr;
064: private List elements;
065: private Object value = null;
066: private ElementHandlerFactory ehf;
067:
068: /**
069: * Creates a new ComplexElementHandler object for Element elem using
070: * ElementHandlerFactory ehf.
071: *
072: * @param ehf ElementHandlerFactory
073: * @param elem Element
074: *
075: * @throws SAXException
076: */
077: public ComplexElementHandler(ElementHandlerFactory ehf, Element elem)
078: throws SAXException {
079: this .ehf = ehf;
080:
081: if (elem == null) {
082: logger.warning("ComplexType provided was null");
083: throw new SAXException(new NullPointerException(
084: "ComplexType provided was null"));
085: }
086:
087: this .elem = elem;
088:
089: try {
090: type = (ComplexType) elem.getType();
091: } catch (ClassCastException e) {
092: logger.warning(e.toString());
093: throw new SAXException(e);
094: }
095: }
096:
097: /**
098: * @see org.geotools.xml.XMLElementHandler#getElement()
099: */
100: public Element getElement() {
101: return elem;
102: }
103:
104: /**
105: * @see org.geotools.xml.XMLElementHandler#characters(java.lang.String)
106: */
107: public void characters(String text1) throws SAXException {
108: if (type.isMixed()) {
109: if (this .text != null) {
110: this .text = this .text.concat(text1);
111: } else {
112: this .text = text1;
113: }
114: } else {
115: if (!"".equals(text1.trim())) {
116: if (type.getName() == null) {
117: throw new SAXException(
118: "This type may not have mixed content");
119: }
120:
121: throw new SAXException("The " + type.getName()
122: + " type may not have mixed content");
123: }
124: }
125: }
126:
127: /**
128: * @param namespaceURI
129: * @param localName
130: * @param hints
131: * @throws SAXException
132: * @throws OperationNotSupportedException
133: */
134: public void endElement(URI namespaceURI, String localName, Map hints)
135: throws OperationNotSupportedException, SAXException {
136: text = (text == null) ? null : text.trim();
137:
138: if (hints == null) {
139: hints = new HashMap();
140: hints.put(ElementHandlerFactory.KEY, ehf);
141: } else {
142: if (!hints.containsKey(ElementHandlerFactory.KEY))
143: hints.put(ElementHandlerFactory.KEY, ehf);
144: }
145:
146: if (elements == null) {
147: if (type != null) {
148: ElementValue[] vals;
149: if (type.isMixed()) {
150: vals = new ElementValue[1];
151: vals[0] = new DefaultElementValue(null, text); // null is ok as
152: // this represents the mixed content
153: } else {
154: vals = new ElementValue[0];
155: }
156: value = type.getValue(elem, vals, attr, hints);
157: } else {
158: value = text;
159: }
160:
161: return;
162: }
163:
164: // validate the complex element ... throws an exception when it's been bad
165: boolean validate = hints == null
166: || !hints.containsKey(DocumentFactory.VALIDATION_HINT)
167: || hints.get(DocumentFactory.VALIDATION_HINT) == null
168: || !(hints.get(DocumentFactory.VALIDATION_HINT) instanceof Boolean)
169: || ((Boolean) hints
170: .get(DocumentFactory.VALIDATION_HINT))
171: .booleanValue();
172: if (validate)
173: validateElementOrder();
174:
175: ElementValue[] vals = new ElementValue[elements.size()
176: + (type.isMixed() ? 1 : 0)];
177:
178: for (int i = 0; i < elements.size(); i++) {
179: XMLElementHandler xeh = (XMLElementHandler) elements.get(i);
180: vals[i] = new DefaultElementValue(xeh.getElement(), xeh
181: .getValue());
182: }
183:
184: if (type.isMixed()) {
185: vals[vals.length - 1] = new DefaultElementValue(null, text);
186:
187: // null is ok as this represents the mixed content
188: }
189:
190: value = type.getValue(elem, vals, attr, hints);
191:
192: // clean memory
193: attr = null;
194: elements = null;
195: text = null;
196: }
197:
198: /*
199: * This starts off the fun or checking element order for complex types
200: * (note mixtures of All, Any, Choice, Element, Group, Sequence).
201: *
202: */
203: private void validateElementOrder() throws SAXException {
204: if ((elements == null) || (elements.size() == 0)) {
205: // TODO ensure we have enough elements
206: return;
207: }
208:
209: int i = 0;
210: int count = 0;
211: int[] i2 = new int[2];
212: int cache = 0; // old pos.
213: i2[1] = 1;
214: while (i < elements.size() && i2[1] == 1) {
215: i2[0] = i;
216: i2[1] = 0;
217: cache = i2[0];
218: i2 = valid(type.getChild(), i);
219: if (i2[1] == 0 && i == i2[0]) {
220: // done running
221: if (count < type.getChild().getMinOccurs()) {
222: StringBuffer buf = new StringBuffer();
223: buf.append("Too few elements for ");
224: buf.append(elem.getNamespace() + ":"
225: + elem.getName());
226: buf.append(" (type = " + type.getName() + ") ");
227: buf.append(": ");
228: buf.append(count);
229: buf.append(" children, ");
230: buf.append(type.getChild().getMinOccurs());
231: buf.append(" minOccurs");
232: throw new SAXException(buf.toString());
233: }
234: } else {
235: if (cache == i2[0]) {
236: // we have not progressed .. progress us
237: i = i2[0] + 1;
238: } else {
239: i = i2[0];
240: }
241: count++;
242: }
243: }
244: if (count > type.getChild().getMaxOccurs()) {
245: StringBuffer buf = new StringBuffer();
246: buf.append("Too many elements for ");
247: buf.append(elem.getNamespace() + ":" + elem.getName());
248: buf.append(" (type = " + type.getName() + ") ");
249: buf.append(": ");
250: buf.append(count);
251: buf.append(" children, ");
252: buf.append(type.getChild().getMaxOccurs());
253: buf.append(" maxOccurs");
254: throw new SAXException(buf.toString());
255: }
256:
257: if (i != elements.size()) {
258: StringBuffer buf = new StringBuffer();
259: buf.append("Invalid Element ordering for ");
260: buf.append(elem.getNamespace() + ":" + elem.getName());
261: buf.append(" (type = " + type.getName() + ") ");
262: buf.append(". There were " + (elements.size() - i)
263: + "elements which were unaccounted for");
264: throw new SAXException(buf.toString());
265: }
266: }
267:
268: /*
269: * Generic validation method which simulates recursion, and avoids casting :)
270: * The index is the starting index in the list of elements, for the particular
271: * ElementGrouping. The last index matched is returned.
272: */
273: private int[] valid(ElementGrouping eg, int index)
274: throws SAXException {
275: if (eg == null) {
276: return new int[] { index, 1 };
277: }
278:
279: switch (eg.getGrouping()) {
280: case ElementGrouping.SEQUENCE:
281: int[] tmp = valid((Sequence) eg, index);
282: return tmp;
283:
284: case ElementGrouping.ALL:
285: return valid((All) eg, index);
286:
287: case ElementGrouping.ANY:
288: return valid((Any) eg, index);
289:
290: case ElementGrouping.CHOICE:
291: return valid((Choice) eg, index);
292:
293: case ElementGrouping.GROUP:
294: return valid((Group) eg, index);
295:
296: case ElementGrouping.ELEMENT:
297: tmp = valid((Element) eg, index);
298: return tmp;
299: }
300:
301: return new int[] { index, 1 };
302: }
303:
304: /*
305: * Validates an All tag
306: * @see valid(ElementGrouping)
307: */
308: private int[] valid(All all, int index) {
309: Element[] elems = all.getElements();
310: int[] r = new int[elems.length];
311:
312: for (int i = 0; i < r.length; i++)
313: r[i] = 0;
314:
315: boolean c = true;
316: int head = index;
317: while (c) {
318: c = false;
319:
320: for (int i = 0; i < elems.length; i++) {
321: if (elems[i].getType().getName().equalsIgnoreCase(
322: ((XMLElementHandler) elements.get(head))
323: .getName())) {
324: r[i]++;
325: head++;
326: i = elems.length;
327: c = true;
328: }
329: }
330: }
331:
332: for (int i = 0; i < r.length; i++) {
333: if ((r[i] < elems[i].getMinOccurs())
334: || (r[i] > elems[i].getMaxOccurs())) {
335: return new int[] { index, 0 };
336: }
337: }
338:
339: return new int[] { head, 1 };
340: }
341:
342: /*
343: * Validates an Any tag
344: * @see valid(ElementGrouping)
345: */
346: private int[] valid(Any any, int index) {
347: if (any.getNamespace().equals(
348: ((XMLElementHandler) elements.get(index)).getElement()
349: .getType().getNamespace())) {
350: return new int[] { index + 1, 1 };
351: }
352:
353: return new int[] { index, 1 };
354: }
355:
356: /*
357: * Validates an Choice tag
358: * @see valid(ElementGrouping)
359: */
360: private int[] valid(Choice choice, int index) throws SAXException {
361: ElementGrouping[] eg = choice.getChildren();
362:
363: if (eg == null) {
364: return new int[] { index, 1 };
365: }
366:
367: int i = 0; // choice child index;
368:
369: int end = index;
370: int t = index;
371: int count = 0;
372: int t2[] = null;
373: while (i < eg.length && end < elements.size()) {
374: t2 = valid(eg[i], t);
375: if (t2[1] == 0 && t2[0] == t) {// nothing, next
376: // move along
377: if (t2[0] > end && count >= eg[i].getMinOccurs()
378: && count <= eg[i].getMaxOccurs())
379: end = t2[0];
380: count = 0;
381: i++;
382: t = index;
383: } else {
384: if (count == eg[i].getMaxOccurs()) {
385: // move along
386: if (t2[0] > end && count >= eg[i].getMinOccurs())
387: end = t2[0];
388: count = 0;
389: i++;
390: t = index;
391: } else {
392: t = t2[0];
393: if (t == elements.size()) {
394: end = t;
395: }
396: count++;
397: }
398: }
399: }
400:
401: return new int[] { end, end == index ? 0 : 1 };
402: }
403:
404: /*
405: * Validates an Group tag
406: * @see valid(ElementGrouping)
407: */
408: private int[] valid(Group group, int index) throws SAXException {
409: if (group.getChild() == null) {
410: return new int[] { index, 1 };
411: }
412:
413: return valid(group.getChild(), index);
414: }
415:
416: /*
417: * Validates an Element tag
418: * @see valid(ElementGrouping)
419: */
420: private int[] valid(Element element, int index) {
421:
422: // does this element equate to the index in the doc?
423:
424: int[] r = null;
425:
426: XMLElementHandler indexHandler = null;
427: if (index < elements.size()) {
428: indexHandler = ((XMLElementHandler) elements.get(index));
429: } else {
430: // not found :)
431: return new int[] { index, 0 };
432: }
433:
434: if (r == null
435: && (indexHandler == null || indexHandler.getElement() == null))
436: return new int[] { index, 0 };
437:
438: if (r == null && indexHandler.getElement() == element)
439: r = new int[] { index + 1, 1 };
440:
441: if (r == null && element.getName() == null)
442: return new int[] { index, 0 };
443:
444: if (r == null
445: && (element.getName() != null && element.getName()
446: .equalsIgnoreCase(indexHandler.getName())))
447: r = new int[] { index + 1, 1 };
448: if (r == null && element.getName() != null) {
449: Element e = indexHandler.getElement();
450: while (r == null && e != null) {
451: if (element.getName().equalsIgnoreCase(e.getName())) {
452: r = new int[] { index + 1, 1 };
453: }
454: e = e.getSubstitutionGroup();
455: }
456: }
457:
458: if (r == null) {
459: r = new int[] { index, 0 };
460: }
461: return r;
462: }
463:
464: /*
465: * Validates a Sequence tag
466: * @see valid(ElementGrouping)
467: */
468: private int[] valid(Sequence seq, int index) throws SAXException {
469: ElementGrouping[] eg = seq.getChildren();
470:
471: if (eg == null) {
472: return new int[] { index, 1 };
473: }
474:
475: int tIndex = index; // top of element matching list
476: int t = 0; // top of child list
477:
478: int count = 0; // used for n-ary at a single spot
479: int i2[] = new int[2];
480: while (t < eg.length && tIndex < elements.size()) {
481: i2 = valid(eg[t], tIndex); // new top element
482: if (i2[1] == 1) { // they matched
483: if (tIndex == i2[0]) {
484: // didn't more ahead ...
485: t++; // force next spot
486: count = 0; // reset
487: } else {
488: count++;
489: if (count <= eg[t].getMaxOccurs()) {
490: tIndex = i2[0]; // store index
491: } else {
492: // error, so redo
493: if (eg[t].getMinOccurs() > count) {
494: // not good
495: //System.out.println("Seq Failed");
496: return new int[] { index, 0 }; // not whole sequence
497: }
498: t++;
499: count = 0; // next defined type
500: }
501: }
502: } else {
503: // didn't match
504:
505: // move along and retest that spot
506: if (eg[t].getMinOccurs() > count) {
507: // not good
508: //System.out.println("Seq Failed");
509: return new int[] { index, 0 }; // not whole sequence
510: }
511: t++;
512: count = 0; // next defined type
513: }
514: }
515: //System.out.println("Seq index = "+tIndex+" Matched");
516: return new int[] { tIndex, 1 };
517: }
518:
519: /**
520: *
521: * TODO summary sentence for startElement ...
522: *
523: * @see org.geotools.xml.XMLElementHandler#startElement(java.net.URI, java.lang.String, org.xml.sax.Attributes)
524: * @param namespaceURI
525: * @param localName
526: * @param attr1
527: */
528: public void startElement(URI namespaceURI, String localName,
529: Attributes attr1) {
530: this .attr = new AttributesImpl(attr1);
531: }
532:
533: /**
534: *
535: * TODO summary sentence for getHandler ...
536: *
537: * @see org.geotools.xml.XMLElementHandler#getHandler(java.net.URI, java.lang.String, java.util.Map)
538: * @param namespaceURI
539: * @param localName
540: * @param hints
541: * @return XMLElementHandler
542: * @throws SAXException
543: */
544: public XMLElementHandler getHandler(URI namespaceURI,
545: String localName, Map hints) throws SAXException {
546: if (elements == null) {
547: elements = new LinkedList();
548: }
549:
550: logger.finest("Starting search for element handler "
551: + localName + " :: " + namespaceURI);
552:
553: Element e = XMLTypeHelper.findChildElement(type, localName,
554: namespaceURI);
555: if (e != null && namespaceURI.equals(e.getNamespace())) {
556: XMLElementHandler r = ehf.createElementHandler(e);
557:
558: if (type.cache(r.getElement(), hints)) {
559: elements.add(r);
560: }
561:
562: return r;
563: }
564:
565: logger.finest("Checking the document schemas");
566:
567: XMLElementHandler r = ehf.createElementHandler(namespaceURI,
568: localName);
569:
570: if (r != null) {
571: if (type.cache(r.getElement(), hints)) {
572: elements.add(r);
573: }
574:
575: return r;
576: }
577:
578: // validation?
579: if (hints != null
580: && hints.containsKey(DocumentFactory.VALIDATION_HINT)) {
581: Boolean valid = (Boolean) hints
582: .get(DocumentFactory.VALIDATION_HINT);
583: if (valid != null && !valid.booleanValue()) {
584: return new IgnoreHandler();
585: }
586: }
587:
588: throw new SAXException("Could not find element handler for "
589: + namespaceURI + " : " + localName + " as a child of "
590: + type.getName() + ".");
591: }
592:
593: /**
594: *
595: * TODO summary sentence for getValue ...
596: *
597: * @see org.geotools.xml.XMLElementHandler#getValue()
598: * @return Object
599: */
600: public Object getValue() {
601: // endElement sets the value
602: return value;
603: }
604:
605: /**
606: * @see org.geotools.xml.XMLElementHandler#getName()
607: */
608: public String getName() {
609: return elem.getName();
610: }
611:
612: /**
613: * Remove the given XMLElementHandler from the Child-List
614: * @param handler
615: */
616: public void removeElement(XMLElementHandler handler) {
617: if (elements != null) {
618: elements.remove(handler);
619: }
620: }
621:
622: /**
623: * returns the Type of the Elementhandler
624: * @return type
625: */
626: public ComplexType getType() {
627: return type;
628: }
629:
630: /**
631: * <p>
632: * Default Implementation used to pass values to type instances
633: * </p>
634: *
635: * @author dzwiers
636: *
637: * @see ElementValue
638: */
639: private static class DefaultElementValue implements ElementValue {
640: Element t;
641: Object value;
642:
643: /**
644: * Stores the two values for use within the specified type
645: *
646: * @param t Element
647: * @param o String
648: */
649: public DefaultElementValue(Element t, Object o) {
650: this .t = t;
651: value = o;
652: }
653:
654: /**
655: *
656: * TODO summary sentence for getElement ...
657: *
658: * @see org.geotools.xml.schema.ElementValue#getElement()
659: * @return Element
660: */
661: public Element getElement() {
662: return t;
663: }
664:
665: /**
666: *
667: * TODO summary sentence for getValue ...
668: *
669: * @see org.geotools.xml.schema.ElementValue#getValue()
670: * @return Object
671: */
672: public Object getValue() {
673: return value;
674: }
675:
676: /* (non-Javadoc)
677: * @see java.lang.Object#toString()
678: */
679: public String toString() {
680: StringBuffer buf = new StringBuffer();
681: if (t != null && t.toString() != null) {
682: buf.append(t.toString());
683: } else {
684: buf.append(getClass().getName());
685: }
686: buf.append("[");
687: buf.append(value);
688: buf.append("]");
689: return buf.toString();
690: }
691: }
692: }
|