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