Home | History | Annotate | Download | only in lexicalpreservation
      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