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 26 CalculatedSyntaxModel(List<CsmElement> elements) { 27 this.elements = elements; 28 } 29 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 37 public String toString() { 38 return "CalculatedSyntaxModel{" + 39 "elements=" + elements + 40 '}'; 41 } 42 43 CalculatedSyntaxModel sub(int start, int end) { 44 return new CalculatedSyntaxModel(elements.subList(start, end)); 45 } 46 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 55 public Node getChild() { 56 return child; 57 } 58 59 CsmChild(Node child) { 60 this.child = child; 61 } 62 63 @Override 64 public void prettyPrint(Node node, SourcePrinter printer) { 65 throw new UnsupportedOperationException(); 66 } 67 68 @Override 69 public String toString() { 70 return "child(" + child.getClass().getSimpleName()+")"; 71 } 72 73 @Override 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 84 public int hashCode() { 85 return child.hashCode(); 86 } 87 } 88 89 Difference 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 Difference.calculate(original, after); 95 } 96 97 Difference 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 Difference.calculate(original, after); 103 } 104 105 Difference 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 Difference.calculate(original, after); 111 } 112 113 public 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 Difference difference = Difference.calculate(original, after); 121 difference.apply(nodeText, observedNode); 122 } 123 124 // Visible for testing 125 CalculatedSyntaxModel calculatedSyntaxModelForNode(CsmElement csm, Node node) { 126 List<CsmElement> elements = new LinkedList<>(); 127 calculatedSyntaxModelForNode(csm, node, elements, new NoChange()); 128 return new CalculatedSyntaxModel(elements); 129 } 130 131 CalculatedSyntaxModel calculatedSyntaxModelForNode(Node node) { 132 return calculatedSyntaxModelForNode(ConcreteSyntaxModel.forClass(node.getClass()), node); 133 } 134 135 private void calculatedSyntaxModelForNode(CsmElement csm, Node node, List<CsmElement> elements, Change change) { 136 if (csm instanceof CsmSequence) { 137 CsmSequence csmSequence = (CsmSequence) csm; 138 csmSequence.getElements().forEach(e -> calculatedSyntaxModelForNode(e, node, elements, change)); 139 } else if (csm instanceof CsmComment) { 140 // nothing to do 141 } else if (csm instanceof CsmSingleReference) { 142 CsmSingleReference csmSingleReference = (CsmSingleReference)csm; 143 Node child; 144 if (change instanceof PropertyChange && ((PropertyChange)change).getProperty() == csmSingleReference.getProperty()) { 145 child = (Node)((PropertyChange)change).getNewValue(); 146 } else { 147 child = csmSingleReference.getProperty().getValueAsSingleReference(node); 148 } 149 if (child != null) { 150 elements.add(new CsmChild(child)); 151 } 152 } else if (csm instanceof CsmNone) { 153 // nothing to do 154 } else if (csm instanceof CsmToken) { 155 elements.add(csm); 156 } else if (csm instanceof CsmOrphanCommentsEnding) { 157 // nothing to do 158 } else if (csm instanceof CsmList) { 159 CsmList csmList = (CsmList) csm; 160 if (csmList.getProperty().isAboutNodes()) { 161 Object rawValue = change.getValue(csmList.getProperty(), node); 162 NodeList nodeList; 163 if (rawValue instanceof Optional) { 164 Optional optional = (Optional)rawValue; 165 if (optional.isPresent()) { 166 if (!(optional.get() instanceof NodeList)) { 167 throw new IllegalStateException("Expected NodeList, found " + optional.get().getClass().getCanonicalName()); 168 } 169 nodeList = (NodeList) optional.get(); 170 } else { 171 nodeList = new NodeList(); 172 } 173 } else { 174 if (!(rawValue instanceof NodeList)) { 175 throw new IllegalStateException("Expected NodeList, found " + rawValue.getClass().getCanonicalName()); 176 } 177 nodeList = (NodeList) rawValue; 178 } 179 if (!nodeList.isEmpty()) { 180 calculatedSyntaxModelForNode(csmList.getPreceeding(), node, elements, change); 181 for (int i = 0; i < nodeList.size(); i++) { 182 if (i != 0) { 183 calculatedSyntaxModelForNode(csmList.getSeparatorPre(), node, elements, change); 184 } 185 elements.add(new CsmChild(nodeList.get(i))); 186 if (i != (nodeList.size() - 1)) { 187 calculatedSyntaxModelForNode(csmList.getSeparatorPost(), node, elements, change); 188 } 189 190 } 191 calculatedSyntaxModelForNode(csmList.getFollowing(), node, elements, change); 192 } 193 } else { 194 Collection collection = (Collection) change.getValue(csmList.getProperty(), node); 195 if (!collection.isEmpty()) { 196 calculatedSyntaxModelForNode(csmList.getPreceeding(), node, elements, change); 197 198 boolean first = true; 199 for (Iterator it = collection.iterator(); it.hasNext(); ) { 200 if (!first) { 201 calculatedSyntaxModelForNode(csmList.getSeparatorPre(), node, elements, change); 202 } 203 Object value = it.next(); 204 if (value instanceof Modifier) { 205 Modifier modifier = (Modifier)value; 206 elements.add(new CsmToken(toToken(modifier))); 207 } else { 208 throw new UnsupportedOperationException(it.next().getClass().getSimpleName()); 209 } 210 if (it.hasNext()) { 211 calculatedSyntaxModelForNode(csmList.getSeparatorPost(), node, elements, change); 212 } 213 first = false; 214 } 215 calculatedSyntaxModelForNode(csmList.getFollowing(), node, elements, change); 216 } 217 } 218 } else if (csm instanceof CsmConditional) { 219 CsmConditional csmConditional = (CsmConditional) csm; 220 boolean satisfied = change.evaluate(csmConditional, node); 221 if (satisfied) { 222 calculatedSyntaxModelForNode(csmConditional.getThenElement(), node, elements, change); 223 } else { 224 calculatedSyntaxModelForNode(csmConditional.getElseElement(), node, elements, change); 225 } 226 } else if (csm instanceof CsmIndent) { 227 elements.add(csm); 228 } else if (csm instanceof CsmUnindent) { 229 elements.add(csm); 230 } else if (csm instanceof CsmAttribute) { 231 CsmAttribute csmAttribute = (CsmAttribute) csm; 232 Object value = change.getValue(csmAttribute.getProperty(), node); 233 String text = value.toString(); 234 if (value instanceof Printable) { 235 text = ((Printable) value).asString(); 236 } 237 elements.add(new CsmToken(csmAttribute.getTokenType(node, value.toString()), text)); 238 } else if ((csm instanceof CsmString) && (node instanceof StringLiteralExpr)) { 239 elements.add(new CsmToken(GeneratedJavaParserConstants.STRING_LITERAL, 240 "\"" + ((StringLiteralExpr) node).getValue() + "\"")); 241 } else if (csm instanceof CsmMix) { 242 CsmMix csmMix = (CsmMix)csm; 243 List<CsmElement> mixElements = new LinkedList<>(); 244 csmMix.getElements().forEach(e -> calculatedSyntaxModelForNode(e, node, mixElements, change)); 245 elements.add(new CsmMix(mixElements)); 246 } else { 247 throw new UnsupportedOperationException(csm.getClass().getSimpleName()+ " " + csm); 248 } 249 } 250 251 private int toToken(Modifier modifier) { 252 switch (modifier) { 253 case PUBLIC: 254 return GeneratedJavaParserConstants.PUBLIC; 255 case PRIVATE: 256 return GeneratedJavaParserConstants.PRIVATE; 257 case PROTECTED: 258 return GeneratedJavaParserConstants.PROTECTED; 259 case STATIC: 260 return GeneratedJavaParserConstants.STATIC; 261 case FINAL: 262 return GeneratedJavaParserConstants.FINAL; 263 case ABSTRACT: 264 return GeneratedJavaParserConstants.ABSTRACT; 265 default: 266 throw new UnsupportedOperationException(modifier.name()); 267 } 268 } 269 270 /// 271 /// Methods that calculate CalculatedSyntaxModel 272 /// 273 274 // Visible for testing 275 CalculatedSyntaxModel calculatedSyntaxModelAfterPropertyChange(Node node, ObservableProperty property, Object oldValue, Object newValue) { 276 return calculatedSyntaxModelAfterPropertyChange(ConcreteSyntaxModel.forClass(node.getClass()), node, property, oldValue, newValue); 277 } 278 279 // Visible for testing 280 CalculatedSyntaxModel calculatedSyntaxModelAfterPropertyChange(CsmElement csm, Node node, ObservableProperty property, Object oldValue, Object newValue) { 281 List<CsmElement> elements = new LinkedList<>(); 282 calculatedSyntaxModelForNode(csm, node, elements, new PropertyChange(property, oldValue, newValue)); 283 return new CalculatedSyntaxModel(elements); 284 } 285 286 // Visible for testing 287 CalculatedSyntaxModel calculatedSyntaxModelAfterListRemoval(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index) { 288 List<CsmElement> elements = new LinkedList<>(); 289 Node container = nodeList.getParentNodeForChildren(); 290 calculatedSyntaxModelForNode(csm, container, elements, new ListRemovalChange(observableProperty, index)); 291 return new CalculatedSyntaxModel(elements); 292 } 293 294 // Visible for testing 295 CalculatedSyntaxModel calculatedSyntaxModelAfterListAddition(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index, Node nodeAdded) { 296 List<CsmElement> elements = new LinkedList<>(); 297 Node container = nodeList.getParentNodeForChildren(); 298 calculatedSyntaxModelForNode(csm, container, elements, new ListAdditionChange(observableProperty, index, nodeAdded)); 299 return new CalculatedSyntaxModel(elements); 300 } 301 302 // Visible for testing 303 CalculatedSyntaxModel calculatedSyntaxModelAfterListAddition(Node container, ObservableProperty observableProperty, int index, Node nodeAdded) { 304 CsmElement csm = ConcreteSyntaxModel.forClass(container.getClass()); 305 Object rawValue = observableProperty.getRawValue(container); 306 if (!(rawValue instanceof NodeList)) { 307 throw new IllegalStateException("Expected NodeList, found " + rawValue.getClass().getCanonicalName()); 308 } 309 NodeList nodeList = (NodeList)rawValue; 310 return calculatedSyntaxModelAfterListAddition(csm, observableProperty, nodeList, index, nodeAdded); 311 } 312 313 // Visible for testing 314 CalculatedSyntaxModel calculatedSyntaxModelAfterListRemoval(Node container, ObservableProperty observableProperty, int index) { 315 CsmElement csm = ConcreteSyntaxModel.forClass(container.getClass()); 316 Object rawValue = observableProperty.getRawValue(container); 317 if (!(rawValue instanceof NodeList)) { 318 throw new IllegalStateException("Expected NodeList, found " + rawValue.getClass().getCanonicalName()); 319 } 320 NodeList nodeList = (NodeList)rawValue; 321 return calculatedSyntaxModelAfterListRemoval(csm, observableProperty, nodeList, index); 322 } 323 324 // Visible for testing 325 private CalculatedSyntaxModel calculatedSyntaxModelAfterListReplacement(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index, Node newValue) { 326 List<CsmElement> elements = new LinkedList<>(); 327 Node container = nodeList.getParentNodeForChildren(); 328 calculatedSyntaxModelForNode(csm, container, elements, new ListReplacementChange(observableProperty, index, newValue)); 329 return new CalculatedSyntaxModel(elements); 330 } 331 332 } 333