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: // J2SE interfaces
019: import java.io.IOException;
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.Collections;
023: import java.util.Iterator;
024: import java.util.LinkedList;
025: import java.util.List;
026: import java.util.NoSuchElementException;
027: import java.util.Set;
028: import java.util.SortedMap;
029: import java.util.TreeMap;
030:
031: import org.geotools.data.DataSourceException;
032: import org.geotools.data.FeatureReader;
033: import org.geotools.feature.collection.FeatureState;
034: import org.geotools.feature.collection.FeatureIteratorImpl;
035: import org.geotools.feature.collection.SubFeatureCollection;
036: import org.geotools.feature.type.FeatureAttributeType;
037: import org.geotools.feature.visitor.FeatureVisitor;
038: import org.opengis.filter.Filter;
039: import org.opengis.filter.sort.SortBy;
040: import org.geotools.filter.SortBy2;
041: import org.geotools.geometry.jts.ReferencedEnvelope;
042: import org.geotools.util.NullProgressListener;
043: import org.geotools.util.ProgressListener;
044: import org.geotools.util.logging.Logging;
045:
046: import com.vividsolutions.jts.geom.Envelope;
047: import com.vividsolutions.jts.geom.Geometry;
048:
049: /**
050: * A basic implementation of FeatureCollection which use a {@link TreeMap} for
051: * its internal storage.
052: * <p>
053: * This should be considered a MemoryFeatureCollection.
054: * </p>
055: *
056: * @author Ian Schneider
057: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/feature/DefaultFeatureCollection.java $
058: * @version $Id: DefaultFeatureCollection.java 27905 2007-11-15 08:51:13Z desruisseaux $
059: */
060: public class DefaultFeatureCollection implements FeatureCollection {
061:
062: /**
063: * Contents of collection, referenced by FeatureID.
064: * <p>
065: * This use will result in collections that are sorted by FID, in keeping
066: * with shapefile etc...
067: * </p>
068: */
069: private SortedMap contents = new TreeMap();
070:
071: /** Internal listener storage list */
072: private List listeners = new ArrayList(2);
073:
074: /** Internal envelope of bounds. */
075: private Envelope bounds = null;
076:
077: private String id; /// fid
078:
079: /**
080: * This contructor should not be used by client code.
081: * <p>
082: * Opportunistic reuse is encouraged, but only for the purposes
083: * of testing or other specialized uses. Normal creation should
084: * occur through <code>org.geotools.core.FeatureCollections.newCollection()</code>
085: * allowing applications to customize any generated collections.
086: * </p>
087: *
088: * </p>
089: * @param id may be null ... feature id
090: * @param featureType optional, may be null
091: */
092: public DefaultFeatureCollection(String id, FeatureType featureType) {
093: this .id = id;
094: if (featureType == null) {
095: List ats = new LinkedList();
096: ats.add(new FeatureAttributeType("_Feature",
097: new DefaultFeatureType("AbstractFeatureType",
098: FeatureTypes.DEFAULT_NAMESPACE,
099: new LinkedList(), new LinkedList(), null),
100: false));
101: featureType = new DefaultFeatureType(
102: "AbstractFeatureCollectionType",
103: FeatureTypes.DEFAULT_NAMESPACE, ats,
104: new LinkedList(), null);
105: }
106: this .featureType = featureType;
107: this .childType = null; // no children yet
108: }
109:
110: private FeatureType featureType;
111: private FeatureType childType;
112:
113: public FeatureType getSchema() {
114: if (childType == null) {
115: // no children guess Features are okay then
116: new DefaultFeatureType("AbstractFeatureType",
117: FeatureTypes.DEFAULT_NAMESPACE, new LinkedList(),
118: new LinkedList(), null);
119: }
120: return childType;
121: }
122:
123: /**
124: * Gets the bounding box for the features in this feature collection.
125: *
126: * @return the envelope of the geometries contained by this feature
127: * collection.
128: */
129: public ReferencedEnvelope getBounds() {
130: if (bounds == null) {
131: bounds = new Envelope();
132:
133: for (Iterator i = contents.values().iterator(); i.hasNext();) {
134: Envelope geomBounds = ((Feature) i.next()).getBounds();
135: // IanS - as of 1.3, JTS expandToInclude ignores "null" Envelope
136: // and simply adds the new bounds...
137: // This check ensures this behavior does not occur.
138: if (!geomBounds.isNull()) {
139: bounds.expandToInclude(geomBounds);
140: }
141: }
142: }
143: return ReferencedEnvelope.reference(bounds);
144: }
145:
146: /**
147: * Adds a listener for collection events.
148: *
149: * @param listener The listener to add
150: */
151: public void addListener(CollectionListener listener) {
152: listeners.add(listener);
153: }
154:
155: /**
156: * Removes a listener for collection events.
157: *
158: * @param listener The listener to remove
159: */
160: public void removeListener(CollectionListener listener) {
161: listeners.remove(listener);
162: }
163:
164: /**
165: * To let listeners know that something has changed.
166: */
167: protected void fireChange(Feature[] features, int type) {
168: bounds = null;
169:
170: CollectionEvent cEvent = new CollectionEvent(this , features,
171: type);
172:
173: for (int i = 0, ii = listeners.size(); i < ii; i++) {
174: ((CollectionListener) listeners.get(i))
175: .collectionChanged(cEvent);
176: }
177: }
178:
179: protected void fireChange(Feature feature, int type) {
180: fireChange(new Feature[] { feature }, type);
181: }
182:
183: protected void fireChange(Collection coll, int type) {
184: Feature[] features = new Feature[coll.size()];
185: features = (Feature[]) coll.toArray(features);
186: fireChange(features, type);
187: }
188:
189: /**
190: * Ensures that this collection contains the specified element (optional
191: * operation). Returns <tt>true</tt> if this collection changed as a
192: * result of the call. (Returns <tt>false</tt> if this collection does
193: * not permit duplicates and already contains the specified element.)
194: *
195: * <p>
196: * Collections that support this operation may place limitations on what
197: * elements may be added to this collection. In particular, some
198: * collections will refuse to add <tt>null</tt> elements, and others will
199: * impose restrictions on the type of elements that may be added.
200: * Collection classes should clearly specify in their documentation any
201: * restrictions on what elements may be added.
202: * </p>
203: *
204: * <p>
205: * If a collection refuses to add a particular element for any reason other
206: * than that it already contains the element, it <i>must</i> throw an
207: * exception (rather than returning <tt>false</tt>). This preserves the
208: * invariant that a collection always contains the specified element after
209: * this call returns.
210: * </p>
211: *
212: * @param o element whose presence in this collection is to be ensured.
213: *
214: * @return <tt>true</tt> if this collection changed as a result of the call
215: */
216: public boolean add(Object o) {
217: return add((Feature) o, true);
218: }
219:
220: protected boolean add(Feature feature, boolean fire) {
221:
222: // This cast is neccessary to keep with the contract of Set!
223: if (feature == null)
224: return false; // cannot add null!
225: final String ID = feature.getID();
226: if (ID == null)
227: return false; // ID is required!
228: if (contents.containsKey(ID))
229: return false; // feature all ready present
230:
231: if (childType == null) {
232: childType = feature.getFeatureType();
233: } else {
234: if (!feature.getFeatureType().equals(childType))
235: Logging.getLogger("org.geotools.feature.collections")
236: .warning(
237: "Feature Collection contains a heterogeneous"
238: + " mix of features");
239: }
240: //TODO check inheritance with FeatureType here!!!
241: contents.put(ID, feature);
242: if (fire) {
243: fireChange(feature, CollectionEvent.FEATURES_ADDED);
244: }
245: return true;
246: }
247:
248: /**
249: * Adds all of the elements in the specified collection to this collection
250: * (optional operation). The behavior of this operation is undefined if
251: * the specified collection is modified while the operation is in
252: * progress. (This implies that the behavior of this call is undefined if
253: * the specified collection is this collection, and this collection is
254: * nonempty.)
255: *
256: * @param collection elements to be inserted into this collection.
257: *
258: * @return <tt>true</tt> if this collection changed as a result of the call
259: *
260: * @see #add(Object)
261: */
262: public boolean addAll(Collection collection) {
263: //TODO check inheritance with FeatureType here!!!
264: boolean changed = false;
265:
266: Iterator iterator = collection.iterator();
267: try {
268: List featuresAdded = new ArrayList(collection.size());
269: while (iterator.hasNext()) {
270: Feature f = (Feature) iterator.next();
271: boolean added = add(f, false);
272: changed |= added;
273:
274: if (added)
275: featuresAdded.add(f);
276: }
277:
278: if (changed) {
279: fireChange(featuresAdded,
280: CollectionEvent.FEATURES_ADDED);
281: }
282:
283: return changed;
284: } finally {
285: if (collection instanceof FeatureCollection) {
286: ((FeatureCollection) collection).close(iterator);
287: }
288: }
289: }
290:
291: /**
292: * Removes all of the elements from this collection (optional operation).
293: * This collection will be empty after this method returns unless it
294: * throws an exception.
295: */
296: public void clear() {
297: if (contents.isEmpty())
298: return;
299:
300: Feature[] oldFeatures = new Feature[contents.size()];
301: oldFeatures = (Feature[]) contents.values()
302: .toArray(oldFeatures);
303:
304: contents.clear();
305: fireChange(oldFeatures, CollectionEvent.FEATURES_REMOVED);
306: }
307:
308: /**
309: * Returns <tt>true</tt> if this collection contains the specified element.
310: * More formally, returns <tt>true</tt> if and only if this collection
311: * contains at least one element <tt>e</tt> such that <tt>(o==null ?
312: * e==null : o.equals(e))</tt>.
313: *
314: * @param o element whose presence in this collection is to be tested.
315: *
316: * @return <tt>true</tt> if this collection contains the specified element
317: */
318: public boolean contains(Object o) {
319: // The contract of Set doesn't say we have to cast here, but I think its
320: // useful for client sanity to get a ClassCastException and not just a
321: // false.
322: if (!(o instanceof Feature))
323: return false;
324:
325: Feature feature = (Feature) o;
326: final String ID = feature.getID();
327:
328: return contents.containsKey(ID); // || contents.containsValue( feature );
329: }
330:
331: /**
332: * Test for collection membership.
333: *
334: * @param collection
335: * @return true if collection is completly covered
336: */
337: public boolean containsAll(Collection collection) {
338: Iterator iterator = collection.iterator();
339: try {
340: while (iterator.hasNext()) {
341: Feature feature = (Feature) iterator.next();
342: if (!contents.containsKey(feature.getID())) {
343: return false;
344: }
345: }
346: return true;
347: } finally {
348: if (collection instanceof FeatureCollection) {
349: ((FeatureCollection) collection).close(iterator);
350: }
351: }
352: }
353:
354: /**
355: * Returns <tt>true</tt> if this collection contains no elements.
356: *
357: * @return <tt>true</tt> if this collection contains no elements
358: */
359: public boolean isEmpty() {
360: return contents.isEmpty();
361: }
362:
363: /**
364: * Returns an iterator over the elements in this collection. There are no
365: * guarantees concerning the order in which the elements are returned
366: * (unless this collection is an instance of some class that provides a
367: * guarantee).
368: *
369: * @return an <tt>Iterator</tt> over the elements in this collection
370: */
371: public Iterator iterator() {
372: final Iterator iterator = contents.values().iterator();
373:
374: return new Iterator() {
375: Feature currFeature = null;
376:
377: public boolean hasNext() {
378: return iterator.hasNext();
379: }
380:
381: public Object next() {
382: currFeature = (Feature) iterator.next();
383: return currFeature;
384: }
385:
386: public void remove() {
387: iterator.remove();
388: fireChange(currFeature,
389: CollectionEvent.FEATURES_REMOVED);
390: }
391: };
392: }
393:
394: /**
395: * Gets a FeatureIterator of this feature collection. This allows
396: * iteration without having to cast.
397: *
398: * @return the FeatureIterator for this collection.
399: */
400: public FeatureIterator features() {
401: return new FeatureIteratorImpl(this );
402: }
403:
404: /**
405: * Removes a single instance of the specified element from this collection,
406: * if it is present (optional operation). More formally, removes an
407: * element <tt>e</tt> such that <tt>(o==null ? e==null :
408: * o.equals(e))</tt>, if this collection contains one or more such
409: * elements. Returns true if this collection contained the specified
410: * element (or equivalently, if this collection changed as a result of the
411: * call).
412: *
413: * @param o element to be removed from this collection, if present.
414: *
415: * @return <tt>true</tt> if this collection changed as a result of the call
416: */
417: public boolean remove(Object o) {
418: if (!(o instanceof Feature))
419: return false;
420:
421: Feature f = (Feature) o;
422: boolean changed = contents.values().remove(f);
423:
424: if (changed) {
425: fireChange(f, CollectionEvent.FEATURES_REMOVED);
426: }
427: return changed;
428: }
429:
430: /**
431: * Removes all this collection's elements that are also contained in the
432: * specified collection (optional operation). After this call returns,
433: * this collection will contain no elements in common with the specified
434: * collection.
435: *
436: * @param collection elements to be removed from this collection.
437: *
438: * @return <tt>true</tt> if this collection changed as a result of the call
439: *
440: * @see #remove(Object)
441: * @see #contains(Object)
442: */
443: public boolean removeAll(Collection collection) {
444: boolean changed = false;
445: Iterator iterator = collection.iterator();
446: try {
447: List removedFeatures = new ArrayList(collection.size());
448: while (iterator.hasNext()) {
449: Feature f = (Feature) iterator.next();
450: boolean removed = contents.values().remove(f);
451:
452: if (removed) {
453: changed = true;
454: removedFeatures.add(f);
455: }
456: }
457:
458: if (changed) {
459: fireChange(removedFeatures,
460: CollectionEvent.FEATURES_REMOVED);
461: }
462:
463: return changed;
464: } finally {
465: if (collection instanceof FeatureCollection) {
466: ((FeatureCollection) collection).close(iterator);
467: }
468: }
469: }
470:
471: /**
472: * Retains only the elements in this collection that are contained in the
473: * specified collection (optional operation). In other words, removes
474: * from this collection all of its elements that are not contained in the
475: * specified collection.
476: *
477: * @param collection elements to be retained in this collection.
478: *
479: * @return <tt>true</tt> if this collection changed as a result of the call
480: *
481: * @see #remove(Object)
482: * @see #contains(Object)
483: */
484: public boolean retainAll(Collection collection) {
485: List removedFeatures = new ArrayList(contents.size()
486: - collection.size());
487: boolean modified = false;
488:
489: for (Iterator it = contents.values().iterator(); it.hasNext();) {
490: Feature f = (Feature) it.next();
491: if (!collection.contains(f)) {
492: it.remove();
493: modified = true;
494: removedFeatures.add(f);
495: }
496: }
497:
498: if (modified) {
499: fireChange(removedFeatures,
500: CollectionEvent.FEATURES_REMOVED);
501: }
502:
503: return modified;
504: }
505:
506: /**
507: * Returns the number of elements in this collection. If this collection
508: * contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
509: * <tt>Integer.MAX_VALUE</tt>.
510: *
511: * @return the number of elements in this collection
512: */
513: public int size() {
514: return contents.size();
515: }
516:
517: /**
518: * Returns an array containing all of the elements in this collection. If
519: * the collection makes any guarantees as to what order its elements are
520: * returned by its iterator, this method must return the elements in the
521: * same order.
522: *
523: * <p>
524: * The returned array will be "safe" in that no references to it are
525: * maintained by this collection. (In other words, this method must
526: * allocate a new array even if this collection is backed by an array).
527: * The caller is thus free to modify the returned array.
528: * </p>
529: *
530: * <p>
531: * This method acts as bridge between array-based and collection-based
532: * APIs.
533: * </p>
534: *
535: * @return an array containing all of the elements in this collection
536: */
537: public Object[] toArray() {
538: return contents.values().toArray();
539: }
540:
541: /**
542: * Returns an array containing all of the elements in this collection; the
543: * runtime type of the returned array is that of the specified array. If
544: * the collection fits in the specified array, it is returned therein.
545: * Otherwise, a new array is allocated with the runtime type of the
546: * specified array and the size of this collection.
547: *
548: * <p>
549: * If this collection fits in the specified array with room to spare (i.e.,
550: * the array has more elements than this collection), the element in the
551: * array immediately following the end of the collection is set to
552: * <tt>null</tt>. This is useful in determining the length of this
553: * collection <i>only</i> if the caller knows that this collection does
554: * not contain any <tt>null</tt> elements.)
555: * </p>
556: *
557: * <p>
558: * If this collection makes any guarantees as to what order its elements
559: * are returned by its iterator, this method must return the elements in
560: * the same order.
561: * </p>
562: *
563: * <p>
564: * Like the <tt>toArray</tt> method, this method acts as bridge between
565: * array-based and collection-based APIs. Further, this method allows
566: * precise control over the runtime type of the output array, and may,
567: * under certain circumstances, be used to save allocation costs
568: * </p>
569: *
570: * <p>
571: * Suppose <tt>l</tt> is a <tt>List</tt> known to contain only strings. The
572: * following code can be used to dump the list into a newly allocated
573: * array of <tt>String</tt>:
574: * <pre>
575: * String[] x = (String[]) v.toArray(new String[0]);
576: * </pre>
577: * </p>
578: *
579: * <p>
580: * Note that <tt>toArray(new Object[0])</tt> is identical in function to
581: * <tt>toArray()</tt>.
582: * </p>
583: *
584: * @param a the array into which the elements of this collection are to be
585: * stored, if it is big enough; otherwise, a new array of the same
586: * runtime type is allocated for this purpose.
587: *
588: * @return an array containing the elements of this collection
589: */
590: public Object[] toArray(Object[] a) {
591: return contents.values().toArray(a);
592: }
593:
594: /* (non-Javadoc)
595: * @see org.geotools.feature.FeatureCollection#getFeatureType()
596: */
597: public FeatureType getFeatureType() {
598: return featureType;
599: }
600:
601: private FeatureCollection parent;
602:
603: /* (non-Javadoc)
604: * @see org.geotools.feature.Feature#getParent()
605: */
606: public FeatureCollection getParent() {
607: // TODO deal with listeners?
608: return parent;
609: }
610:
611: /* (non-Javadoc)
612: * @see org.geotools.feature.Feature#setParent(org.geotools.feature.FeatureCollection)
613: */
614: public void setParent(FeatureCollection collection) {
615: parent = collection;
616: }
617:
618: /* (non-Javadoc)
619: * @see org.geotools.feature.Feature#getID()
620: */
621: public String getID() {
622: return id;
623: }
624:
625: /* (non-Javadoc)
626: * @see org.geotools.feature.Feature#getAttributes(java.lang.Object[])
627: */
628: public Object[] getAttributes(Object[] attributes) {
629: return toArray(attributes);
630: }
631:
632: /* (non-Javadoc)
633: * @see org.geotools.feature.Feature#getAttribute(java.lang.String)
634: */
635: public Object getAttribute(String xPath) {
636: if (xPath.indexOf(featureType.getTypeName()) > -1)
637: if (xPath.endsWith("]")) {
638: return contents.values(); // TODO get index and grab it
639: } else {
640: return contents.values();
641: }
642: return null;
643: }
644:
645: /* (non-Javadoc)
646: * @see org.geotools.feature.Feature#getAttribute(int)
647: */
648: public Object getAttribute(int index) {
649: if (index == 0) {
650: return contents.values();
651: }
652: return null;
653: }
654:
655: /* (non-Javadoc)
656: * @see org.geotools.feature.Feature#setAttribute(int, java.lang.Object)
657: */
658: public void setAttribute(int position, Object val)
659: throws IllegalAttributeException,
660: ArrayIndexOutOfBoundsException {
661: if (position == 0 && val instanceof List) {
662: List nw = (List) val;
663: if (!FeatureState.isFeatures(nw))
664: return;
665:
666: contents.clear();
667: for (Iterator i = nw.iterator(); i.hasNext();) {
668: Feature feature = (Feature) i.next();
669: contents.put(feature.getID(), feature);
670: }
671: fireChange(nw, 0);
672: }
673: }
674:
675: /* (non-Javadoc)
676: * @see org.geotools.feature.Feature#getNumberOfAttributes()
677: */
678: public int getNumberOfAttributes() {
679: return featureType == null ? 1 : featureType
680: .getAttributeCount();
681: }
682:
683: /* (non-Javadoc)
684: * @see org.geotools.feature.Feature#setAttribute(java.lang.String, java.lang.Object)
685: */
686: public void setAttribute(String xPath, Object attribute)
687: throws IllegalAttributeException {
688: if (xPath.indexOf(featureType.getTypeName()) > -1) {
689: if (xPath.endsWith("]")) {
690: // TODO get index and grab it
691: } else {
692: setAttribute(0, attribute);
693: }
694: }
695: }
696:
697: /* (non-Javadoc)
698: * @see org.geotools.feature.Feature#getDefaultGeometry()
699: */
700: public Geometry getDefaultGeometry() {
701: return null;
702: }
703:
704: /* (non-Javadoc)
705: * @see org.geotools.feature.Feature#setDefaultGeometry(com.vividsolutions.jts.geom.Geometry)
706: */
707: public void setDefaultGeometry(Geometry geometry)
708: throws IllegalAttributeException {
709: throw new IllegalAttributeException("Not Supported");
710: }
711:
712: public void close(FeatureIterator close) {
713: if (close instanceof FeatureIteratorImpl) {
714: FeatureIteratorImpl wrapper = (FeatureIteratorImpl) close;
715: wrapper.close();
716: }
717: }
718:
719: public void close(Iterator close) {
720: // nop
721: }
722:
723: public FeatureReader reader() throws IOException {
724: final FeatureIterator iterator = features();
725: return new FeatureReader() {
726: public FeatureType getFeatureType() {
727: return getSchema();
728: }
729:
730: public Feature next() throws IOException,
731: IllegalAttributeException, NoSuchElementException {
732: return iterator.next();
733: }
734:
735: public boolean hasNext() throws IOException {
736: return iterator.hasNext();
737: }
738:
739: public void close() throws IOException {
740: DefaultFeatureCollection.this .close(iterator);
741: }
742: };
743: }
744:
745: public int getCount() throws IOException {
746: return contents.size();
747: }
748:
749: public FeatureCollection collection() throws IOException {
750: FeatureCollection copy = new DefaultFeatureCollection(null,
751: featureType);
752: List list = new ArrayList(contents.size());
753: for (FeatureIterator iterator = features(); iterator.hasNext();) {
754: Feature feature = iterator.next();
755: Feature duplicate;
756: try {
757: duplicate = feature.getFeatureType().duplicate(feature);
758: } catch (IllegalAttributeException e) {
759: throw new DataSourceException("Unable to copy "
760: + feature.getID(), e);
761: }
762: list.add(duplicate);
763: }
764: copy.addAll(list);
765: return copy;
766: }
767:
768: /**
769: * Optimization time ... grab the fid set so other can quickly test membership
770: * during removeAll/retainAll implementations.
771: *
772: * @return Set of fids.
773: */
774: public Set fids() {
775: return Collections.unmodifiableSet(contents.keySet());
776: }
777:
778: /**
779: * Accepts a visitor, which then visits each feature in the collection.
780: * @throws IOException
781: */
782: public void accepts(FeatureVisitor visitor,
783: ProgressListener progress) throws IOException {
784: Iterator iterator = null;
785: if (progress == null)
786: progress = new NullProgressListener();
787: try {
788: float size = size();
789: float position = 0;
790: progress.started();
791: for (iterator = iterator(); !progress.isCanceled()
792: && iterator.hasNext(); progress.progress(position++
793: / size)) {
794: try {
795: Feature feature = (Feature) iterator.next();
796: visitor.visit(feature);
797: } catch (Exception erp) {
798: progress.exceptionOccurred(erp);
799: }
800: }
801: } finally {
802: progress.complete();
803: close(iterator);
804: }
805: }
806:
807: /**
808: * Will return an optimized subCollection based on access
809: * to the origional MemoryFeatureCollection.
810: * <p>
811: * This method is intended in a manner similar to subList,
812: * example use:
813: * <code>
814: * collection.subCollection( myFilter ).clear()
815: * </code>
816: * </p>
817: * @param filter Filter used to determine sub collection.
818: * @since GeoTools 2.2, Filter 1.1
819: */
820: public FeatureCollection subCollection(Filter filter) {
821: if (filter == Filter.INCLUDE) {
822: return this ;
823: }
824: return new SubFeatureCollection(this , filter);
825: }
826:
827: /**
828: * Construct a sorted view of this content.
829: * <p>
830: * Sorts may be combined togther in a stable fashion, in congruence
831: * with the Filter 1.1 specification.
832: * </p>
833: * <p>
834: * This method should also be able to handle GeoTools specific
835: * sorting through detecting order as a SortBy2 instance.
836: * </p>
837: *
838: * @since GeoTools 2.2, Filter 1.1
839: * @param order Filter 1.1 SortBy Construction of a Sort
840: *
841: * @return FeatureList sorted according to provided order
842: *
843: */
844: public FeatureList sort(SortBy order) {
845: if (order == SortBy.NATURAL_ORDER) {
846: // return new FeatureListImpl( this );
847: }
848: if (order instanceof SortBy2) {
849: SortBy2 advanced = (SortBy2) order;
850: return sort(advanced);
851: }
852: return null;
853: }
854:
855: /**
856: * Allows for "Advanced" sort capabilities specific to the
857: * GeoTools platform!
858: * <p>
859: * Advanced in this case really means making use of a generic
860: * Expression, rather then being limited to PropertyName.
861: * </p>
862: * @param order GeoTools SortBy
863: * @return FeatureList sorted according to provided order
864: */
865: public FeatureList sort(SortBy2 order) {
866: if (order == SortBy.NATURAL_ORDER) {
867: // forward
868: } else if (order == SortBy.REVERSE_ORDER) {
869: // backwards
870: }
871: // custom
872: return null; // new OrderedFeatureList( order, compare );
873: }
874:
875: public void purge() {
876: // no resources were harmed in the making of this FeatureCollection
877: }
878: }
|