/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.xml.handlers; import java.net.URI; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.naming.OperationNotSupportedException; import org.geotools.xml.DocumentFactory; import org.geotools.xml.XMLElementHandler; import org.geotools.xml.schema.All; import org.geotools.xml.schema.Any; import org.geotools.xml.schema.Choice; import org.geotools.xml.schema.ComplexType; import org.geotools.xml.schema.Element; import org.geotools.xml.schema.ElementGrouping; import org.geotools.xml.schema.ElementValue; import org.geotools.xml.schema.Group; import org.geotools.xml.schema.Sequence; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; /** * <p> * This class is intended to handle parsing an xml element from an instance * document for elements who's type is both known and complex. This handler is * used within the XMLSAXHandler to handle sax events generated by the SAX * parser. * </p> * * @author dzwiers www.refractions.net * * @see ComplexType * @source $URL$ */ public class ComplexElementHandler extends XMLElementHandler { /** <code>serialVersionUID</code> field */ private static final long serialVersionUID = ComplexElementHandler.class.hashCode(); private ComplexType type; // saves casting all over private Element elem; private String text; private Attributes attr; private List elements; private Object value = null; private ElementHandlerFactory ehf; /** * Creates a new ComplexElementHandler object for Element elem using * ElementHandlerFactory ehf. * * @param ehf ElementHandlerFactory * @param elem Element * * @throws SAXException */ public ComplexElementHandler(ElementHandlerFactory ehf, Element elem) throws SAXException { this.ehf = ehf; if (elem == null) { logger.warning("ComplexType provided was null"); throw new SAXException(new NullPointerException( "ComplexType provided was null")); } this.elem = elem; try { type = (ComplexType) elem.getType(); } catch (ClassCastException e) { logger.warning(e.toString()); throw new SAXException(e); } } /** * @see org.geotools.xml.XMLElementHandler#getElement() */ public Element getElement() { return elem; } /** * @see org.geotools.xml.XMLElementHandler#characters(java.lang.String) */ public void characters(String text1) throws SAXException { if (type.isMixed()) { if (this.text != null) { this.text = this.text.concat(text1); } else { this.text = text1; } } else { if (!"".equals(text1.trim())) { if (type.getName() == null) { throw new SAXException( "This type may not have mixed content"); } throw new SAXException("The " + type.getName() + " type may not have mixed content"); } } } /** * @param namespaceURI * @param localName * @param hints * @throws SAXException * @throws OperationNotSupportedException */ public void endElement(URI namespaceURI, String localName, Map hints) throws OperationNotSupportedException, SAXException { text = (text == null) ? null : text.trim(); if(hints == null){ hints = new HashMap(); hints.put(ElementHandlerFactory.KEY,ehf); }else{ if(!hints.containsKey(ElementHandlerFactory.KEY)) hints.put(ElementHandlerFactory.KEY,ehf); } if (elements == null) { if (type != null) { ElementValue[] vals; if(type.isMixed()){ vals = new ElementValue[1]; vals[0] = new DefaultElementValue(null, text); // null is ok as // this represents the mixed content }else{ vals = new ElementValue[0]; } value = type.getValue(elem, vals, attr, hints); } else { value = text; } return; } // validate the complex element ... throws an exception when it's been bad boolean validate = hints == null || !hints.containsKey(DocumentFactory.VALIDATION_HINT) || hints.get(DocumentFactory.VALIDATION_HINT)==null || !(hints.get(DocumentFactory.VALIDATION_HINT) instanceof Boolean) || ((Boolean)hints.get(DocumentFactory.VALIDATION_HINT)).booleanValue(); if(validate) validateElementOrder(); ElementValue[] vals = new ElementValue[elements.size() + (type.isMixed() ? 1 : 0)]; for (int i = 0; i < elements.size(); i++) { XMLElementHandler xeh = (XMLElementHandler) elements.get(i); vals[i] = new DefaultElementValue(xeh.getElement(), xeh.getValue()); } if (type.isMixed()) { vals[vals.length - 1] = new DefaultElementValue(null, text); // null is ok as this represents the mixed content } value = type.getValue(elem, vals, attr, hints); // clean memory attr = null; elements = null; text = null; } /* * This starts off the fun or checking element order for complex types * (note mixtures of All, Any, Choice, Element, Group, Sequence). * */ private void validateElementOrder() throws SAXException { if ((elements == null) || (elements.size() == 0)) { // TODO ensure we have enough elements return; } int i = 0; int count =0; int[] i2 = new int[2]; int cache = 0; // old pos. i2[1]=1; while(i<elements.size() && i2[1] == 1){ i2[0] = i; i2[1] = 0; cache = i2[0]; i2 = valid(type.getChild(), i); if( i2[1] == 0 && i == i2[0] ){ // done running if (count < type.getChild().getMinOccurs()) { StringBuffer buf = new StringBuffer(); buf.append("Too few elements for " ); buf.append( elem.getNamespace()+":"+elem.getName() ); buf.append( " (type = "+type.getName()+") " ); buf.append( ": " ); buf.append( count ); buf.append( " children, " ); buf.append( type.getChild().getMinOccurs() ); buf.append( " minOccurs" ); throw new SAXException( buf.toString() ); } }else{ if(cache == i2[0]){ // we have not progressed .. progress us i = i2[0]+1; }else{ i = i2[0]; } count++; } } if(count > type.getChild().getMaxOccurs()){ StringBuffer buf = new StringBuffer(); buf.append("Too many elements for " ); buf.append( elem.getNamespace()+":"+elem.getName() ); buf.append( " (type = "+type.getName()+") " ); buf.append( ": " ); buf.append( count ); buf.append( " children, " ); buf.append( type.getChild().getMaxOccurs() ); buf.append( " maxOccurs" ); throw new SAXException( buf.toString() ); } if (i != elements.size()) { StringBuffer buf = new StringBuffer(); buf.append("Invalid Element ordering for " ); buf.append( elem.getNamespace()+":"+elem.getName() ); buf.append( " (type = "+type.getName()+") " ); buf.append( ". There were "+(elements.size()-i)+"elements which were unaccounted for" ); throw new SAXException( buf.toString() ); } } /* * Generic validation method which simulates recursion, and avoids casting :) * The index is the starting index in the list of elements, for the particular * ElementGrouping. The last index matched is returned. */ private int[] valid(ElementGrouping eg, int index) throws SAXException { if (eg == null) { return new int[]{index,1}; } switch (eg.getGrouping()) { case ElementGrouping.SEQUENCE: int[] tmp = valid((Sequence) eg, index); return tmp; case ElementGrouping.ALL: return valid((All) eg, index); case ElementGrouping.ANY: return valid((Any) eg, index); case ElementGrouping.CHOICE: return valid((Choice) eg, index); case ElementGrouping.GROUP: return valid((Group) eg, index); case ElementGrouping.ELEMENT: tmp = valid((Element) eg, index); return tmp; } return new int[]{index,1}; } /* * Validates an All tag * @see valid(ElementGrouping) */ private int[] valid(All all, int index) { Element[] elems = all.getElements(); int[] r = new int[elems.length]; for (int i = 0; i < r.length; i++) r[i] = 0; boolean c = true; int head = index; while (c) { c = false; for (int i = 0; i < elems.length; i++) { if (elems[i].getType().getName().equalsIgnoreCase(((XMLElementHandler) elements .get(head)).getName())) { r[i]++; head++; i = elems.length; c = true; } } } for (int i = 0; i < r.length; i++) { if ((r[i] < elems[i].getMinOccurs()) || (r[i] > elems[i].getMaxOccurs())) { return new int[]{index,0}; } } return new int[]{head,1}; } /* * Validates an Any tag * @see valid(ElementGrouping) */ private int[] valid(Any any, int index) { if (any.getNamespace().equals(((XMLElementHandler) elements.get(index)).getElement() .getType().getNamespace())) { return new int[]{index+1,1}; } return new int[]{index,1}; } /* * Validates an Choice tag * @see valid(ElementGrouping) */ private int[] valid(Choice choice, int index) throws SAXException { ElementGrouping[] eg = choice.getChildren(); if (eg == null) { return new int[]{index,1}; } int i = 0; // choice child index; int end = index; int t = index; int count = 0; int t2[] = null; while(i<eg.length && end<elements.size()){ t2 = valid(eg[i], t); if(t2[1] == 0 && t2[0] == t){// nothing, next // move along if(t2[0]>end && count>=eg[i].getMinOccurs() && count<=eg[i].getMaxOccurs()) end = t2[0]; count = 0; i++; t = index; }else{ if(count==eg[i].getMaxOccurs()){ // move along if(t2[0]>end && count>=eg[i].getMinOccurs()) end = t2[0]; count = 0; i++; t = index; }else{ t = t2[0]; if(t == elements.size()){ end = t; } count++; } } } return new int[]{end,end==index?0:1}; } /* * Validates an Group tag * @see valid(ElementGrouping) */ private int[] valid(Group group, int index) throws SAXException { if (group.getChild() == null) { return new int[]{index,1}; } return valid(group.getChild(), index); } /* * Validates an Element tag * @see valid(ElementGrouping) */ private int[] valid(Element element, int index) { // does this element equate to the index in the doc? int[] r = null; XMLElementHandler indexHandler = null; if(index<elements.size()){ indexHandler = ((XMLElementHandler) elements.get(index)); }else{ // not found :) return new int[]{index,0}; } if(r ==null && (indexHandler == null || indexHandler.getElement() == null)) return new int[]{index,0}; if(r == null && indexHandler.getElement() == element) r = new int[]{index+1,1}; if(r == null && element.getName()==null) return new int[]{index,0}; if(r == null && (element.getName()!=null && element.getName().equalsIgnoreCase(indexHandler.getName()))) r = new int[]{index+1,1}; if(r == null && element.getName()!=null){ Element e = indexHandler.getElement(); while(r == null && e != null){ if(element.getName().equalsIgnoreCase(e.getName())){ r = new int[]{index+1,1}; } e = e.getSubstitutionGroup(); } } if(r == null){ r = new int[]{index,0}; } return r; } /* * Validates a Sequence tag * @see valid(ElementGrouping) */ private int[] valid(Sequence seq, int index) throws SAXException { ElementGrouping[] eg = seq.getChildren(); if (eg == null) { return new int[]{index,1}; } int tIndex = index; // top of element matching list int t = 0; // top of child list int count = 0; // used for n-ary at a single spot int i2[] = new int[2]; while(t<eg.length && tIndex<elements.size()){ i2 = valid(eg[t],tIndex); // new top element if(i2[1]==1){ // they matched if(tIndex==i2[0]){ // didn't more ahead ... t++; // force next spot count = 0; // reset }else{ count ++; if(count<=eg[t].getMaxOccurs()){ tIndex = i2[0]; // store index }else{ // error, so redo if(eg[t].getMinOccurs()>count){ // not good //System.out.println("Seq Failed"); return new int[]{index,0}; // not whole sequence } t++; count=0; // next defined type } } }else{ // didn't match // move along and retest that spot if(eg[t].getMinOccurs()>count){ // not good //System.out.println("Seq Failed"); return new int[]{index,0}; // not whole sequence } t++; count=0; // next defined type } } //System.out.println("Seq index = "+tIndex+" Matched"); return new int[]{tIndex,1}; } /** * * TODO summary sentence for startElement ... * * @see org.geotools.xml.XMLElementHandler#startElement(java.net.URI, java.lang.String, org.xml.sax.Attributes) * @param namespaceURI * @param localName * @param attr1 */ public void startElement(URI namespaceURI, String localName, Attributes attr1) { this.attr = new AttributesImpl(attr1); } /** * * TODO summary sentence for getHandler ... * * @see org.geotools.xml.XMLElementHandler#getHandler(java.net.URI, java.lang.String, java.util.Map) * @param namespaceURI * @param localName * @param hints * @return XMLElementHandler * @throws SAXException */ public XMLElementHandler getHandler(URI namespaceURI, String localName, Map hints) throws SAXException { if (elements == null) { elements = new LinkedList(); } logger.finest("Starting search for element handler " + localName + " :: " + namespaceURI); Element e = XMLTypeHelper.findChildElement(type, localName, namespaceURI); if (e != null && namespaceURI.equals(e.getNamespace())){ XMLElementHandler r = ehf.createElementHandler(e); if (type.cache(r.getElement(), hints)) { elements.add(r); } return r; } logger.finest("Checking the document schemas"); XMLElementHandler r = ehf.createElementHandler(namespaceURI, localName); if (r != null) { if (type.cache(r.getElement(), hints)) { elements.add(r); } return r; } // validation? if(hints != null && hints.containsKey(DocumentFactory.VALIDATION_HINT)){ Boolean valid = (Boolean)hints.get(DocumentFactory.VALIDATION_HINT); if(valid != null && !valid.booleanValue()){ return new IgnoreHandler(); } } throw new SAXException("Could not find element handler for " + namespaceURI + " : " + localName + " as a child of " + type.getName() + "."); } /** * * TODO summary sentence for getValue ... * * @see org.geotools.xml.XMLElementHandler#getValue() * @return Object */ public Object getValue() { // endElement sets the value return value; } /** * @see org.geotools.xml.XMLElementHandler#getName() */ public String getName() { return elem.getName(); } /** * Remove the given XMLElementHandler from the Child-List * @param handler */ public void removeElement(XMLElementHandler handler){ if (elements != null){ elements.remove(handler); } } /** * returns the Type of the Elementhandler * @return type */ public ComplexType getType() { return type; } /** * <p> * Default Implementation used to pass values to type instances * </p> * * @author dzwiers * * @see ElementValue */ private static class DefaultElementValue implements ElementValue { Element t; Object value; /** * Stores the two values for use within the specified type * * @param t Element * @param o String */ public DefaultElementValue(Element t, Object o) { this.t = t; value = o; } /** * * TODO summary sentence for getElement ... * * @see org.geotools.xml.schema.ElementValue#getElement() * @return Element */ public Element getElement() { return t; } /** * * TODO summary sentence for getValue ... * * @see org.geotools.xml.schema.ElementValue#getValue() * @return Object */ public Object getValue() { return value; } /* (non-Javadoc) * @see java.lang.Object#toString() */ public String toString() { StringBuffer buf = new StringBuffer(); if( t != null && t.toString() != null ){ buf.append( t.toString() ); } else { buf.append( getClass().getName() ); } buf.append("["); buf.append( value ); buf.append("]"); return buf.toString(); } } }