001 //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/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: 29966 $ $Date: 2011-03-09 15:19:04 +0100 (Wed, 09 Mar 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 /** 084 * Creates a new instance of <code>DefaultFeature</code> from the given parameters. 085 * 086 * @param id 087 * @param featureType 088 * @param properties 089 * properties of the new feature, given in their intended order 090 */ 091 protected DefaultFeature( String id, FeatureType featureType, FeatureProperty[] properties ) { 092 this( id, featureType, properties, null ); 093 } 094 095 /** 096 * Creates a new instance of <code>DefaultFeature</code> from the given parameters. 097 * 098 * @param id 099 * @param featureType 100 * @param properties 101 * properties of the new feature, given in their intended order 102 * @param owner 103 */ 104 protected DefaultFeature( String id, FeatureType featureType, FeatureProperty[] properties, FeatureProperty owner ) { 105 super( id, featureType, owner ); 106 for ( int i = 0; i < properties.length; i++ ) { 107 FeatureProperty property = properties[i]; 108 URI namespace = property.getName().getNamespace(); 109 if ( ( namespace == null ) ) { 110 PropertyType propertyType = featureType.getProperty( property.getName() ); 111 if ( propertyType == null ) { 112 throw new IllegalArgumentException( "Unknown property '" + property.getName() 113 + "' for feature with type '" + featureType.getName() 114 + "': the feature type has no such property." ); 115 } 116 } else if ( ( !namespace.equals( CommonNamespaces.GMLNS ) ) ) { 117 PropertyType propertyType = featureType.getProperty( property.getName() ); 118 if ( propertyType == null ) { 119 throw new IllegalArgumentException( "Unknown property '" + property.getName() 120 + "' for feature with type '" + featureType.getName() 121 + "': the feature type has no such property." ); 122 } 123 } 124 } 125 this.properties = properties; 126 } 127 128 /** 129 * TODO type checks! 130 * 131 * @throws FeatureException 132 */ 133 @SuppressWarnings("unchecked") 134 public void validate() 135 throws FeatureException { 136 if ( this.propertyMap == null ) { 137 this.propertyMap = buildPropertyMap(); 138 } 139 PropertyType[] propertyTypes = featureType.getProperties(); 140 for ( int i = 0; i < propertyTypes.length; i++ ) { 141 List<Object> propertyList = (List<Object>) this.propertyMap.get( propertyTypes[i].getName() ); 142 if ( propertyList == null ) { 143 if ( propertyTypes[i].getMinOccurs() != 0 ) { 144 throw new FeatureException( "Feature is not a valid instance of type '" + featureType.getName() 145 + "', mandatory property '" + propertyTypes[i].getName() 146 + "' is missing." ); 147 } 148 } else { 149 if ( propertyTypes[i].getMinOccurs() > propertyList.size() ) { 150 throw new FeatureException( "Feature is not a valid instance of type '" + featureType.getName() 151 + "', property '" + propertyTypes[i].getName() + "' has minOccurs=" 152 + propertyTypes[i].getMinOccurs() + ", but is only present " 153 + propertyList.size() + " times." ); 154 } 155 if ( propertyTypes[i].getMaxOccurs() != -1 && propertyTypes[i].getMaxOccurs() < propertyList.size() ) { 156 throw new FeatureException( "Feature is not a valid instance of type '" + featureType.getName() 157 + "', property '" + propertyTypes[i].getName() + "' has maxOccurs=" 158 + propertyTypes[i].getMaxOccurs() + ", but is present " 159 + propertyList.size() + " times." ); 160 } 161 } 162 } 163 } 164 165 /** 166 * Builds a map for efficient lookup of the feature's properties by name. 167 * 168 * @return key class: QualifiedName, value class: FeatureProperty [] 169 */ 170 @SuppressWarnings("unchecked") 171 private Map<QualifiedName, Object> buildPropertyMap() { 172 Map<QualifiedName, Object> propertyMap = new HashMap<QualifiedName, Object>(); 173 for ( int i = 0; i < this.properties.length; i++ ) { 174 List<Object> propertyList = (List<Object>) propertyMap.get( this.properties[i].getName() ); 175 if ( propertyList == null ) { 176 propertyList = new ArrayList<Object>(); 177 } 178 propertyList.add( properties[i] ); 179 propertyMap.put( properties[i].getName(), propertyList ); 180 } 181 Iterator<QualifiedName> propertyNameIter = propertyMap.keySet().iterator(); 182 while ( propertyNameIter.hasNext() ) { 183 QualifiedName propertyName = (QualifiedName) propertyNameIter.next(); 184 List<Object> propertyList = (List<Object>) propertyMap.get( propertyName ); 185 propertyMap.put( propertyName, propertyList.toArray( new FeatureProperty[propertyList.size()] ) ); 186 } 187 return propertyMap; 188 } 189 190 /** 191 * Builds a map for efficient lookup of the feature's properties by name. 192 * 193 * @return key class: QualifiedName, value class: FeatureProperty [] 194 */ 195 private Geometry[] extractGeometryPropertyValues() { 196 List<Object> geometryPropertiesList = new ArrayList<Object>(); 197 for ( int i = 0; i < this.properties.length; i++ ) { 198 if ( this.properties[i].getValue() instanceof Geometry ) { 199 geometryPropertiesList.add( this.properties[i].getValue() ); 200 } else if ( this.properties[i].getValue() instanceof Object[] ) { 201 Object[] objects = (Object[]) this.properties[i].getValue(); 202 for ( int j = 0; j < objects.length; j++ ) { 203 if ( objects[j] instanceof Geometry ) { 204 geometryPropertiesList.add( objects[j] ); 205 } 206 } 207 208 } 209 } 210 Geometry[] geometryPropertyValues = new Geometry[geometryPropertiesList.size()]; 211 geometryPropertyValues = (Geometry[]) geometryPropertiesList.toArray( geometryPropertyValues ); 212 return geometryPropertyValues; 213 } 214 215 /** 216 * Returns all properties of the feature in their original order. 217 * 218 * @return all properties of the feature 219 */ 220 public FeatureProperty[] getProperties() { 221 return this.properties; 222 } 223 224 /** 225 * Returns the properties of the feature with the given name in their original order. 226 * 227 * @return the properties of the feature with the given name or null if the feature has no property with that name 228 */ 229 public FeatureProperty[] getProperties( QualifiedName name ) { 230 if ( this.propertyMap == null ) { 231 this.propertyMap = buildPropertyMap(); 232 } 233 FeatureProperty[] properties = (FeatureProperty[]) propertyMap.get( name ); 234 return properties; 235 } 236 237 /** 238 * Returns the property of the feature identified by the given PropertyPath in their original order. 239 * 240 * NOTE: Current implementation does not handle multiple properties (on the path) or index addressing in the path. 241 * 242 * @see PropertyPath 243 * @return the properties of the feature identified by the given PropertyPath or null if the feature has no such 244 * properties 245 * 246 */ 247 public FeatureProperty getDefaultProperty( PropertyPath path ) 248 throws PropertyPathResolvingException { 249 250 Feature currentFeature = this; 251 FeatureProperty currentProperty = null; 252 253 // check if path begins with the name of the feature type 254 int firstPropIdx = 0; 255 if ( path.getStep( 0 ).getPropertyName().equals( getName() ) ) { 256 firstPropIdx = 1; 257 } 258 259 for ( int i = firstPropIdx; i < path.getSteps(); i += 2 ) { 260 QualifiedName propertyName = path.getStep( i ).getPropertyName(); 261 currentProperty = currentFeature.getDefaultProperty( propertyName ); 262 if ( currentProperty == null ) { 263 return null; 264 } 265 if ( i + 1 < path.getSteps() ) { 266 QualifiedName featureName = path.getStep( i + 1 ).getPropertyName(); 267 Object value = currentProperty.getValue(); 268 if ( !( value instanceof Feature ) ) { 269 String msg = "PropertyPath '" + path + "' cannot be matched to feature. Value of property '" 270 + propertyName + "' is not a feature, but the path does not stop there."; 271 throw new PropertyPathResolvingException( msg ); 272 } 273 currentFeature = (Feature) value; 274 if ( !featureName.equals( currentFeature.getName() ) ) { 275 String msg = "PropertyPath '" + path + "' cannot be matched to feature. Property '" + propertyName 276 + "' contains a feature with name '" + currentFeature.getName() 277 + "', but requested was: '" + featureName + "'."; 278 throw new PropertyPathResolvingException( msg ); 279 } 280 } 281 } 282 return currentProperty; 283 } 284 285 /** 286 * Returns the first property of the feature with the given name. 287 * 288 * @return the first property of the feature with the given name or null if the feature has no such property 289 */ 290 public FeatureProperty getDefaultProperty( QualifiedName name ) { 291 FeatureProperty[] properties = getProperties( name ); 292 if ( properties != null ) { 293 return properties[0]; 294 } 295 return null; 296 } 297 298 /** 299 * Returns the values of all geometry properties of the feature. 300 * 301 * @return the values of all geometry properties of the feature, or a zero-length array if the feature has no 302 * geometry properties 303 */ 304 public Geometry[] getGeometryPropertyValues() { 305 return extractGeometryPropertyValues(); 306 } 307 308 /** 309 * Returns the value of the default geometry property of the feature. If the feature has no geometry property, this 310 * is a Point at the coordinates (0,0). 311 * 312 * @return default geometry or Point at (0,0) if feature has no geometry 313 */ 314 public Geometry getDefaultGeometryPropertyValue() { 315 Geometry[] geometryValues = getGeometryPropertyValues(); 316 if ( geometryValues.length < 1 ) { 317 return GeometryFactory.createPoint( 0, 0, null ); 318 } 319 return geometryValues[0]; 320 } 321 322 /** 323 * Sets the value for the given property. The index is needed to specify the occurences of the property that is to 324 * be replaced. Set to 0 for properties that may only occur once. 325 * 326 * @param property 327 * property name and the property's new value 328 * @param index 329 * position of the property that is to be replaced 330 */ 331 public void setProperty( FeatureProperty property, int index ) { 332 333 if ( "boundedBy".equals( ( property.getName().getLocalName() ) ) ) { 334 if ( property.getValue() instanceof Envelope ) { 335 envelope = (Envelope) property.getValue(); 336 envelopeCalculated = true; 337 } 338 return; 339 } 340 341 FeatureProperty[] oldProperties = getProperties( property.getName() ); 342 if ( oldProperties == null ) { 343 throw new IllegalArgumentException( "Cannot set property '" + property.getName() 344 + "': feature has no property with that name." ); 345 } 346 if ( index > oldProperties.length - 1 ) { 347 throw new IllegalArgumentException( "Cannot set property '" + property.getName() + "' with index " + index 348 + ": feature has only " + oldProperties.length 349 + " properties with that name." ); 350 } 351 oldProperties[index].setValue( property.getValue() ); 352 } 353 354 /** 355 * Adds the given property to the feature's properties. The position of the property is determined by the feature 356 * type. If the feature already has properties with this name, the new one is inserted directly behind them. 357 * 358 * @param property 359 * property to insert 360 */ 361 public void addProperty( FeatureProperty property ) { 362 363 FeatureProperty[] newProperties; 364 365 if ( this.properties == null ) { 366 newProperties = new FeatureProperty[] { property }; 367 } else { 368 newProperties = new FeatureProperty[this.properties.length + 1]; 369 for ( int i = 0; i < this.properties.length; i++ ) { 370 newProperties[i] = this.properties[i]; 371 } 372 // TODO insert at correct position 373 newProperties[this.properties.length] = property; 374 } 375 this.properties = newProperties; 376 this.propertyMap = buildPropertyMap(); 377 } 378 379 /** 380 * Removes the properties with the given name. 381 * 382 * @param propertyName 383 * name of the (possibly multiple) property to remove 384 */ 385 public void removeProperty( QualifiedName propertyName ) { 386 387 List<FeatureProperty> newProperties = new ArrayList<FeatureProperty>( this.properties.length ); 388 for ( FeatureProperty property : this.properties ) { 389 if ( !property.getName().equals( propertyName ) ) { 390 newProperties.add( property ); 391 } 392 } 393 this.properties = newProperties.toArray( new FeatureProperty[newProperties.size()] ); 394 this.propertyMap = buildPropertyMap(); 395 } 396 397 /** 398 * Replaces the given property with a new one. 399 * 400 * @param oldProperty 401 * property to be replaced 402 * @param newProperty 403 * new property 404 */ 405 public void replaceProperty( FeatureProperty oldProperty, FeatureProperty newProperty ) { 406 for ( int i = 0; i < properties.length; i++ ) { 407 if ( properties[i] == oldProperty ) { 408 properties[i] = newProperty; 409 } 410 } 411 } 412 413 @Override 414 public Object clone() 415 throws CloneNotSupportedException { 416 FeatureProperty[] fp = new FeatureProperty[properties.length]; 417 for ( int i = 0; i < fp.length; i++ ) { 418 if ( properties[i].getValue() instanceof DefaultFeatureCollection ) { 419 Object v = ( (DefaultFeatureCollection) properties[i].getValue() ).clone(); 420 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), v ); 421 } else if ( properties[i].getValue() instanceof DefaultFeature ) { 422 Object v = ( (DefaultFeature) properties[i].getValue() ).clone(); 423 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), v ); 424 } else { 425 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), properties[i].getValue() ); 426 } 427 } 428 return FeatureFactory.createFeature( "UUID_" + UUID.randomUUID().toString(), featureType, fp ); 429 } 430 431 /* 432 * (non-Javadoc) 433 * 434 * @see org.deegree.model.feature.Feature#cloneDeep() 435 */ 436 public Feature cloneDeep() 437 throws CloneNotSupportedException { 438 FeatureProperty[] fp = new FeatureProperty[properties.length]; 439 for ( int i = 0; i < fp.length; i++ ) { 440 if ( properties[i].getValue() instanceof DefaultFeatureCollection ) { 441 Object v = ( (DefaultFeatureCollection) properties[i].getValue() ).clone(); 442 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), v ); 443 } else if ( properties[i].getValue() instanceof DefaultFeature ) { 444 Object v = ( (DefaultFeature) properties[i].getValue() ).clone(); 445 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), v ); 446 } 447 if ( properties[i].getValue() instanceof Geometry ) { 448 Geometry geom = (Geometry) ( (GeometryImpl) properties[i].getValue() ).clone(); 449 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), geom ); 450 } else { 451 fp[i] = FeatureFactory.createFeatureProperty( properties[i].getName(), properties[i].getValue() ); 452 } 453 } 454 return FeatureFactory.createFeature( "UUID_" + UUID.randomUUID().toString(), featureType, fp ); 455 } 456 457 @Override 458 public String toString() { 459 String ret = getClass().getName(); 460 ret = ""; 461 for ( int i = 0; i < properties.length; i++ ) { 462 if ( properties[i].getValue() instanceof DefaultFeatureCollection ) { 463 ret += ( " " + properties[i].getName() + ": "); 464 ret += "\n"; 465 ret += ( " " +(DefaultFeatureCollection) properties[i].getValue() ).toString(); 466 ret += "\n"; 467 } else if ( properties[i].getValue() instanceof DefaultFeature ) { 468 ret += ( " " +properties[i].getName() + ": "); 469 ret += "\n"; 470 ret += ( " " +(DefaultFeature) properties[i].getValue() ).toString(); 471 ret += "\n"; 472 } else if ( properties[i].getValue() instanceof Geometry ) { 473 ret += properties[i].getName(); 474 ret += "\n"; 475 } else { 476 String o = "null"; 477 if ( properties[i].getValue() != null ) { 478 o = properties[i].getValue().toString(); 479 } 480 ret += (properties[i].getName() + " = " + o ); 481 ret += "\n"; 482 } 483 } 484 return ret; 485 } 486 }