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 }