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 }