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