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    }