001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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: 29072 $ $Date: 2011-01-06 10:35:49 +0100 (Do, 06 Jan 2011) $ 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 ret = ""; 485 for ( int i = 0; i < properties.length; i++ ) { 486 if ( properties[i].getValue() instanceof DefaultFeatureCollection ) { 487 ret += ( " " + properties[i].getName() + ": "); 488 ret += "\n"; 489 ret += ( " " +(DefaultFeatureCollection) properties[i].getValue() ).toString(); 490 ret += "\n"; 491 } else if ( properties[i].getValue() instanceof DefaultFeature ) { 492 ret += ( " " +properties[i].getName() + ": "); 493 ret += "\n"; 494 ret += ( " " +(DefaultFeature) properties[i].getValue() ).toString(); 495 ret += "\n"; 496 } else if ( properties[i].getValue() instanceof Geometry ) { 497 ret += properties[i].getName(); 498 ret += "\n"; 499 } else { 500 String o = "null"; 501 if ( properties[i].getValue() != null ) { 502 o = properties[i].getValue().toString(); 503 } 504 ret += (properties[i].getName() + " = " + o ); 505 ret += "\n"; 506 } 507 } 508 return ret; 509 } 510 }