/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2006-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.data;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.geometry.BoundingBox;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.index.SpatialIndex;
import com.vividsolutions.jts.index.quadtree.Quadtree;
/**
* Captures changes made to a FeatureStore prior to being committed.
* <p>
* This is used to simulate the functionality of a database including transaction
* independence.
*
* @author Jody Garnett
*/
public class Diff{
/** Map of modified features; by feature id */
private final Map<String,SimpleFeature> modifiedFeatures;
/**
* Map of added features; by feature id.
* <p>
* Note deleted features are represented as a null value recorded against their feature id
*/
private final Map<String,SimpleFeature> addedFeatures;
/** List of added feature ids; values stored in added above */
private final List<String> addedFidList;
/**
* Unmodifiable view of modified features. It is imperative that the user manually synchronize
* on the map when iterating over any of its collection views:
*
* <pre>
* Set s = diff.modified2.keySet(); // Needn't be in synchronized block
* ...
* synchronized(diff) { // Synchronizing on diff, not diff.modified2 or s!
* Iterator i = s.iterator(); // Must be in synchronized block
* while (i.hasNext())
* foo(i.next());
* }
* </pre>
*
* Failure to follow this advice may result in non-deterministic behavior.
*
* <p>
* The returned map will be serializable if the specified map is serializable.
* @deprecated Please use getModified();
*/
public final Map<String, SimpleFeature> modified2;
/**
* Unmodifiable view of added features. It is imperative that the user manually synchronize on
* the map when iterating over any of its collection views:
*
* <pre>
* Set s = diff.added.keySet(); // Needn't be in synchronized block
* ...
* synchronized(diff) { // Synchronizing on m, not diff.added or s!
* Iterator i = s.iterator(); // Must be in synchronized block
* while (i.hasNext())
* foo(i.next());
* }
* </pre>
*
* Failure to follow this advice may result in non-deterministic behavior.
*
* <p>
* The returned map will be serializable if the specified map is serializable.
* @deprecated please use getAdded()
*/
public final Map<String, SimpleFeature> added;
private final List<String> order;
/** counter used to genreate the "next" new feature id */
public int nextFID = 0;
/** Spatial index; allowing quick qccess to features */
private SpatialIndex spatialIndex;
/** Simple object used for locking */
Object mutex;
/** Create an empty Diff */
public Diff() {
// private fields
modifiedFeatures = new ConcurrentHashMap<String, SimpleFeature>();
addedFeatures =new ConcurrentHashMap<String, SimpleFeature>();
addedFidList = new CopyOnWriteArrayList<String>();
// public "views" requiring synchronised( mutex )
modified2 = Collections.unmodifiableMap(modifiedFeatures);
added = Collections.unmodifiableMap(addedFeatures);
order = Collections.unmodifiableList(addedFidList);
spatialIndex = new Quadtree();
mutex = this;
}
/**
* Diff copy.
* @param other
*/
public Diff(Diff other){
// copy data
modifiedFeatures=new ConcurrentHashMap<String, SimpleFeature>(other.modifiedFeatures);
addedFeatures=new ConcurrentHashMap<String, SimpleFeature>(other.addedFeatures);
addedFidList=new CopyOnWriteArrayList<String>(other.addedFidList);
// create public "views"
modified2=Collections.unmodifiableMap(modifiedFeatures);
added=Collections.unmodifiableMap(addedFeatures);
order = Collections.unmodifiableList(addedFidList);
spatialIndex=copySTRtreeFrom(other);
nextFID=other.nextFID;
mutex=this;
}
/**
* Check if modifiedFeatures and addedFeatures are empty.
*
* @return true if Diff is empty
*/
public boolean isEmpty() {
synchronized (mutex) {
return modifiedFeatures.isEmpty() && addedFeatures.isEmpty();
}
}
/**
* Clear diff - called during rollback.
*/
public void clear() {
synchronized (mutex) {
nextFID = 0;
addedFeatures.clear();
addedFidList.clear();
modifiedFeatures.clear();
spatialIndex = new Quadtree();
}
}
/**
* Record a modification to the indicated fid
*
* @param fid
* @param f replacement feature; null to indicate remove
*/
public void modify(String fid, SimpleFeature f) {
synchronized (mutex) {
SimpleFeature old;
if( addedFeatures.containsKey(fid) ){
old = addedFeatures.get(fid);
if( f == null ){
addedFeatures.remove(fid);
addedFidList.remove(fid);
}
else {
addedFeatures.put(fid, f);
}
}
else{
old = modifiedFeatures.get(fid);
modifiedFeatures.put(fid, f);
}
if(old != null) {
spatialIndex.remove(ReferencedEnvelope.reference(old.getBounds()), old);
}
addToSpatialIndex(f);
}
}
public void add(String fid, SimpleFeature f) {
synchronized (mutex) {
addedFeatures.put(fid, f);
addedFidList.add(fid); // preserve order features are added in
addToSpatialIndex(f);
}
}
protected void addToSpatialIndex(SimpleFeature f) {
if (f.getDefaultGeometry() != null) {
BoundingBox bounds = f.getBounds();
if( !bounds.isEmpty() )
spatialIndex.insert(ReferencedEnvelope.reference(bounds), f);
}
}
public void remove(String fid) {
synchronized (mutex) {
SimpleFeature old = null;
if( addedFeatures.containsKey(fid) ){
old = (SimpleFeature) addedFeatures.get(fid);
addedFeatures.remove(fid);
addedFidList.remove(fid);
} else {
old = (SimpleFeature) modifiedFeatures.get(fid);
modifiedFeatures.put(fid, TransactionStateDiff.NULL);
}
if( old != null ) {
spatialIndex.remove(ReferencedEnvelope.reference(old.getBounds()), old);
}
}
}
@SuppressWarnings("unchecked")
public List<SimpleFeature> queryIndex(Envelope env) {
synchronized (mutex) {
return spatialIndex.query(env);
}
}
/** Unmodifieable list indicating the order features were added */
public List<String> getAddedOrder() {
return addedFidList;
}
/**
* Unmodifiable view of modified features. It is imperative that the user manually synchronize
* on the map when iterating over any of its collection views:
*
* <pre>
* Set s = diff.modified2.keySet(); // Needn't be in synchronized block
* ...
* synchronized(diff) { // Synchronizing on diff, not diff.modified2 or s!
* Iterator i = s.iterator(); // Must be in synchronized block
* while (i.hasNext())
* foo(i.next());
* }
* </pre>
*
* Failure to follow this advice may result in non-deterministic behavior.
*
* <p>
* The returned map will be serializable if the specified map is serializable.
*
* @return Map of modified features, null user to represent a removed feature
*/
public Map<String, SimpleFeature> getModified() {
return modified2;
}
/**
* Unmodifiable view of added features. It is imperative that the user manually synchronize on
* the map when iterating over any of its collection views:
*
* <pre>
* Set s = diff.added.keySet(); // Needn't be in synchronized block
* ...
* synchronized(diff) { // Synchronizing on m, not diff.added or s!
* Iterator i = s.iterator(); // Must be in synchronized block
* while (i.hasNext())
* foo(i.next());
* }
* </pre>
*
* Failure to follow this advice may result in non-deterministic behavior.
*
* <p>
* The returned map will be serializable if the specified map is serializable.
*
* @return Map of added features
*/
public Map<String, SimpleFeature> getAdded() {
return added;
}
protected Quadtree copySTRtreeFrom(Diff diff) {
Quadtree tree = new Quadtree();
synchronized (diff) {
Iterator<Entry<String,SimpleFeature>> i = diff.added.entrySet().iterator();
while (i.hasNext()) {
Entry<String,SimpleFeature> e = i.next();
SimpleFeature f = (SimpleFeature) e.getValue();
if (!diff.modifiedFeatures.containsKey(f.getID())) {
tree.insert(ReferencedEnvelope.reference(f.getBounds()), f);
}
}
Iterator<Entry<String,SimpleFeature>> j = diff.modified2.entrySet().iterator();
while( j.hasNext() ){
Entry<String,SimpleFeature> e = j.next();
SimpleFeature f = (SimpleFeature) e.getValue();
tree.insert(ReferencedEnvelope.reference(f.getBounds()), f);
}
}
return tree;
}
}