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.memory;
017:
018: import java.io.IOException;
019: import java.util.Collection;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.LinkedHashMap;
023: import java.util.Map;
024: import java.util.NoSuchElementException;
025:
026: import org.geotools.data.AbstractDataStore;
027: import org.geotools.data.DataSourceException;
028: import org.geotools.data.DataUtilities;
029: import org.geotools.data.FeatureReader;
030: import org.geotools.data.FeatureWriter;
031: import org.geotools.data.Query;
032: import org.geotools.data.SchemaNotFoundException;
033: import org.geotools.data.Transaction;
034: import org.geotools.feature.Feature;
035: import org.geotools.feature.FeatureCollection;
036: import org.geotools.feature.FeatureIterator;
037: import org.geotools.feature.FeatureType;
038: import org.geotools.feature.IllegalAttributeException;
039: import org.geotools.feature.SimpleFeature;
040: import org.opengis.filter.Filter;
041:
042: import com.vividsolutions.jts.geom.Envelope;
043:
044: /**
045: * This is an example implementation of a DataStore used for testing.
046: *
047: * <p>
048: * It serves as an example implementation of:
049: * </p>
050: *
051: * <ul>
052: * <li>
053: * FeatureListenerManager use: allows handling of FeatureEvents
054: * </li>
055: * </ul>
056: *
057: * <p>
058: * This class will also illustrate the use of In-Process locking when the time comes.
059: * </p>
060: *
061: * @author jgarnett
062: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/data/memory/MemoryDataStore.java $
063: */
064: public class MemoryDataStore extends AbstractDataStore {
065: /** Memory holds Map of Feature by fid by typeName. */
066: protected Map memory = new HashMap();
067:
068: /** Schema holds FeatureType by typeName */
069: protected Map schema = new HashMap();
070:
071: public MemoryDataStore() {
072: super (true);
073: }
074:
075: public MemoryDataStore(FeatureCollection collection) {
076: addFeatures(collection);
077: }
078:
079: public MemoryDataStore(Feature[] array) {
080: addFeatures(array);
081: }
082:
083: public MemoryDataStore(FeatureReader reader) throws IOException {
084: addFeatures(reader);
085: }
086:
087: public MemoryDataStore(FeatureIterator reader) throws IOException {
088: addFeatures(reader);
089: }
090:
091: /**
092: * Configures MemoryDataStore with FeatureReader.
093: *
094: * @param reader New contents to add
095: *
096: * @throws IOException If problems are encountered while adding
097: * @throws DataSourceException See IOException
098: */
099: public void addFeatures(FeatureReader reader) throws IOException {
100: try {
101: FeatureType featureType;
102: // use an order preserving map, so that features are returned in the same
103: // order as they were inserted. This is important for repeatable rendering
104: // of overlapping features.
105: Map featureMap = new LinkedHashMap();
106: String typeName;
107: Feature feature;
108:
109: feature = reader.next();
110:
111: if (feature == null) {
112: throw new IllegalArgumentException(
113: "Provided FeatureReader is closed");
114: }
115:
116: featureType = feature.getFeatureType();
117: typeName = featureType.getTypeName();
118:
119: featureMap.put(feature.getID(), feature);
120:
121: while (reader.hasNext()) {
122: feature = reader.next();
123: featureMap.put(feature.getID(), feature);
124: }
125:
126: schema.put(typeName, featureType);
127: memory.put(typeName, featureMap);
128: } catch (IllegalAttributeException e) {
129: throw new DataSourceException("Problem using reader", e);
130: } finally {
131: reader.close();
132: }
133: }
134:
135: /**
136: * Configures MemoryDataStore with FeatureReader.
137: *
138: * @param reader New contents to add
139: *
140: * @throws IOException If problems are encountered while adding
141: * @throws DataSourceException See IOException
142: */
143: public void addFeatures(FeatureIterator reader) throws IOException {
144: try {
145: FeatureType featureType;
146: Map featureMap = new HashMap();
147: String typeName;
148: Feature feature;
149:
150: feature = reader.next();
151:
152: if (feature == null) {
153: throw new IllegalArgumentException(
154: "Provided FeatureReader is closed");
155: }
156:
157: featureType = feature.getFeatureType();
158: typeName = featureType.getTypeName();
159:
160: featureMap.put(feature.getID(), feature);
161:
162: while (reader.hasNext()) {
163: feature = reader.next();
164: featureMap.put(feature.getID(), feature);
165: }
166:
167: schema.put(typeName, featureType);
168: memory.put(typeName, featureMap);
169: } finally {
170: reader.close();
171: }
172: }
173:
174: /**
175: * Configures MemoryDataStore with Collection.
176: *
177: * <p>
178: * You may use this to create a MemoryDataStore from a FeatureCollection.
179: * </p>
180: *
181: * @param collection Collection of features to add
182: *
183: * @throws IllegalArgumentException If provided collection is empty
184: */
185: public void addFeatures(Collection collection) {
186: if ((collection == null) || collection.isEmpty()) {
187: throw new IllegalArgumentException(
188: "Provided FeatureCollection is empty");
189: }
190:
191: synchronized (memory) {
192: for (Iterator i = collection.iterator(); i.hasNext();) {
193: addFeatureInternal((Feature) i.next());
194: }
195: }
196: }
197:
198: /**
199: * Configures MemoryDataStore with feature array.
200: *
201: * @param features Array of features to add
202: *
203: * @throws IllegalArgumentException If provided feature array is empty
204: */
205: public void addFeatures(Feature[] features) {
206: if ((features == null) || (features.length == 0)) {
207: throw new IllegalArgumentException(
208: "Provided features are empty");
209: }
210:
211: synchronized (memory) {
212: for (int i = 0; i < features.length; i++) {
213: addFeatureInternal(features[i]);
214: }
215: }
216: }
217:
218: /**
219: * Adds a single Feature to the correct typeName entry.
220: *
221: * <p>
222: * This is an internal opperation used for setting up MemoryDataStore - please use
223: * FeatureWriter for generatl use.
224: * </p>
225: *
226: * <p>
227: * This method is willing to create new FeatureTypes for MemoryDataStore.
228: * </p>
229: *
230: * @param feature Individual feature to add
231: */
232: public void addFeature(Feature feature) {
233: synchronized (memory) {
234: addFeatureInternal(feature);
235: }
236: }
237:
238: private void addFeatureInternal(Feature feature) {
239: if (feature == null) {
240: throw new IllegalArgumentException(
241: "Provided Feature is empty");
242: }
243:
244: FeatureType featureType;
245: featureType = feature.getFeatureType();
246:
247: String typeName = featureType.getTypeName();
248:
249: Map featuresMap;
250:
251: if (!memory.containsKey(typeName)) {
252: try {
253: createSchema(featureType);
254: } catch (IOException e) {
255: // this should not of happened ?!?
256: // only happens if typeNames is taken and
257: // we just checked
258: }
259: }
260:
261: featuresMap = (Map) memory.get(typeName);
262: featuresMap.put(feature.getID(), feature);
263: }
264:
265: /**
266: * Access featureMap for typeName.
267: *
268: * @param typeName
269: *
270: * @return A Map of Features by FID
271: *
272: * @throws IOException If typeName cannot be found
273: */
274: protected Map features(String typeName) throws IOException {
275: synchronized (memory) {
276: if (memory.containsKey(typeName)) {
277: return (Map) memory.get(typeName);
278: }
279: }
280:
281: throw new IOException("Type name " + typeName + " not found");
282: }
283:
284: /**
285: * List of available types provided by this DataStore.
286: *
287: * @return Array of type names
288: *
289: * @see org.geotools.data.AbstractDataStore#getFeatureTypes()
290: */
291: public String[] getTypeNames() {
292: synchronized (memory) {
293: String[] types = new String[schema.size()];
294: int index = 0;
295:
296: for (Iterator i = schema.keySet().iterator(); i.hasNext(); index++) {
297: types[index] = (String) i.next();
298: }
299:
300: return types;
301: }
302: }
303:
304: /**
305: * FeatureType access by <code>typeName</code>.
306: *
307: * @param typeName
308: *
309: * @return FeatureType for <code>typeName</code>
310: *
311: * @throws IOException
312: * @throws SchemaNotFoundException DOCUMENT ME!
313: *
314: * @see org.geotools.data.AbstractDataStore#getSchema(java.lang.String)
315: */
316: public FeatureType getSchema(String typeName) throws IOException {
317: synchronized (memory) {
318: if (schema.containsKey(typeName)) {
319: return (FeatureType) schema.get(typeName);
320: }
321: throw new SchemaNotFoundException(typeName);
322: }
323: }
324:
325: /**
326: * Adds support for a new featureType to MemoryDataStore.
327: *
328: * <p>
329: * FeatureTypes are stored by typeName, an IOException will be thrown if the requested typeName
330: * is already in use.
331: * </p>
332: *
333: * @param featureType FeatureType to be added
334: *
335: * @throws IOException If featureType already exists
336: *
337: * @see org.geotools.data.DataStore#createSchema(org.geotools.feature.FeatureType)
338: */
339: public void createSchema(FeatureType featureType)
340: throws IOException {
341: String typeName = featureType.getTypeName();
342:
343: if (memory.containsKey(typeName)) {
344: // we have a conflict
345: throw new IOException(typeName + " already exists");
346: }
347: // insertion order preserving map
348: Map featuresMap = new LinkedHashMap();
349: schema.put(typeName, featureType);
350: memory.put(typeName, featuresMap);
351: }
352:
353: /**
354: * Provides FeatureReader over the entire contents of <code>typeName</code>.
355: *
356: * <p>
357: * Implements getFeatureReader contract for AbstractDataStore.
358: * </p>
359: *
360: * @param typeName
361: *
362: *
363: * @throws IOException If typeName could not be found
364: * @throws DataSourceException See IOException
365: *
366: * @see org.geotools.data.AbstractDataStore#getFeatureSource(java.lang.String)
367: */
368: public FeatureReader getFeatureReader(final String typeName)
369: throws IOException {
370: return new FeatureReader() {
371: FeatureType featureType = getSchema(typeName);
372: Iterator iterator = features(typeName).values().iterator();
373:
374: public FeatureType getFeatureType() {
375: return featureType;
376: }
377:
378: public Feature next() throws IOException,
379: IllegalAttributeException, NoSuchElementException {
380: if (iterator == null) {
381: throw new IOException(
382: "Feature Reader has been closed");
383: }
384:
385: try {
386: return featureType.duplicate((Feature) iterator
387: .next());
388: } catch (NoSuchElementException end) {
389: throw new DataSourceException(
390: "There are no more Features", end);
391: }
392: }
393:
394: public boolean hasNext() {
395: return (iterator != null) && iterator.hasNext();
396: }
397:
398: public void close() {
399: if (iterator != null) {
400: iterator = null;
401: }
402:
403: if (featureType != null) {
404: featureType = null;
405: }
406: }
407: };
408: }
409:
410: /**
411: * Provides FeatureWriter over the entire contents of <code>typeName</code>.
412: *
413: * <p>
414: * Implements getFeatureWriter contract for AbstractDataStore.
415: * </p>
416: *
417: * @param typeName name of FeatureType we wish to modify
418: *
419: * @return FeatureWriter of entire contents of typeName
420: *
421: * @throws IOException If writer cannot be obtained for typeName
422: * @throws DataSourceException See IOException
423: *
424: * @see org.geotools.data.AbstractDataStore#getFeatureSource(java.lang.String)
425: */
426: public FeatureWriter createFeatureWriter(final String typeName,
427: final Transaction transaction) throws IOException {
428: return new FeatureWriter() {
429: FeatureType featureType = getSchema(typeName);
430: Map contents = features(typeName);
431: Iterator iterator = contents.values().iterator();
432: SimpleFeature live = null;
433:
434: Feature current = null; // current Feature returned to user
435:
436: public FeatureType getFeatureType() {
437: return featureType;
438: }
439:
440: public Feature next() throws IOException,
441: NoSuchElementException {
442: if (hasNext()) {
443: // existing content
444: live = (SimpleFeature) iterator.next();
445:
446: try {
447: current = featureType.duplicate(live);
448: } catch (IllegalAttributeException e) {
449: throw new DataSourceException("Unable to edit "
450: + live.getID() + " of " + typeName);
451: }
452: } else {
453: // new content
454: live = null;
455:
456: try {
457: current = DataUtilities.template(featureType);
458: } catch (IllegalAttributeException e) {
459: throw new DataSourceException(
460: "Unable to add additional Features of "
461: + typeName);
462: }
463: }
464:
465: return current;
466: }
467:
468: public void remove() throws IOException {
469: if (contents == null) {
470: throw new IOException(
471: "FeatureWriter has been closed");
472: }
473:
474: if (current == null) {
475: throw new IOException(
476: "No feature available to remove");
477: }
478:
479: if (live != null) {
480: // remove existing content
481: iterator.remove();
482: listenerManager.fireFeaturesRemoved(typeName,
483: transaction, live.getBounds(), true);
484: live = null;
485: current = null;
486: } else {
487: // cancel add new content
488: current = null;
489: }
490: }
491:
492: public void write() throws IOException {
493: if (contents == null) {
494: throw new IOException(
495: "FeatureWriter has been closed");
496: }
497:
498: if (current == null) {
499: throw new IOException(
500: "No feature available to write");
501: }
502:
503: if (live != null) {
504: if (live.equals(current)) {
505: // no modifications made to current
506: //
507: live = null;
508: current = null;
509: } else {
510: // accept modifications
511: //
512: try {
513: live.setAttributes(current
514: .getAttributes(null));
515: } catch (IllegalAttributeException e) {
516: throw new DataSourceException(
517: "Unable to accept modifications to "
518: + live.getID() + " on "
519: + typeName);
520: }
521:
522: Envelope bounds = new Envelope();
523: bounds.expandToInclude(live.getBounds());
524: bounds.expandToInclude(current.getBounds());
525: listenerManager.fireFeaturesChanged(typeName,
526: transaction, bounds, true);
527: live = null;
528: current = null;
529: }
530: } else {
531: // add new content
532: //
533: contents.put(current.getID(), current);
534: listenerManager.fireFeaturesAdded(typeName,
535: transaction, current.getBounds(), true);
536: current = null;
537: }
538: }
539:
540: public boolean hasNext() throws IOException {
541: if (contents == null) {
542: throw new IOException(
543: "FeatureWriter has been closed");
544: }
545:
546: return (iterator != null) && iterator.hasNext();
547: }
548:
549: public void close() {
550: if (iterator != null) {
551: iterator = null;
552: }
553:
554: if (featureType != null) {
555: featureType = null;
556: }
557:
558: contents = null;
559: current = null;
560: live = null;
561: }
562: };
563: }
564:
565: /**
566: * @see org.geotools.data.AbstractDataStore#getBounds(java.lang.String,
567: * org.geotools.data.Query)
568: */
569: protected Envelope getBounds(Query query) throws IOException {
570: String typeName = query.getTypeName();
571: Map contents = features(typeName);
572: Iterator iterator = contents.values().iterator();
573:
574: Envelope envelope = null;
575:
576: if (iterator.hasNext()) {
577: int count = 1;
578: Filter filter = query.getFilter();
579: Feature first = (Feature) iterator.next();
580: envelope = new Envelope(first.getDefaultGeometry()
581: .getEnvelopeInternal());
582:
583: while (iterator.hasNext()
584: && (count < query.getMaxFeatures())) {
585: Feature feature = (Feature) iterator.next();
586:
587: if (filter.evaluate(feature)) {
588: count++;
589: envelope
590: .expandToInclude(feature
591: .getDefaultGeometry()
592: .getEnvelopeInternal());
593: }
594: }
595: }
596:
597: return envelope;
598: }
599:
600: /**
601: * @see org.geotools.data.AbstractDataStore#getCount(java.lang.String, org.geotools.data.Query)
602: */
603: protected int getCount(Query query) throws IOException {
604: String typeName = query.getTypeName();
605: Map contents = features(typeName);
606: Iterator iterator = contents.values().iterator();
607:
608: int count = 0;
609:
610: Filter filter = query.getFilter();
611:
612: while (iterator.hasNext() && (count < query.getMaxFeatures())) {
613: if (filter.evaluate((Feature) iterator.next())) {
614: count++;
615: }
616: }
617:
618: return count;
619: }
620: }
|