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.net.URI;
019: import java.net.URISyntaxException;
020: import java.util.Collection;
021: import java.util.Iterator;
022:
023: /**
024: * A basic implementation of FeatureType.
025: *
026: * @author Ian Schneider
027: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/feature/DefaultFeatureType.java $
028: * @version $Id: DefaultFeatureType.java 26186 2007-07-10 02:18:59Z jdeolive $
029: */
030: public class DefaultFeatureType implements FeatureType {
031: /** The name of this FeatureType. */
032: private final String typeName;
033:
034: /** The namespace to uniquely identify this FeatureType. */
035: private final URI namespace;
036:
037: /** The array of types that this FeatureType can have as attributes. */
038: private final AttributeType[] types;
039:
040: /** The FeatureTypes this is descended from. */
041: private final FeatureType[] ancestors;
042:
043: /** The default geometry AttributeType. */
044: private final GeometryAttributeType defaultGeom;
045:
046: private final int hashCode;
047:
048: /** The position of the default Geometry
049: * Leave as package protected for use by DefaultFeature
050: */
051: final int defaultGeomIdx;
052:
053: /** An feature type with no attributes */
054: public static final FeatureType EMPTY = new DefaultFeatureType();
055:
056: /** attname:string -> position:int */
057: private final java.util.Map attLookup;
058:
059: private final static URI toURI(String namespace)
060: throws SchemaException {
061: try {
062: return new URI(namespace);
063: } catch (URISyntaxException badNamespace) {
064: throw new SchemaException(badNamespace);
065: }
066: }
067:
068: public DefaultFeatureType(String typeName, String namespace,
069: Collection types, Collection super Types,
070: GeometryAttributeType defaultGeom) throws SchemaException,
071: NullPointerException {
072: this (typeName, toURI(namespace), types, super Types, defaultGeom);
073: }
074:
075: /**
076: * Constructs a new DefaultFeatureType.
077: *
078: * <p>
079: * Attributes from the superTypes will be copied to the list of attributes
080: * for this feature type.
081: *
082: * @param typeName The name to give this FeatureType.
083: * @param namespace The namespace of the new FeatureType.
084: * @param types The attributeTypes to use for validation.
085: * @param superTypes The ancestors of this FeatureType.
086: * @param defaultGeom The attributeType to set as the defaultGeometry.
087: *
088: * @throws SchemaException For problems making the FeatureType.
089: * @throws NullPointerException If typeName is null.
090: */
091: public DefaultFeatureType(String typeName, URI namespace,
092: Collection types, Collection super Types,
093: GeometryAttributeType defaultGeom)
094: throws NullPointerException {
095: if (typeName == null) {
096: throw new NullPointerException(typeName);
097: }
098:
099: this .typeName = typeName;
100: this .namespace = namespace == null ? FeatureTypes.DEFAULT_NAMESPACE
101: : namespace;
102: this .ancestors = (FeatureType[]) super Types
103: .toArray(new FeatureType[super Types.size()]);
104:
105: Collection attributes = new java.util.ArrayList(types);
106: for (int i = 0, ii = ancestors.length; i < ii; i++) {
107: FeatureType ancestor = ancestors[i];
108: for (int j = 0, jj = ancestor.getAttributeCount(); j < jj; j++) {
109: attributes.add(ancestor.getAttributeType(j));
110: }
111: }
112: if (attributes.size() != 0)
113: this .types = (AttributeType[]) attributes
114: .toArray(new AttributeType[attributes.size()]);
115: else
116: this .types = new AttributeType[0];
117:
118: this .defaultGeom = defaultGeom;
119:
120: attLookup = new java.util.HashMap(this .types.length);
121: for (int i = 0, ii = this .types.length; i < ii; i++) {
122: attLookup.put(this .types[i].getName(), new Integer(i));
123: }
124:
125: this .defaultGeomIdx = find(defaultGeom);
126:
127: hashCode = computeHash();
128: }
129:
130: /**
131: * Builds an empty feature type, useful for testing
132: * @throws SchemaException
133: */
134: private DefaultFeatureType() {
135: this .typeName = "emptyFeatureType";
136: namespace = FeatureTypes.DEFAULT_NAMESPACE;
137: this .types = new AttributeType[0];
138: this .ancestors = new FeatureType[0];
139: this .defaultGeomIdx = -1;
140: this .defaultGeom = null;
141: hashCode = computeHash();
142: attLookup = java.util.Collections.EMPTY_MAP;
143: }
144:
145: /**
146: * Creates a new feature, with a generated unique featureID. This is less
147: * than ideal, as a FeatureID should be persistant over time, generally
148: * created by a datasource. This method is more for testing that doesn't
149: * need featureID.
150: *
151: * @param attributes the array of attribute values
152: *
153: * @return The created feature with this as its feature type.
154: *
155: * @throws IllegalAttributeException if this FeatureType does not validate
156: * the attributes.
157: */
158: public Feature create(Object[] attributes)
159: throws IllegalAttributeException {
160: return create(attributes, null);
161: }
162:
163: /**
164: * Creates a new feature, with the proper featureID, using this
165: * FeatureType.
166: *
167: * @param attributes the array of attribute values.
168: * @param featureID the feature ID.
169: *
170: * @return the created feature.
171: *
172: * @throws IllegalAttributeException if this FeatureType does not validate
173: * the attributes.
174: */
175: public Feature create(Object[] attributes, String featureID)
176: throws IllegalAttributeException {
177: return new DefaultFeature(this , attributes, featureID);
178: }
179:
180: public Feature duplicate(Feature original)
181: throws IllegalAttributeException {
182: if (original == null)
183: return null;
184: FeatureType featureType = original.getFeatureType();
185: if (!featureType.equals(this )) {
186: throw new IllegalAttributeException("Feature type "
187: + featureType + " does not match " + this );
188: }
189: String id = original.getID();
190: int numAtts = featureType.getAttributeCount();
191: Object attributes[] = new Object[numAtts];
192: for (int i = 0; i < numAtts; i++) {
193: AttributeType curAttType = getAttributeType(i);
194: attributes[i] = curAttType.duplicate(original
195: .getAttribute(i));
196: }
197: return featureType.create(attributes, id);
198: }
199:
200: /**
201: * Gets the default geometry AttributeType. If the FeatureType has more
202: * one geometry it is up to the implementor to determine which geometry is
203: * the default. If working with multiple geometries it is best to get the
204: * attributeTypes and iterate through them, checking isGeometry on each.
205: * This should just be used a convenience method when it is known that the
206: * features are flat.
207: *
208: * @return The attribute type of the default geometry, which will contain
209: * the position.
210: *
211: * @deprecated use {@link #getPrimaryGeometry()}
212: */
213: public final GeometryAttributeType getDefaultGeometry() {
214: return getPrimaryGeometry();
215: }
216:
217: /**
218: * Gets the primary geometry AttributeType. If the FeatureType has more
219: * one geometry it is up to the implementor to determine which geometry is
220: * the default. If working with multiple geometries it is best to get the
221: * attributeTypes and iterate through them, checking isGeometry on each.
222: * This should just be used a convenience method when it is known that the
223: * features are flat.
224: *
225: * @return The attribute type of the default geometry, which will contain
226: * the position.
227: */
228: public GeometryAttributeType getPrimaryGeometry() {
229: return defaultGeom;
230: }
231:
232: /**
233: * Gets the attributeType at this xPath, if the specified attributeType
234: * does not exist then null is returned.
235: *
236: * @param xPath XPath pointer to attribute type.
237: *
238: * @return True if attribute exists.
239: */
240: public AttributeType getAttributeType(String xPath) {
241: AttributeType attType = null;
242: int idx = find(xPath);
243: if (idx >= 0)
244: attType = types[idx];
245: return attType;
246: }
247:
248: /**
249: * Find the position of a given AttributeType.
250: *
251: * @param type The type to search for.
252: *
253: * @return -1 if not found, a zero-based index if found.
254: */
255: public int find(AttributeType type) {
256: if (type == null)
257: return -1;
258: int idx = find(type.getName());
259: if (idx < 0 || !types[idx].equals(type))
260: idx = -1;
261: return idx;
262: }
263:
264: /**
265: * Find the position of an AttributeType which matches the given String.
266: * @param attName the name to look for
267: * @return -1 if not found, zero-based index otherwise
268: */
269: public int find(String attName) {
270: Integer idx = (Integer) attLookup.get(attName);
271: return idx == null ? -1 : idx.intValue();
272: }
273:
274: /**
275: * Gets the attributeType at the specified index.
276: *
277: * @param position the position of the attribute to check.
278: *
279: * @return The attribute type at the specified position.
280: */
281: public AttributeType getAttributeType(int position) {
282: return types[position];
283: }
284:
285: public AttributeType[] getAttributeTypes() {
286: return (AttributeType[]) types.clone();
287: }
288:
289: /**
290: * Gets the global schema namespace.
291: *
292: * @return Namespace of schema.
293: */
294: public URI getNamespace() {
295: return namespace;
296: }
297:
298: /**
299: * Gets the type name for this schema.
300: *
301: * @return Namespace of schema.
302: */
303: public String getTypeName() {
304: return typeName;
305: }
306:
307: /**
308: * This is only used twice in the whole geotools code base, and one of
309: * those is for a test, so we're removing it from the interface. If
310: * getAttributeType does not have the AttributeType it will just return
311: * null. Gets the number of occurrences of this attribute.
312: *
313: * @param xPath XPath pointer to attribute type.
314: *
315: * @return Number of occurrences.
316: */
317: public boolean hasAttributeType(String xPath) {
318: return getAttributeType(xPath) != null;
319: }
320:
321: /**
322: * Returns the number of attributes at the first 'level' of the schema.
323: *
324: * @return the total number of first level attributes.
325: */
326: public int getAttributeCount() {
327: return types.length;
328: }
329:
330: public boolean equals(FeatureType other) {
331: if (other == this )
332: return true;
333:
334: if (other == null) {
335: return false;
336: }
337:
338: if ((typeName == null) && (other.getTypeName() != null)) {
339: return false;
340: } else if (!typeName.equals(other.getTypeName())) {
341: return false;
342: }
343:
344: if ((namespace == null) && (other.getNamespace() != null)) {
345: return false;
346: } else if (!namespace.equals(other.getNamespace())) {
347: return false;
348: }
349:
350: if (types.length != other.getAttributeCount()) {
351: return false;
352: }
353:
354: for (int i = 0, ii = types.length; i < ii; i++) {
355: if (!types[i].equals(other.getAttributeType(i))) {
356: return false;
357: }
358: }
359:
360: return true;
361: }
362:
363: private int computeHash() {
364: int hash = typeName.hashCode() ^ namespace.hashCode();
365: for (int i = 0, ii = types.length; i < ii; i++) {
366: hash ^= types[i].hashCode();
367: }
368: return hash;
369: }
370:
371: public int hashCode() {
372: return hashCode;
373: }
374:
375: public String toString() {
376: String info = "name=" + typeName;
377: info += (" , namespace=" + namespace);
378: info += (" , abstract=" + isAbstract());
379:
380: String types1 = "types=(";
381:
382: for (int i = 0, ii = this .types.length; i < ii; i++) {
383: types1 += this .types[i].toString();
384:
385: if (i < ii) {
386: types1 += ",";
387: }
388: }
389:
390: types1 += ")";
391: info += (" , " + types1);
392:
393: return "DefaultFeatureType [" + info + "]";
394: }
395:
396: public boolean equals(Object other) {
397: if (other instanceof FeatureType)
398: return equals((FeatureType) other);
399: return false;
400: }
401:
402: /**
403: * Obtain an array of this FeatureTypes ancestors. Implementors should
404: * return a non-null array (may be of length 0).
405: *
406: * @return An array of ancestors.
407: */
408: public FeatureType[] getAncestors() {
409: return ancestors;
410: }
411:
412: /**
413: * Is this FeatureType an abstract type?
414: *
415: * @return true if abstract, false otherwise.
416: */
417: public boolean isAbstract() {
418: return false;
419: }
420:
421: /**
422: * A convenience method for calling<br>
423: * <code> FeatureType f1; FeatureType f2;
424: * f1.isDescendedFrom(f2.getNamespace(),f2.getName()); </code>
425: *
426: * @param type The type to compare to.
427: *
428: * @return true if descendant, false otherwise.
429: */
430: public boolean isDescendedFrom(FeatureType type) {
431: return isDescendedFrom(type.getNamespace(), type.getTypeName());
432: }
433:
434: /**
435: * Test to determine whether this FeatureType is descended from the given
436: * FeatureType. Think of this relationship likes the "extends"
437: * relationship in java.
438: *
439: * @param nsURI The namespace URI to use.
440: * @param typeName1 The typeName.
441: *
442: * @return true if descendant, false otherwise.
443: *
444: * @task HACK: if nsURI is null only typeName is tested.
445: */
446: public boolean isDescendedFrom(URI nsURI, String typeName1) {
447: for (int i = 0, ii = ancestors.length; i < ii; i++) {
448: if (((nsURI == null) || ancestors[i].getNamespace().equals(
449: nsURI))
450: && ancestors[i].getTypeName().equals(typeName1)) {
451: return true;
452: }
453: }
454: return false;
455: }
456:
457: static final class Abstract extends DefaultFeatureType {
458: public Abstract(String typeName, URI namespace,
459: Collection types, Collection super Types,
460: GeometryAttributeType defaultGeom)
461: throws SchemaException {
462: super (typeName, namespace, types, super Types, defaultGeom);
463:
464: Iterator st = super Types.iterator();
465:
466: while (st.hasNext()) {
467: FeatureType ft = (FeatureType) st.next();
468:
469: if (!ft.isAbstract()) {
470: throw new SchemaException(
471: "Abstract type cannot descend from no abstract type : "
472: + ft);
473: }
474: }
475: }
476:
477: public final boolean isAbstract() {
478: return true;
479: }
480:
481: public Feature create(Object[] atts)
482: throws IllegalAttributeException {
483: throw new UnsupportedOperationException("Abstract Type");
484: }
485:
486: public Feature create(Object[] atts, String id)
487: throws IllegalAttributeException {
488: throw new UnsupportedOperationException("Abstract Type");
489: }
490:
491: }
492:
493: }
|