/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2012, 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.HashMap;
import java.util.List;
import java.util.Map;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.AttributeImpl;
import org.geotools.feature.FeatureBuilder;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureFactory;
import org.opengis.feature.Property;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.PropertyDescriptor;
/**
* The complex feature builder allows the construction of features by
* progressively appending their components and deferring the construction till
* you're ready.
*
* @author Adam Brown (Curtin University of Technology)
*
*/
public class ComplexFeatureBuilder extends FeatureBuilder<FeatureType, Feature> {
Map<Name, ArrayList<Property>> values = new HashMap<Name, ArrayList<Property>>();
AttributeDescriptor ad = null;
public ComplexFeatureBuilder(FeatureType featureType) {
this(featureType, CommonFactoryFinder.getFeatureFactory(null));
}
protected ComplexFeatureBuilder(FeatureType featureType,
FeatureFactory factory) {
super(featureType, factory);
}
public ComplexFeatureBuilder(AttributeDescriptor ad) {
this(ad, CommonFactoryFinder.getFeatureFactory(null));
}
protected ComplexFeatureBuilder(AttributeDescriptor ad,
FeatureFactory factory) {
super((FeatureType) ad.getType(), factory);
this.ad =ad;
}
/**
* Build and return the feature you've been constructing.
* If the id is null it will be assigned from FeatureBuilder.createDefaultFeatureId().
*/
@Override
public Feature buildFeature(String id) {
// Instantiate if null:
id = id == null ? FeatureBuilder.createDefaultFeatureId() : id;
// Validate the values against the featureType; we need to make sure
// that requirements are honoured:
for (PropertyDescriptor propertyDescriptor : super.featureType
.getDescriptors()) {
Name name = propertyDescriptor.getName();
// Create a List of Properties for this name if we don't already
// have one:
if (!values.containsKey(name)) {
values.put(name, new ArrayList<Property>());
}
// Get the List of Properties:
List<Property> properties = values.get(name);
// See if there's a mismatch between the number of properties and
// minOccurs value:
int minOccurs = propertyDescriptor.getMinOccurs();
int numberOfProperties = properties.size();
if (numberOfProperties < minOccurs) {
// If the value is nillable anyway then just default it to null:
if (propertyDescriptor.isNillable()
&& AttributeDescriptor.class
.isAssignableFrom(propertyDescriptor.getClass())) {
do {
Property nullProperty = new AttributeImpl(
propertyDescriptor.getType().getBinding()
.cast(null),
(AttributeDescriptor) propertyDescriptor, null);
properties.add(nullProperty);
} while (++numberOfProperties < minOccurs);
}
// NOTE: I was wondering if you could have another if-else here
// to try to apply default values if they're set..
// it seems like a good idea but the only problem is that
// they're only present on the AttributeDescriptors...
else {
throw new IllegalStateException(
String.format(
"Failed to build feature '%s'; its property '%s' requires at least %s occurrence(s) but number of occurrences was %s.",
featureType.getName(), name, minOccurs,
numberOfProperties));
}
}
}
// Merge the Map<String, ArrayList<Property>> into one collection of
// properties:
Collection<Property> properties = new ArrayList<Property>();
for (Name key : values.keySet()) {
properties.addAll(values.get(key));
}
this.values.clear();
if (ad != null) {
return factory.createFeature(properties, ad, id);
}
else {
return factory.createFeature(properties, featureType, id);
}
}
/**
* Append a property value to the complex feature under construction
* and associate it with the name specified.
* @param name
* The name of the property you wish to set.
* @param value
* The value of the property to append.
*/
public void append(Name name, Property value) {
PropertyDescriptor propertyDescriptor = featureType.getDescriptor(name);
// The 'name' must exist in the type, if not, throw an exception:
if (propertyDescriptor == null) {
throw new IllegalArgumentException(
String.format(
"The name '%s' is not a valid descriptor name for the type '%s'.",
name, this.featureType.getName()));
}
Class<?> expectedClass = propertyDescriptor.getType().getBinding();
if (value != null) {
Class<?> providedClass = value.getType().getBinding();
// Make sure that the provided class and the expected class match or
// that the expectedClass is a base class of the providedClass:
if (!providedClass.equals(expectedClass)
&& !expectedClass.isAssignableFrom(providedClass)) {
throw new IllegalArgumentException(
String.format(
"The value provided contains an object of '%s' but the method expects an object of '%s'.",
providedClass, expectedClass));
}
} else { // value == null
if (propertyDescriptor.isNillable()) {
value = (Property) expectedClass.cast(null);
} else {
// NOTE: This could possibly to changed to allow for processing
// remote xlinks.
value = (Property) expectedClass.cast(null);
}
}
// At this point the converted value has been set so we must persist it
// to the object's state:
ArrayList<Property> valueList;
if (values.containsKey(name)) {
valueList = values.get(name);
// Make sure that the list isn't already at capacity:
int maxOccurs = propertyDescriptor.getMaxOccurs();
if (valueList.size() == maxOccurs) {
throw new IndexOutOfBoundsException(
String.format(
"You can't add another object with the name of '%s' because you already have the maximum number (%s) allowed by the property descriptor.",
name, maxOccurs));
}
} else {
valueList = new ArrayList<Property>();
values.put(name, valueList);
}
valueList.add(value);
}
}