001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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
039    private static final long serialVersionUID = -2580884815577559874L;
040
041    /** Constant to indicate if not calculated the child count. */
042    private static final int NOT_INITIALIZED = Integer.MIN_VALUE;
043
044    /** The line number. **/
045    private int lineNo = NOT_INITIALIZED;
046    /** The column number. **/
047    private int columnNo = NOT_INITIALIZED;
048
049    /** Number of children. */
050    private int childCount = NOT_INITIALIZED;
051    /** The parent token. */
052    private DetailAST parent;
053    /** Previous sibling. */
054    private DetailAST previousSibling;
055
056    /**
057     * All token types in this branch.
058     * Token 'x' (where x is an int) is in this branch
059     * if branchTokenTypes.get(x) is true.
060     */
061    private BitSet branchTokenTypes;
062
063    @Override
064    public void initialize(Token tok) {
065        super.initialize(tok);
066        lineNo = tok.getLine();
067
068        // expect columns to start @ 0
069        columnNo = tok.getColumn() - 1;
070    }
071
072    @Override
073    public void initialize(AST ast) {
074        final DetailAST detailAst = (DetailAST) ast;
075        setText(detailAst.getText());
076        setType(detailAst.getType());
077        lineNo = detailAst.getLineNo();
078        columnNo = detailAst.getColumnNo();
079        hiddenAfter = detailAst.getHiddenAfter();
080        hiddenBefore = detailAst.getHiddenBefore();
081    }
082
083    @Override
084    public void setFirstChild(AST ast) {
085        clearBranchTokenTypes();
086        clearChildCountCache(this);
087        super.setFirstChild(ast);
088        if (ast != null) {
089            ((DetailAST) ast).setParent(this);
090        }
091    }
092
093    @Override
094    public void setNextSibling(AST ast) {
095        clearBranchTokenTypes();
096        clearChildCountCache(parent);
097        super.setNextSibling(ast);
098        if (ast != null && parent != null) {
099            ((DetailAST) ast).setParent(parent);
100        }
101        if (ast != null) {
102            ((DetailAST) ast).previousSibling = this;
103        }
104    }
105
106    /**
107     * Add previous sibling.
108     * @param ast
109     *        DetailAST object.
110     */
111    public void addPreviousSibling(DetailAST ast) {
112        clearBranchTokenTypes();
113        clearChildCountCache(parent);
114        if (ast != null) {
115            //parent is set in setNextSibling or parent.setFirstChild
116            final DetailAST previousSiblingNode = previousSibling;
117
118            if (previousSiblingNode != null) {
119                ast.previousSibling = previousSiblingNode;
120                previousSiblingNode.setNextSibling(ast);
121            }
122            else if (parent != null) {
123                parent.setFirstChild(ast);
124            }
125
126            ast.setNextSibling(this);
127            previousSibling = ast;
128        }
129    }
130
131    /**
132     * Add next sibling.
133     * @param ast
134     *        DetailAST object.
135     */
136    public void addNextSibling(DetailAST ast) {
137        clearBranchTokenTypes();
138        clearChildCountCache(parent);
139        if (ast != null) {
140            //parent is set in setNextSibling
141            final DetailAST nextSibling = getNextSibling();
142
143            if (nextSibling != null) {
144                ast.setNextSibling(nextSibling);
145                nextSibling.previousSibling = ast;
146            }
147
148            ast.previousSibling = this;
149            setNextSibling(ast);
150        }
151    }
152
153    @Override
154    public void addChild(AST ast) {
155        clearBranchTokenTypes();
156        clearChildCountCache(this);
157        if (ast != null) {
158            ((DetailAST) ast).setParent(this);
159            ((DetailAST) ast).previousSibling = getLastChild();
160        }
161        super.addChild(ast);
162    }
163
164    /**
165     * Returns the number of child nodes one level below this node. That is is
166     * does not recurse down the tree.
167     * @return the number of child nodes
168     */
169    public int getChildCount() {
170        // lazy init
171        if (childCount == NOT_INITIALIZED) {
172            childCount = 0;
173            AST child = getFirstChild();
174
175            while (child != null) {
176                childCount += 1;
177                child = child.getNextSibling();
178            }
179        }
180        return childCount;
181    }
182
183    /**
184     * Returns the number of direct child tokens that have the specified type.
185     * @param type the token type to match
186     * @return the number of matching token
187     */
188    public int getChildCount(int type) {
189        int count = 0;
190        for (AST ast = getFirstChild(); ast != null; ast = ast.getNextSibling()) {
191            if (ast.getType() == type) {
192                count++;
193            }
194        }
195        return count;
196    }
197
198    /**
199     * Set the parent token.
200     * @param parent the parent token
201     */
202    private void setParent(DetailAST parent) {
203        DetailAST instance = this;
204        do {
205            instance.clearBranchTokenTypes();
206            instance.parent = parent;
207            final DetailAST nextSibling = instance.getNextSibling();
208            if (nextSibling != null) {
209                nextSibling.previousSibling = instance;
210            }
211
212            instance = nextSibling;
213        } while (instance != null);
214    }
215
216    /**
217     * Returns the parent token.
218     * @return the parent token
219     */
220    public DetailAST getParent() {
221        return parent;
222    }
223
224    /**
225     * Gets line number.
226     * @return the line number
227     */
228    public int getLineNo() {
229        int resultNo = -1;
230
231        if (lineNo == NOT_INITIALIZED) {
232            // an inner AST that has been initialized
233            // with initialize(String text)
234            resultNo = findLineNo(getFirstChild());
235
236            if (resultNo == -1) {
237                resultNo = findLineNo(getNextSibling());
238            }
239        }
240        if (resultNo == -1) {
241            resultNo = lineNo;
242        }
243        return resultNo;
244    }
245
246    /**
247     * Set line number.
248     * @param lineNo
249     *        line number.
250     */
251    public void setLineNo(int lineNo) {
252        this.lineNo = lineNo;
253    }
254
255    /**
256     * Gets column number.
257     * @return the column number
258     */
259    public int getColumnNo() {
260        int resultNo = -1;
261
262        if (columnNo == NOT_INITIALIZED) {
263            // an inner AST that has been initialized
264            // with initialize(String text)
265            resultNo = findColumnNo(getFirstChild());
266
267            if (resultNo == -1) {
268                resultNo = findColumnNo(getNextSibling());
269            }
270        }
271        if (resultNo == -1) {
272            resultNo = columnNo;
273        }
274        return resultNo;
275    }
276
277    /**
278     * Set column number.
279     * @param columnNo
280     *        column number.
281     */
282    public void setColumnNo(int columnNo) {
283        this.columnNo = columnNo;
284    }
285
286    /**
287     * Gets the last child node.
288     * @return the last child node
289     */
290    public DetailAST getLastChild() {
291        DetailAST ast = getFirstChild();
292        while (ast != null && ast.getNextSibling() != null) {
293            ast = ast.getNextSibling();
294        }
295        return ast;
296    }
297
298    /**
299     * Finds column number in the first non-comment node.
300     *
301     * @param ast DetailAST node.
302     * @return Column number if non-comment node exists, -1 otherwise.
303     */
304    private static int findColumnNo(DetailAST ast) {
305        int resultNo = -1;
306        DetailAST node = ast;
307        while (node != null) {
308            // comment node can't be start of any java statement/definition
309            if (TokenUtils.isCommentType(node.getType())) {
310                node = node.getNextSibling();
311            }
312            else {
313                resultNo = node.getColumnNo();
314                break;
315            }
316        }
317        return resultNo;
318    }
319
320    /**
321     * Finds line number in the first non-comment node.
322     *
323     * @param ast DetailAST node.
324     * @return Line number if non-comment node exists, -1 otherwise.
325     */
326    private static int findLineNo(DetailAST ast) {
327        int resultNo = -1;
328        DetailAST node = ast;
329        while (node != null) {
330            // comment node can't be start of any java statement/definition
331            if (TokenUtils.isCommentType(node.getType())) {
332                node = node.getNextSibling();
333            }
334            else {
335                resultNo = node.getLineNo();
336                break;
337            }
338        }
339        return resultNo;
340    }
341
342    /**
343     * Returns token type with branch.
344     * @return the token types that occur in the branch as a sorted set.
345     */
346    private BitSet getBranchTokenTypes() {
347        // lazy init
348        if (branchTokenTypes == null) {
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
436}