001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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: apoth $
070 *
071 * @version $Revision: 24106 $ $Date: 2010-05-04 16:48:20 +0200 (Di, 04. Mai 2010) $
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 ( currentProperty == null ) {
267 return null;
268 }
269 if ( i + 1 < path.getSteps() ) {
270 QualifiedName featureName = path.getStep( i + 1 ).getPropertyName();
271 Object value = currentProperty.getValue();
272 if ( !( value instanceof Feature ) ) {
273 String msg = "PropertyPath '" + path + "' cannot be matched to feature. Value of property '"
274 + propertyName + "' is not a feature, but the path does not stop there.";
275 throw new PropertyPathResolvingException( msg );
276 }
277 currentFeature = (Feature) value;
278 if ( !featureName.equals( currentFeature.getName() ) ) {
279 String msg = "PropertyPath '" + path + "' cannot be matched to feature. Property '" + propertyName
280 + "' contains a feature with name '" + currentFeature.getName()
281 + "', but requested was: '" + featureName + "'.";
282 throw new PropertyPathResolvingException( msg );
283 }
284 }
285 }
286 return currentProperty;
287 }
288
289 /**
290 * Returns the first property of the feature with the given name.
291 *
292 * @return the first property of the feature with the given name or null if the feature has no such property
293 */
294 public FeatureProperty getDefaultProperty( QualifiedName name ) {
295 FeatureProperty[] properties = getProperties( name );
296 if ( properties != null ) {
297 return properties[0];
298 }
299 return null;
300 }
301
302 /**
303 * Returns the properties of the feature at the submitted index of the feature type definition.
304 *
305 * @return the properties of the feature at the submitted index
306 * @deprecated
307 */
308 @Deprecated
309 public FeatureProperty[] getProperties( int index ) {
310 QualifiedName s = featureType.getPropertyName( index );
311 return getProperties( s );
312 }
313
314 /**
315 * Returns the values of all geometry properties of the feature.
316 *
317 * @return the values of all geometry properties of the feature, or a zero-length array if the feature has no
318 * geometry properties
319 */
320 public Geometry[] getGeometryPropertyValues() {
321 // TODO
322 // caching causes problems when updating a property or a propetyvalue
323 // if ( this.geometryPropertyValues == null ) {
324 this.geometryPropertyValues = extractGeometryPropertyValues();
325 // }
326 return this.geometryPropertyValues;
327 }
328
329 /**
330 * Returns the value of the default geometry property of the feature. If the feature has no geometry property, this
331 * is a Point at the coordinates (0,0).
332 *
333 * @return default geometry or Point at (0,0) if feature has no geometry
334 */
335 public Geometry getDefaultGeometryPropertyValue() {
336 Geometry[] geometryValues = getGeometryPropertyValues();
337 if ( geometryValues.length < 1 ) {
338 return GeometryFactory.createPoint( 0, 0, null );
339 }
340 return geometryValues[0];
341 }
342
343 /**
344 * Sets the value for the given property. The index is needed to specify the occurences of the property that is to
345 * be replaced. Set to 0 for properties that may only occur once.
346 *
347 * @param property
348 * property name and the property's new value
349 * @param index
350 * position of the property that is to be replaced
351 */
352 public void setProperty( FeatureProperty property, int index ) {
353
354 if ( "boundedBy".equals( ( property.getName().getLocalName() ) ) ) {
355 if ( property.getValue() instanceof Envelope ) {
356 envelope = (Envelope) property.getValue();
357 envelopeCalculated = true;
358 }
359 return;
360 }
361
362 FeatureProperty[] oldProperties = getProperties( property.getName() );
363 if ( oldProperties == null ) {
364 throw new IllegalArgumentException( "Cannot set property '" + property.getName()
365 + "': feature has no property with that name." );
366 }
367 if ( index > oldProperties.length - 1 ) {
368 throw new IllegalArgumentException( "Cannot set property '" + property.getName() + "' with index " + index
369 + ": feature has only " + oldProperties.length
370 + " properties with that name." );
371 }
372 oldProperties[index].setValue( property.getValue() );
373 this.geometryPropertyValues = extractGeometryPropertyValues();
374 }
375
376 /**
377 * Adds the given property to the feature's properties. The position of the property is determined by the feature
378 * type. If the feature already has properties with this name, the new one is inserted directly behind them.
379 *
380 * @param property
381 * property to insert
382 */
383 public void addProperty( FeatureProperty property ) {
384
385 FeatureProperty[] newProperties;
386
387 if ( this.properties == null ) {
388 newProperties = new FeatureProperty[] { property };
389 } else {
390 newProperties = new FeatureProperty[this.properties.length + 1];
391 for ( int i = 0; i < this.properties.length; i++ ) {
392 newProperties[i] = this.properties[i];
393 }
394 // TODO insert at correct position
395 newProperties[this.properties.length] = property;
396 }
397 this.properties = newProperties;
398 this.propertyMap = buildPropertyMap();
399 this.geometryPropertyValues = extractGeometryPropertyValues();
400 }
401
402 /**
403 * Removes the properties with the given name.
404 *
405 * @param propertyName
406 * name of the (possibly multiple) property to remove
407 */
408 public void removeProperty( QualifiedName propertyName ) {
409
410 List<FeatureProperty> newProperties = new ArrayList<FeatureProperty>( this.properties.length );
411 for ( FeatureProperty property : this.properties ) {
412 if ( !property.getName().equals( propertyName ) ) {
413 newProperties.add( property );
414 }
415 }
416 this.properties = newProperties.toArray( new FeatureProperty[newProperties.size()] );
417 this.propertyMap = buildPropertyMap();
418 this.geometryPropertyValues = extractGeometryPropertyValues();
419 }
420
421 /**
422 * Replaces the given property with a new one.
423 *
424 * @param oldProperty
425 * property to be replaced
426 * @param newProperty
427 * new property
428 */
429 public void replaceProperty( FeatureProperty oldProperty, FeatureProperty newProperty ) {
430 for ( int i = 0; i < properties.length; i++ ) {
431 if ( properties[i] == oldProperty ) {
432 properties[i] = newProperty;
433 }
434 }
435 }
436
437 @Override
438 public Object clone()
439 throws CloneNotSupportedException {
440 FeatureProperty[] fp = new FeatureProperty[properties.length];
441 for ( int i = 0; i < fp.length; i++ ) {
442 if ( properties[i].getValue() instanceof DefaultFeatureCollection ) {
443 Object v = ( (DefaultFeatureCollection) properties[i].getValue() ).clone();
444 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), v );
445 } else if ( properties[i].getValue() instanceof DefaultFeature ) {
446 Object v = ( (DefaultFeature) properties[i].getValue() ).clone();
447 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), v );
448 } else {
449 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), properties[i].getValue() );
450 }
451 }
452 return FeatureFactory.createFeature( "UUID_" + UUID.randomUUID().toString(), featureType, fp );
453 }
454
455 /*
456 * (non-Javadoc)
457 *
458 * @see org.deegree.model.feature.Feature#cloneDeep()
459 */
460 public Feature cloneDeep()
461 throws CloneNotSupportedException {
462 FeatureProperty[] fp = new FeatureProperty[properties.length];
463 for ( int i = 0; i < fp.length; i++ ) {
464 if ( properties[i].getValue() instanceof DefaultFeatureCollection ) {
465 Object v = ( (DefaultFeatureCollection) properties[i].getValue() ).clone();
466 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), v );
467 } else if ( properties[i].getValue() instanceof DefaultFeature ) {
468 Object v = ( (DefaultFeature) properties[i].getValue() ).clone();
469 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), v );
470 }
471 if ( properties[i].getValue() instanceof Geometry ) {
472 Geometry geom = (Geometry) ( (GeometryImpl) properties[i].getValue() ).clone();
473 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), geom );
474 } else {
475 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), properties[i].getValue() );
476 }
477 }
478 return FeatureFactory.createFeature( "UUID_" + UUID.randomUUID().toString(), featureType, fp );
479 }
480
481 @Override
482 public String toString() {
483 String ret = getClass().getName();
484 /*
485 * ret = "\nid = " + getId() + "\n"; ret += "featureType = " + featureType + "\n"; ret += "geoProps = " +
486 * geometryPropertyValues + "\n"; ret += "properties = " + propertyMap + "\n";
487 */
488 return ret;
489 }
490 }