/* 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.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.locationtech.geogig.api.FeatureBuilder;
import org.locationtech.geogig.api.FeatureInfo;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.RevFeature;
import org.locationtech.geogig.api.RevFeatureBuilder;
import org.locationtech.geogig.api.RevFeatureType;
import org.locationtech.geogig.api.RevObject;
import org.locationtech.geogig.api.RevObject.TYPE;
import org.locationtech.geogig.storage.ObjectReader;
import org.locationtech.geogig.storage.ObjectWriter;
import org.locationtech.geogig.storage.text.TextSerializationFactory;
import org.opengis.feature.Feature;
import org.opengis.feature.type.PropertyDescriptor;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/**
* Serializes the differences between two versions of the repository, in plain text format
*
* This is a very basic first version used to test patches created using a plain text format similar
* to the format output by the diff command. This should be extended to support other formats.
*
*/
public class PatchSerializer {
private static TextSerializationFactory factory = new TextSerializationFactory();
/**
* Creates a patch object to apply on a GeoGig working tree
*
* @param reader the read from where to read the patch description
* @return a Patch
*/
public static Patch read(BufferedReader reader) {
Preconditions.checkNotNull(reader);
Patch patch = new Patch();
List<String> subset = Lists.newArrayList();
Map<String, RevFeatureType> featureTypes = Maps.newHashMap();
try {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() && !subset.isEmpty()) {
addElement(subset, patch, featureTypes);
subset.clear();
} else if (!line.isEmpty()) {
subset.add(line);
}
}
if (!subset.isEmpty()) {
addElement(subset, patch, featureTypes);
}
Set<Entry<String, RevFeatureType>> entries = featureTypes.entrySet();
for (Iterator<Entry<String, RevFeatureType>> iterator = entries.iterator(); iterator
.hasNext();) {
Entry<String, RevFeatureType> entry = iterator.next();
patch.addFeatureType(entry.getValue());
}
return patch;
} catch (IOException e) {
throw new IllegalArgumentException("Can't read patch: " + e.getMessage());
}
}
private static void addElement(List<String> lines, Patch patch,
Map<String, RevFeatureType> featureTypes) {
String[] headerTokens = lines.get(0).split("\t");
if (headerTokens.length == 4 || headerTokens.length == 3) {// feature or feature type
// modified // modification
if (lines.size() == 1) { // feature type
FeatureTypeDiff diff = new FeatureTypeDiff(headerTokens[0],
ObjectId.valueOf(headerTokens[1]), ObjectId.valueOf(headerTokens[2]));
patch.addAlteredTree(diff);
} else {// feature
String element = Joiner.on("\n").join(lines.subList(1, lines.size()));
ByteArrayInputStream stream;
stream = new ByteArrayInputStream(element.getBytes(Charsets.UTF_8));
String operation = headerTokens[0].trim();
if (operation.equals("M")) {
String fullPath = headerTokens[1].trim();
String oldMetadataId = headerTokens[2].trim();
String newMetadataId = headerTokens[3].trim();
RevFeatureType newRevFeatureType = featureTypes.get(newMetadataId);
RevFeatureType oldRevFeatureType = featureTypes.get(oldMetadataId);
Map<PropertyDescriptor, AttributeDiff> map = Maps.newHashMap();
for (int i = 1; i < lines.size(); i++) {
addDifference(lines.get(i), map, oldRevFeatureType, newRevFeatureType);
}
FeatureDiff featureDiff = new FeatureDiff(fullPath, map, oldRevFeatureType,
newRevFeatureType);
patch.addModifiedFeature(featureDiff);
} else if (operation.equals("A") || operation.equals("R")) {
String fullPath = headerTokens[1].trim();
String featureTypeId = headerTokens[2].trim();
RevFeatureType revFeatureType;
revFeatureType = featureTypes.get(featureTypeId);
FeatureBuilder featureBuilder = new FeatureBuilder(revFeatureType);
ObjectReader<RevFeature> reader = factory.createFeatureReader();
RevFeature revFeature = reader.read(null, stream);
Feature feature = featureBuilder.build(NodeRef.nodeFromPath(fullPath),
revFeature);
if (operation.equals("R")) {
patch.addRemovedFeature(fullPath, feature, revFeatureType);
} else {
patch.addAddedFeature(fullPath, feature, revFeatureType);
}
} else {
throw new IllegalArgumentException("Wrong patch content: " + lines.get(0));
}
}
} else if (headerTokens.length == 1) {// feature type definition
String element = Joiner.on("\n").join(lines);
ByteArrayInputStream stream = new ByteArrayInputStream(element.getBytes(Charsets.UTF_8));
String[] tokens = lines.get(1).split("\t");
ObjectReader<RevFeatureType> reader = factory.createFeatureTypeReader();
RevFeatureType featureType = reader.read(null, stream);
featureTypes.put(featureType.getId().toString(), featureType);
} else {
throw new IllegalArgumentException("Wrong patch content: " + lines.get(0));
}
}
private static void addDifference(String s, Map<PropertyDescriptor, AttributeDiff> map,
RevFeatureType oldRevFeatureType, RevFeatureType newRevFeatureType) {
String[] tokens = s.split("\t");
PropertyDescriptor descriptor = oldRevFeatureType.type().getDescriptor(tokens[0]);
if (descriptor == null) {
descriptor = newRevFeatureType.type().getDescriptor(tokens[0]);
}
AttributeDiff ad = AttributeDiffFactory.attributeDiffFromText(descriptor.getType()
.getBinding(), s.substring(s.indexOf("\t") + 1));
map.put(descriptor, ad);
}
public static void write(Writer w, Patch patch) throws IOException {
StringBuilder sb = new StringBuilder();
List<RevFeatureType> featureTypes = patch.getFeatureTypes();
for (RevFeatureType featureType : featureTypes) {
ObjectWriter<RevObject> writer = factory.createObjectWriter(TYPE.FEATURETYPE);
ByteArrayOutputStream output = new ByteArrayOutputStream();
writer.write(featureType, output);
sb.append(output.toString());
sb.append('\n');
}
TextSerializationFactory factory = new TextSerializationFactory();
for (FeatureInfo feature : patch.getAddedFeatures()) {
String path = feature.getPath();
sb.append("A\t" + path + "\t" + feature.getFeatureType().getId() + "\n");
ObjectWriter<RevObject> writer = factory.createObjectWriter(TYPE.FEATURE);
ByteArrayOutputStream output = new ByteArrayOutputStream();
RevFeature revFeature = RevFeatureBuilder.build(feature.getFeature());
try {
writer.write(revFeature, output);
} catch (IOException e) {
}
sb.append(output.toString());
sb.append('\n');
}
for (FeatureInfo feature : patch.getRemovedFeatures()) {
String path = feature.getPath();
sb.append("R\t" + path + "\t" + feature.getFeatureType().getId() + "\n");
ObjectWriter<RevObject> writer = factory.createObjectWriter(TYPE.FEATURE);
ByteArrayOutputStream output = new ByteArrayOutputStream();
RevFeature revFeature = RevFeatureBuilder.build(feature.getFeature());
try {
writer.write(revFeature, output);
} catch (IOException e) {
}
sb.append(output.toString());
sb.append('\n');
}
for (FeatureDiff diff : patch.getModifiedFeatures()) {
sb.append("M\t" + diff.getPath() + "\t" + diff.getOldFeatureType().getId().toString()
+ "\t" + diff.getNewFeatureType().getId().toString() + "\n");
sb.append(diff.asText() + "\n");
}
for (FeatureTypeDiff diff : patch.getAlteredTrees()) {
sb.append(diff.toString() + "\n");
}
w.write(sb.toString());
w.flush();
}
}