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    }