/* 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.ByteArrayOutputStream; import java.io.IOException; import java.util.BitSet; import java.util.List; import org.locationtech.geogig.api.FeatureInfo; 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.ObjectWriter; import org.locationtech.geogig.storage.text.TextSerializationFactory; import org.opengis.feature.Feature; import org.opengis.feature.type.PropertyDescriptor; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; /** * A patch that can be applied onto a working tree. * */ public class Patch { private List<FeatureTypeDiff> alteredTrees; /** * Feature types needed to use the patch. This include those used by features involved, and aso * feature type that have been modified */ private List<RevFeatureType> featureTypes; /** * features that have been edited */ private List<FeatureDiff> modifiedFeatures; /** * features that have been added. */ private List<FeatureInfo> addedFeatures; /** * features that have been removed */ private List<FeatureInfo> removedFeatures; public Patch() { modifiedFeatures = Lists.newArrayList(); removedFeatures = Lists.newArrayList(); addedFeatures = Lists.newArrayList(); featureTypes = Lists.newArrayList(); alteredTrees = Lists.newArrayList(); } /** * Returns a list of features modified by this patch * * @return */ public List<FeatureDiff> getModifiedFeatures() { return ImmutableList.copyOf(modifiedFeatures); } /** * Returns a list of features added by this patch * * @return */ public List<FeatureInfo> getAddedFeatures() { return ImmutableList.copyOf(addedFeatures); } /** * Returns a list of features modified by this patch * * @return */ public List<FeatureInfo> getRemovedFeatures() { return ImmutableList.copyOf(removedFeatures); } /** * Adds a feature to the list of newly added ones * * @param path the path of the added feature * @param feature the feature * @param featureType the feature type of the added feature */ public void addAddedFeature(String path, Feature feature, RevFeatureType featureType) { addedFeatures.add(new FeatureInfo(feature, featureType, path)); addFeatureType(featureType); } /** * Adds a feature to the list of removed ones * * @param path the path of the removed feature * @param feature the feature * @param featureType the feature type of the removed feature */ public void addRemovedFeature(String path, Feature feature, RevFeatureType featureType) { removedFeatures.add(new FeatureInfo(feature, featureType, path)); addFeatureType(featureType); } /** * Adds an element to the list of modified ones * * @param diff */ public void addModifiedFeature(FeatureDiff diff) { modifiedFeatures.add(diff); addFeatureType(diff.getNewFeatureType()); addFeatureType(diff.getOldFeatureType()); } /** * returns all the feature types used in this patch * * @return */ public List<RevFeatureType> getFeatureTypes() { return ImmutableList.copyOf(featureTypes); } /** * Given an id, returns the feature type with that type, if it exist in the list of features * types affected by this patch * * @param id * @return */ public Optional<RevFeatureType> getFeatureTypeFromId(ObjectId id) { for (RevFeatureType featureType : featureTypes) { if (featureType.getId().equals(id)) { return Optional.of(featureType); } } return Optional.absent(); } /** * returns all the feature types modified by this patch * * @return */ public ImmutableList<FeatureTypeDiff> getAlteredTrees() { return ImmutableList.copyOf(alteredTrees); } public void addAlteredTree(DiffEntry diff) { ObjectId oldFeatureType = diff.getOldObject() == null ? null : diff.getOldObject() .getMetadataId(); ObjectId newFeatureType = diff.getNewObject() == null ? null : diff.getNewObject() .getMetadataId(); String path = diff.oldPath() == null ? diff.newPath() : diff.oldPath(); alteredTrees.add(new FeatureTypeDiff(path, oldFeatureType, newFeatureType)); } public void addAlteredTree(FeatureTypeDiff diff) { alteredTrees.add(diff); } /** * Adds a new feature type to the list of them used in this patch * * @param featureType */ public void addFeatureType(RevFeatureType featureType) { if (!featureTypes.contains(featureType)) { featureTypes.add(featureType); } } @Override public boolean equals(Object o) { // TODO: this is a temporary simple comparison. Should be more elaborate if (!(o instanceof Patch)) { return false; } Patch p = (Patch) o; return p.toString().equals(toString()); } public boolean isEmpty() { return addedFeatures.isEmpty() && modifiedFeatures.isEmpty() && removedFeatures.isEmpty(); } /** * This method is not intended to serialize the patch, as it misses some needed information. To * serialize the patch, use the {@link PatchSerializer} class instead. Use this method to show * patch content in a human-readable format */ @Override public String toString() { TextSerializationFactory factory = new TextSerializationFactory(); StringBuilder sb = new StringBuilder(); for (FeatureInfo feature : addedFeatures) { 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 : removedFeatures) { 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 : modifiedFeatures) { sb.append("M\t" + diff.getPath() /* * + "\t" + diff.getOldFeatureType().getId().toString() * + "\t" + diff.getNewFeatureType().getId().toString() */+ "\n"); sb.append(diff.toString() + "\n"); } for (FeatureTypeDiff diff : alteredTrees) { sb.append(featureTypeDiffAsString(diff) + "\n"); } return sb.toString(); } private String featureTypeDiffAsString(FeatureTypeDiff diff) { StringBuilder sb = new StringBuilder(); sb.append(diff.toString() + "\n"); if (!diff.getNewFeatureType().equals(ObjectId.NULL) && !diff.getOldFeatureType().equals(ObjectId.NULL)) { RevFeatureType oldFeatureType = getFeatureTypeFromId(diff.getOldFeatureType()).get(); RevFeatureType newFeatureType = getFeatureTypeFromId(diff.getNewFeatureType()).get(); ImmutableList<PropertyDescriptor> oldDescriptors = oldFeatureType.sortedDescriptors(); ImmutableList<PropertyDescriptor> newDescriptors = newFeatureType.sortedDescriptors(); BitSet updatedDescriptors = new BitSet(newDescriptors.size()); for (int i = 0; i < oldDescriptors.size(); i++) { PropertyDescriptor oldDescriptor = oldDescriptors.get(i); int idx = newDescriptors.indexOf(oldDescriptor); if (idx != -1) { updatedDescriptors.set(idx); } else { Class<?> oldType = oldDescriptor.getType().getBinding(); sb.append("R\t" + oldDescriptors.get(i).getName().getLocalPart() + "[" + oldType.getName() + "]"); } } updatedDescriptors.flip(0, updatedDescriptors.length()); for (int i = updatedDescriptors.nextSetBit(0); i >= 0; i = updatedDescriptors .nextSetBit(i + 1)) { PropertyDescriptor newDescriptor = newDescriptors.get(i); Class<?> oldType = newDescriptor.getType().getBinding(); sb.append("A\t" + newDescriptors.get(i).getName().getLocalPart() + "[" + oldType.getName() + "]"); } } return sb.toString(); } /** * Returns the total number of elements in this patch, whether added, removed or modified * * @return */ public int count() { return addedFeatures.size() + removedFeatures.size() + modifiedFeatures.size() + alteredTrees.size(); } /** * Returns the reversed version of the current patch. Applying this reversed patch has the * opposite effect to applying the original one, and can be used to undo changes * * @return a reversed version of this patch */ public Patch reversed() { Patch patch = new Patch(); patch.removedFeatures = addedFeatures; patch.addedFeatures = removedFeatures; for (FeatureDiff diff : modifiedFeatures) { patch.modifiedFeatures.add(diff.reversed()); } for (FeatureTypeDiff diff : alteredTrees) { patch.alteredTrees.add(diff.reversed()); } return patch; } }