001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-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.feature.memory;
017:
018: import java.io.IOException;
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.LinkedList;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.NoSuchElementException;
027:
028: import org.geotools.catalog.ServiceInfo;
029: import org.geotools.data.AbstractDataStore;
030: import org.geotools.data.DataSourceException;
031: import org.geotools.data.DataUtilities;
032: import org.geotools.data.FeatureReader;
033: import org.geotools.data.FeatureWriter;
034: import org.geotools.data.Query;
035: import org.geotools.data.SchemaNotFoundException;
036: import org.geotools.data.Source;
037: import org.geotools.data.Transaction;
038: import org.geotools.data.feature.FeatureAccess;
039: import org.geotools.data.feature.adapter.GTFeatureAdapter;
040: import org.geotools.data.feature.adapter.GTFeatureTypeAdapter;
041: import org.geotools.data.feature.adapter.ISOFeatureAdapter;
042: import org.geotools.data.feature.adapter.ISOFeatureTypeAdapter;
043: import org.geotools.feature.IllegalAttributeException;
044: import org.geotools.feature.iso.simple.SimpleFeatureFactoryImpl;
045: import org.geotools.feature.iso.type.TypeFactoryImpl;
046: import org.geotools.geometry.jts.ReferencedEnvelope;
047: import org.opengis.feature.Feature;
048: import org.opengis.feature.FeatureCollection;
049: import org.opengis.feature.simple.SimpleFeature;
050: import org.opengis.feature.simple.SimpleFeatureFactory;
051: import org.opengis.feature.simple.SimpleFeatureType;
052: import org.opengis.feature.type.AttributeDescriptor;
053: import org.opengis.feature.type.FeatureType;
054: import org.opengis.feature.type.Name;
055: import org.opengis.feature.type.TypeFactory;
056: import org.opengis.filter.Filter;
057:
058: import com.vividsolutions.jts.geom.Envelope;
059:
060: /**
061: * This is an example implementation of a DataStore used for testing.
062: *
063: * <p>
064: * It serves as an example implementation of:
065: * </p>
066: *
067: * <ul>
068: * <li> FeatureListenerManager use: allows handling of FeatureEvents </li>
069: * </ul>
070: *
071: * <p>
072: * This class will also illustrate the use of In-Process locking when the time
073: * comes.
074: * </p>
075: *
076: * @author jgarnett
077: * @source $URL:
078: * http://gtsvn.refractions.net/geotools/trunk/gt/modules/library/main/src/main/java/org/geotools/data/memory/MemoryDataStore.java $
079: */
080: public class MemoryDataAccess extends AbstractDataStore implements
081: FeatureAccess {
082: /** Memory holds Map of Feature by fid by typeName. */
083: protected Map memory = new HashMap();
084:
085: /** Schema holds FeatureType by typeName */
086: protected Map schema = new HashMap();
087:
088: private SimpleFeatureFactory attributeFactory = new SimpleFeatureFactoryImpl();
089:
090: public MemoryDataAccess() {
091: super (true);
092: }
093:
094: public MemoryDataAccess(FeatureCollection collection) {
095: addFeatures(collection);
096: }
097:
098: public MemoryDataAccess(Feature[] array) {
099: addFeatures(array);
100: }
101:
102: public MemoryDataAccess(
103: org.geotools.feature.FeatureCollection collection) {
104: org.geotools.feature.Feature[] features = (org.geotools.feature.Feature[]) collection
105: .toArray(new org.geotools.feature.Feature[0]);
106: addFeatures(features);
107: }
108:
109: public MemoryDataAccess(org.geotools.feature.Feature[] features) {
110: addFeatures(features);
111: }
112:
113: public MemoryDataAccess(FeatureReader reader)
114: throws NoSuchElementException, IOException,
115: IllegalAttributeException {
116: try {
117: ISOFeatureTypeAdapter isoType = null;
118: while (reader.hasNext()) {
119: org.geotools.feature.Feature feature = reader.next();
120: if (isoType == null) {
121: isoType = new ISOFeatureTypeAdapter(feature
122: .getFeatureType());
123: }
124: Feature isoFeature = new ISOFeatureAdapter(feature,
125: isoType, attributeFactory);
126: addFeatureInternal(isoFeature);
127: }
128: } finally {
129: reader.close();
130: }
131: }
132:
133: public void addFeatures(FeatureCollection collection) {
134: Iterator iterator = collection.iterator();
135: List features = new LinkedList();
136: Feature f;
137: try {
138: while (iterator.hasNext()) {
139: f = (Feature) iterator.next();
140: features.add(f);
141: }
142: } finally {
143: collection.close(iterator);
144: }
145: addFeatures(features);
146: }
147:
148: /**
149: * Configures MemoryDataStore with Collection.
150: *
151: * <p>
152: * You may use this to create a MemoryDataStore from a FeatureCollection.
153: * </p>
154: *
155: * @param collection
156: * Collection of features to add
157: *
158: * @throws IllegalArgumentException
159: * If provided collection is empty
160: */
161: public void addFeatures(Collection collection) {
162: if ((collection == null) || collection.isEmpty()) {
163: throw new IllegalArgumentException(
164: "Provided FeatureCollection is empty");
165: }
166:
167: synchronized (memory) {
168: for (Iterator i = collection.iterator(); i.hasNext();) {
169: addFeatureInternal((Feature) i.next());
170: }
171: }
172: }
173:
174: /**
175: * Configures MemoryDataStore with feature array.
176: *
177: * @param features
178: * Array of features to add
179: *
180: * @throws IllegalArgumentException
181: * If provided feature array is empty
182: */
183: public void addFeatures(Feature[] features) {
184: if ((features == null) || (features.length == 0)) {
185: throw new IllegalArgumentException(
186: "Provided features are empty");
187: }
188:
189: synchronized (memory) {
190: for (int i = 0; i < features.length; i++) {
191: addFeatureInternal(features[i]);
192: }
193: }
194: }
195:
196: public void addFeatures(org.geotools.feature.Feature[] features) {
197: if ((features == null) || (features.length == 0)) {
198: throw new IllegalArgumentException(
199: "Provided features are empty");
200: }
201:
202: synchronized (memory) {
203: ISOFeatureTypeAdapter isoType = new ISOFeatureTypeAdapter(
204: features[0].getFeatureType());
205: for (int i = 0; i < features.length; i++) {
206: org.geotools.feature.Feature feature = features[i];
207: Feature isoFeature = new ISOFeatureAdapter(feature,
208: isoType, attributeFactory);
209: addFeatureInternal(isoFeature);
210: }
211: }
212: }
213:
214: /**
215: * Adds a single Feature to the correct typeName entry.
216: *
217: * <p>
218: * This is an internal opperation used for setting up MemoryDataStore -
219: * please use FeatureWriter for generatl use.
220: * </p>
221: *
222: * <p>
223: * This method is willing to create new FeatureTypes for MemoryDataStore.
224: * </p>
225: *
226: * @param feature
227: * Individual feature to add
228: */
229: public void addFeature(org.geotools.feature.Feature feature) {
230: synchronized (memory) {
231: Feature f = new ISOFeatureAdapter(feature, null,
232: attributeFactory);
233: addFeatureInternal(f);
234: }
235: }
236:
237: public void addFeatureInternal(Feature feature) {
238: if (feature == null) {
239: throw new IllegalArgumentException(
240: "Provided Feature is empty");
241: }
242:
243: FeatureType featureType;
244: featureType = (FeatureType) feature.getType();
245:
246: Name typeName = featureType.getName();
247:
248: Map featuresMap;
249:
250: if (!memory.containsKey(typeName)) {
251: try {
252: createSchemaInternal(featureType);
253: } catch (IOException e) {
254: // this should not of happened ?!?
255: // only happens if typeNames is taken and
256: // we just checked
257: }
258: }
259:
260: featuresMap = (Map) memory.get(typeName);
261: featuresMap.put(feature.getID(), feature);
262: }
263:
264: protected Map features(String typeName) throws IOException {
265: Name name = typeName(typeName);
266: return features(name);
267: }
268:
269: /**
270: * Access featureMap for typeName.
271: *
272: * @param typeName
273: *
274: * @return A Map of Features by FID
275: *
276: * @throws IOException
277: * If typeName cannot be found
278: */
279: protected Map features(Name typeName) throws IOException {
280: synchronized (memory) {
281: if (memory.containsKey(typeName)) {
282: return (Map) memory.get(typeName);
283: }
284: }
285:
286: throw new IOException("Type name " + typeName + " not found");
287: }
288:
289: /**
290: * List of available types provided by this DataStore.
291: *
292: * @return Array of type names
293: *
294: * @see org.geotools.data.AbstractDataStore#getFeatureTypes()
295: */
296: public String[] getTypeNames() {
297: synchronized (memory) {
298: String[] types = new String[schema.size()];
299: int index = 0;
300:
301: for (Iterator i = schema.keySet().iterator(); i.hasNext(); index++) {
302: Name next = (Name) i.next();
303: types[index] = next.getLocalPart();
304: }
305:
306: return types;
307: }
308: }
309:
310: /**
311: * FeatureType access by <code>typeName</code>.
312: *
313: * @param typeName
314: *
315: * @return FeatureType for <code>typeName</code>
316: *
317: * @throws IOException
318: * @throws SchemaNotFoundException
319: * DOCUMENT ME!
320: *
321: * @see org.geotools.data.AbstractDataStore#getSchema(java.lang.String)
322: */
323: public org.geotools.feature.FeatureType getSchema(String typeName)
324: throws IOException {
325: FeatureType isoType = getSchemaInternal(typeName);
326: synchronized (memory) {
327: if (!(isoType instanceof SimpleFeatureType)) {
328: throw new IllegalArgumentException(
329: "Do not ask getSchema for non simple types: "
330: + typeName);
331: }
332: org.geotools.feature.FeatureType gtType;
333: if (isoType instanceof ISOFeatureTypeAdapter) {
334: gtType = ((ISOFeatureTypeAdapter) isoType).getAdaptee();
335: } else {
336: gtType = new GTFeatureTypeAdapter(
337: (SimpleFeatureType) isoType);
338: }
339: return gtType;
340: }
341: }
342:
343: public FeatureType getSchemaInternal(String typeName)
344: throws SchemaNotFoundException {
345: Name name = typeName(typeName);
346: return getSchemaInternal(name);
347: }
348:
349: public FeatureType getSchemaInternal(Name name)
350: throws SchemaNotFoundException {
351: synchronized (memory) {
352: if (schema.containsKey(name)) {
353: FeatureType isoType = (FeatureType) schema.get(name);
354: return isoType;
355: }
356: throw new SchemaNotFoundException(String.valueOf(name));
357: }
358: }
359:
360: private Name typeName(String typeName)
361: throws SchemaNotFoundException {
362: for (Iterator it = schema.keySet().iterator(); it.hasNext();) {
363: Name name = (Name) it.next();
364: if (name.getLocalPart().equals(typeName)) {
365: return name;
366: }
367: }
368: throw new SchemaNotFoundException(typeName);
369: }
370:
371: /**
372: * Adds support for a new featureType to MemoryDataStore.
373: *
374: * <p>
375: * FeatureTypes are stored by typeName, an IOException will be thrown if the
376: * requested typeName is already in use.
377: * </p>
378: *
379: * @param featureType
380: * FeatureType to be added
381: *
382: * @throws IOException
383: * If featureType already exists
384: *
385: * @see org.geotools.data.DataStore#createSchema(org.geotools.feature.FeatureType)
386: */
387: public void createSchema(
388: org.geotools.feature.FeatureType featureType)
389: throws IOException {
390:
391: SimpleFeatureType isoType = new ISOFeatureTypeAdapter(
392: featureType);
393:
394: createSchemaInternal(isoType);
395: }
396:
397: public void createSchemaInternal(FeatureType isoType)
398: throws IOException {
399:
400: Name typeName = isoType.getName();
401:
402: if (memory.containsKey(typeName)) {
403: // we have a conflict
404: throw new IOException(typeName + " already exists");
405: }
406: Map featuresMap = new java.util.LinkedHashMap();// HashMap();
407: schema.put(typeName, isoType);
408: memory.put(typeName, featuresMap);
409: }
410:
411: /**
412: * Provides FeatureReader over the entire contents of <code>typeName</code>.
413: *
414: * <p>
415: * Implements getFeatureReader contract for AbstractDataStore.
416: * </p>
417: *
418: * @param typeName
419: *
420: *
421: * @throws IOException
422: * If typeName could not be found
423: * @throws DataSourceException
424: * See IOException
425: *
426: * @see org.geotools.data.AbstractDataStore#getFeatureSource(java.lang.String)
427: */
428: public FeatureReader getFeatureReader(final String typeName)
429: throws IOException {
430: final FeatureType featureType = getSchemaInternal(typeName);
431:
432: if (!(featureType instanceof SimpleFeatureType)) {
433: throw new IllegalArgumentException(
434: "do not use getFeatureReader for non simple Features");
435: }
436:
437: final org.geotools.feature.FeatureType gtFType;
438: if (featureType instanceof ISOFeatureTypeAdapter) {
439: gtFType = ((ISOFeatureTypeAdapter) featureType)
440: .getAdaptee();
441: } else {
442: gtFType = new GTFeatureTypeAdapter(
443: (SimpleFeatureType) featureType);
444: }
445:
446: return new FeatureReader() {
447:
448: Name name = typeName(typeName);
449:
450: Iterator iterator = features(name).values().iterator();
451:
452: public org.geotools.feature.FeatureType getFeatureType() {
453: return gtFType;
454: }
455:
456: public org.geotools.feature.Feature next()
457: throws IOException, IllegalAttributeException,
458: NoSuchElementException {
459: if (iterator == null) {
460: throw new IOException(
461: "Feature Reader has been closed");
462: }
463:
464: try {
465: Object obj = iterator.next();
466:
467: org.geotools.feature.Feature gtFeature;
468:
469: if (obj instanceof org.geotools.feature.Feature) {
470: // it might be we're under a decorator reader
471: gtFeature = (org.geotools.feature.Feature) obj;
472: } else if (obj instanceof ISOFeatureAdapter) {
473: ISOFeatureAdapter featureAdapter = (ISOFeatureAdapter) obj;
474: gtFeature = (featureAdapter).getAdaptee();
475: } else {
476: SimpleFeature simpleFeature = (SimpleFeature) obj;
477: gtFeature = new GTFeatureAdapter(simpleFeature,
478: gtFType);
479: }
480:
481: return gtFType.duplicate(gtFeature);
482: } catch (NoSuchElementException end) {
483: throw new DataSourceException(
484: "There are no more Features", end);
485: }
486: }
487:
488: public boolean hasNext() {
489: return (iterator != null) && iterator.hasNext();
490: }
491:
492: public void close() {
493: if (iterator != null) {
494: iterator = null;
495: }
496: }
497: };
498: }
499:
500: /**
501: * Provides FeatureWriter over the entire contents of <code>typeName</code>.
502: *
503: * <p>
504: * Implements getFeatureWriter contract for AbstractDataStore.
505: * </p>
506: *
507: * @param typeName
508: * name of FeatureType we wish to modify
509: *
510: * @return FeatureWriter of entire contents of typeName
511: *
512: * @throws IOException
513: * If writer cannot be obtained for typeName
514: * @throws DataSourceException
515: * See IOException
516: *
517: * @see org.geotools.data.AbstractDataStore#getFeatureSource(java.lang.String)
518: */
519: public FeatureWriter createFeatureWriter(final String typeName,
520: final Transaction transaction) throws IOException {
521:
522: final FeatureType featureType = getSchemaInternal(typeName);
523:
524: if (!(featureType instanceof SimpleFeatureType)) {
525: throw new IllegalArgumentException(
526: "do not use getFeatureWriter for non simple Features");
527: }
528:
529: final org.geotools.feature.FeatureType gtFType;
530: if (featureType instanceof ISOFeatureTypeAdapter) {
531: gtFType = ((ISOFeatureTypeAdapter) featureType)
532: .getAdaptee();
533: } else {
534: gtFType = new GTFeatureTypeAdapter(
535: (SimpleFeatureType) featureType);
536: }
537:
538: return new FeatureWriter() {
539:
540: Map contents = features(featureType.getName());
541:
542: Iterator iterator = contents.values().iterator();
543:
544: SimpleFeature live = null;
545:
546: org.geotools.feature.SimpleFeature gtLive = null;
547:
548: org.geotools.feature.SimpleFeature current = null; // current
549:
550: // Feature
551:
552: // returned to user
553:
554: public org.geotools.feature.FeatureType getFeatureType() {
555: return gtFType;
556: }
557:
558: public org.geotools.feature.Feature next()
559: throws IOException, NoSuchElementException {
560: if (hasNext()) {
561: // existing content
562: live = (SimpleFeature) iterator.next();
563: if (live instanceof ISOFeatureAdapter) {
564: gtLive = (org.geotools.feature.SimpleFeature) ((ISOFeatureAdapter) live)
565: .getAdaptee();
566: } else {
567: gtLive = new GTFeatureAdapter(live, gtFType);
568: }
569: try {
570: current = (org.geotools.feature.SimpleFeature) gtFType
571: .duplicate(gtLive);
572: } catch (IllegalAttributeException e) {
573: throw new DataSourceException("Unable to edit "
574: + live.getID() + " of " + typeName);
575: }
576: } else {
577: // new content
578: live = null;
579:
580: try {
581: current = (org.geotools.feature.SimpleFeature) DataUtilities
582: .template(gtFType);
583: } catch (IllegalAttributeException e) {
584: throw new DataSourceException(
585: "Unable to add additional Features of "
586: + typeName);
587: }
588: }
589:
590: return current;
591: }
592:
593: public void remove() throws IOException {
594: if (contents == null) {
595: throw new IOException(
596: "FeatureWriter has been closed");
597: }
598:
599: if (current == null) {
600: throw new IOException(
601: "No feature available to remove");
602: }
603:
604: if (live != null) {
605: // remove existing content
606: iterator.remove();
607: listenerManager.fireFeaturesRemoved(typeName,
608: transaction, gtLive.getBounds(), true);
609: live = null;
610: current = null;
611: } else {
612: // cancel add new content
613: current = null;
614: }
615: }
616:
617: public void write() throws IOException {
618: if (contents == null) {
619: throw new IOException(
620: "FeatureWriter has been closed");
621: }
622:
623: if (current == null) {
624: throw new IOException(
625: "No feature available to write");
626: }
627:
628: if (live != null) {
629: if (live.equals(current)) {
630: // no modifications made to current
631: //
632: live = null;
633: current = null;
634: } else {
635: // accept modifications
636: //
637: try {
638: gtLive.setAttributes(current
639: .getAttributes(null));
640: } catch (IllegalAttributeException e) {
641: throw new DataSourceException(
642: "Unable to accept modifications to "
643: + live.getID() + " on "
644: + typeName);
645: }
646:
647: Envelope bounds = new Envelope();
648: bounds.expandToInclude(gtLive.getBounds());
649: bounds.expandToInclude(current.getBounds());
650: listenerManager.fireFeaturesChanged(typeName,
651: transaction, bounds, true);
652: live = null;
653: current = null;
654: }
655: } else {
656: // add new content
657: //
658: contents.put(current.getID(), current);
659: listenerManager.fireFeaturesAdded(typeName,
660: transaction, current.getBounds(), true);
661: current = null;
662: }
663: }
664:
665: public boolean hasNext() throws IOException {
666: if (contents == null) {
667: throw new IOException(
668: "FeatureWriter has been closed");
669: }
670:
671: return (iterator != null) && iterator.hasNext();
672: }
673:
674: public void close() {
675: if (iterator != null) {
676: iterator = null;
677: }
678: contents = null;
679: current = null;
680: live = null;
681: }
682: };
683: }
684:
685: /**
686: * @see org.geotools.data.AbstractDataStore#getBounds(java.lang.String,
687: * org.geotools.data.Query)
688: */
689: protected Envelope getBounds(Query query) throws IOException {
690: String typeName = query.getTypeName();
691: Name name = typeName(typeName);
692:
693: Map contents = features(name);
694:
695: Iterator iterator = contents.values().iterator();
696:
697: ReferencedEnvelope envelope = null;
698:
699: if (iterator.hasNext()) {
700: int count = 1;
701: Filter filter = query.getFilter();
702: Feature first = (Feature) iterator.next();
703:
704: envelope = new ReferencedEnvelope(first.getBounds());
705: // envelope.init(first.getBounds());
706:
707: while (iterator.hasNext()
708: && (count < query.getMaxFeatures())) {
709: Feature feature = (Feature) iterator.next();
710:
711: if (filter.evaluate(feature)) {
712: count++;
713: envelope.include(feature.getBounds());
714: }
715: }
716: }
717:
718: return envelope;
719: }
720:
721: /**
722: * @see org.geotools.data.AbstractDataStore#getCount(java.lang.String,
723: * org.geotools.data.Query)
724: */
725: protected int getCount(Query query) throws IOException {
726: String typeName = query.getTypeName();
727: Name name = typeName(typeName);
728: Map contents = features(name);
729: Iterator iterator = contents.values().iterator();
730:
731: int count = 0;
732:
733: Filter filter = query.getFilter();
734:
735: while (iterator.hasNext() && (count < query.getMaxFeatures())) {
736: if (filter.evaluate(iterator.next())) {
737: count++;
738: }
739: }
740:
741: return count;
742: }
743:
744: // ////////// DataAcess implementation //////////////
745:
746: public Source access(Name typeName) {
747: FeatureType type;
748: try {
749: type = getSchemaInternal(typeName);
750: } catch (SchemaNotFoundException e) {
751: throw new NoSuchElementException(e.getMessage());
752: }
753: Map map;
754: try {
755: map = features(typeName);
756: } catch (IOException e) {
757: throw (RuntimeException) new RuntimeException()
758: .initCause(e);
759: }
760: Collection collection = map.values();
761: return new MemorySource(this , type, collection);
762: }
763:
764: public Object describe(Name typeName) {
765: FeatureType ftype;
766: try {
767: ftype = getSchemaInternal(typeName);
768: } catch (SchemaNotFoundException e) {
769: throw new NoSuchElementException(e.getMessage());
770: }
771: TypeFactory tf = new TypeFactoryImpl();
772: AttributeDescriptor descriptor = tf.createAttributeDescriptor(
773: ftype, ftype.getName(), 0, Integer.MAX_VALUE, true,
774: null);
775: return descriptor;
776: }
777:
778: public void dispose() {
779: // TODO Auto-generated method stub
780: }
781:
782: public ServiceInfo getInfo() {
783: return null;
784: }
785:
786: public List/* <Name> */getNames() {
787: List names = new ArrayList(schema.keySet());
788: return names;
789: }
790: }
|