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.utils;
021
022import antlr.collections.AST;
023import com.puppycrawl.tools.checkstyle.api.DetailAST;
024import com.puppycrawl.tools.checkstyle.api.Scope;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026
027/**
028 * Contains utility methods for working on scope.
029 *
030 */
031public final class ScopeUtil {
032
033    /** Prevent instantiation. */
034    private ScopeUtil() {
035    }
036
037    /**
038     * Returns the Scope specified by the modifier set.
039     *
040     * @param aMods root node of a modifier set
041     * @return a {@code Scope} value
042     */
043    public static Scope getScopeFromMods(DetailAST aMods) {
044        // default scope
045        Scope returnValue = Scope.PACKAGE;
046        for (AST token = aMods.getFirstChild(); token != null
047                && returnValue == Scope.PACKAGE;
048                token = token.getNextSibling()) {
049            if ("public".equals(token.getText())) {
050                returnValue = Scope.PUBLIC;
051            }
052            else if ("protected".equals(token.getText())) {
053                returnValue = Scope.PROTECTED;
054            }
055            else if ("private".equals(token.getText())) {
056                returnValue = Scope.PRIVATE;
057            }
058        }
059        return returnValue;
060    }
061
062    /**
063     * Returns the scope of the surrounding "block".
064     * @param node the node to return the scope for
065     * @return the Scope of the surrounding block
066     */
067    public static Scope getSurroundingScope(DetailAST node) {
068        Scope returnValue = null;
069        for (DetailAST token = node.getParent();
070             token != null;
071             token = token.getParent()) {
072            final int type = token.getType();
073            if (type == TokenTypes.CLASS_DEF
074                || type == TokenTypes.INTERFACE_DEF
075                || type == TokenTypes.ANNOTATION_DEF
076                || type == TokenTypes.ENUM_DEF) {
077                final DetailAST mods =
078                    token.findFirstToken(TokenTypes.MODIFIERS);
079                final Scope modScope = getScopeFromMods(mods);
080                if (returnValue == null || returnValue.isIn(modScope)) {
081                    returnValue = modScope;
082                }
083            }
084            else if (type == TokenTypes.LITERAL_NEW) {
085                returnValue = Scope.ANONINNER;
086                // because Scope.ANONINNER is not in any other Scope
087                break;
088            }
089        }
090
091        return returnValue;
092    }
093
094    /**
095     * Returns whether a node is directly contained within an interface block.
096     *
097     * @param node the node to check if directly contained within an interface block.
098     * @return a {@code boolean} value
099     */
100    public static boolean isInInterfaceBlock(DetailAST node) {
101        return isInBlockOf(node, TokenTypes.INTERFACE_DEF);
102    }
103
104    /**
105     * Returns whether a node is directly contained within an annotation block.
106     *
107     * @param node the node to check if directly contained within an annotation block.
108     * @return a {@code boolean} value
109     */
110    public static boolean isInAnnotationBlock(DetailAST node) {
111        return isInBlockOf(node, TokenTypes.ANNOTATION_DEF);
112    }
113
114    /**
115     * Returns whether a node is directly contained within a specified block.
116     *
117     * @param node the node to check if directly contained within a specified block.
118     * @param tokenType type of token.
119     * @return a {@code boolean} value
120     */
121    private static boolean isInBlockOf(DetailAST node, int tokenType) {
122        boolean returnValue = false;
123
124        // Loop up looking for a containing interface block
125        for (DetailAST token = node.getParent();
126             token != null && !returnValue;
127             token = token.getParent()) {
128            final int type = token.getType();
129            if (type == tokenType) {
130                returnValue = true;
131            }
132            else if (type == TokenTypes.CLASS_DEF
133                || type == TokenTypes.ENUM_DEF
134                || type == TokenTypes.INTERFACE_DEF
135                || type == TokenTypes.ANNOTATION_DEF
136                || type == TokenTypes.LITERAL_NEW) {
137                break;
138            }
139        }
140
141        return returnValue;
142    }
143
144    /**
145     * Returns whether a node is directly contained within an interface or
146     * annotation block.
147     *
148     * @param node the node to check if directly contained within an interface
149     *     or annotation block.
150     * @return a {@code boolean} value
151     */
152    public static boolean isInInterfaceOrAnnotationBlock(DetailAST node) {
153        return isInInterfaceBlock(node) || isInAnnotationBlock(node);
154    }
155
156    /**
157     * Returns whether a node is directly contained within an enum block.
158     *
159     * @param node the node to check if directly contained within an enum block.
160     * @return a {@code boolean} value
161     */
162    public static boolean isInEnumBlock(DetailAST node) {
163        boolean returnValue = false;
164
165        // Loop up looking for a containing interface block
166        for (DetailAST token = node.getParent();
167             token != null && !returnValue;
168             token = token.getParent()) {
169            final int type = token.getType();
170            if (type == TokenTypes.ENUM_DEF) {
171                returnValue = true;
172            }
173            else if (type == TokenTypes.INTERFACE_DEF
174                || type == TokenTypes.ANNOTATION_DEF
175                || type == TokenTypes.CLASS_DEF
176                || type == TokenTypes.LITERAL_NEW) {
177                break;
178            }
179        }
180
181        return returnValue;
182    }
183
184    /**
185     * Returns whether the scope of a node is restricted to a code block.
186     * A code block is a method or constructor body, an initializer block, or lambda body.
187     *
188     * @param node the node to check
189     * @return a {@code boolean} value
190     */
191    public static boolean isInCodeBlock(DetailAST node) {
192        boolean returnValue = false;
193
194        // Loop up looking for a containing code block
195        for (DetailAST token = node.getParent();
196             token != null;
197             token = token.getParent()) {
198            final int type = token.getType();
199            if (type == TokenTypes.METHOD_DEF
200                    || type == TokenTypes.CTOR_DEF
201                    || type == TokenTypes.INSTANCE_INIT
202                    || type == TokenTypes.STATIC_INIT
203                    || type == TokenTypes.LAMBDA) {
204                returnValue = true;
205                break;
206            }
207        }
208
209        return returnValue;
210    }
211
212    /**
213     * Returns whether a node is contained in the outer most type block.
214     *
215     * @param node the node to check
216     * @return a {@code boolean} value
217     */
218    public static boolean isOuterMostType(DetailAST node) {
219        boolean returnValue = true;
220        for (DetailAST parent = node.getParent();
221             parent != null;
222             parent = parent.getParent()) {
223            if (parent.getType() == TokenTypes.CLASS_DEF
224                || parent.getType() == TokenTypes.INTERFACE_DEF
225                || parent.getType() == TokenTypes.ANNOTATION_DEF
226                || parent.getType() == TokenTypes.ENUM_DEF) {
227                returnValue = false;
228                break;
229            }
230        }
231
232        return returnValue;
233    }
234
235    /**
236     * Determines whether a node is a local variable definition.
237     * I.e. if it is declared in a code block, a for initializer,
238     * or a catch parameter.
239     * @param node the node to check.
240     * @return whether aAST is a local variable definition.
241     */
242    public static boolean isLocalVariableDef(DetailAST node) {
243        boolean localVariableDef = false;
244        // variable declaration?
245        if (node.getType() == TokenTypes.VARIABLE_DEF) {
246            final DetailAST parent = node.getParent();
247            final int type = parent.getType();
248            localVariableDef = type == TokenTypes.SLIST
249                    || type == TokenTypes.FOR_INIT
250                    || type == TokenTypes.FOR_EACH_CLAUSE;
251        }
252        // catch parameter?
253        if (node.getType() == TokenTypes.PARAMETER_DEF) {
254            final DetailAST parent = node.getParent();
255            localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH;
256        }
257
258        if (node.getType() == TokenTypes.RESOURCE) {
259            localVariableDef = true;
260        }
261        return localVariableDef;
262    }
263
264    /**
265     * Determines whether a node is a class field definition.
266     * I.e. if a variable is not declared in a code block, a for initializer,
267     * or a catch parameter.
268     * @param node the node to check.
269     * @return whether a node is a class field definition.
270     */
271    public static boolean isClassFieldDef(DetailAST node) {
272        return node.getType() == TokenTypes.VARIABLE_DEF && !isLocalVariableDef(node);
273    }
274
275    /**
276     * Checks whether ast node is in a specific scope.
277     * @param ast the node to check.
278     * @param scope a {@code Scope} value.
279     * @return true if the ast node is in the scope.
280     */
281    public static boolean isInScope(DetailAST ast, Scope scope) {
282        final Scope surroundingScopeOfAstToken = getSurroundingScope(ast);
283        return surroundingScopeOfAstToken == scope;
284    }
285
286}