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    }