/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2006-2011, 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.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.geotools.data.complex.ComplexFeatureConstants;
import org.geotools.data.complex.config.NonFeatureTypeProxy;
import org.geotools.feature.type.AttributeDescriptorImpl;
import org.geotools.feature.type.GeometryDescriptorImpl;
import org.opengis.feature.Association;
import org.opengis.feature.Attribute;
import org.opengis.feature.ComplexAttribute;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureFactory;
import org.opengis.feature.GeometryAttribute;
import org.opengis.feature.Property;
import org.opengis.feature.type.AssociationDescriptor;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.ComplexType;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* Builder for attributes.
*
* @author Justin Deoliveira (The Open Planning Project)
*
*
*
*
* @source $URL$
*/
public class AttributeBuilder {
private static final Logger LOGGER = org.geotools.util.logging.Logging
.getLogger(AttributeBuilder.class.getPackage().getName());
/**
* Factory used to create attributes
*/
FeatureFactory attributeFactory;
/**
* Namespace context.
*/
String namespace;
/**
* Type of complex attribute being built. This field is mutually exclusive with
* {@link #descriptor}
*/
AttributeType type;
/**
* Descriptor of complex attribute being built. This field is mutually exclusive with
* {@link #type}
*/
AttributeDescriptor descriptor;
/**
* Contained properties (associations + attributes)
*/
List properties;
/**
* The crs of the attribute.
*/
CoordinateReferenceSystem crs;
/**
* Default geometry of the feature.
*/
Object defaultGeometry;
public AttributeBuilder(FeatureFactory attributeFactory) {
this.attributeFactory = attributeFactory;
}
//
// Injection
//
// Used to inject dependencies we need during construction time.
//
/**
* Returns the underlying attribute factory.
*/
public FeatureFactory getFeatureFactory() {
return attributeFactory;
}
/**
* Sets the underlying attribute factory.
*/
public void setFeatureFactory(FeatureFactory attributeFactory) {
this.attributeFactory = attributeFactory;
}
//
// State
//
/**
* Initializes the builder to its initial state, the same state it is in directly after being
* instantiated.
*/
public void init() {
descriptor = null;
type = null;
properties = null;
crs = null;
defaultGeometry = null;
}
/**
* Initializes the state of the builder based on a previously built attribute.
* <p>
* This method is useful when copying another attribute.
* </p>
*/
public void init(Attribute attribute) {
init();
descriptor = attribute.getDescriptor();
type = attribute.getType();
if (attribute instanceof ComplexAttribute) {
ComplexAttribute complex = (ComplexAttribute) attribute;
Collection properties = (Collection) complex.getValue();
for (Iterator itr = properties.iterator(); itr.hasNext();) {
Property property = (Property) itr.next();
if (property instanceof Attribute) {
Attribute att = (Attribute) property;
add(att.getIdentifier() == null ? null : att.getIdentifier().toString(), att
.getValue(), att.getName());
} else if (property instanceof Association) {
Association assoc = (Association) property;
associate(assoc.getValue(), assoc.getName());
}
}
}
if (attribute instanceof Feature) {
Feature feature = (Feature) attribute;
if (feature.getDefaultGeometryProperty() != null) {
if (crs == null) {
crs = feature.getDefaultGeometryProperty().getType()
.getCoordinateReferenceSystem();
}
defaultGeometry = feature.getDefaultGeometryProperty().getValue();
}
}
}
/**
* This namespace will be used when constructing attribute names.
*/
public void setNamespaceURI(String namespace) {
this.namespace = namespace;
}
/**
* This namespace will be used when constructing attribute names.
*
* @return namespace will be used when constructing attribute names.
*/
public String getNamespaceURI() {
return namespace;
}
/**
* Sets the type of the attribute being built.
* <p>
* When building a complex attribute, this type is used a reference to obtain the types of
* contained attributes.
* </p>
*/
public void setType(AttributeType type) {
this.type = type;
this.descriptor = null;
}
/**
* Sets the descriptor of the attribute being built.
* <p>
* When building a complex attribute, this type is used a reference to obtain the types of
* contained attributes.
* </p>
*/
public void setDescriptor(AttributeDescriptor descriptor) {
this.descriptor = descriptor;
this.type = (AttributeType) descriptor.getType();
}
/**
* @return The type of the attribute being built.
*/
public AttributeType getType() {
return type;
}
// Feature specific methods
/**
* Sets the coordinate reference system of the built feature.
*/
public void setCRS(CoordinateReferenceSystem crs) {
this.crs = crs;
}
/**
* @return The coordinate reference system of the feature, or null if not set.
*/
public CoordinateReferenceSystem getCRS() {
return crs;
}
/**
* Sets the default geometry of the feature.
*/
public void setDefaultGeometry(Object defaultGeometry) {
this.defaultGeometry = defaultGeometry;
}
/**
* @return The default geometry of the feature.
*/
public Object getDefaultGeometry() {
return defaultGeometry;
}
//
// Complex attribute specific methods
//
/**
* Adds an attribute to the complex attribute being built. <br>
* <p>
* This method uses the result of {@link #getNamespaceURI()} to build a qualified attribute
* name.
* </p>
* <p>
* This method uses the type supplied in {@link #setType(AttributeType)} in order to determine
* the attribute type.
* </p>
*
* @param name
* The name of the attribute.
* @param value
* The value of the attribute.
*
*/
public Attribute add(Object value, String name) {
return add(null, value, name);
}
/**
* Adds an association to the complex attribute being built. <br>
* <p>
* This method uses the result of {@link #getNamespaceURI()} to build a qualified attribute
* name.
* </p>
* <p>
* This method uses the type supplied in {@link #setType(AttributeType)} in order to determine
* the association type.
* </p>
*
* @param value
* The value of the association, an attribute.
* @param name
* The name of the association.
*/
public void associate(Attribute value, String name) {
associate(value, name, namespace);
}
/**
* Adds an attribute to the complex attribute being built. <br>
* <p>
* This method uses the type supplied in {@link #setType(AttributeType)} in order to determine
* the attribute type.
* </p>
*
* @param value
* The value of the attribute.
* @param name
* The name of the attribute.
* @param namespaceURI
* The namespace of the attribute.
*/
public Attribute add(Object value, String name, String namespaceURI) {
return add(null, value, name, namespaceURI);
}
/**
* Adds an association to the complex attribute being built. <br>
* <p>
* This method uses the type supplied in {@link #setType(AttributeType)} in order to determine
* the association type.
* </p>
*
* @param value
* The value of the association, an attribute.
* @param name
* The name of the association.
* @param namespaceURI
* The namespace of the association
*/
public void associate(Attribute attribute, String name, String namespaceURI) {
associate(attribute, Types.typeName(namespaceURI, name));
}
/**
* Adds an attribute to the complex attribute being built overriding the type of the declared
* attribute descriptor by a subtype of it. <br>
* <p>
* This method uses the type supplied in {@link #setType(AttributeType)} in order to determine
* the attribute type.
* </p>
*
* @param id
* the attribtue id
* @param value
* The value of the attribute.
*
* @param name
* The name of the attribute.
* @param type
* the actual type of the attribute, which might be the same as the declared type
* for the given AttributeDescriptor or a derived type.
*
*/
public Attribute add(final String id, final Object value, final Name name,
final AttributeType type) {
// existence check
AttributeDescriptor descriptor = attributeDescriptor(name);
AttributeType declaredType = (AttributeType) descriptor.getType();
if (!declaredType.equals(type)) {
boolean argIsSubType = Types.isSuperType(type, declaredType);
if (!argIsSubType) {
/*
* commented out since we got community schemas where the required instance type is
* not a subtype of the declared one throw new
* IllegalArgumentException(type.getName() + " is not a subtype of " +
* declaredType.getName());
*/
LOGGER.fine("Adding attribute " + name + " of type " + type.getName()
+ " which is not a subtype of " + declaredType.getName());
}
int minOccurs = descriptor.getMinOccurs();
int maxOccurs = descriptor.getMaxOccurs();
boolean nillable = descriptor.isNillable();
// TODO: handle default value
Object defaultValue = null;
if (type instanceof GeometryType) {
descriptor = new GeometryDescriptorImpl((GeometryType) type, name, minOccurs,
maxOccurs, nillable, defaultValue);
} else {
descriptor = new AttributeDescriptorImpl(type, name, minOccurs, maxOccurs,
nillable, defaultValue);
}
}
Attribute attribute;
if (descriptor != null && descriptor.getType() instanceof NonFeatureTypeProxy) {
// we don't want a feature. NonFeatureTypeProxy is used to make non feature types
// a fake feature type, so it can be created as top level feature in app-schema
// mapping file. When created inside other features, it should be encoded as
// complex features though.
attribute = createComplexAttribute(value, null, descriptor, id);
} else {
attribute = create(value, null, descriptor, id);
}
properties().add(attribute);
return attribute;
}
/**
* Adds an attribute to the complex attribute being built. <br>
* <p>
* This method uses the type supplied in {@link #setType(AttributeType)} in order to determine
* the attribute type.
* </p>
*
* @param name
* The name of the attribute.
* @param value
* The value of the attribute.
*
*/
public Attribute add(Object value, Name name) {
return add(null, value, name);
}
/**
* Adds an association to the complex attribute being built. <br>
* <p>
* This method uses the type supplied in {@link #setType(AttributeType)} in order to determine
* the association type.
* </p>
*
* @param value
* The value of the association, an attribute.
* @param name
* The name of the association.
* @param namespaceURI
* The namespace of the association
*/
public void associate(Attribute value, Name name) {
AssociationDescriptor descriptor = associationDescriptor(name);
Association association = attributeFactory.createAssociation(value, descriptor);
properties().add(association);
}
/**
* Adds an attribute to the complex attribute being built. <br>
* <p>
* The result of {@link #getNamespaceURI()} to build a qualified attribute name.
* </p>
* <p>
* This method uses the type supplied in {@link #setType(AttributeType)} in order to determine
* the attribute type.
* </p>
*
* @param id
* The id of the attribute.
* @param name
* The name of the attribute.
* @param value
* The value of the attribute.
*/
public Attribute add(String id, Object value, String name) {
return add(id, value, name, namespace);
}
/**
* Adds an attribute to the complex attribute being built. <br>
* <p>
* This method uses the type supplied in {@link #setType(AttributeType)} in order to determine
* the attribute type.
* </p>
*
* @param id
* The id of the attribute.
* @param value
* The value of the attribute.
* @param name
* The name of the attribute.
* @param namespaceURI
* The namespace of the attribute.
*/
public Attribute add(String id, Object value, String name, String namespaceURI) {
return add(id, value, Types.typeName(namespaceURI, name));
}
/**
* Adds an attribute to the complex attribute being built. <br>
* <p>
* This method uses the type supplied in {@link #setType(AttributeType)} in order to determine
* the attribute type.
* </p>
*
* @param id
* The id of the attribute.
* @param name
* The name of the attribute.
* @param value
* The value of the attribute.
*
*/
public Attribute add(String id, Object value, Name name) {
AttributeDescriptor descriptor = attributeDescriptor(name);
Attribute attribute = create(value, null, descriptor, id);
properties().add(attribute);
return attribute;
}
/**
* Convenience accessor for properties list which does the null check.
*/
protected List properties() {
if (properties == null) {
properties = new ArrayList();
}
return properties;
}
protected AssociationDescriptor associationDescriptor(Name name) {
PropertyDescriptor descriptor = Types.descriptor((ComplexType) type, name);
if (descriptor == null) {
String msg = "Could not locate association: " + name + " in type: " + type.getName();
throw new IllegalArgumentException(msg);
}
if (!(descriptor instanceof AssociationDescriptor)) {
String msg = name + " references a non association";
throw new IllegalArgumentException(msg);
}
return (AssociationDescriptor) descriptor;
}
protected AttributeDescriptor attributeDescriptor(Name name) {
PropertyDescriptor descriptor = Types.findDescriptor((ComplexType) type, name);
if (descriptor == null) {
String msg = "Could not locate attribute: " + name + " in type: " + type.getName();
throw new IllegalArgumentException(msg);
}
if (!(descriptor instanceof AttributeDescriptor)) {
String msg = name + " references a non attribute";
throw new IllegalArgumentException(msg);
}
return (AttributeDescriptor) descriptor;
}
/**
* Factors out attribute creation code, needs to be called with either one of type or descriptor
* null.
*/
protected Attribute create(Object value, AttributeType type, AttributeDescriptor descriptor,
String id) {
if (descriptor != null) {
type = (AttributeType) descriptor.getType();
}
// if (type instanceof FeatureCollectionType) {
// attribute = descriptor != null ? attributeFactory.createFeatureCollection(
// (Collection) value, descriptor, id) : attributeFactory.createFeatureCollection(
// (Collection) value, (FeatureCollectionType) type, id);
// } else
if (type instanceof FeatureType) {
return descriptor != null ? attributeFactory.createFeature((Collection) value,
descriptor, id) : attributeFactory.createFeature((Collection) value,
(FeatureType) type, id);
} else if (type instanceof ComplexType) {
return createComplexAttribute((Collection) value, (ComplexType) type, descriptor, id);
} else if (type instanceof GeometryType) {
return attributeFactory.createGeometryAttribute(value, (GeometryDescriptor) descriptor,
id, getCRS());
} else {
return attributeFactory.createAttribute(value, descriptor, id);
}
}
/**
* Create complex attribute
*
* @param value
* @param type
* @param descriptor
* @param id
* @return
*/
public ComplexAttribute createComplexAttribute(Object value, ComplexType type,
AttributeDescriptor descriptor, String id) {
return descriptor != null ? attributeFactory.createComplexAttribute((Collection) value,
descriptor, id) : attributeFactory.createComplexAttribute((Collection) value, type,
id);
}
/**
* Builds the attribute.
* <p>
* The class of the attribute built is determined from its type set with
* {@link #setType(AttributeType)}.
* </p>
*
* @return The build attribute.
*/
public Attribute build() {
return build(null);
}
/**
* Builds the attribute.
* <p>
* The class of the attribute built is determined from its type set with
* {@link #setType(AttributeType)}.
* </p>
*
* @param id
* The id of the attribute, or null.
*
* @return The build attribute.
*/
public Attribute build(String id) {
Attribute built = create(properties(), type, descriptor, id);
// FIXME
// // if geometry, set the crs
// if (built instanceof GeometryAttribute) {
// ((GeometryAttribute) built).getDescriptor().setCRS(getCRS());
// }
// if feature, set crs and default geometry
if (built instanceof Feature) {
Feature feature = (Feature) built;
// FIXME feature.setCRS(getCRS());
if (defaultGeometry != null) {
for (Iterator itr = feature.getProperties().iterator(); itr.hasNext();) {
Attribute att = (Attribute) itr.next();
if (att instanceof GeometryAttribute) {
if (defaultGeometry.equals(att.getValue())) {
feature.setDefaultGeometryProperty((GeometryAttribute) att);
}
}
}
}
}
properties().clear();
return built;
}
/**
* Special case for any type. Skip validating existence in the schema, since anyType legally can
* be casted into anything.
*
* @param value
* the value to be set
* @param type
* the type of the value
* @param descriptor
* the attribute descriptor of anyType type
* @param id
* @return
*/
public Attribute addAnyTypeValue(Object value, AttributeType type,
AttributeDescriptor descriptor, String id) {
Attribute attribute = create(value, type, descriptor, id);
properties().add(attribute);
return attribute;
}
/**
* Create a complex attribute for XS.AnyType, since it's defined as a simple type. We need a
* complex attribute so we can set xlink:href in it.
*
* @param value
* @param descriptor
* @param id
* @return
*/
public Attribute addComplexAnyTypeAttribute(Object value, AttributeDescriptor descriptor,
String id) {
// need to create a complex attribute for any type, so we can have client properties
// for xlink:href and so we chain features etc.
Map<Object, Object> userData = descriptor.getUserData();
descriptor = new AttributeDescriptorImpl(ComplexFeatureConstants.ANYTYPE_TYPE, descriptor
.getName(), descriptor.getMinOccurs(), descriptor.getMaxOccurs(), descriptor
.isNillable(), descriptor.getDefaultValue());
descriptor.getUserData().putAll(userData);
return createComplexAttribute(value, ComplexFeatureConstants.ANYTYPE_TYPE, descriptor, id);
}
}