JavaParser Source Viewer

Home|JavaParser/com/github/javaparser/printer/lexicalpreservation/DifferenceElementCalculator.java
1/*
2 * Copyright (C) 2007-2010 JĂșlio Vilmar Gesser.
3 * Copyright (C) 2011, 2013-2020 The JavaParser Team.
4 *
5 * This file is part of JavaParser.
6 *
7 * JavaParser can be used either under the terms of
8 * a) the GNU Lesser General Public License as published by
9 *     the Free Software Foundation, either version 3 of the License, or
10 *     (at your option) any later version.
11 * b) the terms of the Apache License
12 *
13 * You should have received a copy of both licenses in LICENCE.LGPL and
14 * LICENCE.APACHE. Please refer to those files for details.
15 *
16 * JavaParser is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 * GNU Lesser General Public License for more details.
20 */
21
22package com.github.javaparser.printer.lexicalpreservation;
23
24import java.util.ArrayList;
25import java.util.LinkedList;
26import java.util.List;
27
28import com.github.javaparser.ast.Node;
29import com.github.javaparser.ast.body.VariableDeclarator;
30import com.github.javaparser.ast.type.Type;
31import com.github.javaparser.printer.concretesyntaxmodel.CsmElement;
32import com.github.javaparser.printer.concretesyntaxmodel.CsmIndent;
33import com.github.javaparser.printer.concretesyntaxmodel.CsmMix;
34import com.github.javaparser.printer.concretesyntaxmodel.CsmToken;
35import com.github.javaparser.printer.concretesyntaxmodel.CsmUnindent;
36import com.github.javaparser.printer.lexicalpreservation.LexicalDifferenceCalculator.CsmChild;
37
38class DifferenceElementCalculator {
39    
40    // internally keep track of a node position in a List<CsmElement>
41    public static class ChildPositionInfo {
42        Node node;
43        Integer position;
44        ChildPositionInfo(Node nodeInteger position) {
45            this.node = node;
46            this.position = position;
47        }
48        @Override
49        public boolean equals(Object other) {
50            if ( other == null || !(other instanceof ChildPositionInfo))
51                return false;
52            ChildPositionInfo cpi = (ChildPositionInfo)other;
53            // verify that the node content and the position are equal 
54            // because we can have nodes with the same content but in different lines
55            // in this case we consider that nodes are not equals
56            return this.node.equals(cpi.node
57                    && this.node.getRange().isPresent() && cpi.node.getRange().isPresent()
58                    && this.node.getRange().get().contains(cpi.node.getRange().get());
59        }
60        @Override
61        public int hashCode() {
62            return node.hashCode() + position.hashCode();
63        }
64    }
65    
66    static boolean matching(CsmElement aCsmElement b) {
67        if (a instanceof CsmChild) {
68            if (b instanceof CsmChild) {
69                CsmChild childA = (CsmChilda;
70                CsmChild childB = (CsmChildb;
71                return childA.getChild().equals(childB.getChild());
72            } else if (b instanceof CsmToken) {
73                return false;
74            } else if (b instanceof CsmIndent) {
75                return false;
76            } else if (b instanceof CsmUnindent) {
77                return false;
78            } else {
79                throw new UnsupportedOperationException(a.getClass().getSimpleName()+ " "+b.getClass().getSimpleName());
80            }
81        } else if (a instanceof CsmToken) {
82            if (b instanceof CsmToken) {
83                // fix #2382:
84                // Tokens are described by their type AND their content
85                // and TokenContentCalculator. By using .equals(), all
86                // three values are compared.
87                CsmToken childA = (CsmToken)a;
88                CsmToken childB = (CsmToken)b;
89                return childA.equals(childB);
90            } else if (b instanceof CsmChild) {
91                return false;
92            } else if (b instanceof CsmIndent) {
93                return false;
94            } else if (b instanceof CsmUnindent) {
95                return false;
96            } else {
97                throw new UnsupportedOperationException(a.getClass().getSimpleName()+ " "+b.getClass().getSimpleName());
98            }
99        } else if (a instanceof CsmIndent) {
100            return b instanceof CsmIndent;
101        } else if (a instanceof CsmUnindent) {
102            return b instanceof CsmUnindent;
103        }
104        throw new UnsupportedOperationException(a.getClass().getSimpleName()+ " "+b.getClass().getSimpleName());
105    }
106
107    private static boolean replacement(CsmElement aCsmElement b) {
108        if (a instanceof CsmIndent || b instanceof CsmIndent || a instanceof CsmUnindent || b instanceof CsmUnindent) {
109            return false;
110        }
111        if (a instanceof CsmChild) {
112            if (b instanceof CsmChild) {
113                CsmChild childA = (CsmChilda;
114                CsmChild childB = (CsmChildb;
115                return childA.getChild().getClass().equals(childB.getChild().getClass());
116            } else if (b instanceof CsmToken) {
117                return false;
118            } else {
119                throw new UnsupportedOperationException(a.getClass().getSimpleName()+ " "+b.getClass().getSimpleName());
120            }
121        } else if (a instanceof CsmToken) {
122            if (b instanceof CsmToken) {
123                CsmToken childA = (CsmToken)a;
124                CsmToken childB = (CsmToken)b;
125                return childA.getTokenType() == childB.getTokenType();
126            } else if (b instanceof CsmChild) {
127                return false;
128            }
129        }
130        throw new UnsupportedOperationException(a.getClass().getSimpleName()+ " "+b.getClass().getSimpleName());
131    }
132
133    /**
134     * Find the positions of all the given children.
135     */
136    private static List<ChildPositionInfofindChildrenPositions(LexicalDifferenceCalculator.CalculatedSyntaxModel calculatedSyntaxModel) {
137        List<ChildPositionInfopositions = new ArrayList<>();
138        for (int i=0;i<calculatedSyntaxModel.elements.size();i++) {
139            CsmElement element = calculatedSyntaxModel.elements.get(i);
140            if (element instanceof CsmChild) {
141                positions.add(new ChildPositionInfo(((CsmChild)element).getChild(), i));
142            }
143        }
144        return positions;
145    }
146
147    /**
148     * Calculate the Difference between two CalculatedSyntaxModel elements, determining which elements were kept,
149     * which were added and which were removed.
150     */
151    static List<DifferenceElementcalculate(LexicalDifferenceCalculator.CalculatedSyntaxModel originalLexicalDifferenceCalculator.CalculatedSyntaxModel after) {
152        // For performance reasons we use the positions of matching children
153        // to guide the calculation of the difference
154        //
155        // Suppose we have:
156        //   qwerty[A]uiop
157        //   qwer[A]uiop
158        //
159        // with [A] being a child and lowercase letters being tokens
160        //
161        // We would calculate the Difference between "qwerty" and "qwer" then we know the A is kept, and then we
162        // would calculate the difference between "uiop" and "uiop"
163
164        List<ChildPositionInfochildrenInOriginal = findChildrenPositions(original);
165        List<ChildPositionInfochildrenInAfter = findChildrenPositions(after);
166
167        List<ChildPositionInfocommonChildren = new ArrayList<>(childrenInOriginal);
168        commonChildren.retainAll(childrenInAfter);
169
170        List<DifferenceElementelements = new LinkedList<>();
171
172        int originalIndex = 0;
173        int afterIndex = 0;
174        int commonChildrenIndex = 0;
175        while (commonChildrenIndex < commonChildren.size()) {
176            ChildPositionInfo child = commonChildren.get(commonChildrenIndex++);
177            // search the position of the node "child" in the original list of cms element
178            int posOfNextChildInOriginal = childrenInOriginal.stream().filter(i->i.equals(child)).map(i->i.position).findFirst().get();
179            // search the position of the node "child" in the modified list of cms element
180            int posOfNextChildInAfter    = childrenInAfter.stream().filter(i->i.equals(child)).map(i->i.position).findFirst().get();
181            
182            if (originalIndex < posOfNextChildInOriginal || afterIndex < posOfNextChildInAfter) {
183                elements.addAll(calculateImpl(original.sub(originalIndexposOfNextChildInOriginal), after.sub(afterIndexposOfNextChildInAfter)));
184            }
185            elements.add(new Kept(new CsmChild(child.node)));
186            originalIndex = posOfNextChildInOriginal + 1;
187            afterIndex = posOfNextChildInAfter + 1;
188        }
189
190        if (originalIndex < original.elements.size() || afterIndex < after.elements.size()) {
191            elements.addAll(calculateImpl(original.sub(originalIndexoriginal.elements.size()), after.sub(afterIndexafter.elements.size())));
192        }
193        return elements;
194    }
195
196    private static void considerRemoval(NodeText nodeTextForChildList<DifferenceElementelements) {
197        for (TextElement el : nodeTextForChild.getElements()) {
198            if (el instanceof ChildTextElement) {
199                ChildTextElement cte = (ChildTextElementel;
200                considerRemoval(LexicalPreservingPrinter.getOrCreateNodeText(cte.getChild()), elements);
201            } else if (el instanceof TokenTextElement) {
202                TokenTextElement tte = (TokenTextElementel;
203                elements.add(new Removed(new CsmToken(tte.getTokenKind(), tte.getText())));
204            } else {
205                throw new UnsupportedOperationException(el.toString());
206            }
207        }
208    }
209
210    private static int considerRemoval(CsmElement removedElementint originalIndexList<DifferenceElementelements) {
211        boolean dealtWith = false;
212        if (removedElement instanceof CsmChild) {
213            CsmChild removedChild = (CsmChildremovedElement;
214            if (removedChild.getChild() instanceof Type && removedChild.getChild().getParentNode().isPresent() &&
215                    removedChild.getChild().getParentNode().get() instanceof VariableDeclarator) {
216                NodeText nodeTextForChild = LexicalPreservingPrinter.getOrCreateNodeText(removedChild.getChild());
217                considerRemoval(nodeTextForChildelements);
218                originalIndex++;
219                dealtWith = true;
220            }
221        }
222        if (!dealtWith) {
223            elements.add(new Removed(removedElement));
224            originalIndex++;
225        }
226        return originalIndex;
227    }
228
229    private static List<DifferenceElementcalculateImpl(LexicalDifferenceCalculator.CalculatedSyntaxModel original,
230                                                         LexicalDifferenceCalculator.CalculatedSyntaxModel after) {
231        List<DifferenceElementelements = new LinkedList<>();
232
233        int originalIndex = 0;
234        int afterIndex = 0;
235
236        // We move through the two CalculatedSyntaxModel, moving both forward when we have a match
237        // and moving just one side forward when we have an element kept or removed
238
239        do {
240            if (originalIndex < original.elements.size() && afterIndex >= after.elements.size()) {
241                CsmElement removedElement = original.elements.get(originalIndex);
242                originalIndex = considerRemoval(removedElementoriginalIndexelements);
243            } else if (originalIndex >= original.elements.size() && afterIndex < after.elements.size()) {
244                elements.add(new Added(after.elements.get(afterIndex)));
245                afterIndex++;
246            } else {
247                CsmElement nextOriginal = original.elements.get(originalIndex);
248                CsmElement nextAfter = after.elements.get(afterIndex);
249
250                if ((nextOriginal instanceof CsmMix) && (nextAfter instanceof CsmMix)) {
251                    if (((CsmMixnextAfter).getElements().equals(((CsmMixnextOriginal).getElements())) {
252                        // No reason to deal with a reshuffled, we are just going to keep everything as it is
253                        ((CsmMixnextAfter).getElements().forEach(el -> elements.add(new Kept(el)));
254                    } else {
255                        elements.add(new Reshuffled((CsmMix)nextOriginal, (CsmMix)nextAfter));
256                    }
257                    originalIndex++;
258                    afterIndex++;
259                } else if (matching(nextOriginalnextAfter)) {
260                    elements.add(new Kept(nextOriginal));
261                    originalIndex++;
262                    afterIndex++;
263                } else if (replacement(nextOriginalnextAfter)) {
264                    originalIndex = considerRemoval(nextOriginaloriginalIndexelements);
265                    elements.add(new Added(nextAfter));
266                    afterIndex++;
267                } else {
268                    // We can try to remove the element or add it and look which one leads to the lower difference
269                    List<DifferenceElementaddingElements = calculate(original.from(originalIndex), after.from(afterIndex + 1));
270                    List<DifferenceElementremovingElements = null;
271                    if (cost(addingElements) > 0) {
272                        removingElements = calculate(original.from(originalIndex + 1), after.from(afterIndex));
273                    }
274
275                    if (removingElements == null || cost(removingElements) > cost(addingElements)) {
276                        elements.add(new Added(nextAfter));
277                        afterIndex++;
278                    } else {
279                        elements.add(new Removed(nextOriginal));
280                        originalIndex++;
281                    }
282                }
283            }
284        } while (originalIndex < original.elements.size() || afterIndex < after.elements.size());
285
286        return elements;
287    }
288
289    private static long cost(List<DifferenceElementelements) {
290        return elements.stream().filter(e -> !(e instanceof Kept)).count();
291    }
292
293
294    /**
295     * Remove from the difference all the elements related to indentation.
296     * This is mainly intended for test purposes.
297     */
298    static void removeIndentationElements(List<DifferenceElementelements) {
299        elements.removeIf(el -> el.getElement() instanceof CsmIndent || el.getElement() instanceof CsmUnindent);
300    }
301}
302
MembersX
DifferenceElementCalculator:calculateImpl:Block:originalIndex
DifferenceElementCalculator:considerRemoval:Block:Block:Block:tte
DifferenceElementCalculator:considerRemoval:Block:Block:removedChild
DifferenceElementCalculator:calculateImpl:Block:afterIndex
DifferenceElementCalculator:cost
DifferenceElementCalculator:ChildPositionInfo:ChildPositionInfo
DifferenceElementCalculator:ChildPositionInfo:equals
DifferenceElementCalculator:calculateImpl:Block:Block:Block:Block:addingElements
DifferenceElementCalculator:calculateImpl:Block:Block:Block:Block:removingElements
DifferenceElementCalculator:ChildPositionInfo:equals:Block:cpi
DifferenceElementCalculator:findChildrenPositions:Block:Block:element
DifferenceElementCalculator:considerRemoval
DifferenceElementCalculator:calculateImpl
DifferenceElementCalculator:calculate:Block:commonChildren
DifferenceElementCalculator:ChildPositionInfo:hashCode
DifferenceElementCalculator:considerRemoval:Block:Block:Block:nodeTextForChild
DifferenceElementCalculator:calculate:Block:elements
DifferenceElementCalculator:calculate:Block:originalIndex
DifferenceElementCalculator:calculate:Block:Block:child
DifferenceElementCalculator:calculateImpl:Block:elements
DifferenceElementCalculator:replacement
DifferenceElementCalculator:calculate:Block:afterIndex
DifferenceElementCalculator:calculate:Block:childrenInOriginal
DifferenceElementCalculator:ChildPositionInfo:position
DifferenceElementCalculator:matching
DifferenceElementCalculator:calculate:Block:Block:posOfNextChildInOriginal
DifferenceElementCalculator:calculateImpl:Block:Block:Block:removedElement
DifferenceElementCalculator:ChildPositionInfo:node
DifferenceElementCalculator:findChildrenPositions:Block:positions
DifferenceElementCalculator:calculate:Block:childrenInAfter
DifferenceElementCalculator:replacement:Block:Block:Block:childA
DifferenceElementCalculator:calculateImpl:Block:Block:Block:nextOriginal
DifferenceElementCalculator:removeIndentationElements
DifferenceElementCalculator:replacement:Block:Block:Block:childB
DifferenceElementCalculator:considerRemoval:Block:Block:Block:cte
DifferenceElementCalculator:matching:Block:Block:Block:childB
DifferenceElementCalculator:calculate
DifferenceElementCalculator:calculate:Block:commonChildrenIndex
DifferenceElementCalculator:matching:Block:Block:Block:childA
DifferenceElementCalculator:calculateImpl:Block:Block:Block:nextAfter
DifferenceElementCalculator:considerRemoval:Block:dealtWith
DifferenceElementCalculator:calculate:Block:Block:posOfNextChildInAfter
DifferenceElementCalculator:findChildrenPositions
Members
X