/* * 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.lang.reflect.Array; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.geotools.feature.NameImpl; import org.geotools.feature.type.AttributeDescriptorImpl; import org.geotools.feature.type.AttributeTypeImpl; import org.geotools.util.Converters; import org.opengis.filter.Filter; import org.opengis.coverage.grid.GridCoverage; import com.vividsolutions.jts.geom.Geometry; /** * Simple, immutable class to store attributes. This class should be * sufficient for all simple (ie. non-schema) attribute implementations of * this interface. * * @author Rob Hranac, VFNY * @author Chris Holmes, TOPP * @author Ian Schneider * @source $URL$ * @version $Id$ */ public class DefaultAttributeType extends AttributeDescriptorImpl implements AttributeType { /** * Constructor with name and type. * * @param name Name of this attribute. * @param type Class type of this attribute. * @param nillable If nulls are allowed for the attribute of this type. * @param min * @param max * @param defaultValue default value when none is suppled * @param f * * @task REVISIT: make this protected? I think it's only used by facotries * at this time. */ protected DefaultAttributeType(String name, Class type, boolean nillable, int min, int max, Object defaultValue, Filter f) { this(createAttributeType(name, type != null ? type : Object.class, f),name,nillable,min,max,defaultValue); } protected DefaultAttributeType(org.opengis.feature.type.AttributeType type,String name,boolean nillable,int min,int max, Object defaultValue) { super(type,new NameImpl((name == null) ? "" : name),min,max,nillable,defaultValue); if(defaultValue!=null && !type.getBinding().isAssignableFrom(defaultValue.getClass())) throw new IllegalArgumentException("Default value does not match type"); } protected DefaultAttributeType(String name, Class type, boolean nillable, int min, int max, Object defaultValue) { this(name,type,nillable,min,max,defaultValue,Filter.INCLUDE); } protected DefaultAttributeType(String name, Class type, boolean nillable, Object defaultValue) { this(name, type, nillable, 1, 1, defaultValue,Filter.INCLUDE); } protected DefaultAttributeType(AttributeType copy) { super(copy.getType(),copy.getName(),copy.getMinOccurs(),copy.getMaxOccurs(),copy.isNillable(),copy.getDefaultValue()); } public DefaultAttributeType(org.opengis.feature.type.AttributeType type, org.opengis.feature.type.Name name, int min, int max, boolean isNillable,Object defaultValue) { super(type, name, min, max, isNillable,defaultValue); } /** * Gets the name of this attribute. * * @return The name of this attribute. */ public String getLocalName() { return getName().getLocalPart(); } /** * Gets the type of this attribute. All attributes that are assigned to * this AttributeType must be an instance of this class. Subclasses are * allowed as well. * * @return The class that the attribute must match to be valid for this * AttributeType. */ public Class getBinding() { return getType().getBinding(); } /** * Return a safe Object copy. * <p> * Obtain a duplicate Object if the object is mutable, or the same Object * reference if it is immutable. * </p> * @return A duplicated Object if the type is mutable or the same Object * if it is immutable or null if the passed Object is null. * @throws IllegalAttributeException if the Object cannot be duplicated. * */ public Object duplicate(Object src) throws IllegalAttributeException { //JD: this method really needs to be replaced with somethign better if (src == null) { return null; } // // The following are things I expect // Features will contain. // if (src instanceof String || src instanceof Integer || src instanceof Double || src instanceof Float || src instanceof Byte || src instanceof Boolean || src instanceof Short || src instanceof Long || src instanceof Character || src instanceof Number) { return src; } if (src instanceof Date) { return new Date( ((Date)src).getTime() ); } if (src instanceof URL || src instanceof URI ) { return src; //immutable } if (src instanceof Object[]) { Object[] array = (Object[]) src; Object[] copy = new Object[array.length]; for (int i = 0; i < array.length; i++) { copy[i] = duplicate(array[i]); } return copy; } if (src instanceof Geometry) { Geometry geometry = (Geometry) src; return geometry.clone(); } if (src instanceof org.geotools.feature.Feature) { org.geotools.feature.Feature feature = (org.geotools.feature.Feature) src; return feature.getFeatureType().duplicate(feature); } // // We are now into diminishing returns // I don't expect Features to contain these often // (eveything is still nice and recursive) // Class type = src.getClass(); if (type.isArray() && type.getComponentType().isPrimitive()) { int length = Array.getLength(src); Object copy = Array.newInstance(type.getComponentType(), length); System.arraycopy(src, 0, copy, 0, length); return copy; } if (type.isArray()) { int length = Array.getLength(src); Object copy = Array.newInstance(type.getComponentType(), length); for (int i = 0; i < length; i++) { Array.set(copy, i, duplicate(Array.get(src, i))); } return copy; } if (src instanceof List) { List list = (List) src; List copy = new ArrayList(list.size()); for (Iterator i = list.iterator(); i.hasNext();) { copy.add(duplicate(i.next())); } return Collections.unmodifiableList(copy); } if (src instanceof Map) { Map map = (Map) src; Map copy = new HashMap(map.size()); for (Iterator i = map.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); copy.put(entry.getKey(), duplicate(entry.getValue())); } return Collections.unmodifiableMap(copy); } if( src instanceof GridCoverage ){ return src; // inmutable } // // I have lost hope and am returning the orgional reference // Please extend this to support additional classes. // // And good luck getting Cloneable to work throw new IllegalAttributeException("Do not know how to deep copy " + type.getName()); } /** * Override of hashCode. * * @return hashCode for this object. */ public int hashCode() { return super.hashCode() ^ type.hashCode(); } /** * Override of equals. * * @param other the object to be tested for equality. * * @return whether other is equal to this attribute Type. */ public boolean equals(Object other) { if (other == null) { return false; } if( !(other instanceof AttributeType) ){ return false; } AttributeType att = (AttributeType) other; if (!super.equals(att)) { return false; } if (!getBinding().equals(att.getBinding())) { return false; } return true; } /** * Returns whether the attribute is a geometry. * * @return true if the attribute's type is a geometry. */ public boolean isGeometry() { return Geometry.class.isAssignableFrom(getBinding()); } /** * 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 += (" , type=" + type); details += (" , nillable=" + isNillable()) + ", min=" + getMinOccurs() + ", max=" + getMaxOccurs(); return "DefaultAttributeType [" + details + "]"; } /** * Allows this AttributeType to convert an argument to its prefered storage * type. If no parsing is possible, returns the original value. If a parse * is attempted, yet fails (i.e. a poor decimal format) throw the * Exception. This is mostly for use internally in Features, but * implementors should simply follow the rules to be safe. * * @param value the object to attempt parsing of. * * @return <code>value</code> converted to the preferred storage of this * <code>AttributeType</code>. If no parsing was possible then * the same object is returned. * * @throws IllegalArgumentException if parsing is attempted and is * unsuccessful. */ public final Object parse(Object value) throws IllegalArgumentException { if ( value == null || getBinding().isAssignableFrom(value.getClass()) ) { return value; } Object parsed = Converters.convert(value, getBinding()); if( parsed == null ) { throw new IllegalArgumentException("Could not convert " + parsed + " to type " + getBinding().getName() ); } return parsed; } /** * Whether the tested object passes the validity constraints of this * AttributeType. At a minimum it should be of the correct class * specified by {@link #getBinding()}, non-null if isNillable is * <tt>false</tt>, and a geometry if isGeometry is <tt>true</tt>. If The * object does not validate then an IllegalArgumentException reporting the * error in validation should be thrown. * * @param attribute The object to be tested for validity. * * @throws IllegalArgumentException if the object does not validate. */ public void validate(Object attribute) throws IllegalArgumentException { if (attribute == null) { if (!isNillable()) { throw new IllegalArgumentException(getLocalName() + " is not nillable"); } return; } else if (getBinding() != attribute.getClass() && !getBinding().isAssignableFrom(attribute.getClass())) { throw new IllegalArgumentException(attribute.getClass().getName() + " is not an acceptable class for " + getLocalName() + " as it is not assignable from " + type); } } public Object createDefaultValue() { return defaultValue; } /* (non-Javadoc) * @see org.geotools.feature.AttributeType#getRestriction() */ public Filter getRestriction() { if ( !getType().getRestrictions().isEmpty() ) { return (Filter) getType().getRestrictions().iterator().next(); } return null; } // // The following methods are called by org.geotools.feature.AttributeType // implementations while they transition to implement the geoapi AttributeDescriptor // interface. // /** * Method for geotools AttributeType implementations to create a geoapi * attribute type. * <p> * This method is usually called from the constructors of the old AttributeType * implememtnations. * </p> * @param name THe attribute type name. * @param binding The attribute type binding. * @param restriction Restriction on the attribute type. * * @return A geoapi attribute type. * * @since 2.5 */ public static org.opengis.feature.type.AttributeType createAttributeType(String name,Class binding,Filter restriction) { return new AttributeTypeImpl( new NameImpl(name),binding,false,false, restriction != null ? Collections.singletonList(restriction) : Collections.EMPTY_LIST, null,null); } public static String getLocalName( AttributeType type ) { return type.getName().getLocalPart(); } public static Class getBinding( AttributeType type ) { return type.getType().getBinding(); } public static Filter getRestriction( AttributeType type ) { if ( type.getType() != null && type.getType().getRestrictions() != null && !type.getType().getRestrictions().isEmpty() ) { return (Filter) type.getType().getRestrictions().iterator().next(); } return null; } }