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; either
009: * version 2.1 of the License, or (at your option) any later version.
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.data.wfs;
017:
018: import java.net.URI;
019: import java.rmi.server.UID;
020: import java.util.logging.Level;
021:
022: import org.geotools.feature.AttributeType;
023: import org.geotools.feature.DefaultFeature;
024: import org.geotools.feature.DefaultFeatureType;
025: import org.geotools.feature.Feature;
026: import org.geotools.feature.FeatureCollection;
027: import org.geotools.feature.FeatureType;
028: import org.geotools.feature.GeometryAttributeType;
029: import org.geotools.feature.IllegalAttributeException;
030: import org.geotools.feature.SimpleFeature;
031: import org.geotools.geometry.jts.ReferencedEnvelope;
032:
033: import com.vividsolutions.jts.geom.Envelope;
034: import com.vividsolutions.jts.geom.Geometry;
035:
036: /**
037: * A FeatureType that adds the information about the XMLSchema used to create the FeatureType.
038: * @author Jesse
039: * @since 1.1.0
040: */
041: class WFSFeatureType implements FeatureType {
042: FeatureType delegate;
043: private URI schemaURI;
044: /**
045: * If lenient then it will not throw exceptions on create() if the attributes aren't legal.
046: */
047: private boolean lenient;
048:
049: public WFSFeatureType(FeatureType delegate, URI schemaURI) {
050: this (delegate, schemaURI, false);
051: }
052:
053: public WFSFeatureType(FeatureType delegate, URI schemaURI,
054: boolean lenient2) {
055: this .delegate = delegate;
056: this .schemaURI = schemaURI;
057: lenient = lenient2;
058: }
059:
060: public Feature create(Object[] attributes, String featureID)
061: throws IllegalAttributeException {
062: if (lenient && delegate instanceof DefaultFeatureType) {
063: WFSFeatureType schema = new WFSFeatureType(delegate,
064: schemaURI);
065: return new LenientFeature(schema, attributes, featureID);
066: } else {
067: return delegate.create(attributes, featureID);
068: }
069: }
070:
071: public void setLenient(boolean lenient) {
072: this .lenient = lenient;
073: }
074:
075: boolean isLenient() {
076: return lenient;
077: }
078:
079: public Feature create(Object[] attributes)
080: throws IllegalAttributeException {
081: return create(attributes, null);
082: }
083:
084: public Feature duplicate(Feature original)
085: throws IllegalAttributeException {
086: if (original == null)
087: return null;
088: FeatureType featureType = original.getFeatureType();
089: if (!featureType.equals(this )) {
090: throw new IllegalAttributeException("Feature type "
091: + featureType + " does not match " + this );
092: }
093: String id = original.getID();
094: int numAtts = featureType.getAttributeCount();
095: Object attributes[] = new Object[numAtts];
096: for (int i = 0; i < numAtts; i++) {
097: AttributeType curAttType = getAttributeType(i);
098: attributes[i] = curAttType.duplicate(original
099: .getAttribute(i));
100: }
101: return featureType.create(attributes, id);
102: }
103:
104: public boolean equals(Object arg0) {
105: if (!(arg0 instanceof WFSFeatureType))
106: return false;
107: WFSFeatureType ft = (WFSFeatureType) arg0;
108: return delegate.equals(ft.delegate);
109: }
110:
111: public int find(AttributeType type) {
112: return delegate.find(type);
113: }
114:
115: public int find(String attName) {
116: return delegate.find(attName);
117: }
118:
119: public FeatureType[] getAncestors() {
120: return delegate.getAncestors();
121: }
122:
123: public int getAttributeCount() {
124: return delegate.getAttributeCount();
125: }
126:
127: public AttributeType getAttributeType(int position) {
128: return delegate.getAttributeType(position);
129: }
130:
131: public AttributeType getAttributeType(String xPath) {
132: return delegate.getAttributeType(xPath);
133: }
134:
135: public AttributeType[] getAttributeTypes() {
136: return delegate.getAttributeTypes();
137: }
138:
139: public GeometryAttributeType getDefaultGeometry() {
140: return delegate.getDefaultGeometry();
141: }
142:
143: public URI getNamespace() {
144: return delegate.getNamespace();
145: }
146:
147: public String getTypeName() {
148: return delegate.getTypeName();
149: }
150:
151: public boolean hasAttributeType(String xPath) {
152: return delegate.hasAttributeType(xPath);
153: }
154:
155: public int hashCode() {
156: return delegate.hashCode();
157: }
158:
159: public boolean isAbstract() {
160: return delegate.isAbstract();
161: }
162:
163: public boolean isDescendedFrom(FeatureType type) {
164: return delegate.isDescendedFrom(type);
165: }
166:
167: public boolean isDescendedFrom(URI nsURI, String typeName) {
168: return delegate.isDescendedFrom(nsURI, typeName);
169: }
170:
171: public URI getSchemaURI() {
172: return schemaURI;
173: }
174:
175: public String toString() {
176: return delegate.toString();
177: }
178:
179: private static class LenientFeature implements SimpleFeature,
180: Feature {
181:
182: /** The unique id of this feature */
183: protected String featureId;
184:
185: /** Flat feature type schema for this feature. */
186: private final WFSFeatureType schema;
187:
188: /** Attributes for the feature. */
189: private Object[] attributes;
190:
191: /** The bounds of this feature. */
192: private Envelope bounds;
193:
194: /** The collection that this Feature is a member of */
195: private FeatureCollection parent;
196:
197: private int defaultGeomIndex = -1;
198:
199: private boolean constructing;
200:
201: /**
202: * Creates a new instance of flat feature, which must take a flat feature
203: * type schema and all attributes as arguments.
204: *
205: * @param schema Feature type schema for this flat feature.
206: * @param attributes Initial attributes for this feature.
207: * @param featureID The unique ID for this feature.
208: *
209: * @throws IllegalAttributeException Attribtues do not conform to feature
210: * type schema.
211: * @throws NullPointerException if schema is null.
212: */
213: protected LenientFeature(WFSFeatureType schema,
214: Object[] attributes, String featureID)
215: throws IllegalAttributeException, NullPointerException {
216: constructing = true;
217: if (schema == null) {
218: throw new NullPointerException("schema");
219: }
220:
221: this .schema = schema;
222: this .featureId = (featureID == null) ? defaultID()
223: : featureID;
224: this .attributes = new Object[schema.getAttributeCount()];
225:
226: setAttributes(attributes);
227:
228: defaultGeomIndex = schema.find(schema.getDefaultGeometry());
229: constructing = false;
230: }
231:
232: /**
233: * Creates a new instance of flat feature, which must take a flat feature
234: * type schema and all attributes as arguments.
235: *
236: * @param schema Feature type schema for this flat feature.
237: * @param attributes Initial attributes for this feature.
238: *
239: * @throws IllegalAttributeException Attribtues do not conform to feature
240: * type schema.
241: *
242: * @task REVISIT: should we allow this? Force users to explicitly set
243: * featureID to null?
244: */
245: protected LenientFeature(WFSFeatureType schema,
246: Object[] attributes) throws IllegalAttributeException {
247: this (schema, attributes, null);
248: }
249:
250: /**
251: * Creates an ID from a hashcode.
252: *
253: * @return an id for the feature.
254: */
255: String defaultID() {
256: return "fid-" + new UID().toString().replace(':', '_');
257: }
258:
259: /**
260: * Gets a reference to the feature type schema for this feature.
261: *
262: * @return A copy of this feature's metadata in the form of a feature type
263: * schema.
264: */
265: public FeatureType getFeatureType() {
266: return schema;
267: }
268:
269: /**
270: * Gets the unique indentification string of this Feature.
271: *
272: * @return The unique id.
273: */
274: public String getID() {
275: return featureId;
276: }
277:
278: /**
279: * Copy all the attributes of this Feature into the given array. If the
280: * argument array is null, a new one will be created. Gets all attributes
281: * from this feature, returned as a complex object array. This array
282: * comes with no metadata, so to interpret this collection the caller
283: * class should ask for the schema as well.
284: *
285: * @param array The array to copy the attributes into.
286: *
287: * @return The array passed in, or a new one if null.
288: */
289: public Object[] getAttributes(Object[] array) {
290: Object[] retArray;
291:
292: if (array == null) {
293: retArray = new Object[attributes.length];
294: } else {
295: retArray = array;
296: }
297:
298: System.arraycopy(attributes, 0, retArray, 0,
299: attributes.length);
300:
301: return retArray;
302: }
303:
304: /**
305: * Gets an attribute for this feature at the location specified by xPath.
306: *
307: * @param xPath XPath representation of attribute location.
308: *
309: * @return Attribute.
310: */
311: public Object getAttribute(String xPath) {
312: int idx = schema.find(xPath);
313:
314: if (idx == -1) {
315: return null;
316: }
317:
318: return attributes[idx];
319: }
320:
321: /**
322: * Gets an attribute by the given zero-based index.
323: *
324: * @param index the position of the attribute to retrieve.
325: *
326: * @return The attribute at the given index.
327: */
328: public Object getAttribute(int index) {
329: return attributes[index];
330: }
331:
332: /**
333: * Sets the attribute at position to val.
334: *
335: * @param position the index of the attribute to set.
336: * @param val the new value to give the attribute at position.
337: *
338: * @throws IllegalAttributeException if the passed in val does not validate
339: * against the AttributeType at that position.
340: */
341: public void setAttribute(int position, Object val)
342: throws IllegalAttributeException {
343: AttributeType type = schema.getAttributeType(position);
344:
345: try {
346: if ((val == null) && !type.isNillable())
347: val = type.createDefaultValue();
348: Object parsed = type.parse(val);
349: try {
350: type.validate(parsed);
351: } catch (Throwable e) {
352: if (constructing) {
353: WFSDataStoreFactory.logger
354: .logp(
355: Level.WARNING,
356: "LenientFeature",
357: "setAttribute",
358: "Illegal Argument but ignored since we are being lenient",
359: e);
360: } else {
361: throw new IllegalAttributeException(type, val,
362: e);
363: }
364: }
365: setAttributeValue(position, parsed);
366: } catch (IllegalArgumentException iae) {
367: throw new IllegalAttributeException(type, val, iae);
368: }
369: }
370:
371: /**
372: * Sets the attribute value at a given position, performing no parsing or
373: * validation. This is so subclasses can have access to setting the array,
374: * without opening it up completely.
375: *
376: * @param position the index of the a ttribute to set.
377: * @param val the new value to give the attribute at position.
378: */
379: protected void setAttributeValue(int position, Object val) {
380: attributes[position] = val;
381: }
382:
383: /**
384: * Sets all attributes for this feature, passed as an array. All
385: * attributes are checked for validity before adding.
386: *
387: * @param attributes All feature attributes.
388: *
389: * @throws IllegalAttributeException Passed attributes do not match feature
390: * type.
391: */
392: public void setAttributes(Object[] attributes)
393: throws IllegalAttributeException {
394: // the passed in attributes were null, lets make that a null array
395: Object[] newAtts = attributes;
396:
397: if (attributes == null) {
398: newAtts = new Object[this .attributes.length];
399: }
400:
401: if (constructing) {
402:
403: // We're trying to make this work no matter what
404: // so try to figure out some mapping
405: Object[] tmp = assumeCorrectOrder(newAtts);
406: if (tmp == null)
407: newAtts = greedyMatch(newAtts);
408: else
409: newAtts = tmp;
410: } else {
411: if (newAtts.length != this .attributes.length) {
412: throw new IllegalAttributeException(
413: "Wrong number of attributes expected "
414: + schema.getAttributeCount()
415: + " got " + newAtts.length);
416: }
417: }
418:
419: for (int i = 0, ii = newAtts.length; i < ii; i++) {
420: setAttribute(i, newAtts[i]);
421: }
422: }
423:
424: private Object[] assumeCorrectOrder(Object[] newAtts) {
425: Object[] tmp = new Object[schema.getAttributeCount()];
426: for (int i = 0; i < newAtts.length
427: && i < schema.getAttributeCount(); i++) {
428: Object object = newAtts[i];
429: AttributeType att = schema.getAttributeType(i);
430: if (object == null)
431: continue;
432: Class requiredClass = att.getType();
433: Class realClass = object.getClass();
434: if (!requiredClass.isAssignableFrom(realClass))
435: return null;
436: else
437: tmp[i] = object;
438:
439: }
440: return tmp;
441: }
442:
443: private Object[] greedyMatch(Object[] newAtts) {
444: Object[] relaxedAttrs = new Object[this .attributes.length];
445: boolean inValid = false;
446: for (int i = 0; i < newAtts.length; i++) {
447: Object object = newAtts[i];
448: boolean found = false;
449: if (object == null)
450: continue;
451: Class realClass = object.getClass();
452: for (int j = 0; j < schema.getAttributeCount(); j++) {
453: AttributeType att = schema.getAttributeType(j);
454: Class requiredClass = att.getType();
455: if (relaxedAttrs[j] == null
456: && requiredClass
457: .isAssignableFrom(realClass)) {
458: relaxedAttrs[j] = object;
459: found = true;
460: break;
461: }
462: }
463: if (!found)
464: inValid = true;
465: }
466: newAtts = relaxedAttrs;
467: if (inValid) {
468: StringBuffer buf = new StringBuffer();
469: buf.append("WFSFeatureType#setAttributes(Object[]):");
470: buf
471: .append("\nAttributes were not correct for the feature Type:");
472: buf.append(schema.getTypeName());
473: buf.append(". Made best guess:\n Recieved: ");
474: for (int i = 0; i < newAtts.length; i++) {
475: Object object = newAtts[i];
476: buf.append(object == null ? "null" : object
477: .toString());
478: buf.append(",");
479: }
480: buf.append("\nBest Guess: \n");
481: for (int i = 0; i < relaxedAttrs.length; i++) {
482: Object object = relaxedAttrs[i];
483: buf.append(object == null ? "null" : object
484: .toString());
485: buf.append(",");
486: }
487:
488: WFSDataStoreFactory.logger.warning(buf.toString());
489: }
490: return newAtts;
491: }
492:
493: /**
494: * Sets a single attribute for this feature, passed as a complex object. If
495: * the attribute does not exist or the object does not conform to the
496: * internal feature type, an exception is thrown.
497: *
498: * @param xPath XPath representation of attribute location.
499: * @param attribute Feature attribute to set.
500: *
501: * @throws IllegalAttributeException Passed attribute does not match
502: * feature type
503: */
504: public void setAttribute(String xPath, Object attribute)
505: throws IllegalAttributeException {
506: int idx = schema.find(xPath);
507:
508: if (idx < 0) {
509: throw new IllegalAttributeException(
510: "No attribute named " + xPath);
511: }
512:
513: setAttribute(idx, attribute);
514: }
515:
516: /**
517: * Gets the geometry for this feature.
518: *
519: * @return Geometry for this feature.
520: */
521: public final Geometry getDefaultGeometry() {
522: if (defaultGeomIndex == -1) {
523: return null;
524: }
525:
526: return (Geometry) attributes[defaultGeomIndex];
527: }
528:
529: /**
530: * Modifies the geometry.
531: *
532: * @param geometry All feature attributes.
533: *
534: * @throws IllegalAttributeException if the feature does not have a
535: * geometry.
536: */
537: public void setDefaultGeometry(Geometry geometry)
538: throws IllegalAttributeException {
539:
540: if (defaultGeomIndex < 0) {
541: throw new IllegalAttributeException(
542: "Feature does not have geometry");
543: }
544:
545: attributes[defaultGeomIndex] = geometry;
546: bounds = null;
547: }
548:
549: /**
550: * Get the number of attributes this feature has. This is simply a
551: * convenience method for calling
552: * getFeatureType().getNumberOfAttributes();
553: *
554: * @return The total number of attributes this Feature contains.
555: */
556: public int getNumberOfAttributes() {
557: return attributes.length;
558: }
559:
560: /**
561: * Get the total bounds of this feature which is calculated by doing a
562: * union of the bounds of each geometry this feature is associated with.
563: *
564: * @return An Envelope containing the total bounds of this Feature.
565: *
566: * @task REVISIT: what to return if there are no geometries in the feature?
567: * For now we'll return a null envelope, make this part of
568: * interface? (IanS - by OGC standards, all Feature must have geom)
569: */
570: public ReferencedEnvelope getBounds() {
571: if (bounds == null) {
572: bounds = new Envelope();
573:
574: for (int i = 0, n = schema.getAttributeCount(); i < n; i++) {
575: if (schema.getAttributeType(i) instanceof GeometryAttributeType) {
576: Geometry g = (Geometry) attributes[i];
577:
578: // IanS - check for null geometry!
579: if (g == null) {
580: continue;
581: }
582:
583: Envelope e = g.getEnvelopeInternal();
584:
585: // IanS
586: // as of JTS 1.3, expandToInclude does not check to see if
587: // Envelope is "null", and simply adds the flagged values.
588: // This ensures that this behavior does not occur.
589: if (!e.isNull()) {
590: bounds.expandToInclude(e);
591: }
592: }
593: }
594: }
595:
596: // lets be defensive
597: return ReferencedEnvelope.reference(new Envelope(bounds));
598: }
599:
600: /**
601: * Creates an exact copy of this feature.
602: *
603: * @return A default feature.
604: *
605: * @throws RuntimeException DOCUMENT ME!
606: */
607: public Object clone() {
608: try {
609: DefaultFeature clone = (DefaultFeature) super .clone();
610:
611: for (int i = 0; i < attributes.length; i++) {
612: try {
613: clone.setAttribute(i, attributes[i]);
614: } catch (IllegalAttributeException e1) {
615: throw new RuntimeException(
616: "The impossible has happened", e1);
617: }
618: }
619:
620: return clone;
621: } catch (CloneNotSupportedException e) {
622: throw new RuntimeException(
623: "The impossible has happened", e);
624: }
625: }
626:
627: /**
628: * Returns a string representation of this feature.
629: *
630: * @return A representation of this feature as a string.
631: */
632: public String toString() {
633: String retString = "Feature[ id=" + getID() + " , ";
634: FeatureType featType = getFeatureType();
635:
636: for (int i = 0, n = attributes.length; i < n; i++) {
637: retString += (featType.getAttributeType(i).getName() + "=");
638: retString += attributes[i];
639:
640: if ((i + 1) < n) {
641: retString += " , ";
642: }
643: }
644:
645: return retString += " ]";
646: }
647:
648: /**
649: * returns a unique code for this feature
650: *
651: * @return A unique int
652: */
653: public int hashCode() {
654: return featureId.hashCode() * schema.hashCode();
655: }
656:
657: /**
658: * override of equals. Returns if the passed in object is equal to this.
659: *
660: * @param obj the Object to test for equality.
661: *
662: * @return <code>true</code> if the object is equal, <code>false</code>
663: * otherwise.
664: */
665: public boolean equals(Object obj) {
666: if (obj == null) {
667: return false;
668: }
669:
670: if (obj == this ) {
671: return true;
672: }
673:
674: if (!(obj instanceof Feature)) {
675: return false;
676: }
677:
678: Feature feat = (Feature) obj;
679:
680: if (!feat.getFeatureType().equals(schema)) {
681: return false;
682: }
683:
684: // this check shouldn't exist, by contract,
685: //all features should have an ID.
686: if (featureId == null) {
687: if (feat.getID() != null) {
688: return false;
689: }
690: }
691:
692: if (!featureId.equals(feat.getID())) {
693: return false;
694: }
695:
696: for (int i = 0, ii = attributes.length; i < ii; i++) {
697: Object otherAtt = feat.getAttribute(i);
698:
699: if (attributes[i] == null) {
700: if (otherAtt != null) {
701: return false;
702: }
703: } else {
704: if (!attributes[i].equals(otherAtt)) {
705: if (attributes[i] instanceof Geometry
706: && otherAtt instanceof Geometry) {
707: // we need to special case Geometry
708: // as JTS is broken
709: // Geometry.equals( Object ) and Geometry.equals( Geometry )
710: // are different
711: // (We should fold this knowledge into AttributeType...)
712: //
713: if (!((Geometry) attributes[i])
714: .equals((Geometry) otherAtt)) {
715: return false;
716: }
717: } else {
718: return false;
719: }
720: }
721: }
722: }
723:
724: return true;
725: }
726:
727: /**
728: * Gets the feature collection this feature is stored in.
729: *
730: * @return the collection that is the parent of this feature.
731: */
732: public FeatureCollection getParent() {
733: return parent;
734: }
735:
736: /**
737: * Sets the parent collection this feature is stored in, if it is not
738: * already set. If it is set then this method does nothing.
739: *
740: * @param collection the collection to be set as parent.
741: */
742: public void setParent(FeatureCollection collection) {
743: if (parent == null) {
744: parent = collection;
745: }
746: }
747:
748: }
749: }
|