001: /*
002: * Geotools2 - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-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: */
017: package org.geotools.data.complex.filter;
018:
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.logging.Level;
024: import java.util.logging.Logger;
025:
026: import javax.xml.XMLConstants;
027: import javax.xml.namespace.QName;
028:
029: import org.geotools.factory.CommonFactoryFinder;
030: import org.geotools.feature.iso.AttributeBuilder;
031: import org.geotools.feature.iso.AttributeFactoryImpl;
032: import org.geotools.feature.iso.Types;
033: import org.geotools.feature.iso.type.TypeFactoryImpl;
034: import org.geotools.util.CheckedArrayList;
035: import org.opengis.feature.Attribute;
036: import org.opengis.feature.ComplexAttribute;
037: import org.opengis.feature.FeatureFactory;
038: import org.opengis.feature.type.AttributeDescriptor;
039: import org.opengis.feature.type.AttributeType;
040: import org.opengis.feature.type.ComplexType;
041: import org.opengis.feature.type.Name;
042: import org.opengis.feature.type.PropertyDescriptor;
043: import org.opengis.feature.type.TypeFactory;
044: import org.opengis.filter.FilterFactory;
045: import org.opengis.filter.expression.Literal;
046: import org.opengis.filter.expression.PropertyName;
047: import org.opengis.util.Cloneable;
048: import org.xml.sax.helpers.NamespaceSupport;
049:
050: /**
051: * Utility class to evaluate XPath expressions against an Attribute instance,
052: * which may be any Attribute, wether it is simple, complex, a feature, etc.
053: * <p>
054: * At the difference of the Filter subsystem, which works against Attribute
055: * contents (for example to evaluate a comparison filter), the XPath subsystem,
056: * for which this class is the single entry point, works against Attribute
057: * instances. That is, the result of an XPath expression, if a single value, is
058: * an Attribtue, not the attribute content, or a List of Attributes, for
059: * instance.
060: * </p>
061: *
062: * @author Gabriel Roldan, Axios Engineering
063: * @version $Id: XPath.java 29122 2008-02-07 03:43:28Z groldan $
064: * @source $URL:
065: * http://svn.geotools.org/geotools/branches/2.4.x/modules/unsupported/community-schemas/community-schema-ds/src/main/java/org/geotools/data/complex/filter/XPath.java $
066: * @since 2.4
067: */
068: public class XPath {
069: private static final Logger LOGGER = org.geotools.util.logging.Logging
070: .getLogger(XPath.class.getPackage().getName());
071:
072: private FilterFactory FF;
073:
074: private FeatureFactory featureFactory;
075:
076: /**
077: * Used to create specific attribute descriptors for
078: * {@link #set(Attribute, String, Object, String, AttributeType)} when the
079: * actual attribute instance is of a derived type of the corresponding one
080: * declared in the feature type.
081: */
082: private TypeFactory descriptorFactory;
083:
084: public XPath() {
085: this .FF = CommonFactoryFinder.getFilterFactory(null);
086: this .featureFactory = new AttributeFactoryImpl();
087: this .descriptorFactory = new TypeFactoryImpl();
088: }
089:
090: public XPath(FilterFactory ff, FeatureFactory featureFactory) {
091: setFilterFactory(ff);
092: setFeatureFactory(featureFactory);
093: this .descriptorFactory = new TypeFactoryImpl();
094: }
095:
096: public void setFilterFactory(FilterFactory ff) {
097: this .FF = ff;
098: }
099:
100: public void setFeatureFactory(FeatureFactory featureFactory) {
101: this .featureFactory = featureFactory;
102: }
103:
104: public static class StepList extends CheckedArrayList implements
105: List, Cloneable {
106: private static final long serialVersionUID = -5612786286175355862L;
107:
108: private StepList() {
109: super (XPath.Step.class);
110: }
111:
112: public StepList(StepList steps) {
113: super (XPath.Step.class);
114: addAll(steps);
115: }
116:
117: public String toString() {
118: StringBuffer sb = new StringBuffer();
119: for (Iterator it = iterator(); it.hasNext();) {
120: Step s = (Step) it.next();
121: sb.append(s.toString());
122: if (it.hasNext()) {
123: sb.append("/");
124: }
125: }
126: return sb.toString();
127: }
128:
129: public Object clone() {
130: StepList copy = new StepList();
131: Step step;
132: for (Iterator it = iterator(); it.hasNext();) {
133: step = (Step) it.next();
134: copy.add(step.clone());
135: }
136: return copy;
137: }
138:
139: /**
140: * Compares this StepList with another for equivalence regardless of the
141: * indexes of each Step.
142: *
143: * @param propertyName
144: * @return <code>true</code> if this step list has the same location
145: * paths than <code>propertyName</code> ignoring the indexes
146: * in each step. <code>false</code> otherwise.
147: */
148: public boolean equalsIgnoreIndex(final StepList propertyName) {
149: if (propertyName == null) {
150: return false;
151: }
152: if (propertyName == this ) {
153: return true;
154: }
155: if (size() != propertyName.size()) {
156: return false;
157: }
158: Iterator mine = iterator();
159: Iterator him = propertyName.iterator();
160: Step myStep;
161: Step hisStep;
162: while (mine.hasNext()) {
163: myStep = (Step) mine.next();
164: hisStep = (Step) him.next();
165: if (!myStep.equalsIgnoreIndex(hisStep)) {
166: return false;
167: }
168: }
169: return true;
170: }
171: }
172:
173: /**
174: *
175: * @author gabriel
176: *
177: */
178: public static class Step implements Cloneable {
179: private int index;
180:
181: private QName attributeName;
182:
183: private boolean isXmlAttribute;
184:
185: /**
186: * Creates a "property" xpath step (i.e. isXmlAttribute() == false).
187: *
188: * @param name
189: * @param index
190: */
191: public Step(final QName name, final int index) {
192: this (name, index, false);
193: }
194:
195: /**
196: * Creates an xpath step for the given qualified name and index; and the
197: * given flag to indicate if it it an "attribute" or "property" step.
198: *
199: * @param name
200: * the qualified name of the step (name should include prefix
201: * to be reflected in toString())
202: * @param index
203: * the index (indexing starts at 1 for Xpath) of the step
204: * @param isXmlAttribute
205: * whether the step referers to an "attribute" or a
206: * "property" (like for attributes and elements in xml)
207: * @throws NullPointerException
208: * if <code>name==null</code>
209: * @throws IllegalArgumentException
210: * if <code>index < 1</code>
211: */
212: public Step(final QName name, final int index,
213: boolean isXmlAttribute) {
214: if (name == null) {
215: throw new NullPointerException("name");
216: }
217: if (index < 1) {
218: throw new IllegalArgumentException(
219: "index shall be >= 1");
220: }
221: this .attributeName = name;
222: this .index = index;
223: this .isXmlAttribute = isXmlAttribute;
224: }
225:
226: /**
227: * Compares this Step with another for equivalence ignoring the steps
228: * indexes.
229: *
230: * @param hisStep
231: * @return
232: */
233: public boolean equalsIgnoreIndex(Step other) {
234: if (other == null) {
235: return false;
236: }
237: if (other == this ) {
238: return true;
239: }
240: return other.getName().equals(getName());
241: }
242:
243: public int getIndex() {
244: return index;
245: }
246:
247: public QName getName() {
248: return attributeName;
249: }
250:
251: public String toString() {
252: StringBuffer sb = new StringBuffer(isXmlAttribute ? "@"
253: : "");
254: if (XMLConstants.DEFAULT_NS_PREFIX != attributeName
255: .getPrefix()) {
256: sb.append(attributeName.getPrefix()).append(':');
257: }
258: sb.append(attributeName.getLocalPart());
259: if (index > 1) {
260: sb.append("[").append(index).append("]");
261: }
262: return sb.toString();
263: }
264:
265: public boolean equals(Object o) {
266: if (!(o instanceof Step)) {
267: return false;
268: }
269: Step s = (Step) o;
270: return attributeName.equals(s.attributeName)
271: && index == s.index
272: && isXmlAttribute == s.isXmlAttribute;
273: }
274:
275: public int hashCode() {
276: return 17 * attributeName.hashCode() + 37 * index;
277: }
278:
279: public Object clone() {
280: return new Step(this .attributeName, this .index,
281: this .isXmlAttribute);
282: }
283:
284: /**
285: * Flag that indicates that this single step refers to an "attribute"
286: * rather than a "property".
287: * <p>
288: * I.e. it was created from the last step of an expression like
289: * <code>foo/bar@attribute</code>.
290: * </p>
291: *
292: * @return
293: */
294: public boolean isXmlAttribute() {
295: return isXmlAttribute;
296: }
297: }
298:
299: /**
300: * Returns the list of stepts in <code>xpathExpression</code> by cleaning
301: * it up removing unnecessary elements.
302: * <p>
303: * </p>
304: *
305: * @param root
306: * non null descriptor of the root attribute, generally the
307: * Feature descriptor. Used to ignore the first step in
308: * xpathExpression if the expression's first step is named as
309: * rootName.
310: *
311: * @param xpathExpression
312: * @return
313: * @throws IllegalArgumentException
314: * if <code>xpathExpression</code> has no steps or it isn't a
315: * valid XPath expression against <code>type</code>.
316: */
317: public static StepList steps(final AttributeDescriptor root,
318: final String xpathExpression,
319: final NamespaceSupport namespaces)
320: throws IllegalArgumentException {
321:
322: if (root == null) {
323: throw new NullPointerException("root");
324: }
325:
326: if (xpathExpression == null) {
327: throw new NullPointerException("xpathExpression");
328: }
329:
330: String expression = xpathExpression.trim();
331:
332: if ("".equals(expression)) {
333: throw new IllegalArgumentException("expression is empty");
334: }
335:
336: StepList steps = new StepList();
337:
338: if ("/".equals(expression)) {
339: expression = root.getName().getLocalPart();
340: }
341:
342: if (expression.startsWith("/")) {
343: expression = expression.substring(1);
344: }
345:
346: final String[] partialSteps = expression.split("[/]");
347:
348: if (partialSteps.length == 0) {
349: throw new IllegalArgumentException("no steps provided");
350: }
351:
352: int startIndex = 0;
353:
354: for (int i = startIndex; i < partialSteps.length; i++) {
355:
356: String step = partialSteps[i];
357: if ("..".equals(step)) {
358: steps.remove(steps.size() - 1);
359: } else if (".".equals(step)) {
360: continue;
361: } else {
362: int index = 1;
363: boolean isXmlAttribute = false;
364: String stepName = step;
365: if (step.indexOf('[') != -1) {
366: int start = step.indexOf('[');
367: int end = step.indexOf(']');
368: stepName = step.substring(0, start);
369: index = Integer.parseInt(step.substring(start + 1,
370: end));
371: }
372: if (step.charAt(0) == '@') {
373: isXmlAttribute = true;
374: stepName = stepName.substring(1);
375: }
376: QName qName = deglose(stepName, root, namespaces);
377: steps.add(new Step(qName, index, isXmlAttribute));
378: }
379: //
380: // if (step.indexOf('[') != -1) {
381: // int start = step.indexOf('[');
382: // int end = step.indexOf(']');
383: // String stepName = step.substring(0, start);
384: // int stepIndex = Integer.parseInt(step.substring(start + 1, end));
385: // QName qName = deglose(stepName, root, namespaces);
386: // steps.add(new Step(qName, stepIndex));
387: // } else if ("..".equals(step)) {
388: // steps.remove(steps.size() - 1);
389: // } else if (".".equals(step)) {
390: // continue;
391: // } else {
392: // QName qName = deglose(step, root, namespaces);
393: // steps.add(new Step(qName, 1));
394: // }
395: }
396:
397: // XPath simplification phase: if the xpath expression contains more
398: // nodes
399: // than the root node itself, and the root node is present, remove the
400: // root
401: // node as it is redundant
402: if (root != null && steps.size() > 1) {
403: Step step = (Step) steps.get(0);
404: Name rootName = root.getName();
405: QName stepName = step.getName();
406: if (Types.equals(rootName, stepName)) {
407: LOGGER.fine("removing root name from xpath " + steps
408: + " as it is redundant");
409: steps.remove(0);
410: }
411: }
412:
413: return steps;
414: }
415:
416: private static QName deglose(final String prefixedName,
417: final AttributeDescriptor root,
418: final NamespaceSupport namespaces) {
419: if (prefixedName == null) {
420: throw new NullPointerException("prefixedName");
421: }
422:
423: QName name = null;
424:
425: final String prefix;
426: final String namespaceUri;
427: final String localName;
428: final Name rootName = root.getName();
429: final String defaultNamespace = rootName.getNamespaceURI() == null ? XMLConstants.NULL_NS_URI
430: : rootName.getNamespaceURI();
431:
432: int prefixIdx = prefixedName.indexOf(':');
433:
434: if (prefixIdx == -1) {
435: localName = prefixedName;
436: namespaceUri = defaultNamespace;
437: if (XMLConstants.NULL_NS_URI.equals(defaultNamespace)) {
438: prefix = XMLConstants.DEFAULT_NS_PREFIX;
439: } else {
440: if (!localName.equals(rootName.getLocalPart())) {
441: LOGGER.warning("Using root's namespace "
442: + defaultNamespace + " for step named '"
443: + localName + "', as no prefix was stated");
444: }
445: prefix = namespaces.getPrefix(defaultNamespace);
446:
447: if (prefix == null) {
448: throw new IllegalStateException(
449: "Default namespace is not mapped to a prefix: "
450: + defaultNamespace);
451: }
452: }
453: } else {
454: prefix = prefixedName.substring(0, prefixIdx);
455: localName = prefixedName.substring(prefixIdx + 1);
456: namespaceUri = namespaces.getURI(prefix);
457: }
458:
459: name = new QName(namespaceUri, localName, prefix);
460:
461: return name;
462: }
463:
464: /**
465: * Sets the value of the attribute of <code>att</code> addressed by
466: * <code>xpath</code> and of type <code>targetNodeType</code> to be
467: * <code>value</code> with id <code>id</code>.
468: *
469: * @param att
470: * the root attribute for which to set the child attribute value
471: * @param xpath
472: * the xpath expression that addresses the <code>att</code>
473: * child whose value is to be set
474: * @param value
475: * the value of the attribute addressed by <code>xpath</code>
476: * @param id
477: * the identifier of the attribute addressed by
478: * <code>xpath</code>, might be <code>null</code>
479: * @param targetNodeType
480: * the expected type of the attribute addressed by
481: * <code>xpath</code>, or <code>null</code> if unknown
482: * @return
483: */
484: public Attribute set(final Attribute att, final StepList xpath,
485: Object value, String id, AttributeType targetNodeType) {
486: if (XPath.LOGGER.isLoggable(Level.CONFIG)) {
487: XPath.LOGGER.entering("XPath", "set", new Object[] { att,
488: xpath, value, id, targetNodeType });
489: }
490:
491: final StepList steps = new StepList(xpath);
492:
493: // if (steps.size() < 2) {
494: // throw new IllegalArgumentException("parent not yet built for " +
495: // xpath);
496: // }
497:
498: ComplexAttribute parent = (ComplexAttribute) att;
499: Name rootName = null;
500: if (parent.getDescriptor() != null) {
501: rootName = parent.getDescriptor().getName();
502: Step rootStep = (Step) steps.get(0);
503: QName stepName = rootStep.getName();
504: if (stepName.getLocalPart().equals(rootName.getLocalPart())) {
505: if (XMLConstants.NULL_NS_URI.equals(stepName
506: .getNamespaceURI())
507: || stepName.getNamespaceURI().equals(
508: rootName.getNamespaceURI())) {
509: // first step is the self reference to att, so skip it
510: steps.remove(0);
511: }
512: }
513: }
514:
515: Iterator stepsIterator = steps.iterator();
516:
517: for (; stepsIterator.hasNext();) {
518: final XPath.Step currStep = (Step) stepsIterator.next();
519: final ComplexType parentType = (ComplexType) parent
520: .getType();
521: final QName stepName = currStep.getName();
522: final Name attributeName = Types.toName(stepName);
523:
524: AttributeDescriptor currStepDescriptor = null;
525:
526: if (targetNodeType == null) {
527: if (null == attributeName.getNamespaceURI()) {
528: currStepDescriptor = (AttributeDescriptor) Types
529: .descriptor(parentType, attributeName
530: .getLocalPart());
531: } else {
532: currStepDescriptor = (AttributeDescriptor) Types
533: .descriptor(parentType, attributeName);
534: }
535:
536: if (currStepDescriptor == null) {
537: // need to take the non easy way, may be the instance has a
538: // value for this step with a different name, of a derived
539: // type of the one declared in the parent type
540: String prefixedStepName = currStep.toString();
541: PropertyName name = FF.property(prefixedStepName);
542: Attribute child = (Attribute) name.evaluate(parent);
543: if (child != null) {
544: currStepDescriptor = child.getDescriptor();
545: }
546: }
547: } else {
548: AttributeDescriptor actualDescriptor;
549: if (null == attributeName.getNamespaceURI()) {
550: actualDescriptor = (AttributeDescriptor) Types
551: .descriptor(parentType, attributeName
552: .getLocalPart(), targetNodeType);
553: } else {
554: actualDescriptor = (AttributeDescriptor) Types
555: .descriptor(parentType, attributeName,
556: targetNodeType);
557: }
558:
559: if (actualDescriptor != null) {
560: int minOccurs = actualDescriptor.getMinOccurs();
561: int maxOccurs = actualDescriptor.getMaxOccurs();
562: boolean nillable = actualDescriptor.isNillable();
563: currStepDescriptor = descriptorFactory
564: .createAttributeDescriptor(targetNodeType,
565: attributeName, minOccurs,
566: maxOccurs, nillable, null);
567: }
568: }
569:
570: if (currStepDescriptor == null) {
571: StringBuffer parentAtts = new StringBuffer();
572: Collection properties = parentType.getProperties();
573: for (Iterator it = properties.iterator(); it.hasNext();) {
574: PropertyDescriptor desc = (PropertyDescriptor) it
575: .next();
576: Name name = desc.getName();
577: parentAtts.append(name.getNamespaceURI());
578: parentAtts.append("#");
579: parentAtts.append(name.getLocalPart());
580: if (it.hasNext()) {
581: parentAtts.append(", ");
582: }
583: }
584: throw new IllegalArgumentException(currStep
585: + " is not a valid location path for type "
586: + parentType.getName() + ". " + currStep
587: + " ns: "
588: + currStep.getName().getNamespaceURI() + ", "
589: + parentType.getName().getLocalPart()
590: + " properties: " + parentAtts);
591: }
592:
593: final boolean isLastStep = !stepsIterator.hasNext();
594:
595: if (isLastStep) {
596: // reached the leaf
597: if (currStepDescriptor == null) {
598: throw new IllegalArgumentException(currStep
599: + " is not a valid location path for type "
600: + parentType.getName());
601: }
602: int index = currStep.getIndex();
603: Attribute attribute = setValue(currStepDescriptor, id,
604: value, index, parent, targetNodeType);
605: return attribute;
606: } else {
607: // parent = appendComplexProperty(parent, currStep,
608: // currStepDescriptor);
609: int index = currStep.getIndex();
610: Attribute _parent = setValue(currStepDescriptor, null,
611: null, index, parent, null);
612: parent = (ComplexAttribute) _parent;
613: }
614: }
615: throw new IllegalStateException();
616: }
617:
618: private Attribute setValue(final AttributeDescriptor descriptor,
619: final String id, Object value, final int index,
620: final ComplexAttribute parent,
621: final AttributeType targetNodeType) {
622:
623: final Name attributeName = descriptor.getName();
624:
625: // adapt value to context
626: Literal literal = FF.literal(value);
627: Class binding = ((AttributeType) descriptor.type())
628: .getBinding();
629: value = literal.evaluate(value, binding);
630:
631: Attribute leafAttribute = null;
632:
633: Object currStepValue = parent.get(attributeName);
634:
635: if (currStepValue instanceof Collection) {
636: List values = new ArrayList((Collection) currStepValue);
637: if (values.size() >= index) {
638: leafAttribute = (Attribute) values.get(index - 1);
639: }
640: } else if (currStepValue instanceof Attribute) {
641: leafAttribute = (Attribute) currStepValue;
642: } else if (currStepValue != null) {
643: throw new IllegalStateException(
644: "Unkown addressed object. Xpath:" + attributeName
645: + ", addressed: "
646: + currStepValue.getClass().getName() + " ["
647: + currStepValue.toString() + "]");
648: }
649:
650: if (leafAttribute == null) {
651: AttributeBuilder builder = new AttributeBuilder(
652: featureFactory);
653: builder.init(parent);
654: if (targetNodeType != null) {
655: leafAttribute = builder.add(id, value, attributeName,
656: targetNodeType);
657: } else {
658: leafAttribute = builder.add(id, value, attributeName);
659: }
660: List newValue = new ArrayList();
661: newValue.addAll((Collection) parent.getValue());
662: newValue.add(leafAttribute);
663: parent.setValue(newValue);
664: }
665:
666: if (value != null) {
667: leafAttribute.setValue(value);
668: }
669: return leafAttribute;
670: }
671:
672: public boolean isComplexType(final StepList attrXPath,
673: final AttributeDescriptor featureType) {
674: PropertyName attExp = FF.property(attrXPath.toString());
675: Object type = attExp.evaluate(featureType);
676: if (type == null) {
677: type = attExp.evaluate(featureType);
678: throw new IllegalArgumentException("path not found: "
679: + attrXPath);
680: }
681:
682: AttributeDescriptor node = (AttributeDescriptor) type;
683: return node.type() instanceof ComplexType;
684: }
685:
686: }
|