/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-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.type;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import org.geotools.feature.AttributeType;
import org.geotools.feature.DefaultAttributeType;
import org.geotools.feature.GeometryAttributeType;
import org.geotools.feature.IllegalAttributeException;
import org.geotools.feature.NameImpl;
import org.opengis.feature.type.GeometryType;
import org.opengis.filter.Filter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import java.util.Arrays;
import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction;
/**
* This represents a Choice of AttributeTypes. That means, an Attribute of this
* type may be one of any of this AttributeType's children. This attribute is
* not valid for Simple Features, and maps to the Choice construct in GML.
*
* <p>
* Another way to think about the ChoiceAttributeType is as a Union
* construction from C - it can store a number of different types of value,
* but it only stores the one value. The parse and validate methods try out
* each of the choices to see if one of them might work, since all are valid.
* The order that the child attributeTypes (the choices you can use) are
* specified is important, because some objects can parse and validate
* against several types. The first choice that returns true is the one that
* will
* </p>
*
* @author dzwiers
* @author Chris Holmes, TOPP
* @source $URL$
*
* @deprecated Will be removed in geotools 2.6.
*/
public class ChoiceAttributeType extends AttributeDescriptorImpl implements AttributeType {
private final AttributeType[] children;
/**
* DOCUMENT ME!
*
* @param copy
*/
public ChoiceAttributeType(ChoiceAttributeType copy) {
super( copy.getType(), copy.getName(), copy.getMinOccurs(), copy.getMaxOccurs(), copy.isNillable(), copy.getDefaultValue() );
this.children = copyChildren(copy.getAttributeTypes());
}
// The field for 'Class type' should be added when GT has moved to java 1.5
public ChoiceAttributeType(String name, int min, int max,
AttributeType[] children, Filter restriction) {
super(DefaultAttributeType.createAttributeType(name, Object.class, restriction),
new NameImpl(name),min,max,calculateNillableStatic(children),null);
//ensure immutable.
this.children = copyChildren(children);
}
public ChoiceAttributeType(String name, AttributeType[] children) {
this(name, 1, 1, children, Filter.EXCLUDE);
}
public Filter getRestriction() {
return DefaultAttributeType.getRestriction(this);
}
protected AttributeType[] copyChildren(AttributeType[] attributes) {
int length = attributes.length;
AttributeType[] returnArray = new AttributeType[length];
System.arraycopy(attributes, 0, returnArray, 0, length);
return returnArray;
}
/**
* {@inheritDoc}
*/
public String getLocalName() {
return DefaultAttributeType.getLocalName(this);
}
/**
* Gets the class of the object. For a choice this is fairly useless, as
* it just returns Object, since we can not tell more than that.
*
* @return currently always returns Object.class, since we can't tell more.
*
* @task REVISIT: Perhaps we should add a getTypes() method that returns an
* array of classes, that would represent the classes that you can
* choose from.
* @task REVISIT: Would also be good if this could dynamically figure out
* the broadest class - like Number if the choices were Double and
* Integer.
*
* @see org.geotools.feature.AttributeType#getBinding()
*/
public Class getBinding() {
return DefaultAttributeType.getBinding(this);
}
public boolean calculateNillable(AttributeType[] children) {
return calculateNillableStatic(children);
}
/**
* static verison of {@link #calculateNillable(AttributeType[])} which can
* be called from constructor.
*/
private static boolean calculateNillableStatic(AttributeType[] children) {
for (int i = 0, ii = children.length; i < ii; i++) {
if (children[i].isNillable()) {
return true;
}
}
//none of the children can take a null, so no nulls are allowed.
return false;
}
/* (non-Javadoc)
* @see org.geotools.feature.AttributeType#isGeometry()
*/
public boolean isGeometry() {
return false;
}
/**
* Goes through the children, and searches for a parser that works. This
* method searches in the order in which the children are specified ...
* please keep this in mind when creating these objects if you care about
* precedence.
*
* @param value The object to parse.
*
* @return The object parsed into the appropriate form for the Attribute.
*
* @throws IllegalArgumentException If the object could not be parsed by
* any of the child attribute Types.
*/
public Object parse(Object value) throws IllegalArgumentException {
for (int i = 0; i < children.length; i++) {
try {
return children[i].parse(value);
} catch (IllegalArgumentException e) {
// ignore ... try the next
}
}
throw new IllegalArgumentException("Could not be parsed :(");
}
/**
* Goes through the children, and searches for a validator that works. This
* method searches in the order in which the children are specified ...
* please keep this in mind when creating these objects if you care about
* precedence.
*
* @param obj The object to validate.
*
* @throws IllegalArgumentException If none of the children can validate.
*/
public void validate(Object obj) throws IllegalArgumentException {
for (int i = 0; i < children.length; i++) {
try {
children[i].validate(obj);
return; // validates
} catch (IllegalArgumentException e) {
// ignore ... try the next
}
}
throw new IllegalArgumentException("Could not be validated :(");
}
/**
* Goes through the children, and searches for a duplicator that works.
* This method searches in the order in which the children are specified
* ... please keep this in mind when creating these objects if you care
* about precedence.
*
* @param src The object to be duplicated.
*
* @return A deep copy of the original object.
*
* @throws IllegalAttributeException For any attribute errors.
* @throws IllegalArgumentException If the object could not be duplicated.
*/
public Object duplicate(Object src) throws IllegalAttributeException {
for (int i = 0; i < children.length; i++) {
try {
return children[i].duplicate(src);
} catch (IllegalArgumentException e) {
// ignore ... try the next
}
}
throw new IllegalArgumentException("Could not be duplicated :(");
}
/**
* Returns the default value for the first child which does not throw an
* exception, null otherwise.
*
* @return The default value of the first choice that does not throw an
* exception.
*/
public Object createDefaultValue() {
for (int i = 0; i < children.length; i++) {
try {
return children[i].createDefaultValue();
} catch (IllegalArgumentException e) {
// ignore ... try the next
}
}
return null;
}
/**
* 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 equivalent value to getAttributeTypes().length
*/
public int getAttributeCount() {
return children.length;
}
/**
* 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) {
AttributeType attType = null;
int idx = find(xPath);
if (idx >= 0) {
attType = children[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) || !children[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) {
int i = 0;
while ((i < children.length) && !attName.equals(children[i].getLocalName()))
i++;
return (i == children.length) ? (-1) : i;
}
/**
* 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 children[position];
}
public AttributeType[] getAttributeTypes() {
return (AttributeType[]) children.clone();
}
public boolean equals(Object other) {
if (other == null) {
return false;
}
if (!(other instanceof ChoiceAttributeType)) {
return false;
}
ChoiceAttributeType att = (ChoiceAttributeType) other;
if (!super.equals(att)) {
return false;
}
//hmmm... This makes the assumption that the order of the choices
//matters - not sure if that's true. Though the order does matter a
//a bit for our parse method, so this is probably right, since two
//with different orders could have diff. behaviors for that method.
if (!Arrays.equals(children, att.getAttributeTypes())) {
return false;
}
return true;
}
/**
* Override of hashCode.
*
* @return hashCode for this object.
*/
public int hashCode() {
int hash = super.hashCode();
for (int i = 0, ii = children.length; i < ii; i++) {
hash ^= children[i].hashCode();
}
return hash;
}
/**
* Gets a representation of this object as a string.
*
* @return A representation of this object as a string
*/
public String toString() {
String details = "name=" + name;
details += ((" , nillable=" + isNillable()) + ", min=" + getMinOccurs() + ", max=" + getMaxOccurs());
details += (", choices: " + Arrays.asList(children));
return "ChoiceAttributeType [" + details + "]";
}
/**
* A special class that is made so a Choice can serve as the Default
* Geometry in a FeatureType, by implementing GeometryAttributeType. It
* must be a choice between other GeometryAttributeTypes.
*
* @author Chris Holmes, TOPP
*
* @task TODO: Need to write code to check that all the geometry attributes
* are in the same crs. Right now we just blindly assume they are
* and return the first.
*/
public static final class Geometric extends ChoiceAttributeType
implements GeometryAttributeType {
public Geometric(Geometric copy) {
super(copy);
}
// The field for 'Class type' should be added when GT has moved to java 1.5
public Geometric(String name, int min, int max,
GeometryAttributeType[] children, Filter restriction) {
super(name, min, max, children, restriction);
}
public Geometric(String name, GeometryAttributeType[] children) {
super(name, children);
}
public GeometryType getType() {
return new GeometryTypeImpl( getName(), Geometry.class, getCoordinateSystem(), false, false, null, null, null);
}
public CoordinateReferenceSystem getCoordinateSystem() {
//Hack - this is not guaranteed to be right, since right now we
//don't check in the constructors that all crses are the same.
GeometryAttributeType first = (GeometryAttributeType) getAttributeType(0);
return first.getCoordinateSystem();
}
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
return getCoordinateSystem();
}
public GeometryFactory getGeometryFactory() {
//Hack - this is not guaranteed to be right, since right now we
//don't check in the constructors that all crses are the same.
GeometryAttributeType first = (GeometryAttributeType) getAttributeType(0);
return first.getGeometryFactory();
}
/* (non-Javadoc)
* @see org.geotools.feature.AttributeType#isGeometry()
*/
public boolean isGeometry() {
return true;
}
}
}