Skip to content

Commit

Permalink
[feat] Automatically resolve ordering conflicts #5
Browse files Browse the repository at this point in the history
Albeit very liberally, also reordering fields
  • Loading branch information
slarse committed Mar 5, 2020
1 parent 2d99e26 commit c8e693d
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 10 deletions.
44 changes: 37 additions & 7 deletions src/main/java/se/kth/spork/spoon/PcsInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
import spoon.reflect.code.CtExpression;
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtType;
import spoon.reflect.path.CtRole;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Class for interpreting a merged PCS structure into a Spoon tree.
Expand Down Expand Up @@ -143,14 +146,22 @@ private SpoonNode traverseConflict(
leftNodes = leftNodes.subList(0, cutoffs.first);
rightNodes = rightNodes.subList(0, cutoffs.second);

visitor.visitConflicting(currentRoot, leftNodes, rightNodes);
for (SpoonNode node : leftNodes) {
traversePcs(node);
}
for (SpoonNode node : rightNodes) {
traversePcs(node);
Optional<List<SpoonNode>> resolved = tryResolveConflict(leftNodes, rightNodes);
if (resolved.isPresent()) {
for (SpoonNode node : resolved.get()) {
visitor.visit(currentRoot, node);
traversePcs(node);
}
} else {
visitor.visitConflicting(currentRoot, leftNodes, rightNodes);
for (SpoonNode node : leftNodes) {
traversePcs(node);
}
for (SpoonNode node : rightNodes) {
traversePcs(node);
}
visitor.endConflict();
}
visitor.endConflict();

return leftNodes.isEmpty() ? next : leftNodes.get(leftNodes.size() - 1);
}
Expand All @@ -170,6 +181,25 @@ private static <V extends SpoonNode> List<V> extractConflictList(V start, Map<V,
return nodes;
}

/**
* Try to resolve a structural conflict automatically.
*/
private static Optional<List<SpoonNode>> tryResolveConflict(List<SpoonNode> leftNodes, List<SpoonNode> rightNodes) {
assert leftNodes.size() > 0;
assert rightNodes.size() > 0;

SpoonNode firstLeft = leftNodes.get(0);
if (!(firstLeft.getElement().getRoleInParent() == CtRole.TYPE_MEMBER))
return Optional.empty();

assert leftNodes.stream().allMatch(node -> node.getElement().getRoleInParent() == CtRole.TYPE_MEMBER);
assert rightNodes.stream().allMatch(node -> node.getElement().getRoleInParent() == CtRole.TYPE_MEMBER);

// FIXME this is too liberal. Fields are not unordered, and this approach makes the merge non-commutative.
List<SpoonNode> result = Stream.of(leftNodes, rightNodes).flatMap(List::stream).collect(Collectors.toList());
return Optional.of(result);
}

/**
* Find the cutoff points in the left and right conflict lists, where they have elements in common.
*/
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/se/kth/spork/cli/CliTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ void mergeTreeShouldEqualReParsedPrettyPrint_whenRightIsModified(
"change_binops",
"change_unary_ops",
"add_field_modifiers",
"method_method_ordering_conflict",
"multiple_method_ordering_conflicts",
})
void mergeTreeShouldEqualReParsedPrettyPrent_whenBothRevisionsAreModified(String testName, @TempDir Path tempDir) throws IOException {
File testDir = Util.BOTH_MODIFIED_DIRPATH.resolve(testName).toFile();
Expand Down
12 changes: 9 additions & 3 deletions src/test/java/se/kth/spork/spoon/Spoon3dmMergeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ void mergeToTree_shouldReturnExpectedTree_whenRightVersionIsModified(String test
"change_binops",
"change_unary_ops",
"add_field_modifiers",
"method_method_ordering_conflict",
"multiple_method_ordering_conflicts",
})
void mergeToTree_shouldReturnExpectedTree_whenBothVersionsAreModified(String testName) throws IOException {
File testDir = Util.BOTH_MODIFIED_DIRPATH.resolve(testName).toFile();
Expand All @@ -96,16 +98,20 @@ void mergeToTree_shouldReturnExpectedTree_whenBothVersionsAreModified(String tes
}

private static void runTestMerge(Util.TestSources sources) {
CtElement expected = Parser.parse(sources.expected);
CtModule expected = Parser.parse(sources.expected);
Object expectedImports = expected.getMetadata(Parser.IMPORT_STATEMENTS);
assert expectedImports != null;

CtElement merged = Spoon3dmMerge.merge(sources.base, sources.left, sources.right);
CtModule merged = (CtModule) Spoon3dmMerge.merge(sources.base, sources.left, sources.right);
Object mergedImports = merged.getMetadata(Parser.IMPORT_STATEMENTS);

// FIXME There should be no need to sort output elements, but resolutions to ordering conflicts can cause strange method ordering
Compare.sortUnorderedElements(expected);
Compare.sortUnorderedElements(merged);

// this assert is just to give a better overview of obvious errors, but it relies on the pretty printer's
// correctness
assertEquals(Cli.prettyPrint((CtModule) expected), Cli.prettyPrint((CtModule) merged));
assertEquals(Cli.prettyPrint(expected), Cli.prettyPrint(merged));

// these asserts are what actually matters
assertEquals(expected, merged);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
public class Sum {
public int sumBetween(int a, int b) {
checkBounds(a, b);

int sum = 0;
for (int i = a; i < b; i++) {
sum += i;
}
return sum;
}

private void checkBounds(int a, int b) {
if (b <= a) {
throw new IllegalArgumentException("b must be greater than or equal to a");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
public class Sum {
public int sumBetween(int a, int b) {
checkBounds(a, b);

int sum = 0;
for (int i = a; i < b; i++) {
sum += i;
}
return sum;
}

private int sumTo(int to) {
checkToBound(to);
int sum = 0;
for (int i = 0; i < to; i++) {
sum += i;
}
return sum;
}

private void checkToBound(int to) {
if (to >= 1_000) {
throw new IllegalArgumentException("I can't count that high: " + to);
}
}

private int multiplyBetween(int a, int b) {
checkBounds(a, b);

int prod = 1;
for (int i = a; i < b; i++) {
prod *= i;
}

return prod;
}

private void checkBounds(int a, int b) {
if (b <= a) {
throw new IllegalArgumentException("b must be greater than or equal to a");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
public class Sum {
public int sumBetween(int a, int b) {
checkBounds(a, b);

int sum = 0;
for (int i = a; i < b; i++) {
sum += i;
}
return sum;
}

private int sumTo(int to) {
checkToBound(to);
int sum = 0;
for (int i = 0; i < to; i++) {
sum += i;
}
return sum;
}

private void checkToBound(int to) {
if (to >= 1_000) {
throw new IllegalArgumentException("I can't count that high: " + to);
}
}

private void checkBounds(int a, int b) {
if (b <= a) {
throw new IllegalArgumentException("b must be greater than or equal to a");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
public class Sum {
public int sumBetween(int a, int b) {
checkBounds(a, b);

int sum = 0;
for (int i = a; i < b; i++) {
sum += i;
}
return sum;
}

private int multiplyBetween(int a, int b) {
checkBounds(a, b);

int prod = 1;
for (int i = a; i < b; i++) {
prod *= i;
}

return prod;
}

private void checkBounds(int a, int b) {
if (b <= a) {
throw new IllegalArgumentException("b must be greater than or equal to a");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
public class Sum {
public int sumBetween(int a, int b) {
checkBounds(a, b);

int sum = 0;
for (int i = a; i < b; i++) {
sum += i;
}
return sum;
}

private void checkBounds(int a, int b) {
if (b <= a) {
throw new IllegalArgumentException("b must be greater than or equal to a");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
public class Sum {
public int sumBetween(int a, int b) {
checkBounds(a, b);

int sum = 0;
for (int i = a; i < b; i++) {
sum += i;
}
return sum;
}

private int sumTo(int to) {
checkToBound(to);
int sum = 0;
for (int i = 0; i < to; i++) {
sum += i;
}
return sum;
}

private void checkToBound(int to) {
if (to >= 1_000) {
throw new IllegalArgumentException("I can't count that high: " + to);
}
}

private int multiplyBetween(int a, int b) {
checkBounds(a, b);

int prod = 1;
for (int i = a; i < b; i++) {
prod *= i;
}

return prod;
}

private void checkBounds(int a, int b) {
if (b <= a) {
throw new IllegalArgumentException("b must be greater than or equal to a");
}
}

public int sumAndMultiplyBetween(int a, int b) {
int sum = sumBetween(a, b):
int prod = multiplyBetween(a, b);
return sum + prod;
}

public int sumBetweenUndirected(int a, int b) {
return a <= b ? sumBetween(a, b) : sumBetween(b, a);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
public class Sum {
public int sumBetween(int a, int b) {
checkBounds(a, b);

int sum = 0;
for (int i = a; i < b; i++) {
sum += i;
}
return sum;
}

private int sumTo(int to) {
checkToBound(to);
int sum = 0;
for (int i = 0; i < to; i++) {
sum += i;
}
return sum;
}

private void checkToBound(int to) {
if (to >= 1_000) {
throw new IllegalArgumentException("I can't count that high: " + to);
}
}

private void checkBounds(int a, int b) {
if (b <= a) {
throw new IllegalArgumentException("b must be greater than or equal to a");
}
}

public int sumBetweenUndirected(int a, int b) {
return a <= b ? sumBetween(a, b) : sumBetween(b, a);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
public class Sum {
public int sumBetween(int a, int b) {
checkBounds(a, b);

int sum = 0;
for (int i = a; i < b; i++) {
sum += i;
}
return sum;
}

private int multiplyBetween(int a, int b) {
checkBounds(a, b);

int prod = 1;
for (int i = a; i < b; i++) {
prod *= i;
}

return prod;
}

private void checkBounds(int a, int b) {
if (b <= a) {
throw new IllegalArgumentException("b must be greater than or equal to a");
}
}

public int sumAndMultiplyBetween(int a, int b) {
int sum = sumBetween(a, b):
int prod = multiplyBetween(a, b);
return sum + prod;
}
}

0 comments on commit c8e693d

Please sign in to comment.