/*
* 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.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.geotools.data.DataUtilities;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.NameImpl;
import org.geotools.feature.type.FeatureTypeFactoryImpl;
import org.geotools.filter.IllegalFilterException;
import org.geotools.filter.LengthFunction;
import org.geotools.resources.Classes;
import org.geotools.util.SimpleInternationalString;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.FeatureTypeFactory;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Expression;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.InternationalString;
import com.vividsolutions.jts.geom.Geometry;
/**
* Builder for attribute types and descriptors.
* <p>
* Building an attribute type:
* <pre>
* <code>
* //create the builder
* AttributeTypeBuilder builder = new AttributeTypeBuilder();
*
* //set type information
* builder.setName( "intType" ):
* builder.setBinding( Integer.class );
* builder.setNillable( false );
*
* //build the type
* AttributeType type = builder.buildType();
* </code>
* </pre>
* </p>
* <p>
* Building an attribute descriptor:
* <pre>
* <code>
* //create the builder
* AttributeTypeBuilder builder = new AttributeTypeBuilder();
*
* //set type information
* builder.setName( "intType" ):
* builder.setBinding( Integer.class );
* builder.setNillable( false );
*
* //set descriptor information
* builder.setMinOccurs(0);
* builder.setMaxOccurs(1);
* builder.setNillable(true);
*
* //build the descriptor
* AttributeDescriptor descriptor = builder.buildDescriptor("intProperty");
* </code>
* </pre>
* <p>
* This class maintains state and is not thread safe.
* </p>
*
* @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
*
*
* @source $URL$
*/
public class AttributeTypeBuilder {
/**
* factory
*/
protected FeatureTypeFactory factory;
//AttributeType
//
/**
* Local name used to name a descriptor; or combined with namespaceURI to name a type.
*/
protected String name;
/**
* Separator used to combine namespaceURI and name.
*/
private String separator = ":";
/**
* namespace used to distingish between otherwise identical type names.
*/
protected String namespaceURI;
/**
* abstract flag
*/
protected boolean isAbstract = false;
/**
* restrictions
*/
protected List<Filter> restrictions;
/**
* string description
*/
protected String description;
/**
* identifiable flag
*/
protected boolean isIdentifiable = false;
/**
* bound java class
*/
protected Class binding;
/**
* super type
*/
protected AttributeType superType;
/**
* default value
*/
protected Object defaultValue;
protected boolean isDefaultValueSet = false;
//GeometryType
//
protected CoordinateReferenceSystem crs;
protected boolean isCrsSet = false;
//AttributeDescriptor
//
/**
* Minimum number of occurrences allowed.
* See minOccurs() function for the default value
* based on nillable if not explicitly set.
*/
protected Integer minOccurs = null;
/**
* Maximum number of occurrences allowed.
* See maxOccurs() function for the default value (of 1).
*/
protected Integer maxOccurs = null;
/**
* True if value is allowed to be null.
* <p>
* Depending on this value minOccurs, maxOccurs and defaultValue()
* will return different results.
* <p>
* The default value is <code>true</code>.
*/
protected boolean isNillable = true;
/**
* If this value is set an additional restriction
* will be added based on the length function.
*/
protected Integer length = null;
/**
* User data for the attribute.
*/
protected Map userData = null;
/**
* filter factory
*/
protected FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);
/**
* Constructs the builder.
*
*/
public AttributeTypeBuilder() {
this( new FeatureTypeFactoryImpl() );
init();
}
/**
* Constructs the builder specifying the factory used to build attribute
* types.
*
*/
public AttributeTypeBuilder( FeatureTypeFactory factory ) {
this.factory = factory;
init();
}
/**
* Resets all internal state.
*/
protected void init() {
resetTypeState();
resetDescriptorState();
}
/**
* Resets all builder state used to build the attribute type.
* <p>
* This method is called automatically after {@link #buildType()} and
* {@link #buildGeometryType()}.
* </p>
*/
protected void resetTypeState() {
name = null;
namespaceURI = null;
isAbstract = false;
restrictions = null;
description = null;
isIdentifiable = false;
binding = null;
defaultValue = null;
superType = null;
crs = null;
length = null;
isCrsSet = false;
isDefaultValueSet = false;
}
protected void resetDescriptorState() {
minOccurs = null;
maxOccurs = null;
isNillable = true;
userData = new HashMap();
}
public AttributeTypeBuilder setFactory(FeatureTypeFactory factory) {
this.factory = factory;
return this;
}
/**
* Initializes builder state from another attribute type.
*/
public AttributeTypeBuilder init( AttributeType type ) {
name = type.getName().getLocalPart();
separator = type.getName().getSeparator();
namespaceURI = type.getName().getNamespaceURI();
isAbstract = type.isAbstract();
if ( type.getRestrictions() != null ) {
restrictions().addAll( type.getRestrictions() );
}
description = type.getDescription() != null ? type.getDescription().toString() : null;
isIdentifiable = type.isIdentified();
binding = type.getBinding();
superType = type.getSuper();
if ( type instanceof GeometryType ) {
crs = ((GeometryType)type).getCoordinateReferenceSystem();
}
return this;
}
/**
* Initializes builder state from another attribute descriptor.
*/
public void init( AttributeDescriptor descriptor ) {
init( descriptor.getType() );
minOccurs = descriptor.getMinOccurs();
maxOccurs = descriptor.getMaxOccurs();
isNillable = descriptor.isNillable();
}
// Type methods
//
public void setBinding(Class binding) {
this.binding = binding;
//JD: tidbit here
if ( !isDefaultValueSet ) {
//genereate a good default value based on class
try {
defaultValue = DataUtilities.defaultValue(binding);
}
catch( Exception e ) {
//do nothing
}
}
}
public void setName(String name) {
this.name = name;
}
public void setNamespaceURI(String namespaceURI) {
this.namespaceURI = namespaceURI;
}
public void setCRS(CoordinateReferenceSystem crs) {
this.crs = crs;
isCrsSet = true;
}
public boolean isCRSSet() {
return isCrsSet;
}
public void setDescription(String description) {
this.description = description;
}
public void setAbstract(boolean isAbstract) {
this.isAbstract = isAbstract;
}
public void setIdentifiable(boolean isIdentifiable) {
this.isIdentifiable = isIdentifiable;
}
public void setLength(int length) {
this.length = length;
}
public void addRestriction(Filter restriction) {
restrictions().add(restriction);
}
public void addUserData( Object key, Object value ) {
userData.put( key, value );
}
// Descriptor methods
//
public void setNillable(boolean isNillable) {
this.isNillable = isNillable;
}
public void setMaxOccurs(int maxOccurs) {
this.maxOccurs = maxOccurs;
}
public void setMinOccurs(int minOccurs) {
this.minOccurs = minOccurs;
}
public void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
isDefaultValueSet = true;
}
public AttributeTypeBuilder binding(Class binding) {
setBinding(binding);
return this;
}
public AttributeTypeBuilder name(String name) {
setName(name);
return this;
}
public AttributeTypeBuilder namespaceURI(String namespaceURI) {
setNamespaceURI(namespaceURI);
return this;
}
public AttributeTypeBuilder crs(CoordinateReferenceSystem crs) {
setCRS(crs);
return this;
}
public AttributeTypeBuilder description(String description) {
setDescription(description);
return this;
}
public AttributeTypeBuilder abstrct(boolean isAbstract) {
setAbstract(isAbstract);
return this;
}
public AttributeTypeBuilder identifiable(boolean isIdentifiable) {
setIdentifiable(isIdentifiable);
return this;
}
public AttributeTypeBuilder length( int length ) {
setLength(length);
return this;
}
public AttributeTypeBuilder restriction(Filter restriction) {
addRestriction(restriction);
return this;
}
// Descriptor methods
//
public AttributeTypeBuilder nillable(boolean isNillable) {
setNillable(isNillable);
return this;
}
public AttributeTypeBuilder maxOccurs(int maxOccurs) {
setMaxOccurs(maxOccurs);
return this;
}
public AttributeTypeBuilder minOccurs(int minOccurs) {
setMinOccurs(minOccurs);
return this;
}
public AttributeTypeBuilder defaultValue(Object defaultValue) {
setDefaultValue(defaultValue);
return this;
}
public AttributeTypeBuilder userData( Object key, Object value ) {
addUserData( key, value );
return this;
}
// construction methods
//
/**
* Builds the attribute type.
* <p>
* This method resets all state after the attribute is built.
* </p>
*/
public AttributeType buildType() {
if(length != null){
Filter lengthRestriction = lengthRestriction(length);
restrictions().add( lengthRestriction );
}
AttributeType type = factory.createAttributeType(
name(), binding, isIdentifiable, isAbstract,
restrictions(), superType, description());
resetTypeState();
return type;
}
protected String typeName(){
if( name == null ){
return Classes.getShortName( binding );
}
return name;
}
private InternationalString description() {
return description != null ? new SimpleInternationalString(description) : null;
}
/**
* Builds the geometry attribute type.
* <p>
* This method resets all state after the attribute is built.
* </p>
*/
public GeometryType buildGeometryType() {
GeometryType type = factory.createGeometryType(
name(), binding, crs, isIdentifiable, isAbstract,
restrictions(), superType, description());
resetTypeState();
return type;
}
/**
* Builds an attribute descriptor first building an attribute type from
* internal state.
* <p>
* If {@link #crs} has been set via {@link #setCRS(CoordinateReferenceSystem)}
* the internal attribute type will be built via {@link #buildGeometryType()},
* otherwise it will be built via {@link #buildType()}.
* </p>
* <p>
* This method calls through to {@link #buildDescriptor(String, AttributeType)}.
* </p>
* @param name The name of the descriptor.
*
* @see #buildDescriptor(String, AttributeType)
*/
public AttributeDescriptor buildDescriptor( String name ) {
setName(name);
if(binding == null)
throw new IllegalStateException("No binding has been provided for this attribute");
if ( crs != null || Geometry.class.isAssignableFrom(binding)) {
return buildDescriptor(name, buildGeometryType());
}
else {
return buildDescriptor(name, buildType());
}
}
/**
* Builds an attribute descriptor specifying its attribute type.
* <p>
* Internal state is reset after the descriptor is built.
* </p>
* @param name The name of the descriptor.
* @param type The type referenced by the descriptor.
*
*/
public AttributeDescriptor buildDescriptor(String name, AttributeType type) {
return buildDescriptor(new NameImpl(name), type );
}
/**
* Builds a geometry descriptor specifying its attribute type.
* <p>
* Internal state is reset after the descriptor is built.
* </p>
* @param name The name of the descriptor.
* @param type The geometry type referenced by the descriptor.
*
*/
public GeometryDescriptor buildDescriptor(String name, GeometryType type) {
return buildDescriptor( new NameImpl(name), type );
}
public AttributeDescriptor buildDescriptor(Name name, AttributeType type ) {
//build the descriptor
AttributeDescriptor descriptor = factory.createAttributeDescriptor(
type, name, minOccurs(), maxOccurs(), isNillable, defaultValue());
//set the user data
descriptor.getUserData().putAll( userData );
resetDescriptorState();
return descriptor;
}
public GeometryDescriptor buildDescriptor(Name name, GeometryType type ) {
GeometryDescriptor descriptor = factory.createGeometryDescriptor(
type, name, minOccurs(), maxOccurs(), isNillable, defaultValue());
// set the user data
descriptor.getUserData().putAll( userData );
resetDescriptorState();
return descriptor;
}
/**
* This is not actually right but we do it for backwards compatibility.
* @return minOccurs if set or a default based on isNillable.
*/
private int minOccurs(){
if( minOccurs == null ){
return isNillable ? 0 : 1;
}
return minOccurs;
}
/**
* This is not actually right but we do it for backwards compatibility.
* @return minOccurs if set or a default based on isNillable.
*/
private int maxOccurs(){
if( maxOccurs == null ){
return 1;
}
return maxOccurs;
}
private Name name(){
if( separator == null ){
return new NameImpl( namespaceURI, typeName() );
}
else {
return new NameImpl( namespaceURI, separator, typeName() );
}
}
private Object defaultValue(){
if( defaultValue == null && !isNillable && binding != null){
defaultValue = DataUtilities.defaultValue( binding );
}
return defaultValue;
}
// internal / subclass api
//
protected List<Filter> restrictions() {
if (restrictions == null ) {
restrictions = new ArrayList();
}
return restrictions;
}
/**
* Helper method to create a "length" filter.
*/
protected Filter lengthRestriction(int length ){
if ( length < 0 ) {
return null;
}
LengthFunction lengthFunction = (LengthFunction)ff.function("LengthFunction",
new Expression[]{ff.property(".")});
if( lengthFunction == null ) {
return null;
}
Filter cf = null;
try {
cf = ff.lessOrEqual(lengthFunction, ff.literal(length));
} catch (IllegalFilterException e) {
// TODO something
}
return cf == null ? Filter.EXCLUDE : cf;
}
}