Skip to content

Commit 211383c

Browse files
committed
[fix] Properly handle modifier merge conflict #43
1 parent 46acd59 commit 211383c

File tree

15 files changed

+119
-11
lines changed

15 files changed

+119
-11
lines changed

src/main/java/se/kth/spork/cli/PrinterPreprocessor.java

+24-3
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22

33
import se.kth.spork.base3dm.Revision;
44
import se.kth.spork.spoon.ContentConflict;
5+
import se.kth.spork.spoon.ContentMerger;
56
import se.kth.spork.spoon.RoledValue;
67
import se.kth.spork.util.LineBasedMerge;
78
import se.kth.spork.util.Pair;
89
import spoon.reflect.code.CtComment;
910
import spoon.reflect.cu.SourcePosition;
1011
import spoon.reflect.declaration.CtElement;
12+
import spoon.reflect.declaration.ModifierKind;
13+
import spoon.reflect.path.CtRole;
1114
import spoon.reflect.visitor.CtScanner;
1215
import spoon.reflect.visitor.printer.CommentOffset;
1316

14-
import java.util.HashMap;
15-
import java.util.List;
16-
import java.util.Map;
17+
import java.util.*;
1718

1819
/**
1920
* A pre-processor that must run before pretty-printing a merged tree. It does things like embedding conflict values
@@ -72,6 +73,7 @@ private static void unsetSourcePosition(CtElement element) {
7273
* @param conflict A content conflict.
7374
* @param element The element associated with the conflict.
7475
*/
76+
@SuppressWarnings("unchecked")
7577
private void processConflict(ContentConflict conflict, CtElement element) {
7678
Object leftVal = conflict.getLeft().getValue();
7779
Object rightVal = conflict.getRight().getValue();
@@ -106,6 +108,25 @@ private void processConflict(ContentConflict conflict, CtElement element) {
106108
printerMap.put("super", Pair.of(Revision.RIGHT, "extends"));
107109
}
108110
break;
111+
case MODIFIER:
112+
Collection<ModifierKind> leftMods = (Collection<ModifierKind>) leftVal;
113+
Collection<ModifierKind> rightMods = (Collection<ModifierKind>) rightVal;
114+
Set<ModifierKind> leftVisibilities = ContentMerger.categorizeModifiers(leftMods).first;
115+
Set<ModifierKind> rightVisibilities = ContentMerger.categorizeModifiers(rightMods).first;
116+
117+
if (leftVisibilities.isEmpty()) {
118+
// use the right-hand visibility in actual tree to force something to be printed
119+
Collection<ModifierKind> mods = element.getValueByRole(CtRole.MODIFIER);
120+
ModifierKind rightVis = rightVisibilities.iterator().next();
121+
mods.add(rightVis);
122+
element.setValueByRole(CtRole.MODIFIER, mods);
123+
printerMap.put(rightVis.toString(), Pair.of(Revision.LEFT, ""));
124+
} else {
125+
String leftVisStr = leftVisibilities.iterator().next().toString();
126+
String rightVisStr = rightVisibilities.isEmpty()
127+
? "" : rightVisibilities.iterator().next().toString();
128+
printerMap.put(leftVisStr, Pair.of(Revision.RIGHT, rightVisStr));
129+
}
109130
}
110131

