001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.api;
021
022import java.util.BitSet;
023
024import antlr.CommonASTWithHiddenTokens;
025import antlr.Token;
026import antlr.collections.AST;
027import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
028
029/**
030 * An extension of the CommonAST that records the line and column number.
031 *
032 * @author Oliver Burn
033 * @author lkuehne
034 * @see <a href="http://www.antlr.org/">ANTLR Website</a>
035 * @noinspection FieldNotUsedInToString, SerializableHasSerializationMethods
036 */
037public final class DetailAST extends CommonASTWithHiddenTokens {
038    private static final long serialVersionUID = -2580884815577559874L;
039
040    /** Constant to indicate if not calculated the child count. */
041    private static final int NOT_INITIALIZED = Integer.MIN_VALUE;
042
043    /** The line number. **/
044    private int lineNo = NOT_INITIALIZED;
045    /** The column number. **/
046    private int columnNo = NOT_INITIALIZED;
047
048    /** Number of children. */
049    private int childCount = NOT_INITIALIZED;
050    /** The parent token. */
051    private DetailAST parent;
052    /** Previous sibling. */
053    private DetailAST previousSibling;
054
055    /**
056     * All token types in this branch.
057     * Token 'x' (where x is an int) is in this branch
058     * if branchTokenTypes.get(x) is true.
059     */
060    private BitSet branchTokenTypes;
061
062    @Override
063    public void initialize(Token tok) {
064        super.initialize(tok);
065        lineNo = tok.getLine();
066
067        // expect columns to start @ 0
068        columnNo = tok.getColumn() - 1;
069    }
070
071    @Override
072    public void initialize(AST ast) {
073        final DetailAST detailAst = (DetailAST) ast;
074        setText(detailAst.getText());
075        setType(detailAst.getType());
076        lineNo = detailAst.getLineNo();
077        columnNo = detailAst.getColumnNo();
078        hiddenAfter = detailAst.getHiddenAfter();
079        hiddenBefore = detailAst.getHiddenBefore();
080    }
081
082    @Override
083    public void setFirstChild(AST ast) {
084        clearBranchTokenTypes();
085        clearChildCountCache(this);
086        super.setFirstChild(ast);
087        if (ast != null) {
088            ((DetailAST) ast).setParent(this);
089        }
090    }
091
092    @Override
093    public void setNextSibling(AST ast) {
094        clearBranchTokenTypes();
095        clearChildCountCache(parent);
096        super.setNextSibling(ast);
097        if (ast != null && parent != null) {
098            ((DetailAST) ast).setParent(parent);
099        }
100        if (ast != null) {
101            ((DetailAST) ast).previousSibling = this;
102        }
103    }
104
105    /**
106     * Add previous sibling.
107     * @param ast
108     *        DetailAST object.
109     */
110    public void addPreviousSibling(DetailAST ast) {
111        clearBranchTokenTypes();
112        clearChildCountCache(parent);
113        if (ast != null) {
114            //parent is set in setNextSibling or parent.setFirstChild
115            final DetailAST previousSiblingNode = previousSibling;
116
117            if (previousSiblingNode != null) {
118                ast.previousSibling = previousSiblingNode;
119                previousSiblingNode.setNextSibling(ast);
120            }
121            else if (parent != null) {
122                parent.setFirstChild(ast);
123            }
124
125            ast.setNextSibling(this);
126            previousSibling = ast;
127        }
128    }
129
130    /**
131     * Add next sibling.
132     * @param ast
133     *        DetailAST object.
134     */
135    public void addNextSibling(DetailAST ast) {
136        clearBranchTokenTypes();
137        clearChildCountCache(parent);
138        if (ast != null) {
139            //parent is set in setNextSibling
140            final DetailAST nextSibling = getNextSibling();
141
142            if (nextSibling != null) {
143                ast.setNextSibling(nextSibling);
144                nextSibling.previousSibling = ast;
145            }
146
147            ast.previousSibling = this;
148            setNextSibling(ast);
149        }
150    }
151
152    @Override
153    public void addChild(AST ast) {
154        clearBranchTokenTypes();
155        clearChildCountCache(this);
156        if (ast != null) {
157            ((DetailAST) ast).setParent(this);
158            ((DetailAST) ast).previousSibling = getLastChild();
159        }
160        super.addChild(ast);
161    }
162
163    /**
164     * Returns the number of child nodes one level below this node. That is is
165     * does not recurse down the tree.
166     * @return the number of child nodes
167     */
168    public int getChildCount() {
169        // lazy init
170        if (childCount == NOT_INITIALIZED) {
171            childCount = 0;
172            AST child = getFirstChild();
173
174            while (child != null) {
175                childCount += 1;
176                child = child.getNextSibling();
177            }
178        }
179        return childCount;
180    }
181
182    /**
183     * Returns the number of direct child tokens that have the specified type.
184     * @param type the token type to match
185     * @return the number of matching token
186     */
187    public int getChildCount(int type) {
188        int count = 0;
189        for (AST ast = getFirstChild(); ast != null; ast = ast.getNextSibling()) {
190            if (ast.getType() == type) {
191                count++;
192            }
193        }
194        return count;
195    }
196
197    /**
198     * Set the parent token.
199     * @param parent the parent token
200     */
201    private void setParent(DetailAST parent) {
202        DetailAST instance = this;
203        do {
204            instance.clearBranchTokenTypes();
205            instance.parent = parent;
206            final DetailAST nextSibling = instance.getNextSibling();
207            if (nextSibling != null) {
208                nextSibling.previousSibling = instance;
209            }
210
211            instance = nextSibling;
212        } while (instance != null);
213    }
214
215    /**
216     * Returns the parent token.
217     * @return the parent token
218     */
219    public DetailAST getParent() {
220        return parent;
221    }
222
223    /**
224     * Gets line number.
225     * @return the line number
226     */
227    public int getLineNo() {
228        int resultNo = -1;
229
230        if (lineNo == NOT_INITIALIZED) {
231            // an inner AST that has been initialized
232            // with initialize(String text)
233            resultNo = findLineNo(getFirstChild());
234
235            if (resultNo == -1) {
236                resultNo = findLineNo(getNextSibling());
237            }
238        }
239        if (resultNo == -1) {
240            resultNo = lineNo;
241        }
242        return resultNo;
243    }
244
245    /**
246     * Set line number.
247     * @param lineNo
248     *        line number.
249     */
250    public void setLineNo(int lineNo) {
251        this.lineNo = lineNo;
252    }
253
254    /**
255     * Gets column number.
256     * @return the column number
257     */
258    public int getColumnNo() {
259        int resultNo = -1;
260
261        if (columnNo == NOT_INITIALIZED) {
262            // an inner AST that has been initialized
263            // with initialize(String text)
264            resultNo = findColumnNo(getFirstChild());
265
266            if (resultNo == -1) {
267                resultNo = findColumnNo(getNextSibling());
268            }
269        }
270        if (resultNo == -1) {
271            resultNo = columnNo;
272        }
273        return resultNo;
274    }
275
276    /**
277     * Set column number.
278     * @param columnNo
279     *        column number.
280     */
281    public void setColumnNo(int columnNo) {
282        this.columnNo = columnNo;
283    }
284
285    /**
286     * Gets the last child node.
287     * @return the last child node
288     */
289    public DetailAST getLastChild() {
290        DetailAST ast = getFirstChild();
291        while (ast != null && ast.getNextSibling() != null) {
292            ast = ast.getNextSibling();
293        }
294        return ast;
295    }
296
297    /**
298     * Finds column number in the first non-comment node.
299     *
300     * @param ast DetailAST node.
301     * @return Column number if non-comment node exists, -1 otherwise.
302     */
303    private static int findColumnNo(DetailAST ast) {
304        int resultNo = -1;
305        DetailAST node = ast;
306        while (node != null) {
307            // comment node can't be start of any java statement/definition
308            if (TokenUtils.isCommentType(node.getType())) {
309                node = node.getNextSibling();
310            }
311            else {
312                resultNo = node.getColumnNo();
313                break;
314            }
315        }
316        return resultNo;
317    }
318
319    /**
320     * Finds line number in the first non-comment node.
321     *
322     * @param ast DetailAST node.
323     * @return Line number if non-comment node exists, -1 otherwise.
324     */
325    private static int findLineNo(DetailAST ast) {
326        int resultNo = -1;
327        DetailAST node = ast;
328        while (node != null) {
329            // comment node can't be start of any java statement/definition
330            if (TokenUtils.isCommentType(node.getType())) {
331                node = node.getNextSibling();
332            }
333            else {
334                resultNo = node.getLineNo();
335                break;
336            }
337        }
338        return resultNo;
339    }
340
341    /**
342     * Returns token type with branch.
343     * @return the token types that occur in the branch as a sorted set.
344     */
345    private BitSet getBranchTokenTypes() {
346        // lazy init
347        if (branchTokenTypes == null) {
348
349            branchTokenTypes = new BitSet();
350            branchTokenTypes.set(getType());
351
352            // add union of all children
353            DetailAST child = getFirstChild();
354            while (child != null) {
355                final BitSet childTypes = child.getBranchTokenTypes();
356                branchTokenTypes.or(childTypes);
357
358                child = child.getNextSibling();
359            }
360        }
361        return branchTokenTypes;
362    }
363
364    /**
365     * Checks if this branch of the parse tree contains a token
366     * of the provided type.
367     * @param type a TokenType
368     * @return true if and only if this branch (including this node)
369     *     contains a token of type {@code type}.
370     */
371    public boolean branchContains(int type) {
372        return getBranchTokenTypes().get(type);
373    }
374
375    /**
376     * Returns the previous sibling or null if no such sibling exists.
377     * @return the previous sibling or null if no such sibling exists.
378     */
379    public DetailAST getPreviousSibling() {
380        return previousSibling;
381    }
382
383    /**
384     * Returns the first child token that makes a specified type.
385     * @param type the token type to match
386     * @return the matching token, or null if no match
387     */
388    public DetailAST findFirstToken(int type) {
389        DetailAST returnValue = null;
390        for (DetailAST ast = getFirstChild(); ast != null; ast = ast.getNextSibling()) {
391            if (ast.getType() == type) {
392                returnValue = ast;
393                break;
394            }
395        }
396        return returnValue;
397    }
398
399    @Override
400    public String toString() {
401        return super.toString() + "[" + getLineNo() + "x" + getColumnNo() + "]";
402    }
403
404    @Override
405    public DetailAST getNextSibling() {
406        return (DetailAST) super.getNextSibling();
407    }
408
409    @Override
410    public DetailAST getFirstChild() {
411        return (DetailAST) super.getFirstChild();
412    }
413
414    /**
415     * Clears the child count for the ast instance.
416     * @param ast The ast to clear.
417     */
418    private static void clearChildCountCache(DetailAST ast) {
419        if (ast != null) {
420            ast.childCount = NOT_INITIALIZED;
421        }
422    }
423
424    /**
425     * Clears branchTokenTypes cache for all parents of the current DetailAST instance, and the
426     * child count for the current DetailAST instance.
427     */
428    private void clearBranchTokenTypes() {
429        DetailAST prevParent = parent;
430        while (prevParent != null) {
431            prevParent.branchTokenTypes = null;
432            prevParent = prevParent.parent;
433        }
434    }
435}