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.data.store;
017:
018: import java.io.IOException;
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.HashSet;
022: import java.util.Iterator;
023: import java.util.LinkedList;
024: import java.util.List;
025: import java.util.NoSuchElementException;
026: import java.util.Set;
027: import java.util.logging.Level;
028: import java.util.logging.Logger;
029:
030: import org.geotools.data.FeatureReader;
031: import org.geotools.data.FeatureWriter;
032: import org.geotools.data.collection.DelegateFeatureReader;
033: import org.geotools.feature.CollectionEvent;
034: import org.geotools.feature.CollectionListener;
035: import org.geotools.feature.DefaultFeatureType;
036: import org.geotools.feature.Feature;
037: import org.geotools.feature.FeatureCollection;
038: import org.geotools.feature.FeatureIterator;
039: import org.geotools.feature.FeatureList;
040: import org.geotools.feature.FeatureType;
041: import org.geotools.feature.FeatureTypes;
042: import org.geotools.feature.IllegalAttributeException;
043: import org.geotools.feature.collection.DelegateFeatureIterator;
044: import org.geotools.feature.collection.FeatureState;
045: import org.geotools.feature.collection.SubFeatureCollection;
046: import org.geotools.feature.type.FeatureAttributeType;
047: import org.geotools.feature.visitor.FeatureVisitor;
048: import org.geotools.filter.SortBy2;
049: import org.geotools.geometry.jts.ReferencedEnvelope;
050: import org.geotools.util.NullProgressListener;
051: import org.geotools.util.ProgressListener;
052: import org.opengis.filter.Filter;
053: import org.opengis.filter.sort.SortBy;
054:
055: import com.vividsolutions.jts.geom.Envelope;
056: import com.vividsolutions.jts.geom.Geometry;
057:
058: /**
059: * A starting point for implementing FeatureCollection's backed
060: * by real data.
061: * <p>
062: * The API you are required to implement is *identical* the the barebones
063: * FeatureResults interface:
064: * <ul>
065: * <li>getSchema()
066: * <li>reader()
067: * <li>getBounds()
068: * <li>getCount()
069: * <li>collection()
070: * </p>
071: * <p>
072: * This class will implement the 'extra' methods required by FeatureCollection
073: * for you (in simple terms based on the FeatureResults API). Anything that is
074: * <i>often</i> customized is available to you as a constructor parameters.
075: * <p>
076: * Enjoy.
077: * </p>
078: * @author jgarnett
079: * @since 2.1.RC0
080: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/data/store/DataFeatureCollection.java $
081: */
082: public abstract class DataFeatureCollection implements
083: FeatureCollection {
084:
085: /** logger */
086: static Logger LOGGER = org.geotools.util.logging.Logging
087: .getLogger("org.geotools.data");
088:
089: /** Internal listener storage list */
090: private List listeners = new ArrayList(2);
091:
092: static private int unique = 0;
093:
094: /**
095: * Collection based on a generic collection
096: */
097: protected DataFeatureCollection() {
098: this ("features" + (unique++));
099: }
100:
101: /**
102: * Collection based on a generic collection
103: */
104: protected DataFeatureCollection(String id) {
105: ID = id;
106: featureType = null;
107: }
108:
109: /** Subclass must think about what consitructors it needs. */
110: protected DataFeatureCollection(String id, FeatureType featureType) {
111: ID = id;
112: this .featureType = featureType;
113: }
114:
115: /**
116: * To let listeners know that something has changed.
117: */
118: protected void fireChange(Feature[] features, int type) {
119: CollectionEvent cEvent = new CollectionEvent(this , features,
120: type);
121:
122: for (int i = 0, ii = listeners.size(); i < ii; i++) {
123: ((CollectionListener) listeners.get(i))
124: .collectionChanged(cEvent);
125: }
126: }
127:
128: protected void fireChange(Feature feature, int type) {
129: fireChange(new Feature[] { feature }, type);
130: }
131:
132: protected void fireChange(Collection coll, int type) {
133: Feature[] features = new Feature[coll.size()];
134: features = (Feature[]) coll.toArray(features);
135: fireChange(features, type);
136: }
137:
138: public FeatureReader reader() throws IOException {
139: return new DelegateFeatureReader(getSchema(), features());
140: }
141:
142: //
143: // Feature Results methods
144: //
145: // To be implemented by subclass
146: //
147: public abstract FeatureType getSchema();
148:
149: public abstract ReferencedEnvelope getBounds();
150:
151: public abstract int getCount() throws IOException;;
152:
153: //public abstract FeatureCollection collection() throws IOException;
154:
155: //
156: // Additional Subclass "hooks"
157: //
158: /**
159: * Subclass may provide an implementation of this method to indicate
160: * that read/write support is provided.
161: * <p>
162: * All operations that attempt to modify the "data" will
163: * use this method, allowing them to throw an "UnsupportedOperationException"
164: * in the same manner as Collections.unmodifiableCollection(Collection c)
165: * </p>
166: * @throws UnsupportedOperationException To indicate that write support is not avaiable
167: */
168: protected FeatureWriter writer() throws IOException {
169: throw new UnsupportedOperationException(
170: "Modification of this collection is not supported");
171: }
172:
173: //
174: // FeatureCollection methods
175: //
176: // implemented in terms of feature results
177: //
178: /**
179: * Adds a listener for collection events.
180: *
181: * @param listener The listener to add
182: */
183: public void addListener(CollectionListener listener) {
184: listeners.add(listener);
185: }
186:
187: /**
188: * Removes a listener for collection events.
189: *
190: * @param listener The listener to remove
191: */
192: public void removeListener(CollectionListener listener) {
193: listeners.remove(listener);
194: }
195:
196: //
197: // Content Access
198: //
199: /** Set of open resource iterators & featureIterators */
200: private final Set open = new HashSet();
201:
202: /**
203: * FeatureIterator is entirely based on iterator().
204: * <p>
205: * So when we implement FeatureCollection.iterator() this will work
206: * out of the box.
207: */
208: public FeatureIterator features() {
209: FeatureIterator iterator = new DelegateFeatureIterator(this ,
210: iterator());
211: open.add(iterator);
212: return iterator;
213: }
214:
215: /**
216: * Iterator may (or may) not support modification.
217: */
218: final public Iterator iterator() {
219: Iterator iterator;
220: try {
221: iterator = openIterator();
222: } catch (IOException e) {
223: throw new RuntimeException(e);
224: }
225:
226: open.add(iterator);
227: return iterator;
228: }
229:
230: /**
231: * Returns a FeatureWriterIterator, or FeatureReaderIterator over content.
232: * <p>
233: * If you have a way to tell that you are readonly please subclass with
234: * a less hardcore check - this implementations catches a
235: * UnsupportedOpperationsException from wrtier()!
236: *
237: * @return Iterator, should be closed closeIterator
238: */
239: protected Iterator openIterator() throws IOException {
240: try {
241: return new FeatureWriterIterator(writer());
242: } catch (IOException badWriter) {
243: return new NoContentIterator(badWriter);
244: } catch (UnsupportedOperationException readOnly) {
245: }
246: try {
247: return new FeatureReaderIterator(reader());
248: } catch (IOException e) {
249: return new NoContentIterator(e);
250: }
251: }
252:
253: final public void close(Iterator close) {
254: try {
255: closeIterator(close);
256: } catch (IOException e) {
257: LOGGER.log(Level.WARNING, "Error closing iterator", e);
258: }
259: open.remove(close);
260: }
261:
262: protected void closeIterator(Iterator close) throws IOException {
263: if (close == null) {
264: // iterator probably failed during consturction !
265: } else if (close instanceof FeatureReaderIterator) {
266: FeatureReaderIterator iterator = (FeatureReaderIterator) close;
267: iterator.close(); // only needs package visability
268: } else if (close instanceof FeatureWriterIterator) {
269: FeatureWriterIterator iterator = (FeatureWriterIterator) close;
270: iterator.close(); // only needs package visability
271: }
272: }
273:
274: public void close(FeatureIterator iterator) {
275: iterator.close();
276: open.remove(iterator);
277: }
278:
279: /** Default implementation based on getCount() - this may be expensive */
280: public int size() {
281: try {
282: return getCount();
283: } catch (IOException e) {
284: if (LOGGER.isLoggable(Level.FINE))
285: LOGGER
286: .log(
287: Level.FINE,
288: "IOException while calculating size() of FeatureCollection",
289: e);
290: return 0;
291: }
292: }
293:
294: public void purge() {
295: for (Iterator i = open.iterator(); i.hasNext();) {
296: Object iterator = i.next();
297: try {
298: if (iterator instanceof Iterator) {
299: closeIterator((Iterator) iterator);
300: }
301: if (iterator instanceof FeatureIterator) {
302: ((FeatureIterator) iterator).close();
303: }
304: } catch (Throwable e) {
305: // TODO: Log e = ln
306: } finally {
307: i.remove();
308: }
309: }
310: }
311:
312: //
313: // Off into implementation land!
314: //
315: /**
316: * Default implementation based on creating an reader, testing hasNext, and closing.
317: * <p>
318: * For once the Collections API does not give us an escape route, we *have* to check the data.
319: * </p>
320: */
321: public boolean isEmpty() {
322: FeatureReader reader = null;
323: try {
324: reader = reader();
325: try {
326: return !reader.hasNext();
327: } catch (IOException e) {
328: return true; // error seems like no features are available
329: }
330: } catch (IOException e) {
331: return true;
332: } finally {
333: if (reader != null) {
334: try {
335: reader.close();
336: } catch (IOException e) {
337: // return value already set
338: }
339: }
340: }
341: }
342:
343: public boolean contains(Object o) {
344: if (!(o instanceof Feature))
345: return false;
346: Feature value = (Feature) o;
347: String ID = value.getID();
348:
349: FeatureReader reader = null;
350: try {
351: reader = reader();
352: try {
353: while (reader.hasNext()) {
354: Feature feature = reader.next();
355: if (!ID.equals(feature.getID())) {
356: continue; // skip with out full equal check
357: }
358: if (value.equals(feature))
359: return true;
360: }
361: return false; // not found
362: } catch (IOException e) {
363: return false; // error seems like no features are available
364: } catch (NoSuchElementException e) {
365: return false; // error seems like no features are available
366: } catch (IllegalAttributeException e) {
367: return false; // error seems like no features are available
368: }
369: } catch (IOException e) {
370: return false;
371: } finally {
372: if (reader != null) {
373: try {
374: reader.close();
375: } catch (IOException e) {
376: // return value already set
377: }
378: }
379: }
380: }
381:
382: public Object[] toArray() {
383: return toArray(new Feature[size()]);
384: }
385:
386: public Object[] toArray(Object[] array) {
387: List list = new ArrayList();
388: Iterator i = iterator();
389: try {
390: while (i.hasNext()) {
391: list.add(i.next());
392: }
393: } finally {
394: close(i);
395: }
396: return list.toArray(array);
397: }
398:
399: public boolean add(Object arg0) {
400: return false;
401: }
402:
403: public boolean remove(Object arg0) {
404: return false;
405: }
406:
407: public boolean containsAll(Collection arg0) {
408: return false;
409: }
410:
411: /**
412: * Optimized implementation of addAll that recognizes the
413: * use of collections obtained with subCollection( filter ).
414: * <p>
415: * This method is constructed by either:
416: * <ul>
417: * <li>Filter OR
418: * <li>Removing an extact match of Filter AND
419: * </ul>
420: *
421: */
422: public boolean addAll(Collection arg0) {
423: return false;
424: }
425:
426: public boolean removeAll(Collection arg0) {
427: return false;
428: }
429:
430: public boolean retainAll(Collection arg0) {
431: return false;
432: }
433:
434: public void clear() {
435:
436: }
437:
438: //
439: // Feature methods
440: //
441: // Remember the FT model is baed on the idea of a single AttributeType
442: // of FeatureAttributeType with the value of getSchema
443: //
444: private FeatureCollection parent;
445: private final String ID;
446: /** The featureType of this actual colletion */
447: FeatureType featureType;
448:
449: /**
450: * FeatureType of this FeatureCollection.
451: * <p>
452: * Unless a FeatureType was provided during consturction (or this method is
453: * overriden) a FeatureType will be generated based on getSchmea according
454: * to the following assumptions:
455: * <ul>
456: * <li>FeatureType is gml:AbstractFeatureCollectionType
457: * <li>first attribute is getSchema.typeName
458: * <li>the attribute FeatureType the same as returned by getSchema()
459: * </ul>
460: * </p>
461: */
462: public synchronized FeatureType getFeatureType() {
463: if (featureType == null) {
464: List ats = new LinkedList();
465: ats.add(new FeatureAttributeType(getSchema().getTypeName(),
466: getSchema(), false));
467: featureType = new DefaultFeatureType(
468: "AbstractFeatureCollectionType",
469: FeatureTypes.DEFAULT_NAMESPACE, ats,
470: new LinkedList(), null);
471: }
472: return featureType;
473: }
474:
475: public FeatureCollection getParent() {
476: return parent; // TODO deal with listeners?
477: }
478:
479: public void setParent(FeatureCollection collection) {
480: parent = collection;
481: }
482:
483: public String getID() {
484: return ID;
485: }
486:
487: public Object[] getAttributes(Object[] attributes) {
488: List list = (List) getAttribute(0);
489: return list.toArray(attributes);
490: }
491:
492: /**
493: * Not really interested yet ..
494: */
495: public Object getAttribute(String xPath) {
496: if (xPath.indexOf(featureType.getTypeName()) > -1)
497: if (xPath.endsWith("]")) {
498: // TODO get index and grab it
499: return getAttribute(0);
500: } else {
501: return getAttribute(0);
502: }
503: return null;
504: }
505:
506: public Object getAttribute(int index) {
507: if (index == 0) {
508: FeatureReader reader = null;
509: try {
510: reader = reader();
511: FeatureType schema = getSchema();
512:
513: List list = new ArrayList();
514: while (reader.hasNext()) {
515: Feature feature = reader.next();
516: Feature copy = schema.duplicate(feature);
517: list.add(copy);
518: }
519: return list;
520: } catch (IOException e) {
521: return null; // could not find contents
522: } catch (NoSuchElementException e) {
523: return null; // could not find contents
524: } catch (IllegalAttributeException e) {
525: return null; // could not find contents
526: } finally {
527: if (reader != null) {
528: try {
529: reader.close();
530: } catch (IOException e) {
531: }
532: }
533: }
534: }
535: return null;
536: }
537:
538: public void setAttribute(int position, Object val)
539: throws IllegalAttributeException,
540: ArrayIndexOutOfBoundsException {
541: if (position == 0 && val instanceof Collection) {
542: Collection list = (Collection) val;
543: if (!FeatureState.isFeatures(list))
544: return;
545:
546: FeatureWriter writer = null;
547: try {
548: writer = writer(); // will error out if readOnly
549: while (writer.hasNext()) {
550: writer.next();
551: writer.remove();
552: }
553: // add in list contents
554: Feature feature = null;
555: for (Iterator i = list.iterator(); i.hasNext();) {
556: feature = (Feature) i.next();
557:
558: Feature newFeature = writer.next(); // grab a "new" Feature
559: Object values[] = feature.getAttributes(null);
560: for (int a = 0; a < values.length; a++) {
561: newFeature.setAttribute(a, values[a]);
562: }
563: writer.write();
564: }
565: } catch (IOException io) {
566: throw (ArrayIndexOutOfBoundsException) new ArrayIndexOutOfBoundsException()
567: .initCause(io);
568: } finally {
569: if (writer != null) {
570: try {
571: writer.close();
572: } catch (IOException io) {
573: throw (IllegalAttributeException) new IllegalAttributeException(
574: "Unsuccessful:" + io).initCause(io);
575: }
576: }
577: }
578: }
579: }
580:
581: public int getNumberOfAttributes() {
582: return size();
583: }
584:
585: public void setAttribute(String xPath, Object attribute)
586: throws IllegalAttributeException {
587: if (xPath.indexOf(featureType.getTypeName()) > -1) {
588: if (xPath.endsWith("]")) {
589: // TODO get index and grab it
590: } else {
591: setAttribute(0, attribute);
592: }
593: }
594: /*
595: FeatureWriter writer = null;
596: try {
597: writer = writer(); // will error out if readOnly
598: for( int index=0; writer.hasNext(); index++ ){
599: Feature feature = writer.next();
600: if( index != position ) break;
601:
602: }
603: } catch (IOException io) {
604: throw (ArrayIndexOutOfBoundsException)new ArrayIndexOutOfBoundsException().initCause( io );
605: }
606: finally {
607: if( writer != null ){
608: try {
609: writer.close();
610: } catch (IOException io) {
611: throw (IllegalAttributeException) new IllegalAttributeException("Unsuccessful:"+io).initCause( io );
612: }
613: }
614: }*/
615: }
616:
617: public Geometry getDefaultGeometry() {
618: return null;
619: }
620:
621: public void setDefaultGeometry(Geometry geometry)
622: throws IllegalAttributeException {
623: throw new IllegalAttributeException(
624: "DefaultGeometry not supported");
625: }
626:
627: /**
628: * Accepts a visitor, which then visits each feature in the collection.
629: * @throws IOException
630: */
631: public void accepts(FeatureVisitor visitor,
632: ProgressListener progress) throws IOException {
633: Iterator iterator = null;
634: if (progress == null)
635: progress = new NullProgressListener();
636: try {
637: float size = size();
638: float position = 0;
639: progress.started();
640: for (iterator = iterator(); !progress.isCanceled()
641: && iterator.hasNext(); progress.progress(position++
642: / size)) {
643: try {
644: Feature feature = (Feature) iterator.next();
645: visitor.visit(feature);
646: } catch (Exception erp) {
647: progress.exceptionOccurred(erp);
648: }
649: }
650: } finally {
651: progress.complete();
652: close(iterator);
653: }
654: }
655:
656: /**
657: * Will return an optimized subCollection based on access
658: * to the origional FeatureSource.
659: * <p>
660: * The subCollection is constructed by using an AND Filter.
661: * For the converse of this opperation please see
662: * collection.addAll( Collection ), it has been optimized
663: * to be aware of these filter based SubCollections.
664: * </p>
665: * <p>
666: * This method is intended in a manner similar to subList,
667: * example use:
668: * <code>
669: * collection.subCollection( myFilter ).clear()
670: * </code>
671: * </p>
672: * @param filter Filter used to determine sub collection.
673: * @since GeoTools 2.2, Filter 1.1
674: */
675: public FeatureCollection subCollection(Filter filter) {
676: if (filter == Filter.INCLUDE) {
677: return this ;
678: }
679: return new SubFeatureCollection(this , filter);
680: }
681:
682: /**
683: * Construct a sorted view of this content.
684: * <p>
685: * Sorts may be combined togther in a stable fashion, in congruence
686: * with the Filter 1.1 specification.
687: * </p>
688: * This method should also be able to handle GeoTools specific
689: * sorting through detecting order as a SortBy2 instance.
690: *
691: * @param order
692: *
693: * @since GeoTools 2.2, Filter 1.1
694: * @return FeatureList sorted according to provided order
695:
696: */
697: public FeatureList sort(SortBy order) {
698: if (order instanceof SortBy2) {
699: SortBy2 advanced = (SortBy2) order;
700: return sort(advanced);
701: }
702: return null; // new OrderedFeatureList( this, order );
703: }
704:
705: /**
706: * Allows for "Advanced" sort capabilities specific to the
707: * GeoTools platform!
708: * <p>
709: * Advanced in this case really means making use of a generic
710: * Expression, rather then being limited to PropertyName.
711: * </p>
712: * @param order GeoTools SortBy
713: * @return FeatureList sorted according to provided order
714: */
715: public FeatureList sort(SortBy2 order) {
716: return null;
717: }
718: }
|