1d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti/*
2d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * Copyright (C) 2007-2010 Júlio Vilmar Gesser.
3d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * Copyright (C) 2011, 2013-2016 The JavaParser Team.
4d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti *
5d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * This file is part of JavaParser.
6d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti *
7d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * JavaParser can be used either under the terms of
8d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * a) the GNU Lesser General Public License as published by
9d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti *     the Free Software Foundation, either version 3 of the License, or
10d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti *     (at your option) any later version.
11d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * b) the terms of the Apache License
12d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti *
13d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * You should have received a copy of both licenses in LICENCE.LGPL and
14d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * LICENCE.APACHE. Please refer to those files for details.
15d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti *
16d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * JavaParser is distributed in the hope that it will be useful,
17d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * but WITHOUT ANY WARRANTY; without even the implied warranty of
18d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * GNU Lesser General Public License for more details.
20d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti */
21d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
22d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettipackage com.github.javaparser.printer.lexicalpreservation;
23d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
24d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport com.github.javaparser.*;
25a86af3ac653113b302dcfa9facc6c67159fabf1bDanny van Bruggenimport com.github.javaparser.ast.DataKey;
26d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport com.github.javaparser.ast.Node;
27d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport com.github.javaparser.ast.NodeList;
287e85803c3a66d340e1cbdbc6c98081104ad412c3Federico Tomassettiimport com.github.javaparser.ast.body.VariableDeclarator;
290c57e5931daabf22632ad49f2ad0657df59b858bFederico Tomassettiimport com.github.javaparser.ast.comments.Comment;
30f0f313058c4389cd48052112e3fb906c060be30fFederico Tomassettiimport com.github.javaparser.ast.comments.JavadocComment;
31f8f8a6fc81e2d717568c0ab9895605c1f4b69bbcFederico Tomassettiimport com.github.javaparser.ast.nodeTypes.NodeWithVariables;
32d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport com.github.javaparser.ast.observer.AstObserver;
33d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport com.github.javaparser.ast.observer.ObservableProperty;
34d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport com.github.javaparser.ast.observer.PropagatingAstObserver;
35de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassettiimport com.github.javaparser.ast.type.PrimitiveType;
36d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport com.github.javaparser.ast.visitor.TreeVisitor;
3703df427399267338f87748888d1f921bb9a1c0daFederico Tomassettiimport com.github.javaparser.printer.ConcreteSyntaxModel;
3803df427399267338f87748888d1f921bb9a1c0daFederico Tomassettiimport com.github.javaparser.printer.concretesyntaxmodel.CsmElement;
399e91a1d76dbfb28f2bc7c32028fe459b073ee861Federico Tomassettiimport com.github.javaparser.printer.concretesyntaxmodel.CsmMix;
4003df427399267338f87748888d1f921bb9a1c0daFederico Tomassettiimport com.github.javaparser.printer.concretesyntaxmodel.CsmToken;
41d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport com.github.javaparser.utils.Pair;
4215cb49e74025eb4011187f31735aa4772993fb5dFederico Tomassettiimport com.github.javaparser.utils.Utils;
43d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
44d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport java.io.IOException;
45d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport java.io.StringWriter;
46d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport java.io.Writer;
47d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport java.lang.reflect.InvocationTargetException;
48d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport java.lang.reflect.Method;
49d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassettiimport java.lang.reflect.ParameterizedType;
50d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport java.util.*;
51d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettiimport java.util.stream.Collectors;
52d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
5383d60da8afce695e7356af94c549e30ab585448cDanny van Bruggenimport static com.github.javaparser.GeneratedJavaParserConstants.*;
546a1ee724c337694ee28b2a09af41c236b880ba66Danny van Bruggenimport static com.github.javaparser.TokenTypes.eolTokenKind;
558324ea0bfcac6db2f2ef2c3d12a84744842aa8adDanny van Bruggenimport static com.github.javaparser.utils.Utils.assertNotNull;
56d4db09d04aca5a9df4278652d55d680c4a826efcFederico Tomassettiimport static com.github.javaparser.utils.Utils.decapitalize;
5748789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggenimport static java.util.Comparator.*;
5840d239e3cb84cc77fcd16c3f9fde3f4f8b635419Federico Tomassetti
59d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti/**
60d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti * A Lexical Preserving Printer is used to capture all the lexical information while parsing, update them when
619a228915f61892f6ba99fcf1e42e42e1e2622a00Danny van Bruggen * operating on the AST and then used them to reproduce the source code
628324ea0bfcac6db2f2ef2c3d12a84744842aa8adDanny van Bruggen * in its original formatting including the AST changes.
63d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti */
64d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassettipublic class LexicalPreservingPrinter {
65d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
66a86af3ac653113b302dcfa9facc6c67159fabf1bDanny van Bruggen    /**
67a86af3ac653113b302dcfa9facc6c67159fabf1bDanny van Bruggen     * The nodetext for a node is stored in the node's data field. This is the key to set and retrieve it.
68a86af3ac653113b302dcfa9facc6c67159fabf1bDanny van Bruggen     */
6948789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen    public static final DataKey<NodeText> NODE_TEXT_DATA = new DataKey<NodeText>() {
7048789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen    };
71a86af3ac653113b302dcfa9facc6c67159fabf1bDanny van Bruggen
72d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    //
73d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    // Factory methods
74d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    //
75d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
76d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    /**
77d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti     * Parse the code and setup the LexicalPreservingPrinter.
7848789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen     *
796149218487aac50fad2256c8723639671747d7bbDanny van Bruggen     * @deprecated use setup(Node) and the static methods on this class.
80d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti     */
8140d239e3cb84cc77fcd16c3f9fde3f4f8b635419Federico Tomassetti    public static <N extends Node> Pair<ParseResult<N>, LexicalPreservingPrinter> setup(ParseStart<N> parseStart,
8240d239e3cb84cc77fcd16c3f9fde3f4f8b635419Federico Tomassetti                                                                                        Provider provider) {
83d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        ParseResult<N> parseResult = new JavaParser().parse(parseStart, provider);
8440d239e3cb84cc77fcd16c3f9fde3f4f8b635419Federico Tomassetti        if (!parseResult.isSuccessful()) {
8540d239e3cb84cc77fcd16c3f9fde3f4f8b635419Federico Tomassetti            throw new RuntimeException("Parsing failed, unable to setup the lexical preservation printer: "
8640d239e3cb84cc77fcd16c3f9fde3f4f8b635419Federico Tomassetti                    + parseResult.getProblems());
8740d239e3cb84cc77fcd16c3f9fde3f4f8b635419Federico Tomassetti        }
888324ea0bfcac6db2f2ef2c3d12a84744842aa8adDanny van Bruggen        LexicalPreservingPrinter lexicalPreservingPrinter = new LexicalPreservingPrinter(parseResult.getResult().get());
89d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        return new Pair<>(parseResult, lexicalPreservingPrinter);
90d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    }
91d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
92dd0f96d32e589249fdd05ab40f9d087c79415730Danny van Bruggen    /**
93dd0f96d32e589249fdd05ab40f9d087c79415730Danny van Bruggen     * Prepares the node so it can be used in the print methods.
94dd0f96d32e589249fdd05ab40f9d087c79415730Danny van Bruggen     * The correct order is:
95dd0f96d32e589249fdd05ab40f9d087c79415730Danny van Bruggen     * <ol>
9648789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen     * <li>Parse some code</li>
9748789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen     * <li>Call this setup method on the result</li>
9848789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen     * <li>Make changes to the AST as desired</li>
9948789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen     * <li>Use one of the print methods on this class to print out the original source code with your changes added</li>
100dd0f96d32e589249fdd05ab40f9d087c79415730Danny van Bruggen     * </ol>
10148789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen     *
102dd0f96d32e589249fdd05ab40f9d087c79415730Danny van Bruggen     * @return the node passed as a parameter for your convenience.
103dd0f96d32e589249fdd05ab40f9d087c79415730Danny van Bruggen     */
104dd0f96d32e589249fdd05ab40f9d087c79415730Danny van Bruggen    public static <N extends Node> N setup(N node) {
1058324ea0bfcac6db2f2ef2c3d12a84744842aa8adDanny van Bruggen        assertNotNull(node);
1066149218487aac50fad2256c8723639671747d7bbDanny van Bruggen
1078324ea0bfcac6db2f2ef2c3d12a84744842aa8adDanny van Bruggen        node.getTokenRange().ifPresent(r -> {
1088324ea0bfcac6db2f2ef2c3d12a84744842aa8adDanny van Bruggen            storeInitialText(node);
109d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
1107a48b21bb866b8d4baf2ce50992c0dfd93f4bc3bFederico Tomassetti            // Setup observer
1116149218487aac50fad2256c8723639671747d7bbDanny van Bruggen            AstObserver observer = createObserver();
1127a48b21bb866b8d4baf2ce50992c0dfd93f4bc3bFederico Tomassetti
1138324ea0bfcac6db2f2ef2c3d12a84744842aa8adDanny van Bruggen            node.registerForSubtree(observer);
1148324ea0bfcac6db2f2ef2c3d12a84744842aa8adDanny van Bruggen        });
115dd0f96d32e589249fdd05ab40f9d087c79415730Danny van Bruggen        return node;
116d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    }
1179a228915f61892f6ba99fcf1e42e42e1e2622a00Danny van Bruggen
1186149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    //
1196149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    // Constructor and setup
1206149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    //
121d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
1226149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    /**
1236149218487aac50fad2256c8723639671747d7bbDanny van Bruggen     * @deprecated use setup(Node) to prepare a node for lexical preservation,
1246149218487aac50fad2256c8723639671747d7bbDanny van Bruggen     * then use the static methods on this class to print it.
1256149218487aac50fad2256c8723639671747d7bbDanny van Bruggen     */
1266149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    @Deprecated
1276149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    public LexicalPreservingPrinter(Node node) {
1286149218487aac50fad2256c8723639671747d7bbDanny van Bruggen        setup(node);
1296149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    }
1306149218487aac50fad2256c8723639671747d7bbDanny van Bruggen
1316149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    private static AstObserver createObserver() {
132d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        return new PropagatingAstObserver() {
133d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            @Override
134d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            public void concretePropertyChange(Node observedNode, ObservableProperty property, Object oldValue, Object newValue) {
13540d239e3cb84cc77fcd16c3f9fde3f4f8b635419Federico Tomassetti                // Not really a change, ignoring
13640d239e3cb84cc77fcd16c3f9fde3f4f8b635419Federico Tomassetti                if ((oldValue != null && oldValue.equals(newValue)) || (oldValue == null && newValue == null)) {
137d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                    return;
138d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                }
1395e40a8838a5118df43a847ce8c52dddac0843b22Federico Tomassetti                if (property == ObservableProperty.RANGE || property == ObservableProperty.COMMENTED_NODE) {
140d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                    return;
141d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                }
142793f207b1b17498cdb201309ba94c25cb9e4fe20Federico Tomassetti                if (property == ObservableProperty.COMMENT) {
14308bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                    if (!observedNode.getParentNode().isPresent()) {
14408bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                        throw new IllegalStateException();
14508bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                    }
1466149218487aac50fad2256c8723639671747d7bbDanny van Bruggen                    NodeText nodeText = getOrCreateNodeText(observedNode.getParentNode().get());
1477a48b21bb866b8d4baf2ce50992c0dfd93f4bc3bFederico Tomassetti                    if (oldValue == null) {
14808bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                        // Find the position of the comment node and put in front of it the comment and a newline
14908bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                        int index = nodeText.findChild(observedNode);
15048789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                        nodeText.addChild(index, (Comment) newValue);
1516a1ee724c337694ee28b2a09af41c236b880ba66Danny van Bruggen                        nodeText.addToken(index + 1, eolTokenKind(), Utils.EOL);
1527a48b21bb866b8d4baf2ce50992c0dfd93f4bc3bFederico Tomassetti                    } else if (newValue == null) {
15308bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                        if (oldValue instanceof JavadocComment) {
15448789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                            JavadocComment javadocComment = (JavadocComment) oldValue;
15583d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen                            List<TokenTextElement> matchingTokens = nodeText.getElements().stream().filter(e -> e.isToken(JAVADOC_COMMENT)
15648789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                                    && ((TokenTextElement) e).getText().equals("/**" + javadocComment.getContent() + "*/")).map(e -> (TokenTextElement) e).collect(Collectors.toList());
15708bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                            if (matchingTokens.size() != 1) {
15808bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                                throw new IllegalStateException();
15908bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                            }
16008bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                            int index = nodeText.findElement(matchingTokens.get(0));
16108bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                            nodeText.removeElement(index);
1629192b9d17903947058b755b987d1c57a05821e58Danny van Bruggen                            if (nodeText.getElements().get(index).isNewline()) {
16308bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                                nodeText.removeElement(index);
16408bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                            }
16508bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                        } else {
16608bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                            throw new UnsupportedOperationException();
16708bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                        }
168516947a3d4eda478d20dcbb4216d6c52cf11d169Federico Tomassetti                    } else {
16908bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                        if (oldValue instanceof JavadocComment) {
17048789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                            JavadocComment oldJavadocComment = (JavadocComment) oldValue;
17183d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen                            List<TokenTextElement> matchingTokens = nodeText.getElements().stream().filter(e -> e.isToken(JAVADOC_COMMENT)
17248789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                                    && ((TokenTextElement) e).getText().equals("/**" + oldJavadocComment.getContent() + "*/")).map(e -> (TokenTextElement) e).collect(Collectors.toList());
17308bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                            if (matchingTokens.size() != 1) {
17408bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                                throw new IllegalStateException();
17508bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                            }
17648789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                            JavadocComment newJavadocComment = (JavadocComment) newValue;
17783d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen                            nodeText.replace(matchingTokens.get(0), new TokenTextElement(JAVADOC_COMMENT, "/**" + newJavadocComment.getContent() + "*/"));
17808bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                        } else {
17908bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                            throw new UnsupportedOperationException();
18008bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                        }
18108bd6db12a6e7a8c9eebba117981bbde6399d4ffFederico Tomassetti                    }
182793f207b1b17498cdb201309ba94c25cb9e4fe20Federico Tomassetti                }
1836149218487aac50fad2256c8723639671747d7bbDanny van Bruggen                NodeText nodeText = getOrCreateNodeText(observedNode);
184f0f313058c4389cd48052112e3fb906c060be30fFederico Tomassetti
185f0f313058c4389cd48052112e3fb906c060be30fFederico Tomassetti                if (nodeText == null) {
186f0f313058c4389cd48052112e3fb906c060be30fFederico Tomassetti                    throw new NullPointerException(observedNode.getClass().getSimpleName());
187f0f313058c4389cd48052112e3fb906c060be30fFederico Tomassetti                }
188c73ae5d79b62de91209477538b45ab5216fb108eFederico Tomassetti
189c73ae5d79b62de91209477538b45ab5216fb108eFederico Tomassetti                new LexicalDifferenceCalculator().calculatePropertyChange(nodeText, observedNode, property, oldValue, newValue);
190d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            }
191d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
192d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            @Override
19315cb49e74025eb4011187f31735aa4772993fb5dFederico Tomassetti            public void concreteListChange(NodeList changedList, ListChangeType type, int index, Node nodeAddedOrRemoved) {
1946149218487aac50fad2256c8723639671747d7bbDanny van Bruggen                NodeText nodeText = getOrCreateNodeText(changedList.getParentNodeForChildren());
195d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                if (type == ListChangeType.REMOVAL) {
19646d1656c7a0b6a5313077495d02f4f98f69be414Danny van Bruggen                    new LexicalDifferenceCalculator().calculateListRemovalDifference(findNodeListName(changedList), changedList, index).apply(nodeText, changedList.getParentNodeForChildren());
197d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                } else if (type == ListChangeType.ADDITION) {
19848789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                    new LexicalDifferenceCalculator().calculateListAdditionDifference(findNodeListName(changedList), changedList, index, nodeAddedOrRemoved).apply(nodeText, changedList.getParentNodeForChildren());
199d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                } else {
200d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                    throw new UnsupportedOperationException();
201d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                }
202d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            }
203ca2826ef2cf2a40c0c0d061ee68ba525d2481becFederico Tomassetti
204ca2826ef2cf2a40c0c0d061ee68ba525d2481becFederico Tomassetti            @Override
20515cb49e74025eb4011187f31735aa4772993fb5dFederico Tomassetti            public void concreteListReplacement(NodeList changedList, int index, Node oldValue, Node newValue) {
2066149218487aac50fad2256c8723639671747d7bbDanny van Bruggen                NodeText nodeText = getOrCreateNodeText(changedList.getParentNodeForChildren());
20746d1656c7a0b6a5313077495d02f4f98f69be414Danny van Bruggen                new LexicalDifferenceCalculator().calculateListReplacementDifference(findNodeListName(changedList), changedList, index, newValue).apply(nodeText, changedList.getParentNodeForChildren());
208ca2826ef2cf2a40c0c0d061ee68ba525d2481becFederico Tomassetti            }
209d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        };
210d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    }
211d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
2126149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    private static void storeInitialText(Node root) {
213198c4d502c00c07c674543bfb1eebedafcf4b193Federico Tomassetti        Map<Node, List<JavaToken>> tokensByNode = new IdentityHashMap<>();
214d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
2158a2ab8b7cc640f95ca1fb10bc0e8534d766e9299Sergey Reshetnik        // We go over tokens and find to which nodes they belong. Note that we do not traverse the tokens as they were
2168a2ab8b7cc640f95ca1fb10bc0e8534d766e9299Sergey Reshetnik        // on a list but as they were organized in a tree. At each time we select only the branch corresponding to the
2178a2ab8b7cc640f95ca1fb10bc0e8534d766e9299Sergey Reshetnik        // range of interest and ignore all other branches
2188324ea0bfcac6db2f2ef2c3d12a84744842aa8adDanny van Bruggen        for (JavaToken token : root.getTokenRange().get()) {
2193608acd141a4d3c2105672b4b19b26e4d2badb5cDanny van Bruggen            Range tokenRange = token.getRange().orElseThrow(() -> new RuntimeException("Token without range: " + token));
2202a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik            Node owner = findNodeForToken(root, tokenRange);
2212a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik            if (owner == null) {
2222a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik                throw new RuntimeException("Token without node owning it: " + token);
2232a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik            }
224d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            if (!tokensByNode.containsKey(owner)) {
225d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                tokensByNode.put(owner, new LinkedList<>());
226d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            }
227d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            tokensByNode.get(owner).add(token);
228d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        }
229d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
230d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        // Now that we know the tokens we use them to create the initial NodeText for each node
231d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        new TreeVisitor() {
232d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            @Override
233d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            public void process(Node node) {
2349a7cda9c8fb4cbb33b5f38daea68921ae201ef31Federico Tomassetti                if (!PhantomNodeLogic.isPhantomNode(node)) {
2356149218487aac50fad2256c8723639671747d7bbDanny van Bruggen                    LexicalPreservingPrinter.storeInitialTextForOneNode(node, tokensByNode.get(node));
2369a7cda9c8fb4cbb33b5f38daea68921ae201ef31Federico Tomassetti                }
237d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            }
238d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        }.visitBreadthFirst(root);
239d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    }
240d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
2412a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik    private static Node findNodeForToken(Node node, Range tokenRange) {
2422a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik        if (PhantomNodeLogic.isPhantomNode(node)) {
2432a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik            return null;
2442a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik        }
2452a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik        if (node.getRange().get().contains(tokenRange)) {
2462a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik            for (Node child : node.getChildNodes()) {
2472a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik                Node found = findNodeForToken(child, tokenRange);
2482a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik                if (found != null) {
2492a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik                    return found;
2502a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik                }
2512a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik            }
2522a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik            return node;
2532a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik        } else {
2542a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik            return null;
2552a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik        }
2562a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik    }
2572a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik
2586149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    private static void storeInitialTextForOneNode(Node node, List<JavaToken> nodeTokens) {
259d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        if (nodeTokens == null) {
260d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            nodeTokens = Collections.emptyList();
261d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        }
262d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        List<Pair<Range, TextElement>> elements = new LinkedList<>();
263d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        for (Node child : node.getChildNodes()) {
264f671c0e58dbbe6688926060fee6a9b6cbc9d8f76Federico Tomassetti            if (!PhantomNodeLogic.isPhantomNode(child)) {
265516947a3d4eda478d20dcbb4216d6c52cf11d169Federico Tomassetti                if (!child.getRange().isPresent()) {
266516947a3d4eda478d20dcbb4216d6c52cf11d169Federico Tomassetti                    throw new RuntimeException("Range not present on node " + child);
267516947a3d4eda478d20dcbb4216d6c52cf11d169Federico Tomassetti                }
2686149218487aac50fad2256c8723639671747d7bbDanny van Bruggen                elements.add(new Pair<>(child.getRange().get(), new ChildTextElement(child)));
269f671c0e58dbbe6688926060fee6a9b6cbc9d8f76Federico Tomassetti            }
270d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        }
271d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        for (JavaToken token : nodeTokens) {
2723608acd141a4d3c2105672b4b19b26e4d2badb5cDanny van Bruggen            elements.add(new Pair<>(token.getRange().get(), new TokenTextElement(token)));
273d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        }
27448789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen        elements.sort(comparing(e -> e.a.begin));
2756149218487aac50fad2256c8723639671747d7bbDanny van Bruggen        node.setData(NODE_TEXT_DATA, new NodeText(elements.stream().map(p -> p.b).collect(Collectors.toList())));
276d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    }
2772a393b02804ec16ccec1877ca3a0289c924cc384Sergey Reshetnik
278d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    //
279de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti    // Iterators
280de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti    //
281de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti
2826149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    private static Iterator<TokenTextElement> tokensPreceeding(final Node node) {
283de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti        if (!node.getParentNode().isPresent()) {
28446d1656c7a0b6a5313077495d02f4f98f69be414Danny van Bruggen            return new TextElementIteratorsFactory.EmptyIterator<>();
285de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti        }
286de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti        // There is the awfully painful case of the fake types involved in variable declarators and
287de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti        // fields or variable declaration that are, of course, an exception...
288de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti
289de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti        NodeText parentNodeText = getOrCreateNodeText(node.getParentNode().get());
290de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti        int index = parentNodeText.tryToFindChild(node);
291de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti        if (index == NodeText.NOT_FOUND) {
292de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti            if (node.getParentNode().get() instanceof VariableDeclarator) {
293de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti                return tokensPreceeding(node.getParentNode().get());
294de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti            } else {
295de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti                throw new IllegalArgumentException(
296de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti                        String.format("I could not find child '%s' in parent '%s'. parentNodeText: %s",
297de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti                                node, node.getParentNode().get(), parentNodeText));
298de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti            }
299de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti        }
300de711eeae58ca512235ce47ed433ed24658f4098Federico Tomassetti
301de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti        return new TextElementIteratorsFactory.CascadingIterator<>(
302dea490b3c4b649d5ede5f12dbf6740b816681cf2Federico Tomassetti                TextElementIteratorsFactory.partialReverseIterator(parentNodeText, index - 1),
303de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti                () -> tokensPreceeding(node.getParentNode().get()));
304de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti    }
305de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti
306de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti    //
307d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    // Printing methods
308d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    //
309d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
310d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    /**
311d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti     * Print a Node into a String, preserving the lexical information.
312d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti     */
3136149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    public static String print(Node node) {
314d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        StringWriter writer = new StringWriter();
315d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        try {
316d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            print(node, writer);
317d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        } catch (IOException e) {
318d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            throw new RuntimeException("Unexpected IOException on a StringWriter", e);
319d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        }
320d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        return writer.toString();
321d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    }
322d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
323d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    /**
324d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti     * Print a Node into a Writer, preserving the lexical information.
325d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti     */
3266149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    public static void print(Node node, Writer writer) throws IOException {
327a86af3ac653113b302dcfa9facc6c67159fabf1bDanny van Bruggen        if (!node.containsData(NODE_TEXT_DATA)) {
328eeb2701b8cbc85c513d0759d6fa3f2fc41b81ebcFederico Tomassetti            getOrCreateNodeText(node);
329d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        }
330a86af3ac653113b302dcfa9facc6c67159fabf1bDanny van Bruggen        final NodeText text = node.getData(NODE_TEXT_DATA);
331eeb2701b8cbc85c513d0759d6fa3f2fc41b81ebcFederico Tomassetti        writer.append(text.expand());
332d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    }
333d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
334d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    //
335d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    // Methods to handle transformations
336d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    //
337d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
33848789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen    private static void prettyPrintingTextNode(Node node, NodeText nodeText) {
339de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti        if (node instanceof PrimitiveType) {
34048789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen            PrimitiveType primitiveType = (PrimitiveType) node;
341dec68cfc83301b4e7ee2bbf1886959d6aa05f85aFederico Tomassetti            switch (primitiveType.getType()) {
342521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                case BOOLEAN:
34383d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen                    nodeText.addToken(BOOLEAN, node.toString());
344521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                    break;
345521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                case CHAR:
34683d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen                    nodeText.addToken(CHAR, node.toString());
347521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                    break;
348521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                case BYTE:
34983d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen                    nodeText.addToken(BYTE, node.toString());
350521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                    break;
351521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                case SHORT:
35283d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen                    nodeText.addToken(SHORT, node.toString());
353521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                    break;
354dec68cfc83301b4e7ee2bbf1886959d6aa05f85aFederico Tomassetti                case INT:
35583d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen                    nodeText.addToken(INT, node.toString());
356dec68cfc83301b4e7ee2bbf1886959d6aa05f85aFederico Tomassetti                    break;
357521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                case LONG:
35883d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen                    nodeText.addToken(LONG, node.toString());
359521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                    break;
360521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                case FLOAT:
36183d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen                    nodeText.addToken(FLOAT, node.toString());
362521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                    break;
363521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                case DOUBLE:
36483d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen                    nodeText.addToken(DOUBLE, node.toString());
365521384346671f17f0f4e6c2af86ba413aad83ef5Federico Tomassetti                    break;
366dec68cfc83301b4e7ee2bbf1886959d6aa05f85aFederico Tomassetti                default:
367dec68cfc83301b4e7ee2bbf1886959d6aa05f85aFederico Tomassetti                    throw new IllegalArgumentException();
368dec68cfc83301b4e7ee2bbf1886959d6aa05f85aFederico Tomassetti            }
36948789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen            return;
370de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti        }
371f0f313058c4389cd48052112e3fb906c060be30fFederico Tomassetti        if (node instanceof JavadocComment) {
37248789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen            nodeText.addToken(JAVADOC_COMMENT, "/**" + ((JavadocComment) node).getContent() + "*/");
37348789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen            return;
374f0f313058c4389cd48052112e3fb906c060be30fFederico Tomassetti        }
375de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti
37648789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen        interpret(node, ConcreteSyntaxModel.forClass(node.getClass()), nodeText);
37703df427399267338f87748888d1f921bb9a1c0daFederico Tomassetti    }
37803df427399267338f87748888d1f921bb9a1c0daFederico Tomassetti
3796149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    private static NodeText interpret(Node node, CsmElement csm, NodeText nodeText) {
38003df427399267338f87748888d1f921bb9a1c0daFederico Tomassetti        LexicalDifferenceCalculator.CalculatedSyntaxModel calculatedSyntaxModel = new LexicalDifferenceCalculator().calculatedSyntaxModelForNode(csm, node);
38187b56cb1f18252c95d88b979a449f06d55def290Federico Tomassetti
38287db3c76d76680bd1bea566d44ab74b4dca55b71Federico Tomassetti        List<TokenTextElement> indentation = findIndentation(node);
38387b56cb1f18252c95d88b979a449f06d55def290Federico Tomassetti
38487db3c76d76680bd1bea566d44ab74b4dca55b71Federico Tomassetti        boolean pendingIndentation = false;
38503df427399267338f87748888d1f921bb9a1c0daFederico Tomassetti        for (CsmElement element : calculatedSyntaxModel.elements) {
38648789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen            if (pendingIndentation && !(element instanceof CsmToken && ((CsmToken) element).isNewLine())) {
38746d1656c7a0b6a5313077495d02f4f98f69be414Danny van Bruggen                indentation.forEach(nodeText::addElement);
38887db3c76d76680bd1bea566d44ab74b4dca55b71Federico Tomassetti            }
38987db3c76d76680bd1bea566d44ab74b4dca55b71Federico Tomassetti            pendingIndentation = false;
39003df427399267338f87748888d1f921bb9a1c0daFederico Tomassetti            if (element instanceof LexicalDifferenceCalculator.CsmChild) {
39103df427399267338f87748888d1f921bb9a1c0daFederico Tomassetti                nodeText.addChild(((LexicalDifferenceCalculator.CsmChild) element).getChild());
3929e91a1d76dbfb28f2bc7c32028fe459b073ee861Federico Tomassetti            } else if (element instanceof CsmToken) {
3939e91a1d76dbfb28f2bc7c32028fe459b073ee861Federico Tomassetti                CsmToken csmToken = (CsmToken) element;
39487db3c76d76680bd1bea566d44ab74b4dca55b71Federico Tomassetti                nodeText.addToken(csmToken.getTokenType(), csmToken.getContent(node));
39587db3c76d76680bd1bea566d44ab74b4dca55b71Federico Tomassetti                if (csmToken.isNewLine()) {
39687db3c76d76680bd1bea566d44ab74b4dca55b71Federico Tomassetti                    pendingIndentation = true;
39787db3c76d76680bd1bea566d44ab74b4dca55b71Federico Tomassetti                }
3989e91a1d76dbfb28f2bc7c32028fe459b073ee861Federico Tomassetti            } else if (element instanceof CsmMix) {
39948789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                CsmMix csmMix = (CsmMix) element;
4009e91a1d76dbfb28f2bc7c32028fe459b073ee861Federico Tomassetti                csmMix.getElements().forEach(e -> interpret(node, e, nodeText));
40103df427399267338f87748888d1f921bb9a1c0daFederico Tomassetti            } else {
40203df427399267338f87748888d1f921bb9a1c0daFederico Tomassetti                throw new UnsupportedOperationException(element.getClass().getSimpleName());
40303df427399267338f87748888d1f921bb9a1c0daFederico Tomassetti            }
40403df427399267338f87748888d1f921bb9a1c0daFederico Tomassetti        }
4057e85803c3a66d340e1cbdbc6c98081104ad412c3Federico Tomassetti        // Array brackets are a pain... we do not have a way to represent them explicitly in the AST
4067e85803c3a66d340e1cbdbc6c98081104ad412c3Federico Tomassetti        // so they have to be handled in a special way
4077e85803c3a66d340e1cbdbc6c98081104ad412c3Federico Tomassetti        if (node instanceof VariableDeclarator) {
4089a228915f61892f6ba99fcf1e42e42e1e2622a00Danny van Bruggen            VariableDeclarator variableDeclarator = (VariableDeclarator) node;
40948789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen            variableDeclarator.getParentNode().ifPresent(parent ->
41048789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                    ((NodeWithVariables<?>) parent).getMaximumCommonType().ifPresent(mct -> {
41148789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                        int extraArrayLevels = variableDeclarator.getType().getArrayLevel() - mct.getArrayLevel();
41248789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                        for (int i = 0; i < extraArrayLevels; i++) {
41348789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                            nodeText.addElement(new TokenTextElement(LBRACKET));
41448789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                            nodeText.addElement(new TokenTextElement(RBRACKET));
41548789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                        }
41648789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                    })
41748789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen            );
4187e85803c3a66d340e1cbdbc6c98081104ad412c3Federico Tomassetti        }
41903df427399267338f87748888d1f921bb9a1c0daFederico Tomassetti        return nodeText;
420d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    }
421d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
422d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    // Visible for testing
4236149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    static NodeText getOrCreateNodeText(Node node) {
424a86af3ac653113b302dcfa9facc6c67159fabf1bDanny van Bruggen        if (!node.containsData(NODE_TEXT_DATA)) {
4256149218487aac50fad2256c8723639671747d7bbDanny van Bruggen            NodeText nodeText = new NodeText();
426a86af3ac653113b302dcfa9facc6c67159fabf1bDanny van Bruggen            node.setData(NODE_TEXT_DATA, nodeText);
427fab5a9b624d0748ef3e924573f55386010a2694eFederico Tomassetti            prettyPrintingTextNode(node, nodeText);
428d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        }
429a86af3ac653113b302dcfa9facc6c67159fabf1bDanny van Bruggen        return node.getData(NODE_TEXT_DATA);
430d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    }
431d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
4324002ee095f065fb93ce3ad4355bc5fb7b63dafaaFederico Tomassetti    // Visible for testing
4336149218487aac50fad2256c8723639671747d7bbDanny van Bruggen    static List<TokenTextElement> findIndentation(Node node) {
434152592f7c8110f04ea6ca6e6c6d57360fd40ee16Federico Tomassetti        List<TokenTextElement> followingNewlines = new LinkedList<>();
435de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti        Iterator<TokenTextElement> it = tokensPreceeding(node);
436de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti        while (it.hasNext()) {
437de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti            TokenTextElement tte = it.next();
43883d60da8afce695e7356af94c549e30ab585448cDanny van Bruggen            if (tte.getTokenKind() == SINGLE_LINE_COMMENT
4399192b9d17903947058b755b987d1c57a05821e58Danny van Bruggen                    || tte.isNewline()) {
440152592f7c8110f04ea6ca6e6c6d57360fd40ee16Federico Tomassetti                break;
441152592f7c8110f04ea6ca6e6c6d57360fd40ee16Federico Tomassetti            } else {
442152592f7c8110f04ea6ca6e6c6d57360fd40ee16Federico Tomassetti                followingNewlines.add(tte);
443152592f7c8110f04ea6ca6e6c6d57360fd40ee16Federico Tomassetti            }
444152592f7c8110f04ea6ca6e6c6d57360fd40ee16Federico Tomassetti        }
445152592f7c8110f04ea6ca6e6c6d57360fd40ee16Federico Tomassetti        Collections.reverse(followingNewlines);
44648789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen        for (int i = 0; i < followingNewlines.size(); i++) {
447152592f7c8110f04ea6ca6e6c6d57360fd40ee16Federico Tomassetti            if (!followingNewlines.get(i).isSpaceOrTab()) {
448152592f7c8110f04ea6ca6e6c6d57360fd40ee16Federico Tomassetti                return followingNewlines.subList(0, i);
449de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti            }
450de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti        }
451152592f7c8110f04ea6ca6e6c6d57360fd40ee16Federico Tomassetti        return followingNewlines;
452de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti    }
453de11a2ae80166defacd1f9c51b77f52be4b53eb1Federico Tomassetti
454d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    //
455d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    // Helper methods
456d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    //
457d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti
458d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti    private static boolean isReturningOptionalNodeList(Method m) {
459d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti        if (!m.getReturnType().getCanonicalName().equals(Optional.class.getCanonicalName())) {
460d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti            return false;
461d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti        }
462d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti        if (!(m.getGenericReturnType() instanceof ParameterizedType)) {
463d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti            return false;
464d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti        }
465d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti        ParameterizedType parameterizedType = (ParameterizedType) m.getGenericReturnType();
466d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti        java.lang.reflect.Type optionalArgument = parameterizedType.getActualTypeArguments()[0];
467a86af3ac653113b302dcfa9facc6c67159fabf1bDanny van Bruggen        return (optionalArgument.getTypeName().startsWith(NodeList.class.getCanonicalName()));
468d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti    }
469d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti
470d533531b8d35f2d899f9e826e1993a16226e7580Federico Tomassetti    private static ObservableProperty findNodeListName(NodeList nodeList) {
471d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        Node parent = nodeList.getParentNodeForChildren();
472d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        for (Method m : parent.getClass().getMethods()) {
473d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            if (m.getParameterCount() == 0 && m.getReturnType().getCanonicalName().equals(NodeList.class.getCanonicalName())) {
474d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                try {
4751fa1ea21c227ad0cf06a3d12638f63061186f02fFederico Tomassetti                    Object raw = m.invoke(parent);
4761fa1ea21c227ad0cf06a3d12638f63061186f02fFederico Tomassetti                    if (!(raw instanceof NodeList)) {
4771fa1ea21c227ad0cf06a3d12638f63061186f02fFederico Tomassetti                        throw new IllegalStateException("Expected NodeList, found " + raw.getClass().getCanonicalName());
4781fa1ea21c227ad0cf06a3d12638f63061186f02fFederico Tomassetti                    }
47948789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                    NodeList result = (NodeList) raw;
480d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                    if (result == nodeList) {
481d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                        String name = m.getName();
482d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                        if (name.startsWith("get")) {
483d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                            name = name.substring("get".length());
484d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                        }
485d4db09d04aca5a9df4278652d55d680c4a826efcFederico Tomassetti                        return ObservableProperty.fromCamelCaseName(decapitalize(name));
486d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                    }
487d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                } catch (IllegalAccessException | InvocationTargetException e) {
488d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                    throw new RuntimeException(e);
489d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti                }
490d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti            } else if (m.getParameterCount() == 0 && isReturningOptionalNodeList(m)) {
491d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti                try {
49248789abfd3738d660abb6f8cd758a40c57379597Danny van Bruggen                    Optional<NodeList<?>> raw = (Optional<NodeList<?>>) m.invoke(parent);
493d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti                    if (raw.isPresent() && raw.get() == nodeList) {
494d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti                        String name = m.getName();
495d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti                        if (name.startsWith("get")) {
496d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti                            name = name.substring("get".length());
497d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti                        }
498d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti                        return ObservableProperty.fromCamelCaseName(decapitalize(name));
499d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti                    }
500d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti                } catch (IllegalAccessException | InvocationTargetException e) {
501d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti                    throw new RuntimeException(e);
502d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti                }
503d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti            }
504d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti        }
505d6aebf46978852659303ad0fe057e1cddd9e391dFederico Tomassetti        throw new IllegalArgumentException("Cannot find list name of NodeList of size " + nodeList.size());
506d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti    }
507d25e79e2c567cc2fc312fabe15fa35be886f31b2Federico Tomassetti}
508