001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/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
053 import org.deegree.datatypes.QualifiedName;
054 import org.deegree.io.datastore.PropertyPathResolvingException;
055 import org.deegree.model.feature.schema.FeatureType;
056 import org.deegree.model.feature.schema.PropertyType;
057 import org.deegree.model.spatialschema.Geometry;
058 import org.deegree.model.spatialschema.GeometryFactory;
059 import org.deegree.ogcbase.CommonNamespaces;
060 import org.deegree.ogcbase.PropertyPath;
061 import org.deegree.ogcbase.PropertyPathStep;
062
063 /**
064 * Features are, according to the Abstract Specification, digital representations of real world
065 * entities. Feature Identity thus refers to mechanisms to identify such representations: not to
066 * identify the real world entities that are the subject of a representation. Thus two different
067 * representations of a real world entity (say the Mississippi River) will be two different features
068 * with distinct identities. Real world identification systems, such as title numbers, while
069 * possibly forming a sound basis for an implementation of a feature identity mechanism, are not of
070 * themselves such a mechanism.
071 *
072 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
073 * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider</a>
074 *
075 * @author last edited by: $Author: apoth $
076 *
077 * @version $Revision: 9343 $ $Date: 2007-12-27 14:30:32 +0100 (Do, 27 Dez 2007) $
078 */
079 public class DefaultFeature extends AbstractFeature implements Serializable {
080
081 private static final long serialVersionUID = -1636744791597960465L;
082
083 // key class: QualifiedName
084 // value class: FeatureProperty []
085 protected Map propertyMap;
086
087 protected FeatureProperty[] properties;
088
089 protected Object[] propertyValues;
090
091 protected Geometry[] geometryPropertyValues;
092
093 /**
094 * Creates a new instance of <code>DefaultFeature</code> from the given parameters.
095 *
096 * @param id
097 * @param featureType
098 * @param properties
099 * properties of the new feature, given in their intended order
100 */
101 protected DefaultFeature( String id, FeatureType featureType, FeatureProperty[] properties ) {
102 this( id, featureType, properties, null );
103 }
104
105 /**
106 * Creates a new instance of <code>DefaultFeature</code> from the given parameters.
107 *
108 * @param id
109 * @param featureType
110 * @param properties
111 * properties of the new feature, given in their intended order
112 * @param owner
113 */
114 protected DefaultFeature( String id, FeatureType featureType, FeatureProperty[] properties, FeatureProperty owner ) {
115 super( id, featureType, owner );
116 for ( int i = 0; i < properties.length; i++ ) {
117 FeatureProperty property = properties[i];
118 URI namespace = property.getName().getNamespace();
119 if ( ( namespace == null ) ) {
120 PropertyType propertyType = featureType.getProperty( property.getName() );
121 if ( propertyType == null ) {
122 throw new IllegalArgumentException( "Unknown property '" + property.getName()
123 + "' for feature with type '" + featureType.getName()
124 + "': the feature type has no such property." );
125 }
126 } else if ( ( !namespace.equals( CommonNamespaces.GMLNS ) ) ) {
127 PropertyType propertyType = featureType.getProperty( property.getName() );
128 if ( propertyType == null ) {
129 throw new IllegalArgumentException( "Unknown property '" + property.getName()
130 + "' for feature with type '" + featureType.getName()
131 + "': the feature type has no such property." );
132 }
133 }
134 }
135 this.properties = properties;
136 }
137
138 /**
139 * TODO type checks!
140 *
141 * @throws FeatureException
142 */
143 public void validate()
144 throws FeatureException {
145 if ( this.propertyMap == null ) {
146 this.propertyMap = buildPropertyMap();
147 }
148 PropertyType[] propertyTypes = featureType.getProperties();
149 for ( int i = 0; i < propertyTypes.length; i++ ) {
150 List propertyList = (List) this.propertyMap.get( propertyTypes[i].getName() );
151 if ( propertyList == null ) {
152 if ( propertyTypes[i].getMinOccurs() != 0 ) {
153 throw new FeatureException( "Feature is not a valid instance of type '" + featureType.getName()
154 + "', mandatory property '" + propertyTypes[i].getName()
155 + "' is missing." );
156 }
157 } else {
158 if ( propertyTypes[i].getMinOccurs() > propertyList.size() ) {
159 throw new FeatureException( "Feature is not a valid instance of type '" + featureType.getName()
160 + "', property '" + propertyTypes[i].getName() + "' has minOccurs="
161 + propertyTypes[i].getMinOccurs() + ", but is only present "
162 + propertyList.size() + " times." );
163 }
164 if ( propertyTypes[i].getMaxOccurs() != -1 && propertyTypes[i].getMaxOccurs() < propertyList.size() ) {
165 throw new FeatureException( "Feature is not a valid instance of type '" + featureType.getName()
166 + "', property '" + propertyTypes[i].getName() + "' has maxOccurs="
167 + propertyTypes[i].getMaxOccurs() + ", but is present "
168 + propertyList.size() + " times." );
169 }
170 }
171 }
172 }
173
174 /**
175 * Builds a map for efficient lookup of the feature's properties by name.
176 *
177 * @return key class: QualifiedName, value class: FeatureProperty []
178 */
179 private Map buildPropertyMap() {
180 Map propertyMap = new HashMap();
181 for ( int i = 0; i < this.properties.length; i++ ) {
182 List propertyList = (List) propertyMap.get( this.properties[i].getName() );
183 if ( propertyList == null ) {
184 propertyList = new ArrayList();
185 }
186 propertyList.add( properties[i] );
187 propertyMap.put( properties[i].getName(), propertyList );
188 }
189 Iterator propertyNameIter = propertyMap.keySet().iterator();
190 while ( propertyNameIter.hasNext() ) {
191 QualifiedName propertyName = (QualifiedName) propertyNameIter.next();
192 List propertyList = (List) propertyMap.get( propertyName );
193 propertyMap.put( propertyName, propertyList.toArray( new FeatureProperty[propertyList.size()] ) );
194 }
195 return propertyMap;
196 }
197
198 /**
199 * Builds a map for efficient lookup of the feature's properties by name.
200 *
201 * @return key class: QualifiedName, value class: FeatureProperty []
202 */
203 private Geometry[] extractGeometryPropertyValues() {
204 List geometryPropertiesList = new ArrayList();
205 for ( int i = 0; i < this.properties.length; i++ ) {
206 if ( this.properties[i].getValue() instanceof Geometry ) {
207 geometryPropertiesList.add( this.properties[i].getValue() );
208 } else if ( this.properties[i].getValue() instanceof Object[] ) {
209 Object[] objects = (Object[]) this.properties[i].getValue();
210 for ( int j = 0; j < objects.length; j++ ) {
211 if ( objects[j] instanceof Geometry ) {
212 geometryPropertiesList.add( objects[j] );
213 }
214 }
215
216 }
217 }
218 Geometry[] geometryPropertyValues = new Geometry[geometryPropertiesList.size()];
219 geometryPropertyValues = (Geometry[]) geometryPropertiesList.toArray( geometryPropertyValues );
220 return geometryPropertyValues;
221 }
222
223 /**
224 * Returns all properties of the feature in their original order.
225 *
226 * @return all properties of the feature
227 */
228 public FeatureProperty[] getProperties() {
229 return this.properties;
230 }
231
232 /**
233 * Returns the properties of the feature with the given name in their original order.
234 *
235 * @return the properties of the feature with the given name or null if the feature has no
236 * property with that name
237 */
238 public FeatureProperty[] getProperties( QualifiedName name ) {
239 if ( this.propertyMap == null ) {
240 this.propertyMap = buildPropertyMap();
241 }
242 FeatureProperty[] properties = (FeatureProperty[]) propertyMap.get( name );
243 return properties;
244 }
245
246 /**
247 * Returns the property of the feature identified by the given PropertyPath in their original
248 * order.
249 *
250 * NOTE: Current implementation does not handle multiple properties (on the path) or index
251 * addressing in the path.
252 *
253 * @see PropertyPath
254 * @return the properties of the feature identified by the given PropertyPath or null if the
255 * feature has no such properties
256 *
257 */
258 public FeatureProperty getDefaultProperty( PropertyPath path )
259 throws PropertyPathResolvingException {
260
261 Feature currentFeature = this;
262 FeatureProperty currentProperty = null;
263
264 // check if path begins with the name of the feature type
265 int firstPropIdx = 0;
266 if ( path.getStep( 0 ).getPropertyName().equals( getName() ) ) {
267 firstPropIdx = 1;
268 }
269
270 for ( int i = firstPropIdx; i < path.getSteps(); i += 2 ) {
271 QualifiedName propertyName = path.getStep( i ).getPropertyName();
272 currentProperty = currentFeature.getDefaultProperty( propertyName );
273 if ( i + 1 < path.getSteps() ) {
274 QualifiedName featureName = path.getStep( i + 1 ).getPropertyName();
275 Object value = currentProperty.getValue();
276 if ( !( value instanceof Feature ) ) {
277 String msg = "PropertyPath '" + path + "' cannot be matched to feature. Value of property '"
278 + propertyName + "' is not a feature, but the path does not stop there.";
279 throw new PropertyPathResolvingException( msg );
280 }
281 currentFeature = (Feature) value;
282 if ( !featureName.equals( currentFeature.getName() ) ) {
283 String msg = "PropertyPath '" + path + "' cannot be matched to feature. Property '" + propertyName
284 + "' contains a feature with name '" + currentFeature.getName()
285 + "', but requested was: '" + featureName + "'.";
286 throw new PropertyPathResolvingException( msg );
287 }
288 }
289 }
290 if ( currentProperty == null ) {
291 List<PropertyPathStep> list = path.getAllSteps();
292 currentProperty = new DefaultFeatureProperty( list.get( list.size() - 1 ).getPropertyName(), null );
293 }
294 return currentProperty;
295 }
296
297 /**
298 * Returns the first property of the feature with the given name.
299 *
300 * @return the first property of the feature with the given name or null if the feature has no
301 * such property
302 */
303 public FeatureProperty getDefaultProperty( QualifiedName name ) {
304 FeatureProperty[] properties = getProperties( name );
305 if ( properties != null ) {
306 return properties[0];
307 }
308 return new DefaultFeatureProperty( name, null );
309 }
310
311 /**
312 * Returns the properties of the feature at the submitted index of the feature type definition.
313 *
314 * @return the properties of the feature at the submitted index
315 * @deprecated
316 */
317 public FeatureProperty[] getProperties( int index ) {
318 QualifiedName s = featureType.getPropertyName( index );
319 return getProperties( s );
320 }
321
322 /**
323 * Returns the values of all geometry properties of the feature.
324 *
325 * @return the values of all geometry properties of the feature, or a zero-length array if the
326 * feature has no geometry properties
327 */
328 public Geometry[] getGeometryPropertyValues() {
329 if ( this.geometryPropertyValues == null ) {
330 this.geometryPropertyValues = extractGeometryPropertyValues();
331 }
332 return this.geometryPropertyValues;
333 }
334
335 /**
336 * Returns the value of the default geometry property of the feature. If the feature has no
337 * geometry property, this is a Point at the coordinates (0,0).
338 *
339 * @return default geometry or Point at (0,0) if feature has no geometry
340 */
341 public Geometry getDefaultGeometryPropertyValue() {
342 Geometry[] geometryValues = getGeometryPropertyValues();
343 if ( geometryValues.length < 1 ) {
344 return GeometryFactory.createPoint( 0, 0, null );
345 }
346 return geometryValues[0];
347 }
348
349 /**
350 * Sets the value for the given property. The index is needed to specify the occurences of the
351 * property that is to be replaced. Set to 0 for properties that may only occur once.
352 *
353 * @param property
354 * property name and the property's new value
355 * @param index
356 * position of the property that is to be replaced
357 */
358 public void setProperty( FeatureProperty property, int index ) {
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
375 * determined by the feature type. If the feature already has properties with this name, the new
376 * one is inserted directly behind them.
377 *
378 * @param property
379 * property to insert
380 */
381 public void addProperty( FeatureProperty property ) {
382
383 FeatureProperty[] newProperties;
384
385 if ( this.properties == null ) {
386 newProperties = new FeatureProperty[] { property };
387 } else {
388 newProperties = new FeatureProperty[this.properties.length + 1];
389 for ( int i = 0; i < this.properties.length; i++ ) {
390 newProperties[i] = this.properties[i];
391 }
392 // TODO insert at correct position
393 newProperties[this.properties.length] = property;
394 }
395 this.properties = newProperties;
396 this.propertyMap = buildPropertyMap();
397 this.geometryPropertyValues = extractGeometryPropertyValues();
398 }
399
400 /**
401 * Removes the properties with the given name.
402 *
403 * @param propertyName
404 * name of the (possibly multiple) property to remove
405 */
406 public void removeProperty( QualifiedName propertyName ) {
407
408 List<FeatureProperty> newProperties = new ArrayList<FeatureProperty>( this.properties.length );
409 for ( FeatureProperty property : this.properties ) {
410 if ( !property.getName().equals( propertyName ) ) {
411 newProperties.add( property );
412 }
413 }
414 this.properties = newProperties.toArray( new FeatureProperty[newProperties.size()] );
415 this.propertyMap = buildPropertyMap();
416 this.geometryPropertyValues = extractGeometryPropertyValues();
417 }
418
419 /**
420 * Replaces the given property with a new one.
421 *
422 * @param oldProperty
423 * property to be replaced
424 * @param newProperty
425 * new property
426 */
427 public void replaceProperty( FeatureProperty oldProperty, FeatureProperty newProperty ) {
428 for ( int i = 0; i < properties.length; i++ ) {
429 if ( properties[i] == oldProperty ) {
430 properties[i] = newProperty;
431 }
432 }
433 }
434
435 @Override
436 public String toString() {
437 String ret = getClass().getName();
438 /*
439 * ret = "\nid = " + getId() + "\n"; ret += "featureType = " + featureType + "\n"; ret +=
440 * "geoProps = " + geometryPropertyValues + "\n"; ret += "properties = " + propertyMap +
441 * "\n";
442 */
443 return ret;
444 }
445 }