/* 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.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Set; import org.locationtech.geogig.api.AbstractGeoGigOp; import org.locationtech.geogig.api.FeatureInfo; import org.locationtech.geogig.api.NodeRef; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.Ref; 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.plumbing.RevObjectParse; import org.locationtech.geogig.repository.DepthSearch; import org.locationtech.geogig.repository.WorkingTree; import org.opengis.feature.type.PropertyDescriptor; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; /** * Verifies if a patch can be applied to the current working tree * * @see WorkingTree * @see Patch */ public class VerifyPatchOp extends AbstractGeoGigOp<VerifyPatchResults> { private Patch patch; private boolean reverse; /** * Sets the patch to verify * * @param patch the patch to verify * @return {@code this} */ public VerifyPatchOp setPatch(Patch patch) { this.patch = patch; return this; } /** * Sets whether to verify the original patch or its reversed version * * @param reverse true if the patch should be verified in its reversed version * @return {@code this} */ public VerifyPatchOp setReverse(boolean reverse) { this.reverse = reverse; return this; } /** * Executes the verify command * * @return the result of checking if the patch can be applied */ protected VerifyPatchResults _call() throws RuntimeException { Preconditions.checkArgument(patch != null, "No patch file provided"); Patch patch = reverse ? this.patch.reversed() : this.patch; Patch toApply = new Patch(); Patch toReject = new Patch(); for (RevFeatureType ft : patch.getFeatureTypes()) { toApply.addFeatureType(ft); toReject.addFeatureType(ft); } String path; Optional<RevObject> obj; List<FeatureDiff> diffs = patch.getModifiedFeatures(); for (FeatureDiff diff : diffs) { path = diff.getPath(); String refSpec = Ref.WORK_HEAD + ":" + path; obj = command(RevObjectParse.class).setRefSpec(refSpec).call(); if (!obj.isPresent()) { toReject.addModifiedFeature(diff); break; } RevFeature feature = (RevFeature) obj.get(); DepthSearch depthSearch = new DepthSearch(stagingDatabase()); Optional<NodeRef> noderef = depthSearch.find(workingTree().getTree(), path); RevFeatureType featureType = command(RevObjectParse.class) .setObjectId(noderef.get().getMetadataId()).call(RevFeatureType.class).get(); ImmutableList<PropertyDescriptor> descriptors = featureType.sortedDescriptors(); Set<Entry<PropertyDescriptor, AttributeDiff>> attrDiffs = diff.getDiffs().entrySet(); boolean ok = true; for (Iterator<Entry<PropertyDescriptor, AttributeDiff>> iterator = attrDiffs.iterator(); iterator .hasNext();) { Entry<PropertyDescriptor, AttributeDiff> entry = iterator.next(); AttributeDiff attrDiff = entry.getValue(); PropertyDescriptor descriptor = entry.getKey(); switch (attrDiff.getType()) { case ADDED: if (descriptors.contains(descriptor)) { ok = false; } break; case REMOVED: case MODIFIED: if (!descriptors.contains(descriptor)) { ok = false; break; } for (int i = 0; i < descriptors.size(); i++) { if (descriptors.get(i).equals(descriptor)) { Optional<Object> value = feature.getValues().get(i); if (!attrDiff.canBeAppliedOn(value)) { ok = false; } break; } } } } if (!ok) { toReject.addModifiedFeature(diff); } else { toApply.addModifiedFeature(diff); } } List<FeatureInfo> added = patch.getAddedFeatures(); for (FeatureInfo feature : added) { String refSpec = Ref.WORK_HEAD + ":" + feature.getPath(); obj = command(RevObjectParse.class).setRefSpec(refSpec).call(); if (obj.isPresent()) { toReject.addAddedFeature(feature.getPath(), feature.getFeature(), feature.getFeatureType()); } else { toApply.addAddedFeature(feature.getPath(), feature.getFeature(), feature.getFeatureType()); } } List<FeatureInfo> removed = patch.getRemovedFeatures(); for (FeatureInfo feature : removed) { String refSpec = Ref.WORK_HEAD + ":" + feature.getPath(); obj = command(RevObjectParse.class).setRefSpec(refSpec).call(); if (!obj.isPresent()) { toReject.addRemovedFeature(feature.getPath(), feature.getFeature(), feature.getFeatureType()); } else { RevFeature revFeature = (RevFeature) obj.get(); DepthSearch depthSearch = new DepthSearch(stagingDatabase()); Optional<NodeRef> noderef = depthSearch.find(workingTree().getTree(), feature.getPath()); RevFeatureType revFeatureType = command(RevObjectParse.class) .setObjectId(noderef.get().getMetadataId()).call(RevFeatureType.class) .get(); RevFeature patchRevFeature = RevFeatureBuilder.build(feature.getFeature()); if (revFeature.equals(patchRevFeature) && revFeatureType.equals(feature.getFeatureType())) { toApply.addRemovedFeature(feature.getPath(), feature.getFeature(), feature.getFeatureType()); } else { toReject.addRemovedFeature(feature.getPath(), feature.getFeature(), feature.getFeatureType()); } } } ImmutableList<FeatureTypeDiff> alteredTrees = patch.getAlteredTrees(); for (FeatureTypeDiff diff : alteredTrees) { DepthSearch depthSearch = new DepthSearch(stagingDatabase()); Optional<NodeRef> noderef = depthSearch.find(workingTree().getTree(), diff.getPath()); ObjectId metadataId = noderef.isPresent() ? noderef.get().getMetadataId() : ObjectId.NULL; if (Objects.equal(metadataId, diff.getOldFeatureType())) { toApply.addAlteredTree(diff); } else { toReject.addAlteredTree(diff); } } return new VerifyPatchResults(toApply, toReject); } }