/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.feature; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.geotools.feature.simple.SimpleFeatureTypeImpl; import org.geotools.resources.Utilities; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.util.InternationalString; /** * A basic implementation of FeatureType. * * @author Ian Schneider * @source $URL$ * @version $Id$ */ public class DefaultFeatureType extends SimpleFeatureTypeImpl implements FeatureType { /** The name of this FeatureType. */ //private final String typeName; /** The namespace to uniquely identify this FeatureType. */ //private final URI namespace; /** The array of types that this FeatureType can have as attributes. */ //private final AttributeType[] types; /** The FeatureTypes this is descended from. */ //private final FeatureType[] ancestors; /** The default geometry AttributeType. */ //private final GeometryAttributeType defaultGeom; private final int hashCode; /** The position of the default Geometry * Leave as package protected for use by DefaultFeature */ //final int defaultGeomIdx; /** An feature type with no attributes */ public static final FeatureType EMPTY = new DefaultFeatureType(); /** attname:string -> position:int */ //private final java.util.Map attLookup; private final static URI toURI( String namespace ) throws SchemaException { try { return new URI( namespace ); } catch (URISyntaxException badNamespace ) { throw new SchemaException( badNamespace ); } } public DefaultFeatureType(String typeName, String namespace, Collection types, Collection superTypes, GeometryAttributeType defaultGeom) throws SchemaException, NullPointerException { this( typeName, toURI(namespace ), types, superTypes, defaultGeom ); } public DefaultFeatureType(String typeName, URI namespace, Collection types, Collection superTypes, GeometryAttributeType defaultGeom) throws NullPointerException { this( namespace != null ? new NameImpl( namespace.toString(), typeName ) : new NameImpl( FeatureTypes.DEFAULT_NAMESPACE.toString(), typeName ), types, superTypes, defaultGeom ); } private static final <T> List<T> toList( Collection<T> collection ){ if( collection == null ){ return new ArrayList<T>(); } else { return new ArrayList<T>(collection); } } private static final FeatureType toFeatureType( Collection types ){ if( types == null || types.isEmpty() ){ return null; } if ( types.size() > 1 ) { throw new IllegalArgumentException("May only specify a single parent"); } return (FeatureType) types.iterator().next(); } /** * Constructs a new DefaultFeatureType. * * <p> * Attributes from the superTypes will be copied to the list of attributes * for this feature type. * * @param typeName The name to give this FeatureType. * @param namespace The namespace of the new FeatureType. * @param types The attributeTypes to use for validation. * @param superTypes The ancestors of this FeatureType. * @param defaultGeom The attributeType to set as the defaultGeometry. * * @throws SchemaException For problems making the FeatureType. * @throws NullPointerException If typeName is null. */ public DefaultFeatureType( org.opengis.feature.type.Name name, Collection types, Collection superTypes, GeometryAttributeType defaultGeom) throws NullPointerException { super( name, (List)types, defaultGeom, false, null, toFeatureType(superTypes), null ); if (name == null) { throw new NullPointerException("Name required"); } // this.typeName = typeName; // this.namespace = namespace == null ? FeatureTypes.DEFAULT_NAMESPACE : namespace; // this.ancestors = toFeatureTypes( superTypes ); // // Collection attributes = new java.util.ArrayList( types ); // for (int i = 0, ii = ancestors.length; i < ii; i++) { // FeatureType ancestor = ancestors[i]; // for (int j = 0, jj = ancestor.getAttributeCount(); j < jj; j++) { // attributes.add(ancestor.getAttributeType(j)); // } // } // if(attributes.size()!=0) // this.types = (AttributeType[]) attributes.toArray(new AttributeType[attributes.size()]); // else // this.types = new AttributeType[0]; // // this.defaultGeom = defaultGeom; //attLookup = new java.util.HashMap(this.types.length); //attLookup = new java.util.HashMap(types.size()); //for (int i = 0, ii = this.types.length; i < ii; i++) { // int i = 0; // for( Iterator t = types.iterator(); t.hasNext(); ) { // AttributeType type = (AttributeType) t.next(); // //attLookup.put(this.types[i].getLocalName(),new Integer(i)); // attLookup.put(type.getLocalName(),new Integer(i++)); // } //this.defaultGeomIdx = find(defaultGeom); hashCode = computeHash(); } public DefaultFeatureType( org.opengis.feature.type.Name name, List schema, GeometryDescriptor defaultGeometry, boolean isAbstract, List restrictions,org.opengis.feature.type.AttributeType superType, InternationalString description) { super(name, schema, defaultGeometry,isAbstract, restrictions,superType, description); hashCode = computeHash(); } /** * Builds an empty feature type, useful for testing * @throws SchemaException */ private DefaultFeatureType() { this("emptyFeatreType", FeatureTypes.DEFAULT_NAMESPACE, Collections.EMPTY_LIST, Collections.EMPTY_LIST, null ); // this.typeName = "emptyFeatureType"; // namespace = FeatureTypes.DEFAULT_NAMESPACE; // this.types = new AttributeType[0]; // this.ancestors = new FeatureType[0]; // this.defaultGeomIdx = -1; // this.defaultGeom = null; // hashCode = computeHash(); // attLookup = java.util.Collections.EMPTY_MAP; } /* * additional initialization shared among constructors. */ private void init() { } /** * Creates a new feature, with a generated unique featureID. This is less * than ideal, as a FeatureID should be persistant over time, generally * created by a datasource. This method is more for testing that doesn't * need featureID. * * @param attributes the array of attribute values * * @return The created feature with this as its feature type. * * @throws IllegalAttributeException if this FeatureType does not validate * the attributes. */ public Feature create(Object[] attributes) throws IllegalAttributeException { return create(attributes, null); } /** * Creates a new feature, with the proper featureID, using this * FeatureType. * * @param attributes the array of attribute values. * @param featureID the feature ID. * * @return the created feature. * * @throws IllegalAttributeException if this FeatureType does not validate * the attributes. */ public Feature create(Object[] attributes, String featureID) throws IllegalAttributeException { if ( attributes == null && getAttributeCount() != 0 ) { throw new IllegalAttributeException("attributes null"); } try { DefaultFeatureBuilder builder = new DefaultFeatureBuilder( this ); builder.add( attributes ); return (Feature) builder.buildFeature(featureID); } catch( Exception e ) { throw (IllegalAttributeException) new IllegalAttributeException("illegal attribute").initCause(e); } //return new DefaultFeature(this, attributes, featureID); } public Feature duplicate(Feature original) throws IllegalAttributeException{ if( original == null ) return null; FeatureType featureType = original.getFeatureType(); if (!featureType.equals(this)) { throw new IllegalAttributeException("Feature type " + featureType + " does not match " + this); } try { DefaultFeatureBuilder builder = new DefaultFeatureBuilder(original); return (Feature) builder.buildFeature(original.getID()); } catch( Exception e ) { throw (IllegalAttributeException) new IllegalAttributeException("illegal attribute").initCause(e); } // String id = original.getID(); // int numAtts = featureType.getAttributeCount(); // Object attributes[] = new Object[numAtts]; // for (int i = 0; i < numAtts; i++) { // AttributeType curAttType = getAttributeType(i); // attributes[i] = curAttType.duplicate(original.getAttribute(i)); // } // return featureType.create(attributes, id ); } /** * Gets the primary geometry AttributeType. If the FeatureType has more * one geometry it is up to the implementor to determine which geometry is * the default. If working with multiple geometries it is best to get the * attributeTypes and iterate through them, checking isGeometry on each. * This should just be used a convenience method when it is known that the * features are flat. * * @return The attribute type of the default geometry, which will contain * the position. */ public GeometryAttributeType getGeometryDescriptor() { // return defaultGeom; return (GeometryAttributeType) super.getGeometryDescriptor(); } /** * Gets the attributeType at this xPath, if the specified attributeType * does not exist then null is returned. * * @param xPath XPath pointer to attribute type. * * @return True if attribute exists. */ public AttributeType getAttributeType(String xPath) { return (AttributeType) getDescriptor(xPath); // AttributeType attType = null; // int idx = find(xPath); // if (idx >= 0) // attType = types[idx]; // return attType; } /** * Find the position of a given AttributeType. * * @param type The type to search for. * * @return -1 if not found, a zero-based index if found. */ public int find(AttributeType type) { if (type == null) return -1; int idx = find(type.getLocalName()); if (idx < 0 || !getAttributeDescriptors().get(idx).equals(type)) idx = -1; return idx; } /** * Find the position of an AttributeType which matches the given String. * @param attName the name to look for * @return -1 if not found, zero-based index otherwise */ public int find(String attName) { return indexOf(attName); // Integer idx = (Integer) attLookup.get(attName); // return idx == null ? -1 : idx.intValue(); } /** * Gets the attributeType at the specified index. * * @param position the position of the attribute to check. * * @return The attribute type at the specified position. */ public AttributeType getAttributeType(int position) { // return types[position]; return (AttributeType) getAttributeDescriptors().get(position); } public AttributeType[] getAttributeTypes() { // return (AttributeType[]) types.clone(); return (AttributeType[]) getAttributeDescriptors().toArray( new AttributeType[ getAttributeDescriptors().size()]); } /** * Gets the global schema namespace. * * @return Namespace of schema. */ public URI getNamespace() { // return namespace; try { return getName().getNamespaceURI() != null ? new URI( getName().getNamespaceURI() ) : null; } catch (URISyntaxException e) { //dont have to worry, would have thrown an exception in constructor return null; } } /** * Gets the type name for this schema. * * @return Namespace of schema. */ public String getTypeName() { // return typeName; return getName().getLocalPart(); } /** * This is only used twice in the whole geotools code base, and one of * those is for a test, so we're removing it from the interface. If * getAttributeType does not have the AttributeType it will just return * null. Gets the number of occurrences of this attribute. * * @param xPath XPath pointer to attribute type. * * @return Number of occurrences. */ public boolean hasAttributeType(String xPath) { return getAttributeType(xPath) != null; } /** * Returns the number of attributes at the first 'level' of the schema. * * @return the total number of first level attributes. */ public int getAttributeCount() { // return types.length; return getAttributeDescriptors().size(); } // public boolean equals(FeatureType other) { // if(other == this) // return true; // // if (other == null) { // return false; // } // // if ((typeName == null) && (other.getTypeName() != null)) { // return false; // } else if (!typeName.equals(other.getTypeName())) { // return false; // } // // if ((namespace == null) && (other.getNamespace() != null)) { // return false; // } else if (!namespace.equals(other.getNamespace())) { // return false; // } // // if (types.length != other.getAttributeCount()) { // return false; // } // // for (int i = 0, ii = types.length; i < ii; i++) { // if (!types[i].equals(other.getAttributeType(i))) { // return false; // } // } // // return true; // } private int computeHash() { // int hash = typeName.hashCode() ^ namespace.hashCode(); // for (int i = 0, ii = types.length; i < ii; i++) { // hash ^= types[i].hashCode(); // } // return hash; return super.hashCode(); } public int hashCode() { return hashCode; } // public String toString() { // String info = "name=" + typeName; // info += (" , namespace=" + namespace); // info += (" , abstract=" + isAbstract()); // // String types1 = "types=("; // // for (int i = 0, ii = this.types.length; i < ii; i++) { // types1 += this.types[i].toString(); // // if (i < ii) { // types1 += ","; // } // } // // types1 += ")"; // info += (" , " + types1); // // return "DefaultFeatureType [" + info + "]"; // } public boolean equals(Object other) { if (other instanceof FeatureType) return super.equals((FeatureType) other); return false; } /** * Obtain an array of this FeatureTypes ancestors. Implementors should * return a non-null array (may be of length 0). * * @return An array of ancestors. */ public FeatureType[] getAncestors() { // return ancestors; return null; } /** * Is this FeatureType an abstract type? * * @return true if abstract, false otherwise. */ public boolean isAbstract() { return false; } /** * A convenience method for calling<br> * <code> FeatureType f1; FeatureType f2; * f1.isDescendedFrom(f2.getNamespace(),f2.getName()); </code> * * @param type The type to compare to. * * @return true if descendant, false otherwise. */ public boolean isDescendedFrom(FeatureType type) { return isDescendedFrom(type.getNamespace(), type.getTypeName()); } /** * Test to determine whether this FeatureType is descended from the given * FeatureType. Think of this relationship likes the "extends" * relationship in java. * * @param nsURI The namespace URI to use. * @param typeName1 The typeName. * * @return true if descendant, false otherwise. * * @task HACK: if nsURI is null only typeName is tested. */ public boolean isDescendedFrom(URI nsURI, String typeName1) { FeatureType superType = (FeatureType) getSuper(); while( superType != null ) { if ( nsURI == null ) { //dont match on namespace if ( Utilities.equals(superType.getTypeName(), typeName1) ) { return true; } } else { if ( Utilities.equals(superType.getNamespace(),nsURI) && Utilities.equals(superType.getTypeName(), typeName1)) { return true; } } superType = (FeatureType) superType.getSuper(); } //one more effort, if no return false; } static final class Abstract extends DefaultFeatureType { public Abstract(String typeName, URI namespace, Collection types, Collection superTypes, GeometryAttributeType defaultGeom) throws SchemaException { super(typeName, namespace, types, superTypes, defaultGeom); Iterator st = superTypes.iterator(); while (st.hasNext()) { FeatureType ft = (FeatureType) st.next(); //JD: removing this check, as its not the case in xml //if (!ft.isAbstract()) { // throw new SchemaException( // "Abstract type cannot descend from no abstract type : " // + ft); //} } } public final boolean isAbstract() { return true; } public Feature create(Object[] atts) throws IllegalAttributeException { throw new UnsupportedOperationException("Abstract Type"); } public Feature create(Object[] atts, String id) throws IllegalAttributeException { throw new UnsupportedOperationException("Abstract Type"); } } }