• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.github.javaparser.printer.lexicalpreservation;
2 
3 import com.github.javaparser.GeneratedJavaParserConstants;
4 import com.github.javaparser.ast.Modifier;
5 import com.github.javaparser.ast.Node;
6 import com.github.javaparser.ast.NodeList;
7 import com.github.javaparser.ast.expr.StringLiteralExpr;
8 import com.github.javaparser.ast.observer.ObservableProperty;
9 import com.github.javaparser.printer.ConcreteSyntaxModel;
10 import com.github.javaparser.printer.Printable;
11 import com.github.javaparser.printer.SourcePrinter;
12 import com.github.javaparser.printer.concretesyntaxmodel.*;
13 import com.github.javaparser.printer.lexicalpreservation.changes.*;
14 
15 import java.util.*;
16 
17 class LexicalDifferenceCalculator {
18 
19     /**
20      * The ConcreteSyntaxModel represents the general format. This model is a calculated version of the ConcreteSyntaxModel,
21      * with no condition, no lists, just tokens and node children.
22      */
23     static class CalculatedSyntaxModel {
24         final List<CsmElement> elements;
25 
CalculatedSyntaxModel(List<CsmElement> elements)26         CalculatedSyntaxModel(List<CsmElement> elements) {
27             this.elements = elements;
28         }
29 
from(int index)30         public CalculatedSyntaxModel from(int index) {
31             List<CsmElement> newList = new LinkedList<>();
32             newList.addAll(elements.subList(index, elements.size()));
33             return new CalculatedSyntaxModel(newList);
34         }
35 
36         @Override
toString()37         public String toString() {
38             return "CalculatedSyntaxModel{" +
39                     "elements=" + elements +
40                     '}';
41         }
42 
sub(int start, int end)43         CalculatedSyntaxModel sub(int start, int end) {
44             return new CalculatedSyntaxModel(elements.subList(start, end));
45         }
46 
removeIndentationElements()47         void removeIndentationElements() {
48             elements.removeIf(el -> el instanceof CsmIndent || el instanceof CsmUnindent);
49         }
50     }
51 
52     static class CsmChild implements CsmElement {
53         private final Node child;
54 
getChild()55         public Node getChild() {
56             return child;
57         }
58 
CsmChild(Node child)59         CsmChild(Node child) {
60             this.child = child;
61         }
62 
63         @Override
prettyPrint(Node node, SourcePrinter printer)64         public void prettyPrint(Node node, SourcePrinter printer) {
65             throw new UnsupportedOperationException();
66         }
67 
68         @Override
toString()69         public String toString() {
70             return "child(" + child.getClass().getSimpleName()+")";
71         }
72 
73         @Override
equals(Object o)74         public boolean equals(Object o) {
75             if (this == o) return true;
76             if (o == null || getClass() != o.getClass()) return false;
77 
78             CsmChild csmChild = (CsmChild) o;
79 
80             return child.equals(csmChild.child);
81         }
82 
83         @Override
hashCode()84         public int hashCode() {
85             return child.hashCode();
86         }
87     }
88 
calculateListRemovalDifference(ObservableProperty observableProperty, NodeList nodeList, int index)89     List<DifferenceElement> calculateListRemovalDifference(ObservableProperty observableProperty, NodeList nodeList, int index) {
90         Node container = nodeList.getParentNodeForChildren();
91         CsmElement element = ConcreteSyntaxModel.forClass(container.getClass());
92         CalculatedSyntaxModel original = calculatedSyntaxModelForNode(element, container);
93         CalculatedSyntaxModel after = calculatedSyntaxModelAfterListRemoval(element, observableProperty, nodeList, index);
94         return DifferenceElementCalculator.calculate(original, after);
95     }
96 
calculateListAdditionDifference(ObservableProperty observableProperty, NodeList nodeList, int index, Node nodeAdded)97     List<DifferenceElement> calculateListAdditionDifference(ObservableProperty observableProperty, NodeList nodeList, int index, Node nodeAdded) {
98         Node container = nodeList.getParentNodeForChildren();
99         CsmElement element = ConcreteSyntaxModel.forClass(container.getClass());
100         CalculatedSyntaxModel original = calculatedSyntaxModelForNode(element, container);
101         CalculatedSyntaxModel after = calculatedSyntaxModelAfterListAddition(element, observableProperty, nodeList, index, nodeAdded);
102         return DifferenceElementCalculator.calculate(original, after);
103     }
104 
calculateListReplacementDifference(ObservableProperty observableProperty, NodeList nodeList, int index, Node newValue)105     List<DifferenceElement> calculateListReplacementDifference(ObservableProperty observableProperty, NodeList nodeList, int index, Node newValue) {
106         Node container = nodeList.getParentNodeForChildren();
107         CsmElement element = ConcreteSyntaxModel.forClass(container.getClass());
108         CalculatedSyntaxModel original = calculatedSyntaxModelForNode(element, container);
109         CalculatedSyntaxModel after = calculatedSyntaxModelAfterListReplacement(element, observableProperty, nodeList, index, newValue);
110         return DifferenceElementCalculator.calculate(original, after);
111     }
112 
calculatePropertyChange(NodeText nodeText, Node observedNode, ObservableProperty property, Object oldValue, Object newValue)113     void calculatePropertyChange(NodeText nodeText, Node observedNode, ObservableProperty property, Object oldValue, Object newValue) {
114         if (nodeText == null) {
115             throw new NullPointerException();
116         }
117         CsmElement element = ConcreteSyntaxModel.forClass(observedNode.getClass());
118         CalculatedSyntaxModel original = calculatedSyntaxModelForNode(element, observedNode);
119         CalculatedSyntaxModel after = calculatedSyntaxModelAfterPropertyChange(element, observedNode, property, oldValue, newValue);
120         List<DifferenceElement> differenceElements = DifferenceElementCalculator.calculate(original, after);
121         Difference difference = new Difference(differenceElements, nodeText, observedNode);
122         difference.apply();
123     }
124 
125     // Visible for testing
calculatedSyntaxModelForNode(CsmElement csm, Node node)126     CalculatedSyntaxModel calculatedSyntaxModelForNode(CsmElement csm, Node node) {
127         List<CsmElement> elements = new LinkedList<>();
128         calculatedSyntaxModelForNode(csm, node, elements, new NoChange());
129         return new CalculatedSyntaxModel(elements);
130     }
131 
calculatedSyntaxModelForNode(Node node)132     CalculatedSyntaxModel calculatedSyntaxModelForNode(Node node) {
133         return calculatedSyntaxModelForNode(ConcreteSyntaxModel.forClass(node.getClass()), node);
134     }
135 
calculatedSyntaxModelForNode(CsmElement csm, Node node, List<CsmElement> elements, Change change)136     private void calculatedSyntaxModelForNode(CsmElement csm, Node node, List<CsmElement> elements, Change change) {
137         if (csm instanceof CsmSequence) {
138             CsmSequence csmSequence = (CsmSequence) csm;
139             csmSequence.getElements().forEach(e -> calculatedSyntaxModelForNode(e, node, elements, change));
140         } else if (csm instanceof CsmComment) {
141             // nothing to do
142         } else if (csm instanceof CsmSingleReference) {
143             CsmSingleReference csmSingleReference = (CsmSingleReference)csm;
144             Node child;
145             if (change instanceof PropertyChange && ((PropertyChange)change).getProperty() == csmSingleReference.getProperty()) {
146                 child = (Node)((PropertyChange)change).getNewValue();
147             } else {
148                 child = csmSingleReference.getProperty().getValueAsSingleReference(node);
149             }
150             if (child != null) {
151                 elements.add(new CsmChild(child));
152             }
153         } else if (csm instanceof CsmNone) {
154             // nothing to do
155         } else if (csm instanceof CsmToken) {
156             elements.add(csm);
157         } else if (csm instanceof CsmOrphanCommentsEnding) {
158             // nothing to do
159         } else if (csm instanceof CsmList) {
160             CsmList csmList = (CsmList) csm;
161             if (csmList.getProperty().isAboutNodes()) {
162                 Object rawValue = change.getValue(csmList.getProperty(), node);
163                 NodeList nodeList;
164                 if (rawValue instanceof Optional) {
165                     Optional optional = (Optional)rawValue;
166                     if (optional.isPresent()) {
167                         if (!(optional.get() instanceof NodeList)) {
168                             throw new IllegalStateException("Expected NodeList, found " + optional.get().getClass().getCanonicalName());
169                         }
170                         nodeList = (NodeList) optional.get();
171                     } else {
172                         nodeList = new NodeList();
173                     }
174                 } else {
175                     if (!(rawValue instanceof NodeList)) {
176                         throw new IllegalStateException("Expected NodeList, found " + rawValue.getClass().getCanonicalName());
177                     }
178                     nodeList = (NodeList) rawValue;
179                 }
180                 if (!nodeList.isEmpty()) {
181                     calculatedSyntaxModelForNode(csmList.getPreceeding(), node, elements, change);
182                     for (int i = 0; i < nodeList.size(); i++) {
183                         if (i != 0) {
184                             calculatedSyntaxModelForNode(csmList.getSeparatorPre(), node, elements, change);
185                         }
186                         elements.add(new CsmChild(nodeList.get(i)));
187                         if (i != (nodeList.size() - 1)) {
188                             calculatedSyntaxModelForNode(csmList.getSeparatorPost(), node, elements, change);
189                         }
190 
191                     }
192                     calculatedSyntaxModelForNode(csmList.getFollowing(), node, elements, change);
193                 }
194             } else {
195                 Collection collection = (Collection) change.getValue(csmList.getProperty(), node);
196                 if (!collection.isEmpty()) {
197                     calculatedSyntaxModelForNode(csmList.getPreceeding(), node, elements, change);
198 
199                     boolean first = true;
200                     for (Iterator it = collection.iterator(); it.hasNext(); ) {
201                         if (!first) {
202                             calculatedSyntaxModelForNode(csmList.getSeparatorPre(), node, elements, change);
203                         }
204                         Object value = it.next();
205                         if (value instanceof Modifier) {
206                             Modifier modifier = (Modifier)value;
207                             elements.add(new CsmToken(toToken(modifier)));
208                         } else {
209                             throw new UnsupportedOperationException(it.next().getClass().getSimpleName());
210                         }
211                         if (it.hasNext()) {
212                             calculatedSyntaxModelForNode(csmList.getSeparatorPost(), node, elements, change);
213                         }
214                         first = false;
215                     }
216                     calculatedSyntaxModelForNode(csmList.getFollowing(), node, elements, change);
217                 }
218             }
219         } else if (csm instanceof CsmConditional) {
220             CsmConditional csmConditional = (CsmConditional) csm;
221             boolean satisfied = change.evaluate(csmConditional, node);
222             if (satisfied) {
223                 calculatedSyntaxModelForNode(csmConditional.getThenElement(), node, elements, change);
224             } else {
225                 calculatedSyntaxModelForNode(csmConditional.getElseElement(), node, elements, change);
226             }
227         } else if (csm instanceof CsmIndent) {
228             elements.add(csm);
229         } else if (csm instanceof CsmUnindent) {
230             elements.add(csm);
231         } else if (csm instanceof CsmAttribute) {
232             CsmAttribute csmAttribute = (CsmAttribute) csm;
233             Object value = change.getValue(csmAttribute.getProperty(), node);
234             String text = value.toString();
235             if (value instanceof Printable) {
236                 text = ((Printable) value).asString();
237             }
238             elements.add(new CsmToken(csmAttribute.getTokenType(node, value.toString(), text), text));
239         } else if ((csm instanceof CsmString) && (node instanceof StringLiteralExpr)) {
240             elements.add(new CsmToken(GeneratedJavaParserConstants.STRING_LITERAL,
241                     "\"" + ((StringLiteralExpr) node).getValue() + "\""));
242         } else if (csm instanceof CsmMix) {
243             CsmMix csmMix = (CsmMix)csm;
244             List<CsmElement> mixElements = new LinkedList<>();
245             csmMix.getElements().forEach(e -> calculatedSyntaxModelForNode(e, node, mixElements, change));
246             elements.add(new CsmMix(mixElements));
247         } else if (csm instanceof CsmChild) {
248             elements.add(csm);
249         } else {
250             throw new UnsupportedOperationException(csm.getClass().getSimpleName()+ " " + csm);
251         }
252     }
253 
toToken(Modifier modifier)254     public static int toToken(Modifier modifier) {
255         switch (modifier.getKeyword()) {
256             case PUBLIC:
257                 return GeneratedJavaParserConstants.PUBLIC;
258             case PRIVATE:
259                 return GeneratedJavaParserConstants.PRIVATE;
260             case PROTECTED:
261                 return GeneratedJavaParserConstants.PROTECTED;
262             case STATIC:
263                 return GeneratedJavaParserConstants.STATIC;
264             case FINAL:
265                 return GeneratedJavaParserConstants.FINAL;
266             case ABSTRACT:
267                 return GeneratedJavaParserConstants.ABSTRACT;
268             default:
269                 throw new UnsupportedOperationException(modifier.getKeyword().name());
270         }
271     }
272 
273     ///
274     /// Methods that calculate CalculatedSyntaxModel
275     ///
276 
277     // Visible for testing
calculatedSyntaxModelAfterPropertyChange(Node node, ObservableProperty property, Object oldValue, Object newValue)278     CalculatedSyntaxModel calculatedSyntaxModelAfterPropertyChange(Node node, ObservableProperty property, Object oldValue, Object newValue) {
279         return calculatedSyntaxModelAfterPropertyChange(ConcreteSyntaxModel.forClass(node.getClass()), node, property, oldValue, newValue);
280     }
281 
282     // Visible for testing
calculatedSyntaxModelAfterPropertyChange(CsmElement csm, Node node, ObservableProperty property, Object oldValue, Object newValue)283     CalculatedSyntaxModel calculatedSyntaxModelAfterPropertyChange(CsmElement csm, Node node, ObservableProperty property, Object oldValue, Object newValue) {
284         List<CsmElement> elements = new LinkedList<>();
285         calculatedSyntaxModelForNode(csm, node, elements, new PropertyChange(property, oldValue, newValue));
286         return new CalculatedSyntaxModel(elements);
287     }
288 
289     // Visible for testing
calculatedSyntaxModelAfterListRemoval(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index)290     CalculatedSyntaxModel calculatedSyntaxModelAfterListRemoval(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index) {
291         List<CsmElement> elements = new LinkedList<>();
292         Node container = nodeList.getParentNodeForChildren();
293         calculatedSyntaxModelForNode(csm, container, elements, new ListRemovalChange(observableProperty, index));
294         return new CalculatedSyntaxModel(elements);
295     }
296 
297     // Visible for testing
calculatedSyntaxModelAfterListAddition(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index, Node nodeAdded)298     CalculatedSyntaxModel calculatedSyntaxModelAfterListAddition(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index, Node nodeAdded) {
299         List<CsmElement> elements = new LinkedList<>();
300         Node container = nodeList.getParentNodeForChildren();
301         calculatedSyntaxModelForNode(csm, container, elements, new ListAdditionChange(observableProperty, index, nodeAdded));
302         return new CalculatedSyntaxModel(elements);
303     }
304 
305     // Visible for testing
calculatedSyntaxModelAfterListAddition(Node container, ObservableProperty observableProperty, int index, Node nodeAdded)306     CalculatedSyntaxModel calculatedSyntaxModelAfterListAddition(Node container, ObservableProperty observableProperty, int index, Node nodeAdded) {
307         CsmElement csm = ConcreteSyntaxModel.forClass(container.getClass());
308         Object rawValue = observableProperty.getRawValue(container);
309         if (!(rawValue instanceof NodeList)) {
310             throw new IllegalStateException("Expected NodeList, found " + rawValue.getClass().getCanonicalName());
311         }
312         NodeList nodeList = (NodeList)rawValue;
313         return calculatedSyntaxModelAfterListAddition(csm, observableProperty, nodeList, index, nodeAdded);
314     }
315 
316     // Visible for testing
calculatedSyntaxModelAfterListRemoval(Node container, ObservableProperty observableProperty, int index)317     CalculatedSyntaxModel calculatedSyntaxModelAfterListRemoval(Node container, ObservableProperty observableProperty, int index) {
318         CsmElement csm = ConcreteSyntaxModel.forClass(container.getClass());
319         Object rawValue = observableProperty.getRawValue(container);
320         if (!(rawValue instanceof NodeList)) {
321             throw new IllegalStateException("Expected NodeList, found " + rawValue.getClass().getCanonicalName());
322         }
323         NodeList nodeList = (NodeList)rawValue;
324         return calculatedSyntaxModelAfterListRemoval(csm, observableProperty, nodeList, index);
325     }
326 
327     // Visible for testing
calculatedSyntaxModelAfterListReplacement(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index, Node newValue)328     private CalculatedSyntaxModel calculatedSyntaxModelAfterListReplacement(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index, Node newValue) {
329         List<CsmElement> elements = new LinkedList<>();
330         Node container = nodeList.getParentNodeForChildren();
331         calculatedSyntaxModelForNode(csm, container, elements, new ListReplacementChange(observableProperty, index, newValue));
332         return new CalculatedSyntaxModel(elements);
333     }
334 
335 }
336