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    }