111132
if (!printerMap.isEmpty()) {

src/main/java/se/kth/spork/cli/SporkPrettyPrinter.java

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ public SporkPrettyPrinter(Environment env) {
3232
printerConflictMaps = new ArrayDeque<>();
3333
setPrinterTokenWriter(new DefaultTokenWriter(printerHelper));
3434

35+
// This is required to avoid NullPointerExceptions when debugging, as the debugger calls toString from time
36+
// to time
37+
printerConflictMaps.push(Optional.empty());
38+
3539
// this line is SUPER important because without it implicit elements will be printed. For example,
3640
// instead of just String, it will print java.lang.String. Which isn't great.
3741
setIgnoreImplicit(false);

src/main/java/se/kth/spork/spoon/ContentMerger.java

+49-8
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ static void handleContentConflicts(TStar<SpoonNode, RoledValues> delta) {
7676

7777
Optional<?> merged = Optional.empty();
7878

79+
// sometimes a value can be partially merged (e.g. modifiers), and then we want to be
80+
// able to set the merged value, AND flag a conflict.
81+
boolean conflictPresent = false;
82+
7983
// if either value is equal to base, we keep THE OTHER one
8084
if (baseValOpt.isPresent() && baseValOpt.get().equals(leftVal)) {
8185
merged = Optional.of(rightVal);
@@ -93,10 +97,12 @@ static void handleContentConflicts(TStar<SpoonNode, RoledValues> delta) {
9397
}
9498
break;
9599
case MODIFIER:
96-
merged = mergeModifierKinds(
100+
Pair<Boolean, Optional<Set<ModifierKind>>> mergePair = mergeModifierKinds(
97101
baseValOpt.map(o -> (Set<ModifierKind>) o),
98102
(Set<ModifierKind>) leftVal,
99103
(Set<ModifierKind>) rightVal);
104+
conflictPresent = mergePair.first;
105+
merged = mergePair.second;
100106
break;
101107
case COMMENT_CONTENT:
102108
merged = mergeComments(baseValOpt.orElse(""), leftVal, rightVal);
@@ -116,7 +122,9 @@ static void handleContentConflicts(TStar<SpoonNode, RoledValues> delta) {
116122

117123
if (merged.isPresent()) {
118124
mergedRoledValues.set(i, role, merged.get());
119-
unresolvedConflicts.pop();
125+
126+
if (!conflictPresent)
127+
unresolvedConflicts.pop();
120128
}
121129
}
122130

@@ -167,6 +175,28 @@ static void handleContentConflicts(TStar<SpoonNode, RoledValues> delta) {
167175
return Triple.of(visibility, keywords, other);
168176
}
169177

178+
/**
179+
* Separate modifiers into visibility (public, private, protected), keywords (static, final) and all
180+
* others.
181+
*
182+
* @param modifiers A collection of modifiers.
183+
* @return A triple with visibility in first, keywords in second and other in third.
184+
*/
185+
public static Triple<Set<ModifierKind>, Set<ModifierKind>, Set<ModifierKind>>
186+
categorizeModifiers(Collection<ModifierKind> modifiers) {
187+
return categorizeModifiers(modifiers.stream());
188+
}
189+
190+
/**
191+
* Extract the visibility modifier(s).
192+
*
193+
* @param modifiers A collection of modifiers.
194+
* @return A possibly empty set of visibility modifiers.
195+
*/
196+
public static Set<ModifierKind> getVisibility(Collection<ModifierKind> modifiers) {
197+
return categorizeModifiers(modifiers).first;
198+
}
199+
170200
private static Optional<?> mergeIsUpper(Optional<CtElement> baseElem, CtElement leftElem, CtElement rightElem) {
171201
CtWildcardReference left = (CtWildcardReference) leftElem;
172202
CtWildcardReference right = (CtWildcardReference) rightElem;
@@ -201,16 +231,23 @@ private static Optional<?> mergeComments(Object base, Object left, Object right)
201231
return Optional.of(merge.first);
202232
}
203233

204-
private static Optional<Set<ModifierKind>>
234+
/**
235+
* Return a pair (conflict, mergedModifiers).
236+
* If the conflict value is true, there is a conflict in the visibility modifiers, and the merged value
237+
* will always be the left one.
238+
*/
239+
private static Pair<Boolean, Optional<Set<ModifierKind>>>
205240
mergeModifierKinds(Optional<Set<ModifierKind>> base, Set<ModifierKind> left, Set<ModifierKind> right) {
206-
// all revisions must have the same primary value
207-
208241
Set<ModifierKind> baseModifiers = base.orElseGet(HashSet::new);
209242

210243
Stream<ModifierKind> modifiers = Stream.of(baseModifiers, left, right).flatMap(Set::stream);
211244
Triple<Set<ModifierKind>, Set<ModifierKind>, Set<ModifierKind>>
212245
categorizedMods = categorizeModifiers(modifiers);
213246

247+
Set<ModifierKind> baseVis = getVisibility(baseModifiers);
248+
Set<ModifierKind> leftVis = getVisibility(left);
249+
Set<ModifierKind> rightVis = getVisibility(right);
250+
214251
Set<ModifierKind> visibility = categorizedMods.first;
215252
Set<ModifierKind> keywords = categorizedMods.second;
216253
Set<ModifierKind> other = categorizedMods.third;
@@ -221,8 +258,12 @@ private static Optional<?> mergeComments(Object base, Object left, Object right)
221258

222259
// visibility is the only place where we can have obvious addition conflicts
223260
// TODO further analyze conflicts among other modifiers (e.g. you can't combine static and volatile)
224-
if (visibility.size() != 1) {
225-
return Optional.empty();
261+
boolean conflict = visibility.size() != 1 ||
262+
!leftVis.equals(rightVis) && !leftVis.equals(baseVis) && !rightVis.equals(baseVis);
263+
264+
if (conflict) {
265+
// use left version on conflict to follow the convention
266+
visibility = leftVis;
226267
}
227268

228269
Set<ModifierKind> mods = Stream.of(visibility, keywords, other).flatMap(Set::stream)
@@ -235,7 +276,7 @@ private static Optional<?> mergeComments(Object base, Object left, Object right)
235276
)
236277
.collect(Collectors.toSet());
237278

238-
return Optional.of(mods);
279+
return Pair.of(conflict,Optional.of(mods));
239280
}
240281

241282
private static _ContentTriple getContentRevisions(Set<Content<SpoonNode, RoledValues>> contents) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
static class cls {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<<<<<<< LEFT
2+
public
3+
=======
4+
private
5+
>>>>>>> RIGHT
6+
static class Cls {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
public static class Cls {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
private static class Cls {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Cls {
2+
void method() {}
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Cls {
2+
<<<<<<< LEFT
3+
public
4+
=======
5+
private
6+
>>>>>>> RIGHT
7+
void method() {}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Cls {
2+
public void method() {}
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Cls {
2+
private void method() {}
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Cls {
2+
public void method() {}
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class Cls {
2+
<<<<<<< LEFT
3+
=======
4+
protected
5+
>>>>>>> RIGHT
6+
final static void method() {}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Cls {
2+
final void method() {}
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Cls {
2+
protected static void method() {}
3+
}

0 commit comments

Comments
 (0)