001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-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.type;
017:
018: import com.vividsolutions.jts.geom.GeometryFactory;
019: import org.geotools.feature.AttributeType;
020: import org.geotools.feature.GeometryAttributeType;
021: import org.geotools.feature.IllegalAttributeException;
022: import org.opengis.filter.Filter;
023: import org.opengis.referencing.crs.CoordinateReferenceSystem;
024: import java.util.Arrays;
025:
026: /**
027: * This represents a Choice of AttributeTypes. That means, an Attribute of this
028: * type may be one of any of this AttributeType's children. This attribute is
029: * not valid for Simple Features, and maps to the Choice construct in GML.
030: *
031: * <p>
032: * Another way to think about the ChoiceAttributeType is as a Union
033: * construction from C - it can store a number of different types of value,
034: * but it only stores the one value. The parse and validate methods try out
035: * each of the choices to see if one of them might work, since all are valid.
036: * The order that the child attributeTypes (the choices you can use) are
037: * specified is important, because some objects can parse and validate
038: * against several types. The first choice that returns true is the one that
039: * will
040: * </p>
041: *
042: * @author dzwiers
043: * @author Chris Holmes, TOPP
044: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/feature/type/ChoiceAttributeType.java $
045: */
046: public class ChoiceAttributeType implements AttributeType {
047: private final boolean nill;
048: private final int min;
049: private final int max;
050: private final String name;
051: private final AttributeType[] children;
052: private Filter restriction;
053:
054: /**
055: * DOCUMENT ME!
056: *
057: * @param copy
058: */
059: public ChoiceAttributeType(ChoiceAttributeType copy) {
060: nill = copy.isNillable();
061: min = copy.getMinOccurs();
062: max = copy.getMaxOccurs();
063: name = copy.getName();
064: this .children = copyChildren(copy.getAttributeTypes());
065: restriction = copy.getRestriction();
066: }
067:
068: // The field for 'Class type' should be added when GT has moved to java 1.5
069: public ChoiceAttributeType(String name, int min, int max,
070: AttributeType[] children, Filter restriction) {
071: nill = calculateNillable(children);
072: this .min = min;
073: this .max = max;
074: this .name = name;
075: //ensure immutable.
076: this .children = copyChildren(children);
077: this .restriction = restriction;
078: }
079:
080: public ChoiceAttributeType(String name, AttributeType[] children) {
081: this (name, 1, 1, children, Filter.EXCLUDE);
082: }
083:
084: public Filter getRestriction() {
085: return restriction;
086: }
087:
088: protected AttributeType[] copyChildren(AttributeType[] attributes) {
089: int length = attributes.length;
090: AttributeType[] returnArray = new AttributeType[length];
091: System.arraycopy(attributes, 0, returnArray, 0, length);
092: return returnArray;
093: }
094:
095: /* (non-Javadoc)
096: * @see org.geotools.feature.AttributeType#getName()
097: */
098: public final String getName() {
099: return getLocalName();
100: }
101:
102: /**
103: * {@inheritDoc}
104: */
105: public String getLocalName() {
106: return name;
107: }
108:
109: /**
110: * Gets the class of the object. For a choice this is fairly useless, as
111: * it just returns Object, since we can not tell more than that.
112: *
113: * @return currently always returns Object.class, since we can't tell more.
114: *
115: * @task REVISIT: Perhaps we should add a getTypes() method that returns an
116: * array of classes, that would represent the classes that you can
117: * choose from.
118: * @task REVISIT: Would also be good if this could dynamically figure out
119: * the broadest class - like Number if the choices were Double and
120: * Integer.
121: *
122: * @see org.geotools.feature.AttributeType#getType()
123: */
124: public final Class getType() {
125: return getBinding();
126: }
127:
128: public Class getBinding() {
129: //The field for 'Class type' should be added when GT has moved to java 1.5
130: return Object.class;
131: }
132:
133: /* (non-Javadoc)
134: * @see org.geotools.feature.AttributeType#isNillable()
135: */
136: public boolean isNillable() {
137: return nill;
138: }
139:
140: public boolean calculateNillable(AttributeType[] children) {
141: for (int i = 0, ii = children.length; i < ii; i++) {
142: if (children[i].isNillable()) {
143: return true;
144: }
145: }
146:
147: //none of the children can take a null, so no nulls are allowed.
148: return false;
149: }
150:
151: /* (non-Javadoc)
152: * @see org.geotools.feature.AttributeType#getMinOccurs()
153: */
154: public int getMinOccurs() {
155: return min;
156: }
157:
158: /* (non-Javadoc)
159: * @see org.geotools.feature.AttributeType#getMaxOccurs()
160: */
161: public int getMaxOccurs() {
162: return max;
163: }
164:
165: /* (non-Javadoc)
166: * @see org.geotools.feature.AttributeType#isGeometry()
167: */
168: public boolean isGeometry() {
169: return false;
170: }
171:
172: /**
173: * Goes through the children, and searches for a parser that works. This
174: * method searches in the order in which the children are specified ...
175: * please keep this in mind when creating these objects if you care about
176: * precedence.
177: *
178: * @param value The object to parse.
179: *
180: * @return The object parsed into the appropriate form for the Attribute.
181: *
182: * @throws IllegalArgumentException If the object could not be parsed by
183: * any of the child attribute Types.
184: */
185: public Object parse(Object value) throws IllegalArgumentException {
186: for (int i = 0; i < children.length; i++) {
187: try {
188: return children[i].parse(value);
189: } catch (IllegalArgumentException e) {
190: // ignore ... try the next
191: }
192: }
193:
194: throw new IllegalArgumentException("Could not be parsed :(");
195: }
196:
197: /**
198: * Goes through the children, and searches for a validator that works. This
199: * method searches in the order in which the children are specified ...
200: * please keep this in mind when creating these objects if you care about
201: * precedence.
202: *
203: * @param obj The object to validate.
204: *
205: * @throws IllegalArgumentException If none of the children can validate.
206: */
207: public void validate(Object obj) throws IllegalArgumentException {
208: for (int i = 0; i < children.length; i++) {
209: try {
210: children[i].validate(obj);
211:
212: return; // validates
213: } catch (IllegalArgumentException e) {
214: // ignore ... try the next
215: }
216: }
217:
218: throw new IllegalArgumentException("Could not be validated :(");
219: }
220:
221: /**
222: * Goes through the children, and searches for a duplicator that works.
223: * This method searches in the order in which the children are specified
224: * ... please keep this in mind when creating these objects if you care
225: * about precedence.
226: *
227: * @param src The object to be duplicated.
228: *
229: * @return A deep copy of the original object.
230: *
231: * @throws IllegalAttributeException For any attribute errors.
232: * @throws IllegalArgumentException If the object could not be duplicated.
233: */
234: public Object duplicate(Object src)
235: throws IllegalAttributeException {
236: for (int i = 0; i < children.length; i++) {
237: try {
238: return children[i].duplicate(src);
239: } catch (IllegalArgumentException e) {
240: // ignore ... try the next
241: }
242: }
243:
244: throw new IllegalArgumentException("Could not be duplicated :(");
245: }
246:
247: /**
248: * Returns the default value for the first child which does not throw an
249: * exception, null otherwise.
250: *
251: * @return The default value of the first choice that does not throw an
252: * exception.
253: */
254: public Object createDefaultValue() {
255: for (int i = 0; i < children.length; i++) {
256: try {
257: return children[i].createDefaultValue();
258: } catch (IllegalArgumentException e) {
259: // ignore ... try the next
260: }
261: }
262:
263: return null;
264: }
265:
266: /**
267: * This is only used twice in the whole geotools code base, and one of
268: * those is for a test, so we're removing it from the interface. If
269: * getAttributeType does not have the AttributeType it will just return
270: * null. Gets the number of occurrences of this attribute.
271: *
272: * @param xPath XPath pointer to attribute type.
273: *
274: * @return Number of occurrences.
275: */
276: public boolean hasAttributeType(String xPath) {
277: return getAttributeType(xPath) != null;
278: }
279:
280: /**
281: * Returns the number of attributes at the first 'level' of the schema.
282: *
283: * @return equivalent value to getAttributeTypes().length
284: */
285: public int getAttributeCount() {
286: return children.length;
287: }
288:
289: /**
290: * Gets the attributeType at this xPath, if the specified attributeType
291: * does not exist then null is returned.
292: *
293: * @param xPath XPath pointer to attribute type.
294: *
295: * @return True if attribute exists.
296: */
297: public AttributeType getAttributeType(String xPath) {
298: AttributeType attType = null;
299: int idx = find(xPath);
300:
301: if (idx >= 0) {
302: attType = children[idx];
303: }
304:
305: return attType;
306: }
307:
308: /**
309: * Find the position of a given AttributeType.
310: *
311: * @param type The type to search for.
312: *
313: * @return -1 if not found, a zero-based index if found.
314: */
315: public int find(AttributeType type) {
316: if (type == null) {
317: return -1;
318: }
319:
320: int idx = find(type.getName());
321:
322: if ((idx < 0) || !children[idx].equals(type)) {
323: idx = -1;
324: }
325:
326: return idx;
327: }
328:
329: /**
330: * Find the position of an AttributeType which matches the given String.
331: *
332: * @param attName the name to look for
333: *
334: * @return -1 if not found, zero-based index otherwise
335: */
336: public int find(String attName) {
337: int i = 0;
338:
339: while ((i < children.length)
340: && !attName.equals(children[i].getName()))
341: i++;
342:
343: return (i == children.length) ? (-1) : i;
344: }
345:
346: /**
347: * Gets the attributeType at the specified index.
348: *
349: * @param position the position of the attribute to check.
350: *
351: * @return The attribute type at the specified position.
352: */
353: public AttributeType getAttributeType(int position) {
354: return children[position];
355: }
356:
357: public AttributeType[] getAttributeTypes() {
358: return (AttributeType[]) children.clone();
359: }
360:
361: public boolean equals(Object other) {
362: if (other == null) {
363: return false;
364: }
365:
366: if (!(other instanceof ChoiceAttributeType)) {
367: return false;
368: }
369:
370: ChoiceAttributeType att = (ChoiceAttributeType) other;
371:
372: if (name == null) {
373: if (att.getName() != null) {
374: return false;
375: }
376: } else if (!name.equals(att.getName())) {
377: return false;
378: }
379:
380: //hmmm... This makes the assumption that the order of the choices
381: //matters - not sure if that's true. Though the order does matter a
382: //a bit for our parse method, so this is probably right, since two
383: //with different orders could have diff. behaviors for that method.
384: if (!Arrays.equals(children, att.getAttributeTypes())) {
385: return false;
386: }
387:
388: return true;
389: }
390:
391: /**
392: * Override of hashCode.
393: *
394: * @return hashCode for this object.
395: */
396: public int hashCode() {
397: int hash = name.hashCode();
398:
399: for (int i = 0, ii = children.length; i < ii; i++) {
400: hash ^= children[i].hashCode();
401: }
402:
403: return hash;
404: }
405:
406: /**
407: * Gets a representation of this object as a string.
408: *
409: * @return A representation of this object as a string
410: */
411: public String toString() {
412: String details = "name=" + name;
413: details += ((" , nillable=" + nill) + ", min=" + min + ", max=" + max);
414: details += (", choices: " + Arrays.asList(children));
415:
416: return "ChoiceAttributeType [" + details + "]";
417: }
418:
419: /**
420: * A special class that is made so a Choice can serve as the Default
421: * Geometry in a FeatureType, by implementing GeometryAttributeType. It
422: * must be a choice between other GeometryAttributeTypes.
423: *
424: * @author Chris Holmes, TOPP
425: *
426: * @task TODO: Need to write code to check that all the geometry attributes
427: * are in the same crs. Right now we just blindly assume they are
428: * and return the first.
429: */
430: public static final class Geometric extends ChoiceAttributeType
431: implements GeometryAttributeType {
432: public Geometric(Geometric copy) {
433: super (copy);
434: }
435:
436: // The field for 'Class type' should be added when GT has moved to java 1.5
437: public Geometric(String name, int min, int max,
438: GeometryAttributeType[] children, Filter restriction) {
439: super (name, min, max, children, restriction);
440: }
441:
442: public Geometric(String name, GeometryAttributeType[] children) {
443: super (name, children);
444: }
445:
446: public CoordinateReferenceSystem getCoordinateSystem() {
447: //Hack - this is not guaranteed to be right, since right now we
448: //don't check in the constructors that all crses are the same.
449: GeometryAttributeType first = (GeometryAttributeType) getAttributeType(0);
450:
451: return first.getCoordinateSystem();
452: }
453:
454: public GeometryFactory getGeometryFactory() {
455: //Hack - this is not guaranteed to be right, since right now we
456: //don't check in the constructors that all crses are the same.
457: GeometryAttributeType first = (GeometryAttributeType) getAttributeType(0);
458:
459: return first.getGeometryFactory();
460: }
461:
462: /* (non-Javadoc)
463: * @see org.geotools.feature.AttributeType#isGeometry()
464: */
465: public boolean isGeometry() {
466: return true;
467: }
468: }
469: }
|