diff options
Diffstat (limited to 'javaparser-core/src/main/java/com/github/javaparser/printer/lexicalpreservation/LexicalDifferenceCalculator.java')
-rw-r--r-- | javaparser-core/src/main/java/com/github/javaparser/printer/lexicalpreservation/LexicalDifferenceCalculator.java | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/javaparser-core/src/main/java/com/github/javaparser/printer/lexicalpreservation/LexicalDifferenceCalculator.java b/javaparser-core/src/main/java/com/github/javaparser/printer/lexicalpreservation/LexicalDifferenceCalculator.java new file mode 100644 index 000000000..9263dfd8a --- /dev/null +++ b/javaparser-core/src/main/java/com/github/javaparser/printer/lexicalpreservation/LexicalDifferenceCalculator.java @@ -0,0 +1,332 @@ +package com.github.javaparser.printer.lexicalpreservation; + +import com.github.javaparser.GeneratedJavaParserConstants; +import com.github.javaparser.ast.Modifier; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.expr.StringLiteralExpr; +import com.github.javaparser.ast.observer.ObservableProperty; +import com.github.javaparser.printer.ConcreteSyntaxModel; +import com.github.javaparser.printer.Printable; +import com.github.javaparser.printer.SourcePrinter; +import com.github.javaparser.printer.concretesyntaxmodel.*; +import com.github.javaparser.printer.lexicalpreservation.changes.*; + +import java.util.*; + +class LexicalDifferenceCalculator { + + /** + * The ConcreteSyntaxModel represents the general format. This model is a calculated version of the ConcreteSyntaxModel, + * with no condition, no lists, just tokens and node children. + */ + static class CalculatedSyntaxModel { + final List<CsmElement> elements; + + CalculatedSyntaxModel(List<CsmElement> elements) { + this.elements = elements; + } + + public CalculatedSyntaxModel from(int index) { + List<CsmElement> newList = new LinkedList<>(); + newList.addAll(elements.subList(index, elements.size())); + return new CalculatedSyntaxModel(newList); + } + + @Override + public String toString() { + return "CalculatedSyntaxModel{" + + "elements=" + elements + + '}'; + } + + CalculatedSyntaxModel sub(int start, int end) { + return new CalculatedSyntaxModel(elements.subList(start, end)); + } + + void removeIndentationElements() { + elements.removeIf(el -> el instanceof CsmIndent || el instanceof CsmUnindent); + } + } + + static class CsmChild implements CsmElement { + private final Node child; + + public Node getChild() { + return child; + } + + CsmChild(Node child) { + this.child = child; + } + + @Override + public void prettyPrint(Node node, SourcePrinter printer) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return "child(" + child.getClass().getSimpleName()+")"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CsmChild csmChild = (CsmChild) o; + + return child.equals(csmChild.child); + } + + @Override + public int hashCode() { + return child.hashCode(); + } + } + + Difference calculateListRemovalDifference(ObservableProperty observableProperty, NodeList nodeList, int index) { + Node container = nodeList.getParentNodeForChildren(); + CsmElement element = ConcreteSyntaxModel.forClass(container.getClass()); + CalculatedSyntaxModel original = calculatedSyntaxModelForNode(element, container); + CalculatedSyntaxModel after = calculatedSyntaxModelAfterListRemoval(element, observableProperty, nodeList, index); + return Difference.calculate(original, after); + } + + Difference calculateListAdditionDifference(ObservableProperty observableProperty, NodeList nodeList, int index, Node nodeAdded) { + Node container = nodeList.getParentNodeForChildren(); + CsmElement element = ConcreteSyntaxModel.forClass(container.getClass()); + CalculatedSyntaxModel original = calculatedSyntaxModelForNode(element, container); + CalculatedSyntaxModel after = calculatedSyntaxModelAfterListAddition(element, observableProperty, nodeList, index, nodeAdded); + return Difference.calculate(original, after); + } + + Difference calculateListReplacementDifference(ObservableProperty observableProperty, NodeList nodeList, int index, Node newValue) { + Node container = nodeList.getParentNodeForChildren(); + CsmElement element = ConcreteSyntaxModel.forClass(container.getClass()); + CalculatedSyntaxModel original = calculatedSyntaxModelForNode(element, container); + CalculatedSyntaxModel after = calculatedSyntaxModelAfterListReplacement(element, observableProperty, nodeList, index, newValue); + return Difference.calculate(original, after); + } + + public void calculatePropertyChange(NodeText nodeText, Node observedNode, ObservableProperty property, Object oldValue, Object newValue) { + if (nodeText == null) { + throw new NullPointerException(); + } + CsmElement element = ConcreteSyntaxModel.forClass(observedNode.getClass()); + CalculatedSyntaxModel original = calculatedSyntaxModelForNode(element, observedNode); + CalculatedSyntaxModel after = calculatedSyntaxModelAfterPropertyChange(element, observedNode, property, oldValue, newValue); + Difference difference = Difference.calculate(original, after); + difference.apply(nodeText, observedNode); + } + + // Visible for testing + CalculatedSyntaxModel calculatedSyntaxModelForNode(CsmElement csm, Node node) { + List<CsmElement> elements = new LinkedList<>(); + calculatedSyntaxModelForNode(csm, node, elements, new NoChange()); + return new CalculatedSyntaxModel(elements); + } + + CalculatedSyntaxModel calculatedSyntaxModelForNode(Node node) { + return calculatedSyntaxModelForNode(ConcreteSyntaxModel.forClass(node.getClass()), node); + } + + private void calculatedSyntaxModelForNode(CsmElement csm, Node node, List<CsmElement> elements, Change change) { + if (csm instanceof CsmSequence) { + CsmSequence csmSequence = (CsmSequence) csm; + csmSequence.getElements().forEach(e -> calculatedSyntaxModelForNode(e, node, elements, change)); + } else if (csm instanceof CsmComment) { + // nothing to do + } else if (csm instanceof CsmSingleReference) { + CsmSingleReference csmSingleReference = (CsmSingleReference)csm; + Node child; + if (change instanceof PropertyChange && ((PropertyChange)change).getProperty() == csmSingleReference.getProperty()) { + child = (Node)((PropertyChange)change).getNewValue(); + } else { + child = csmSingleReference.getProperty().getValueAsSingleReference(node); + } + if (child != null) { + elements.add(new CsmChild(child)); + } + } else if (csm instanceof CsmNone) { + // nothing to do + } else if (csm instanceof CsmToken) { + elements.add(csm); + } else if (csm instanceof CsmOrphanCommentsEnding) { + // nothing to do + } else if (csm instanceof CsmList) { + CsmList csmList = (CsmList) csm; + if (csmList.getProperty().isAboutNodes()) { + Object rawValue = change.getValue(csmList.getProperty(), node); + NodeList nodeList; + if (rawValue instanceof Optional) { + Optional optional = (Optional)rawValue; + if (optional.isPresent()) { + if (!(optional.get() instanceof NodeList)) { + throw new IllegalStateException("Expected NodeList, found " + optional.get().getClass().getCanonicalName()); + } + nodeList = (NodeList) optional.get(); + } else { + nodeList = new NodeList(); + } + } else { + if (!(rawValue instanceof NodeList)) { + throw new IllegalStateException("Expected NodeList, found " + rawValue.getClass().getCanonicalName()); + } + nodeList = (NodeList) rawValue; + } + if (!nodeList.isEmpty()) { + calculatedSyntaxModelForNode(csmList.getPreceeding(), node, elements, change); + for (int i = 0; i < nodeList.size(); i++) { + if (i != 0) { + calculatedSyntaxModelForNode(csmList.getSeparatorPre(), node, elements, change); + } + elements.add(new CsmChild(nodeList.get(i))); + if (i != (nodeList.size() - 1)) { + calculatedSyntaxModelForNode(csmList.getSeparatorPost(), node, elements, change); + } + + } + calculatedSyntaxModelForNode(csmList.getFollowing(), node, elements, change); + } + } else { + Collection collection = (Collection) change.getValue(csmList.getProperty(), node); + if (!collection.isEmpty()) { + calculatedSyntaxModelForNode(csmList.getPreceeding(), node, elements, change); + + boolean first = true; + for (Iterator it = collection.iterator(); it.hasNext(); ) { + if (!first) { + calculatedSyntaxModelForNode(csmList.getSeparatorPre(), node, elements, change); + } + Object value = it.next(); + if (value instanceof Modifier) { + Modifier modifier = (Modifier)value; + elements.add(new CsmToken(toToken(modifier))); + } else { + throw new UnsupportedOperationException(it.next().getClass().getSimpleName()); + } + if (it.hasNext()) { + calculatedSyntaxModelForNode(csmList.getSeparatorPost(), node, elements, change); + } + first = false; + } + calculatedSyntaxModelForNode(csmList.getFollowing(), node, elements, change); + } + } + } else if (csm instanceof CsmConditional) { + CsmConditional csmConditional = (CsmConditional) csm; + boolean satisfied = change.evaluate(csmConditional, node); + if (satisfied) { + calculatedSyntaxModelForNode(csmConditional.getThenElement(), node, elements, change); + } else { + calculatedSyntaxModelForNode(csmConditional.getElseElement(), node, elements, change); + } + } else if (csm instanceof CsmIndent) { + elements.add(csm); + } else if (csm instanceof CsmUnindent) { + elements.add(csm); + } else if (csm instanceof CsmAttribute) { + CsmAttribute csmAttribute = (CsmAttribute) csm; + Object value = change.getValue(csmAttribute.getProperty(), node); + String text = value.toString(); + if (value instanceof Printable) { + text = ((Printable) value).asString(); + } + elements.add(new CsmToken(csmAttribute.getTokenType(node, value.toString()), text)); + } else if ((csm instanceof CsmString) && (node instanceof StringLiteralExpr)) { + elements.add(new CsmToken(GeneratedJavaParserConstants.STRING_LITERAL, + "\"" + ((StringLiteralExpr) node).getValue() + "\"")); + } else if (csm instanceof CsmMix) { + CsmMix csmMix = (CsmMix)csm; + List<CsmElement> mixElements = new LinkedList<>(); + csmMix.getElements().forEach(e -> calculatedSyntaxModelForNode(e, node, mixElements, change)); + elements.add(new CsmMix(mixElements)); + } else { + throw new UnsupportedOperationException(csm.getClass().getSimpleName()+ " " + csm); + } + } + + private int toToken(Modifier modifier) { + switch (modifier) { + case PUBLIC: + return GeneratedJavaParserConstants.PUBLIC; + case PRIVATE: + return GeneratedJavaParserConstants.PRIVATE; + case PROTECTED: + return GeneratedJavaParserConstants.PROTECTED; + case STATIC: + return GeneratedJavaParserConstants.STATIC; + case FINAL: + return GeneratedJavaParserConstants.FINAL; + case ABSTRACT: + return GeneratedJavaParserConstants.ABSTRACT; + default: + throw new UnsupportedOperationException(modifier.name()); + } + } + + /// + /// Methods that calculate CalculatedSyntaxModel + /// + + // Visible for testing + CalculatedSyntaxModel calculatedSyntaxModelAfterPropertyChange(Node node, ObservableProperty property, Object oldValue, Object newValue) { + return calculatedSyntaxModelAfterPropertyChange(ConcreteSyntaxModel.forClass(node.getClass()), node, property, oldValue, newValue); + } + + // Visible for testing + CalculatedSyntaxModel calculatedSyntaxModelAfterPropertyChange(CsmElement csm, Node node, ObservableProperty property, Object oldValue, Object newValue) { + List<CsmElement> elements = new LinkedList<>(); + calculatedSyntaxModelForNode(csm, node, elements, new PropertyChange(property, oldValue, newValue)); + return new CalculatedSyntaxModel(elements); + } + + // Visible for testing + CalculatedSyntaxModel calculatedSyntaxModelAfterListRemoval(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index) { + List<CsmElement> elements = new LinkedList<>(); + Node container = nodeList.getParentNodeForChildren(); + calculatedSyntaxModelForNode(csm, container, elements, new ListRemovalChange(observableProperty, index)); + return new CalculatedSyntaxModel(elements); + } + + // Visible for testing + CalculatedSyntaxModel calculatedSyntaxModelAfterListAddition(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index, Node nodeAdded) { + List<CsmElement> elements = new LinkedList<>(); + Node container = nodeList.getParentNodeForChildren(); + calculatedSyntaxModelForNode(csm, container, elements, new ListAdditionChange(observableProperty, index, nodeAdded)); + return new CalculatedSyntaxModel(elements); + } + + // Visible for testing + CalculatedSyntaxModel calculatedSyntaxModelAfterListAddition(Node container, ObservableProperty observableProperty, int index, Node nodeAdded) { + CsmElement csm = ConcreteSyntaxModel.forClass(container.getClass()); + Object rawValue = observableProperty.getRawValue(container); + if (!(rawValue instanceof NodeList)) { + throw new IllegalStateException("Expected NodeList, found " + rawValue.getClass().getCanonicalName()); + } + NodeList nodeList = (NodeList)rawValue; + return calculatedSyntaxModelAfterListAddition(csm, observableProperty, nodeList, index, nodeAdded); + } + + // Visible for testing + CalculatedSyntaxModel calculatedSyntaxModelAfterListRemoval(Node container, ObservableProperty observableProperty, int index) { + CsmElement csm = ConcreteSyntaxModel.forClass(container.getClass()); + Object rawValue = observableProperty.getRawValue(container); + if (!(rawValue instanceof NodeList)) { + throw new IllegalStateException("Expected NodeList, found " + rawValue.getClass().getCanonicalName()); + } + NodeList nodeList = (NodeList)rawValue; + return calculatedSyntaxModelAfterListRemoval(csm, observableProperty, nodeList, index); + } + + // Visible for testing + private CalculatedSyntaxModel calculatedSyntaxModelAfterListReplacement(CsmElement csm, ObservableProperty observableProperty, NodeList nodeList, int index, Node newValue) { + List<CsmElement> elements = new LinkedList<>(); + Node container = nodeList.getParentNodeForChildren(); + calculatedSyntaxModelForNode(csm, container, elements, new ListReplacementChange(observableProperty, index, newValue)); + return new CalculatedSyntaxModel(elements); + } + +} |