/* * 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.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.swing.text.html.HTMLDocument.Iterator; import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.Factory; import org.geotools.factory.FactoryRegistryException; import org.geotools.factory.GeoTools; import org.geotools.factory.Hints; import org.geotools.feature.type.FeatureTypeFactoryImpl; import org.opengis.feature.simple.SimpleFeatureType; /** * A schema builder, because FeatureTypes are meant to be immutable, this * object is mutable. * * <p> * The basic idea for usage is that you configure the builder to whatever state * is desired, setting properties and adding AttributeTypes. When the desired * state is acheived, the expected FeatureType can be retrieved by calling<br> * <code>getFeatureType()</code> * </p> * <p> * Repeated calls to getFeatureType will return the <i>same</i> FeatureType * given that no calls which modify the state of the factory are made. * </p> * * <p> * Here's an example of how to use this: * <code><pre> * FeatureTypeBuilder build = FeatureTypeFactory.newInstance(); * build.addType(...); * build.setName(...); * build.setNamespace(...); * FeatureType type = build.getFeatureType(); * </pre></code> * There are also a set of convenience methods for creation of FeatureTypes. * These are the various newFeatureType methods. * </p> * * </p> * * @author Ian Schneider * @source $URL$ * @version $Id$ */ public abstract class FeatureTypeBuilder extends FeatureTypes implements Factory { /** abstract base type for all feature types */ public final static FeatureType ABSTRACT_FEATURE_TYPE; static { FeatureType featureType = null; try { featureType = new DefaultFeatureType("Feature",new URI("http://www.opengis.net/gml"), null, null, null); } catch(Exception e ) { //shold not happen } ABSTRACT_FEATURE_TYPE = featureType; } /** If the base types have been initialized */ private static boolean initialized; /** The name to give the FeatureType to be created. */ private String name; /** The namespace to give the FeatureType to be created. */ private URI namespace; /** If something in the factory has changed. */ private boolean dirty = true; /** The type created. */ private FeatureType type = null; /** The current defaultGeometry of the FeatureType returned. */ private GeometryAttributeType defaultGeometry = null; /** If the type is abstract. */ private boolean abstractType = false; /** The types that this is derived from. */ private java.util.Set superTypes; /** * Implementation hints - since this is a builder all * hints are passed onto the FeatureType. */ Map hints; /** * An empty public constructor. Subclasses should not provide a * constructor. * @deprecated */ public FeatureTypeBuilder() { this( Collections.EMPTY_MAP ); } /** * An empty public constructor. Subclasses should not provide a * constructor. */ public FeatureTypeBuilder( Map hints) { this.hints = hints; } /** * Returns the implementation hints. The default implementation returns en empty map. * <p> * Since the building of a FeatureType involves the collaboration of may * Factory classes (that may be discovered over the course of the build process) * we are forced to indicate that *all* hints are used. * </p> * <p> * Strictly this is a Builder (not a factory) and has no need declair which * hints are used (as one can never *keep* this builder in a factory registery. * (It is stateful and cannot be used concurrently for example). */ public Map getImplementationHints() { return hints; } /** * Create a new FeatureTypeFactory with the given typeName. * * @param name The typeName of the feature to create. * * @return A new FeatureTypeFactory instance. * * @throws FactoryRegistryException If there exists a configuration error. */ public static FeatureTypeFactory newInstance(String typeName) throws FactoryRegistryException { // warning not sure if CommonFactoryFinder is going to cache the instance or not? // Hints hints = GeoTools.getDefaultHints(); if( hints == null ){ hints = new Hints( Hints.FEATURE_TYPE_FACTORY_NAME, typeName ); } else { hints.put( Hints.FEATURE_TYPE_FACTORY_NAME, typeName ); } hints.put( Hints.FEATURE_TYPE_FACTORY_NAME, typeName ); return new DefaultFeatureTypeFactory(); } /** * Import all of the AttributeTypes from the given FeatureType into this * factory. * * <p> * If strict is true, non-uniquely named AttributeTypes will throw an * exception. * </p> * * <p> * If strict is false, these will be silently ignored, but not added. * </p> * * <p> * No other information is imported. * </p> * * @param type The FeatureType to import from. * @param strict Enforce namespace restrictions. * * @throws IllegalArgumentException If strict is true and there are naming * problems. */ public void importType(FeatureType type, boolean strict) throws IllegalArgumentException { for (int i = 0, ii = type.getAttributeCount(); i < ii; i++) { try { addType(type.getAttributeType(i)); } catch (IllegalArgumentException iae) { if (strict) { throw iae; } } } } /** * Set the super types of this factory. The types will be copied into a * Set. * * @param types A Collection of types. */ public final void setSuperTypes(java.util.Collection types) { superTypes = new java.util.LinkedHashSet(types); } /** * Obtain the super types of this factory. Any user types will be appended * to the built in types of this factory. * * @return A Collection representing the super types of the FeatureType * this factory will create. */ public final java.util.Collection getSuperTypes() { Set supers = (superTypes == null) ? new HashSet() : superTypes; boolean add = true; for ( java.util.Iterator s = supers.iterator(); s.hasNext(); ) { FeatureType superType = (FeatureType) s.next(); if ( superType.isDescendedFrom(ABSTRACT_FEATURE_TYPE) ) { add = false; } } if ( add ) { supers.add(ABSTRACT_FEATURE_TYPE); } return supers; } /** * A convienence method for importing AttributeTypes, simply calls<br> * <code> importType(type,false) </code> * * @param type The type to import. */ public void importType(FeatureType type) { importType(type, false); } /** * Set the name of the FeatureType this factory will produce. * * @param name The new name. May be null. */ public void setName(String name) { dirty |= isDifferent(name, this.name); this.name = name; } /** * Get the current configuration of the name of this factory. * * @return The current name. May be null. */ public final String getName() { return name; } /** * Set the namespace of the FeatureType this factory will produce. * * @param namespace The new namespace. May be null. */ public void setNamespace(URI namespace) { dirty |= isDifferent(namespace, this.namespace); this.namespace = namespace; } /** * Get the current configuration of the namespace of this factory. * * @return The current namespace. May be null. */ public final URI getNamespace() { return namespace; } /** * Is this factory configured to be abstract? * * @return True if it is, false if it aint. */ public final boolean isAbstract() { return abstractType; } /** * Configure this factory to produce an abstract type. * * @param a True or false. */ public final void setAbstract(boolean a) { dirty = true; this.abstractType = a; } private boolean isDifferent(String s1, String s2) { if (s1 != null) { return !s1.equals(s2); } if (s2 != null) { return !s2.equals(s1); } return s1 != s2; } private boolean isDifferent(URI u1, URI u2) { if (u1 != null) { return !u1.equals(u2); } if (u2 != null) { return !u2.equals(u1); } return u1 != u2; } /** * Remove all the AttributeTypes in this factory. */ public final void removeAll() { int cnt = getAttributeCount(); for (int i = cnt; i > 0; i++) { removeType(i - 1); } } /** * Add an array of AttributeTypes to this factory. * * @param types The types or a null array. * * @throws NullPointerException If any of the types are null. * @throws IllegalArgumentException If there are naming problems. */ public final void addTypes(AttributeType[] types) throws NullPointerException, IllegalArgumentException { if (types == null) { return; } for (int i = 0; i < types.length; i++) { addType(types[i]); } } /** * A the given AttributeType to this factory. * * @param type The type to add. * * @throws NullPointerException If the type is null. * @throws IllegalArgumentException If another type exists with the same * name. */ public final void addType(AttributeType type) throws NullPointerException, IllegalArgumentException { if (type == null) { throw new NullPointerException("type"); } dirty = true; check(type); add(type); } /** * Remove the given type from this factory. * * @param type The type to remove. * * @throws NullPointerException If the type is null. */ public final void removeType(AttributeType type) throws NullPointerException { if (type == null) { throw new NullPointerException("type"); } dirty = true; AttributeType removed = remove(type); if (removed == defaultGeometry) { defaultGeometry = null; } } /** * Insert the given type at the index specified. * * @param idx The index to insert at. * @param type The AttributeType to insert. * * @throws NullPointerException If the type is null. * @throws IllegalArgumentException If the AttributeType is not allowed. * @throws ArrayIndexOutOfBoundsException If the index is out of range. */ public final void addType(int idx, AttributeType type) throws NullPointerException, IllegalArgumentException, ArrayIndexOutOfBoundsException { if (type == null) { throw new NullPointerException("type"); } dirty = true; check(type); add(idx, type); } /** * Remove the AttributeType at the given index. * * @param idx The index to remove at. * * @throws ArrayIndexOutOfBoundsException If the index is out of bounds. */ public final void removeType(int idx) throws ArrayIndexOutOfBoundsException { dirty = true; AttributeType removed = remove(idx); if (removed == defaultGeometry) { defaultGeometry = null; } } /** * Set the AttributeType at the given index. Overwrites the existing type. * * @param idx The index to use. * @param type The type to use. * * @throws IllegalArgumentException If the type is not good. * @throws NullPointerException if they type passed in is null * @throws ArrayIndexOutOfBoundsException if the index is out of bounds. */ public final void setType(int idx, AttributeType type) throws IllegalArgumentException, NullPointerException, ArrayIndexOutOfBoundsException { if (type == null) { throw new NullPointerException("type"); } dirty = true; check(type); AttributeType removed = set(idx, type); if (removed == defaultGeometry) { defaultGeometry = null; } } /** * Swap the AttributeTypes at the given locations. * * @param idx1 The index of the first. * @param idx2 The index of the second. * * @throws ArrayIndexOutOfBoundsException if either index is not in the * array bounds. */ public final void swap(int idx1, int idx2) throws ArrayIndexOutOfBoundsException { // implementation note: // we must rely on the subclass implementation, which, hopefully does // not do any checking. If we used setType, there is a name overlap. AttributeType tmp = get(idx1); set(idx1, get(idx2)); set(idx2, tmp); // must do this! dirty = true; } /** * Return the AttributeType currently used as the defaultGeometry property * for the FeatureType this factory will create. * * @return The AttributeType representing the defaultGeometry or null. */ public final GeometryAttributeType getDefaultGeometry() { return defaultGeometry; } /** * Sets the defaultGeometry of this factory. If the defaultGeometry * AttributeType does not exist as an AttributeType within this factory, * it is added. This will overwrite the existing defaultGeometry, yet not * remove it from the existing AttributeTypes. * * @param defaultGeometry The AttributeType to use as the defaultGeometry. * May be null. * * @throws IllegalArgumentException if the type is not a geometry. */ public final void setDefaultGeometry(GeometryAttributeType defaultGeometry) throws IllegalArgumentException { // check if Geometry if ((defaultGeometry != null) && !defaultGeometry.isGeometry()) { String mess = "Attempted to set a non-geometry type as " + "defaultGeometry: "; throw new IllegalArgumentException(mess + defaultGeometry); } dirty = true; // do this! this.defaultGeometry = defaultGeometry; // if the defaultGeometry hasn't been added, add it! if ((defaultGeometry != null) && !contains(defaultGeometry)) { addType(defaultGeometry); } } /** * Get a FeatureType which reflects the state of this factory. Any * modifications to the state of the factory (adding, removing, or * reordering any AttributeTypes or changing any other properties - * isNillable,name,etc.), will cause the factory to "retool" itself. If * the factory has not changed since a call to this method, the return * value will be the same FeatureType which the previous method returned. * Otherwise, a new FeatureType will be created. * * @return The featureType reflecting the current factory state. * * @throws SchemaException if name is null or blank */ public final FeatureType getFeatureType() throws SchemaException { // we're dirty, recreate the FeatureType if (dirty || (type == null)) { // no defaultGeometry assigned, search for one. if (defaultGeometry == null) { for (int i = 0, ii = getAttributeCount(); i < ii; i++) { if (get(i) instanceof GeometryAttributeType) { defaultGeometry = (GeometryAttributeType) get(i); break; } } } if ((name == null) || (name.trim().length() == 0)) { throw new SchemaException( "Cannot create FeatureType with null or blank name"); } type = createFeatureType(); // oops, the subclass messed up... if (type == null) { throw new NullPointerException(getClass().getName() + ".createFeatureType()"); } if (isAbstract() && !type.isAbstract()) { throw new RuntimeException( "FeatureTypeFactory poorly implemented, " + "expected abstract type, received " + type); } // not dirty anymore. dirty = false; } return type; } /** * Returns a string representation of this factory. * * @return The string representing this factory. */ public String toString() { String types = ""; for (int i = 0, ii = getAttributeCount(); i < ii; i++) { types += get(i); if (i < ii) { types += " , "; } } return "FeatureTypeFactory(" + getClass().getName() + ") [ " + types + " ]"; } /** * Check to see if this factory contains the given AttributeType. The * comparison is done by name. * * @param type The AttributeType to search for by name. * * @return <tt>true</tt> if a like-named AttributeType exists, * <tt>false</tt> otherwise. */ public final boolean contains(AttributeType type) { for (int i = 0, ii = getAttributeCount(); i < ii; i++) { if (get(i).getLocalName().equals(type.getLocalName())) { return true; } } return false; } /** * Checks to see if this factory already contains the type. * * @param type * * @throws IllegalArgumentException DOCUMENT ME! */ protected void check(AttributeType type) { if (contains(type)) { throw new IllegalArgumentException("Duplicate AttributeTypes " + type); } } protected void addBaseTypes(Set types) { // base class hook } /** * DOCUMENT ME! * */ protected abstract FeatureType createFeatureType() throws SchemaException; /** * DOCUMENT ME! * * @param type * * @throws IllegalArgumentException */ protected abstract void add(AttributeType type) throws IllegalArgumentException; /** * DOCUMENT ME! * * @param type * */ protected abstract AttributeType remove(AttributeType type); /** * DOCUMENT ME! * * @param idx * @param type * * @throws ArrayIndexOutOfBoundsException * @throws IllegalArgumentException */ protected abstract void add(int idx, AttributeType type) throws ArrayIndexOutOfBoundsException, IllegalArgumentException; /** * DOCUMENT ME! * * @param idx * * * @throws ArrayIndexOutOfBoundsException */ protected abstract AttributeType remove(int idx) throws ArrayIndexOutOfBoundsException; /** * DOCUMENT ME! * * @param idx * * * @throws ArrayIndexOutOfBoundsException */ public abstract AttributeType get(int idx) throws ArrayIndexOutOfBoundsException; /** * DOCUMENT ME! * * @param idx * @param type * * * @throws ArrayIndexOutOfBoundsException * @throws IllegalArgumentException */ protected abstract AttributeType set(int idx, AttributeType type) throws ArrayIndexOutOfBoundsException, IllegalArgumentException; /** * DOCUMENT ME! * */ public abstract int getAttributeCount(); }