/* Copyright (c) 2013-2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Victor Olaya (Boundless) - initial implementation
*/
package org.locationtech.geogig.api.plumbing.diff;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.locationtech.geogig.api.RevFeature;
import org.locationtech.geogig.api.RevFeatureType;
import org.opengis.feature.type.PropertyDescriptor;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.vividsolutions.jts.geom.Geometry;
/**
* Defines the differences between 2 versions of the a given feature
*
*/
public class FeatureDiff {
private Map<PropertyDescriptor, AttributeDiff> diffs;
private String path;
private RevFeatureType newFeatureType;
private RevFeatureType oldFeatureType;
public FeatureDiff(String path, Map<PropertyDescriptor, AttributeDiff> diffs,
RevFeatureType oldFeatureType, RevFeatureType newFeatureType) {
this.path = path;
this.diffs = Maps.newHashMap(diffs);
this.newFeatureType = newFeatureType;
this.oldFeatureType = oldFeatureType;
}
/**
*
* @param path the full path to the feature, including its name
* @param newRevFeature the new version of the feature
* @param oldRevFeature the old version of the feature
* @param newRevFeatureType the new version of the feature type
* @param oldRevFeatureType the old version of the feature type
* @param all - true if all attributes should be added regardless of change
*/
public FeatureDiff(String path, RevFeature newRevFeature, RevFeature oldRevFeature,
RevFeatureType newRevFeatureType, RevFeatureType oldRevFeatureType, boolean all) {
this.path = path;
this.newFeatureType = newRevFeatureType;
this.oldFeatureType = oldRevFeatureType;
diffs = new HashMap<PropertyDescriptor, AttributeDiff>();
ImmutableList<PropertyDescriptor> oldAttributes = oldRevFeatureType.sortedDescriptors();
ImmutableList<PropertyDescriptor> newAttributes = newRevFeatureType.sortedDescriptors();
ImmutableList<Optional<Object>> oldValues = oldRevFeature.getValues();
ImmutableList<Optional<Object>> newValues = newRevFeature.getValues();
BitSet updatedAttributes = new BitSet(newValues.size());
for (int i = 0; i < oldAttributes.size(); i++) {
Optional<Object> oldValue = oldValues.get(i);
int idx = newAttributes.indexOf(oldAttributes.get(i));
if (idx != -1) {
Optional<Object> newValue = newValues.get(idx);
if (!oldValue.equals(newValue) || all) {
if (Geometry.class
.isAssignableFrom(oldAttributes.get(i).getType().getBinding())) {
diffs.put(
oldAttributes.get(i),
new GeometryAttributeDiff(Optional.fromNullable((Geometry) oldValue
.orNull()), Optional.fromNullable((Geometry) newValue
.orNull())));
} else {
diffs.put(oldAttributes.get(i), new GenericAttributeDiffImpl(oldValue,
newValue));
}
}
updatedAttributes.set(idx);
} else {
if (Geometry.class.isAssignableFrom(oldAttributes.get(i).getType().getBinding())) {
diffs.put(
oldAttributes.get(i),
new GeometryAttributeDiff(Optional.fromNullable((Geometry) oldValue
.orNull()), Optional.fromNullable((Geometry) null)));
} else {
diffs.put(oldAttributes.get(i), new GenericAttributeDiffImpl(oldValue, null));
}
}
}
updatedAttributes.flip(0, newValues.size());
for (int i = updatedAttributes.nextSetBit(0); i >= 0; i = updatedAttributes
.nextSetBit(i + 1)) {
if (Geometry.class.isAssignableFrom(newAttributes.get(i).getType().getBinding())) {
diffs.put(
newAttributes.get(i),
new GeometryAttributeDiff(Optional.fromNullable((Geometry) null), Optional
.fromNullable((Geometry) newValues.get(i).orNull())));
} else {
diffs.put(newAttributes.get(i),
new GenericAttributeDiffImpl(null, newValues.get(i)));
}
}
}
public boolean hasDifferences() {
return diffs.size() != 0;
}
public Map<PropertyDescriptor, AttributeDiff> getDiffs() {
return ImmutableMap.copyOf(diffs);
}
/**
* Returns the full path to the feature, including its name
*
* @return
*/
public String getPath() {
return path;
}
/**
* The feature type of the new version of the feature
*
* @return
*/
public RevFeatureType getNewFeatureType() {
return newFeatureType;
}
/**
* The feature type of the old version of the feature
*
* @return
*/
public RevFeatureType getOldFeatureType() {
return oldFeatureType;
}
/**
* Returns a human-readable representation of this class. To get a string that can be used to
* serialize this object, use the asText() method
*/
public String toString() {
StringBuilder sb = new StringBuilder();
Set<Entry<PropertyDescriptor, AttributeDiff>> entries = diffs.entrySet();
Iterator<Entry<PropertyDescriptor, AttributeDiff>> iter = entries.iterator();
while (iter.hasNext()) {
Entry<PropertyDescriptor, AttributeDiff> entry = iter.next();
PropertyDescriptor pd = entry.getKey();
AttributeDiff ad = entry.getValue();
sb.append(pd.getName() + "\t" + ad.toString() + "\n");
}
return sb.toString();
}
/**
* Returns a serialized text version of this object
*
* @return
*/
public String asText() {
StringBuilder sb = new StringBuilder();
Set<Entry<PropertyDescriptor, AttributeDiff>> entries = diffs.entrySet();
Iterator<Entry<PropertyDescriptor, AttributeDiff>> iter = entries.iterator();
while (iter.hasNext()) {
Entry<PropertyDescriptor, AttributeDiff> entry = iter.next();
PropertyDescriptor pd = entry.getKey();
AttributeDiff ad = entry.getValue();
sb.append(pd.getName().toString() + "\t" + ad.asText() + "\n");
}
return sb.toString();
}
/**
* Returns a reversed version of the feature difference
*
* @return
*/
public FeatureDiff reversed() {
Map<PropertyDescriptor, AttributeDiff> map = Maps.newHashMap();
Set<Entry<PropertyDescriptor, AttributeDiff>> entries = diffs.entrySet();
for (Iterator<Entry<PropertyDescriptor, AttributeDiff>> iterator = entries.iterator(); iterator
.hasNext();) {
Entry<PropertyDescriptor, AttributeDiff> entry = iterator.next();
map.put(entry.getKey(), entry.getValue().reversed());
}
return new FeatureDiff(path, map, newFeatureType, oldFeatureType);
}
@Override
public boolean equals(Object o) {
// TODO: this is a temporary simple comparison. Should be more elaborate
if (!(o instanceof FeatureDiff)) {
return false;
}
FeatureDiff f = (FeatureDiff) o;
return f.asText().equals(asText());
// return f.diffs.equals(diffs) && f.path.equals(path);
}
/**
* Checks whether a FeatureDiff conflicts with this one
*
* @param featureDiff the featureDiff to check against this one
*/
public boolean conflicts(FeatureDiff featureDiff) {
Map<PropertyDescriptor, AttributeDiff> otherDiffs = featureDiff.diffs;
for (PropertyDescriptor pd : otherDiffs.keySet()) {
if (diffs.containsKey(pd)) {
AttributeDiff ad = diffs.get(pd);
AttributeDiff otherAd = otherDiffs.get(pd);
if (ad.conflicts(otherAd)) {
return true;
}
}
}
return false;
}
}