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