001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/model/feature/DefaultFeature.java $
002: /*---------------- FILE HEADER ------------------------------------------
003:
004: This file is part of deegree.
005: Copyright (C) 2001-2008 by:
006: EXSE, Department of Geography, University of Bonn
007: http://www.giub.uni-bonn.de/deegree/
008: lat/lon GmbH
009: http://www.lat-lon.de
010:
011: This library is free software; you can redistribute it and/or
012: modify it under the terms of the GNU Lesser General Public
013: License as published by the Free Software Foundation; either
014: version 2.1 of the License, or (at your option) any later version.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: Contact:
026:
027: Andreas Poth
028: lat/lon GmbH
029: Aennchenstraße 19
030: 53177 Bonn
031: Germany
032: E-Mail: poth@lat-lon.de
033:
034: Prof. Dr. Klaus Greve
035: Department of Geography
036: University of Bonn
037: Meckenheimer Allee 166
038: 53115 Bonn
039: Germany
040: E-Mail: greve@giub.uni-bonn.de
041:
042: ---------------------------------------------------------------------------*/
043: package org.deegree.model.feature;
044:
045: import java.io.Serializable;
046: import java.net.URI;
047: import java.util.ArrayList;
048: import java.util.HashMap;
049: import java.util.Iterator;
050: import java.util.List;
051: import java.util.Map;
052: import java.util.UUID;
053:
054: import org.deegree.datatypes.QualifiedName;
055: import org.deegree.io.datastore.PropertyPathResolvingException;
056: import org.deegree.model.feature.schema.FeatureType;
057: import org.deegree.model.feature.schema.PropertyType;
058: import org.deegree.model.spatialschema.Geometry;
059: import org.deegree.model.spatialschema.GeometryFactory;
060: import org.deegree.ogcbase.CommonNamespaces;
061: import org.deegree.ogcbase.PropertyPath;
062: import org.deegree.ogcbase.PropertyPathStep;
063:
064: /**
065: * Features are, according to the Abstract Specification, digital representations of real world
066: * entities. Feature Identity thus refers to mechanisms to identify such representations: not to
067: * identify the real world entities that are the subject of a representation. Thus two different
068: * representations of a real world entity (say the Mississippi River) will be two different features
069: * with distinct identities. Real world identification systems, such as title numbers, while
070: * possibly forming a sound basis for an implementation of a feature identity mechanism, are not of
071: * themselves such a mechanism.
072: *
073: * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
074: * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider</a>
075: *
076: * @author last edited by: $Author: apoth $
077: *
078: * @version $Revision: 10533 $ $Date: 2008-03-10 02:31:08 -0700 (Mon, 10 Mar 2008) $
079: */
080: public class DefaultFeature extends AbstractFeature implements
081: Serializable {
082:
083: private static final long serialVersionUID = -1636744791597960465L;
084:
085: // key class: QualifiedName
086: // value class: FeatureProperty []
087: protected Map propertyMap;
088:
089: protected FeatureProperty[] properties;
090:
091: protected Object[] propertyValues;
092:
093: protected Geometry[] geometryPropertyValues;
094:
095: /**
096: * Creates a new instance of <code>DefaultFeature</code> from the given parameters.
097: *
098: * @param id
099: * @param featureType
100: * @param properties
101: * properties of the new feature, given in their intended order
102: */
103: protected DefaultFeature(String id, FeatureType featureType,
104: FeatureProperty[] properties) {
105: this (id, featureType, properties, null);
106: }
107:
108: /**
109: * Creates a new instance of <code>DefaultFeature</code> from the given parameters.
110: *
111: * @param id
112: * @param featureType
113: * @param properties
114: * properties of the new feature, given in their intended order
115: * @param owner
116: */
117: protected DefaultFeature(String id, FeatureType featureType,
118: FeatureProperty[] properties, FeatureProperty owner) {
119: super (id, featureType, owner);
120: for (int i = 0; i < properties.length; i++) {
121: FeatureProperty property = properties[i];
122: URI namespace = property.getName().getNamespace();
123: if ((namespace == null)) {
124: PropertyType propertyType = featureType
125: .getProperty(property.getName());
126: if (propertyType == null) {
127: throw new IllegalArgumentException(
128: "Unknown property '"
129: + property.getName()
130: + "' for feature with type '"
131: + featureType.getName()
132: + "': the feature type has no such property.");
133: }
134: } else if ((!namespace.equals(CommonNamespaces.GMLNS))) {
135: PropertyType propertyType = featureType
136: .getProperty(property.getName());
137: if (propertyType == null) {
138: throw new IllegalArgumentException(
139: "Unknown property '"
140: + property.getName()
141: + "' for feature with type '"
142: + featureType.getName()
143: + "': the feature type has no such property.");
144: }
145: }
146: }
147: this .properties = properties;
148: }
149:
150: /**
151: * TODO type checks!
152: *
153: * @throws FeatureException
154: */
155: public void validate() throws FeatureException {
156: if (this .propertyMap == null) {
157: this .propertyMap = buildPropertyMap();
158: }
159: PropertyType[] propertyTypes = featureType.getProperties();
160: for (int i = 0; i < propertyTypes.length; i++) {
161: List propertyList = (List) this .propertyMap
162: .get(propertyTypes[i].getName());
163: if (propertyList == null) {
164: if (propertyTypes[i].getMinOccurs() != 0) {
165: throw new FeatureException(
166: "Feature is not a valid instance of type '"
167: + featureType.getName()
168: + "', mandatory property '"
169: + propertyTypes[i].getName()
170: + "' is missing.");
171: }
172: } else {
173: if (propertyTypes[i].getMinOccurs() > propertyList
174: .size()) {
175: throw new FeatureException(
176: "Feature is not a valid instance of type '"
177: + featureType.getName()
178: + "', property '"
179: + propertyTypes[i].getName()
180: + "' has minOccurs="
181: + propertyTypes[i].getMinOccurs()
182: + ", but is only present "
183: + propertyList.size() + " times.");
184: }
185: if (propertyTypes[i].getMaxOccurs() != -1
186: && propertyTypes[i].getMaxOccurs() < propertyList
187: .size()) {
188: throw new FeatureException(
189: "Feature is not a valid instance of type '"
190: + featureType.getName()
191: + "', property '"
192: + propertyTypes[i].getName()
193: + "' has maxOccurs="
194: + propertyTypes[i].getMaxOccurs()
195: + ", but is present "
196: + propertyList.size() + " times.");
197: }
198: }
199: }
200: }
201:
202: /**
203: * Builds a map for efficient lookup of the feature's properties by name.
204: *
205: * @return key class: QualifiedName, value class: FeatureProperty []
206: */
207: private Map buildPropertyMap() {
208: Map propertyMap = new HashMap();
209: for (int i = 0; i < this .properties.length; i++) {
210: List propertyList = (List) propertyMap
211: .get(this .properties[i].getName());
212: if (propertyList == null) {
213: propertyList = new ArrayList();
214: }
215: propertyList.add(properties[i]);
216: propertyMap.put(properties[i].getName(), propertyList);
217: }
218: Iterator propertyNameIter = propertyMap.keySet().iterator();
219: while (propertyNameIter.hasNext()) {
220: QualifiedName propertyName = (QualifiedName) propertyNameIter
221: .next();
222: List propertyList = (List) propertyMap.get(propertyName);
223: propertyMap.put(propertyName, propertyList
224: .toArray(new FeatureProperty[propertyList.size()]));
225: }
226: return propertyMap;
227: }
228:
229: /**
230: * Builds a map for efficient lookup of the feature's properties by name.
231: *
232: * @return key class: QualifiedName, value class: FeatureProperty []
233: */
234: private Geometry[] extractGeometryPropertyValues() {
235: List geometryPropertiesList = new ArrayList();
236: for (int i = 0; i < this .properties.length; i++) {
237: if (this .properties[i].getValue() instanceof Geometry) {
238: geometryPropertiesList.add(this .properties[i]
239: .getValue());
240: } else if (this .properties[i].getValue() instanceof Object[]) {
241: Object[] objects = (Object[]) this .properties[i]
242: .getValue();
243: for (int j = 0; j < objects.length; j++) {
244: if (objects[j] instanceof Geometry) {
245: geometryPropertiesList.add(objects[j]);
246: }
247: }
248:
249: }
250: }
251: Geometry[] geometryPropertyValues = new Geometry[geometryPropertiesList
252: .size()];
253: geometryPropertyValues = (Geometry[]) geometryPropertiesList
254: .toArray(geometryPropertyValues);
255: return geometryPropertyValues;
256: }
257:
258: /**
259: * Returns all properties of the feature in their original order.
260: *
261: * @return all properties of the feature
262: */
263: public FeatureProperty[] getProperties() {
264: return this .properties;
265: }
266:
267: /**
268: * Returns the properties of the feature with the given name in their original order.
269: *
270: * @return the properties of the feature with the given name or null if the feature has no
271: * property with that name
272: */
273: public FeatureProperty[] getProperties(QualifiedName name) {
274: if (this .propertyMap == null) {
275: this .propertyMap = buildPropertyMap();
276: }
277: FeatureProperty[] properties = (FeatureProperty[]) propertyMap
278: .get(name);
279: return properties;
280: }
281:
282: /**
283: * Returns the property of the feature identified by the given PropertyPath in their original
284: * order.
285: *
286: * NOTE: Current implementation does not handle multiple properties (on the path) or index
287: * addressing in the path.
288: *
289: * @see PropertyPath
290: * @return the properties of the feature identified by the given PropertyPath or null if the
291: * feature has no such properties
292: *
293: */
294: public FeatureProperty getDefaultProperty(PropertyPath path)
295: throws PropertyPathResolvingException {
296:
297: Feature currentFeature = this ;
298: FeatureProperty currentProperty = null;
299:
300: // check if path begins with the name of the feature type
301: int firstPropIdx = 0;
302: if (path.getStep(0).getPropertyName().equals(getName())) {
303: firstPropIdx = 1;
304: }
305:
306: for (int i = firstPropIdx; i < path.getSteps(); i += 2) {
307: QualifiedName propertyName = path.getStep(i)
308: .getPropertyName();
309: currentProperty = currentFeature
310: .getDefaultProperty(propertyName);
311: if (i + 1 < path.getSteps()) {
312: QualifiedName featureName = path.getStep(i + 1)
313: .getPropertyName();
314: Object value = currentProperty.getValue();
315: if (!(value instanceof Feature)) {
316: String msg = "PropertyPath '"
317: + path
318: + "' cannot be matched to feature. Value of property '"
319: + propertyName
320: + "' is not a feature, but the path does not stop there.";
321: throw new PropertyPathResolvingException(msg);
322: }
323: currentFeature = (Feature) value;
324: if (!featureName.equals(currentFeature.getName())) {
325: String msg = "PropertyPath '"
326: + path
327: + "' cannot be matched to feature. Property '"
328: + propertyName
329: + "' contains a feature with name '"
330: + currentFeature.getName()
331: + "', but requested was: '" + featureName
332: + "'.";
333: throw new PropertyPathResolvingException(msg);
334: }
335: }
336: }
337: if (currentProperty == null) {
338: List<PropertyPathStep> list = path.getAllSteps();
339: currentProperty = new DefaultFeatureProperty(list.get(
340: list.size() - 1).getPropertyName(), null);
341: }
342: return currentProperty;
343: }
344:
345: /**
346: * Returns the first property of the feature with the given name.
347: *
348: * @return the first property of the feature with the given name or null if the feature has no
349: * such property
350: */
351: public FeatureProperty getDefaultProperty(QualifiedName name) {
352: FeatureProperty[] properties = getProperties(name);
353: if (properties != null) {
354: return properties[0];
355: }
356: return new DefaultFeatureProperty(name, null);
357: }
358:
359: /**
360: * Returns the properties of the feature at the submitted index of the feature type definition.
361: *
362: * @return the properties of the feature at the submitted index
363: * @deprecated
364: */
365: public FeatureProperty[] getProperties(int index) {
366: QualifiedName s = featureType.getPropertyName(index);
367: return getProperties(s);
368: }
369:
370: /**
371: * Returns the values of all geometry properties of the feature.
372: *
373: * @return the values of all geometry properties of the feature, or a zero-length array if the
374: * feature has no geometry properties
375: */
376: public Geometry[] getGeometryPropertyValues() {
377: if (this .geometryPropertyValues == null) {
378: this .geometryPropertyValues = extractGeometryPropertyValues();
379: }
380: return this .geometryPropertyValues;
381: }
382:
383: /**
384: * Returns the value of the default geometry property of the feature. If the feature has no
385: * geometry property, this is a Point at the coordinates (0,0).
386: *
387: * @return default geometry or Point at (0,0) if feature has no geometry
388: */
389: public Geometry getDefaultGeometryPropertyValue() {
390: Geometry[] geometryValues = getGeometryPropertyValues();
391: if (geometryValues.length < 1) {
392: return GeometryFactory.createPoint(0, 0, null);
393: }
394: return geometryValues[0];
395: }
396:
397: /**
398: * Sets the value for the given property. The index is needed to specify the occurences of the
399: * property that is to be replaced. Set to 0 for properties that may only occur once.
400: *
401: * @param property
402: * property name and the property's new value
403: * @param index
404: * position of the property that is to be replaced
405: */
406: public void setProperty(FeatureProperty property, int index) {
407: FeatureProperty[] oldProperties = getProperties(property
408: .getName());
409: if (oldProperties == null) {
410: throw new IllegalArgumentException("Cannot set property '"
411: + property.getName()
412: + "': feature has no property with that name.");
413: }
414: if (index > oldProperties.length - 1) {
415: throw new IllegalArgumentException("Cannot set property '"
416: + property.getName() + "' with index " + index
417: + ": feature has only " + oldProperties.length
418: + " properties with that name.");
419: }
420: oldProperties[index].setValue(property.getValue());
421: this .geometryPropertyValues = extractGeometryPropertyValues();
422: }
423:
424: /**
425: * Adds the given property to the feature's properties. The position of the property is
426: * determined by the feature type. If the feature already has properties with this name, the new
427: * one is inserted directly behind them.
428: *
429: * @param property
430: * property to insert
431: */
432: public void addProperty(FeatureProperty property) {
433:
434: FeatureProperty[] newProperties;
435:
436: if (this .properties == null) {
437: newProperties = new FeatureProperty[] { property };
438: } else {
439: newProperties = new FeatureProperty[this .properties.length + 1];
440: for (int i = 0; i < this .properties.length; i++) {
441: newProperties[i] = this .properties[i];
442: }
443: // TODO insert at correct position
444: newProperties[this .properties.length] = property;
445: }
446: this .properties = newProperties;
447: this .propertyMap = buildPropertyMap();
448: this .geometryPropertyValues = extractGeometryPropertyValues();
449: }
450:
451: /**
452: * Removes the properties with the given name.
453: *
454: * @param propertyName
455: * name of the (possibly multiple) property to remove
456: */
457: public void removeProperty(QualifiedName propertyName) {
458:
459: List<FeatureProperty> newProperties = new ArrayList<FeatureProperty>(
460: this .properties.length);
461: for (FeatureProperty property : this .properties) {
462: if (!property.getName().equals(propertyName)) {
463: newProperties.add(property);
464: }
465: }
466: this .properties = newProperties
467: .toArray(new FeatureProperty[newProperties.size()]);
468: this .propertyMap = buildPropertyMap();
469: this .geometryPropertyValues = extractGeometryPropertyValues();
470: }
471:
472: /**
473: * Replaces the given property with a new one.
474: *
475: * @param oldProperty
476: * property to be replaced
477: * @param newProperty
478: * new property
479: */
480: public void replaceProperty(FeatureProperty oldProperty,
481: FeatureProperty newProperty) {
482: for (int i = 0; i < properties.length; i++) {
483: if (properties[i] == oldProperty) {
484: properties[i] = newProperty;
485: }
486: }
487: }
488:
489: /**
490: * @return a shallow clone of a feature. Property values will not be cloned except for
491: * properties that are features (DefaultFeature) or feature collections
492: * (DefaultFeatureCollection)
493: */
494: @Override
495: public Object clone() {
496: FeatureProperty[] fp = new FeatureProperty[properties.length];
497: for (int i = 0; i < fp.length; i++) {
498: if (properties[i].getValue() instanceof DefaultFeatureCollection) {
499: Object v = ((DefaultFeatureCollection) properties[i]
500: .getValue()).clone();
501: fp[i] = FeatureFactory.createFeatureProperty(
502: properties[i].getName(), v);
503: } else if (properties[i].getValue() instanceof DefaultFeature) {
504: Object v = ((DefaultFeature) properties[i].getValue())
505: .clone();
506: fp[i] = FeatureFactory.createFeatureProperty(
507: properties[i].getName(), v);
508: } else {
509: fp[i] = FeatureFactory.createFeatureProperty(
510: properties[i].getName(), properties[i]
511: .getValue());
512: }
513: }
514: return FeatureFactory.createFeature(UUID.randomUUID()
515: .toString(), featureType, fp);
516: }
517:
518: @Override
519: public String toString() {
520: String ret = getClass().getName();
521: /*
522: * ret = "\nid = " + getId() + "\n"; ret += "featureType = " + featureType + "\n"; ret +=
523: * "geoProps = " + geometryPropertyValues + "\n"; ret += "properties = " + propertyMap +
524: * "\n";
525: */
526: return ret;
527: }
528: }
|