001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.feature;
017:
018: import java.lang.reflect.Array;
019: import java.net.URI;
020: import java.net.URL;
021: import java.util.ArrayList;
022: import java.util.Collections;
023: import java.util.Date;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028:
029: import org.geotools.util.Converters;
030: import org.opengis.filter.Filter;
031: import org.opengis.coverage.grid.GridCoverage;
032:
033: import com.vividsolutions.jts.geom.Geometry;
034:
035: /**
036: * Simple, immutable class to store attributes. This class should be
037: * sufficient for all simple (ie. non-schema) attribute implementations of
038: * this interface.
039: *
040: * @author Rob Hranac, VFNY
041: * @author Chris Holmes, TOPP
042: * @author Ian Schneider
043: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/feature/DefaultAttributeType.java $
044: * @version $Id: DefaultAttributeType.java 27881 2007-11-13 16:29:42Z aaime $
045: */
046: public class DefaultAttributeType implements AttributeType {
047: /** Name of this attribute. */
048: protected final String name;
049:
050: /** Class type of this attribute. */
051: protected final Class type;
052:
053: /** Indicates if nulls are allowed for this attribute */
054: protected final boolean nillable;
055: /** min/max */
056: protected final int min, max;
057: protected Object defaultValue;
058:
059: /**
060: * Defaults are flat, always return 1.
061: * @task REVISIT: return 0? That's our current assumption in some
062: * code, but
063: */
064: public int getMinOccurs() {
065: return min;
066: }
067:
068: /**
069: * Defaults are flat, always return 1.
070: */
071: public int getMaxOccurs() {
072: return max;
073: }
074:
075: /**
076: * Constructor with name and type.
077: *
078: * @param name Name of this attribute.
079: * @param type Class type of this attribute.
080: * @param nillable If nulls are allowed for the attribute of this type.
081: * @param min
082: * @param max
083: * @param defaultValue default value when none is suppled
084: * @param f
085: *
086: * @task REVISIT: make this protected? I think it's only used by facotries
087: * at this time.
088: */
089: protected DefaultAttributeType(String name, Class type,
090: boolean nillable, int min, int max, Object defaultValue,
091: Filter f) {
092: this .name = (name == null) ? "" : name;
093: this .type = (type == null) ? Object.class : type;
094: this .nillable = nillable;
095: this .min = min;
096: this .max = max;
097: this .defaultValue = defaultValue;
098: this .filter = f;
099: if (defaultValue != null
100: && !type.isAssignableFrom(defaultValue.getClass()))
101: throw new IllegalArgumentException(
102: "Default value does not match type");
103: }
104:
105: protected DefaultAttributeType(String name, Class type,
106: boolean nillable, int min, int max, Object defaultValue) {
107: this (name, type, nillable, min, max, defaultValue,
108: Filter.INCLUDE);
109: }
110:
111: protected DefaultAttributeType(String name, Class type,
112: boolean nillable, Object defaultValue) {
113: this (name, type, nillable, 1, 1, defaultValue, Filter.INCLUDE);
114: }
115:
116: protected DefaultAttributeType(AttributeType copy) {
117: this .name = copy.getName();
118: this .type = copy.getType();
119: this .nillable = copy.isNillable();
120: this .min = copy.getMinOccurs();
121: this .max = copy.getMaxOccurs();
122: this .defaultValue = copy.createDefaultValue();
123: }
124:
125: /**
126: * Gets the name of this attribute.
127: *
128: * @return The name of this attribute.
129: */
130: public final String getName() {
131: return getLocalName();
132: }
133:
134: /**
135: * {@inheritDoc}
136: */
137: public String getLocalName() {
138: return name;
139: }
140:
141: /**
142: * Gets the type of this attribute. All attributes that are assigned to
143: * this AttributeType must be an instance of this class. Subclasses are
144: * allowed as well.
145: *
146: * @return The class that the attribute must match to be valid for this
147: * AttributeType.
148: */
149: public final Class getType() {
150: return getBinding();
151: }
152:
153: /**
154: * {@inheritDoc}
155: */
156: public Class getBinding() {
157: return type;
158: }
159:
160: /**
161: * Returns whether nulls are allowed for this attribute.
162: *
163: * @return true if nulls are permitted, false otherwise.
164: */
165: public boolean isNillable() {
166: return nillable;
167: }
168:
169: /**
170: * Return a safe Object copy.
171: * <p>
172: * Obtain a duplicate Object if the object is mutable, or the same Object
173: * reference if it is immutable.
174: * </p>
175: * @return A duplicated Object if the type is mutable or the same Object
176: * if it is immutable or null if the passed Object is null.
177: * @throws IllegalAttributeException if the Object cannot be duplicated.
178: *
179: */
180: public Object duplicate(Object src)
181: throws IllegalAttributeException {
182: //JD: this method really needs to be replaced with somethign better
183:
184: if (src == null) {
185: return null;
186: }
187:
188: //
189: // The following are things I expect
190: // Features will contain.
191: //
192: if (src instanceof String || src instanceof Integer
193: || src instanceof Double || src instanceof Float
194: || src instanceof Byte || src instanceof Boolean
195: || src instanceof Short || src instanceof Long
196: || src instanceof Character || src instanceof Number) {
197: return src;
198: }
199:
200: if (src instanceof Date) {
201: return new Date(((Date) src).getTime());
202: }
203:
204: if (src instanceof URL || src instanceof URI) {
205: return src; //immutable
206: }
207:
208: if (src instanceof Object[]) {
209: Object[] array = (Object[]) src;
210: Object[] copy = new Object[array.length];
211:
212: for (int i = 0; i < array.length; i++) {
213: copy[i] = duplicate(array[i]);
214: }
215:
216: return copy;
217: }
218:
219: if (src instanceof Geometry) {
220: Geometry geometry = (Geometry) src;
221:
222: return geometry.clone();
223: }
224:
225: if (src instanceof org.geotools.feature.Feature) {
226: org.geotools.feature.Feature feature = (org.geotools.feature.Feature) src;
227:
228: return feature.getFeatureType().duplicate(feature);
229: }
230:
231: //
232: // We are now into diminishing returns
233: // I don't expect Features to contain these often
234: // (eveything is still nice and recursive)
235: //
236: Class type = src.getClass();
237:
238: if (type.isArray() && type.getComponentType().isPrimitive()) {
239: int length = Array.getLength(src);
240: Object copy = Array.newInstance(type.getComponentType(),
241: length);
242: System.arraycopy(src, 0, copy, 0, length);
243:
244: return copy;
245: }
246:
247: if (type.isArray()) {
248: int length = Array.getLength(src);
249: Object copy = Array.newInstance(type.getComponentType(),
250: length);
251:
252: for (int i = 0; i < length; i++) {
253: Array.set(copy, i, duplicate(Array.get(src, i)));
254: }
255:
256: return copy;
257: }
258:
259: if (src instanceof List) {
260: List list = (List) src;
261: List copy = new ArrayList(list.size());
262:
263: for (Iterator i = list.iterator(); i.hasNext();) {
264: copy.add(duplicate(i.next()));
265: }
266:
267: return Collections.unmodifiableList(copy);
268: }
269:
270: if (src instanceof Map) {
271: Map map = (Map) src;
272: Map copy = new HashMap(map.size());
273:
274: for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
275: Map.Entry entry = (Map.Entry) i.next();
276: copy.put(entry.getKey(), duplicate(entry.getValue()));
277: }
278:
279: return Collections.unmodifiableMap(copy);
280: }
281:
282: if (src instanceof GridCoverage) {
283: return src; // inmutable
284: }
285:
286: //
287: // I have lost hope and am returning the orgional reference
288: // Please extend this to support additional classes.
289: //
290: // And good luck getting Cloneable to work
291: throw new IllegalAttributeException(
292: "Do not know how to deep copy " + type.getName());
293: }
294:
295: /**
296: * Override of hashCode.
297: *
298: * @return hashCode for this object.
299: */
300: public int hashCode() {
301: return name.hashCode() ^ type.hashCode();
302: }
303:
304: /**
305: * Override of equals.
306: *
307: * @param other the object to be tested for equality.
308: *
309: * @return whether other is equal to this attribute Type.
310: */
311: public boolean equals(Object other) {
312: if (other == null) {
313: return false;
314: }
315:
316: if (!(other instanceof AttributeType)) {
317: return false;
318: }
319:
320: AttributeType att = (AttributeType) other;
321:
322: if (name == null) {
323: if (att.getName() != null) {
324: return false;
325: }
326: }
327:
328: if (!name.equals(att.getName())) {
329: return false;
330: }
331:
332: if (!type.equals(att.getType())) {
333: return false;
334: }
335:
336: return true;
337: }
338:
339: /**
340: * Returns whether the attribute is a geometry.
341: *
342: * @return true if the attribute's type is a geometry.
343: */
344: public boolean isGeometry() {
345: return Geometry.class.isAssignableFrom(this .type);
346: }
347:
348: /**
349: * Gets a representation of this object as a string.
350: *
351: * @return A representation of this object as a string
352: */
353: public String toString() {
354: String details = "name=" + name;
355: details += (" , type=" + type);
356: details += (" , nillable=" + nillable) + ", min=" + min
357: + ", max=" + min;
358:
359: return "DefaultAttributeType [" + details + "]";
360: }
361:
362: /**
363: * Allows this AttributeType to convert an argument to its prefered storage
364: * type. If no parsing is possible, returns the original value. If a parse
365: * is attempted, yet fails (i.e. a poor decimal format) throw the
366: * Exception. This is mostly for use internally in Features, but
367: * implementors should simply follow the rules to be safe.
368: *
369: * @param value the object to attempt parsing of.
370: *
371: * @return <code>value</code> converted to the preferred storage of this
372: * <code>AttributeType</code>. If no parsing was possible then
373: * the same object is returned.
374: *
375: * @throws IllegalArgumentException if parsing is attempted and is
376: * unsuccessful.
377: */
378: public Object parse(Object value) throws IllegalArgumentException {
379: if (type.isInstance(value) || value == null)
380: return value;
381:
382: Object converted = Converters.convert(value, type);
383: if (converted == null)
384: throw new IllegalArgumentException(value.getClass()
385: .getName()
386: + " is not an acceptable class for "
387: + getName()
388: + " as it is not assignable from " + type);
389: return converted;
390: }
391:
392: /**
393: * Whether the tested object passes the validity constraints of this
394: * AttributeType. At a minimum it should be of the correct class
395: * specified by {@link #getType()}, non-null if isNillable is
396: * <tt>false</tt>, and a geometry if isGeometry is <tt>true</tt>. If The
397: * object does not validate then an IllegalArgumentException reporting the
398: * error in validation should be thrown.
399: *
400: * @param attribute The object to be tested for validity.
401: *
402: * @throws IllegalArgumentException if the object does not validate.
403: */
404: public void validate(Object attribute)
405: throws IllegalArgumentException {
406: if (attribute == null) {
407: if (!isNillable()) {
408: throw new IllegalArgumentException(getName()
409: + " is not nillable");
410: }
411:
412: return;
413: } else if (type != attribute.getClass()
414: && !type.isInstance(attribute)
415: && Converters.convert(attribute, type) == null) {
416: throw new IllegalArgumentException(attribute.getClass()
417: .getName()
418: + " is not an acceptable class for "
419: + getName()
420: + " as it is not assignable from " + type);
421: }
422: }
423:
424: public Object createDefaultValue() {
425: return defaultValue;
426: }
427:
428: /* (non-Javadoc)
429: * @see org.geotools.feature.AttributeType#getRestriction()
430: */
431: public Filter getRestriction() {
432: return filter;
433: }
434:
435: private Filter filter;
436:
437: }
|