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.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 * @author Oliver Burn
031 */
032public final class ScopeUtils {
033    /** Prevent instantiation. */
034    private ScopeUtils() {
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        boolean returnValue = false;
102
103        // Loop up looking for a containing interface block
104        for (DetailAST token = node.getParent();
105             token != null && !returnValue;
106             token = token.getParent()) {
107
108            final int type = token.getType();
109
110            if (type == TokenTypes.INTERFACE_DEF) {
111                returnValue = true;
112            }
113            else if (type == TokenTypes.CLASS_DEF
114                || type == TokenTypes.ENUM_DEF
115                || type == TokenTypes.ANNOTATION_DEF
116                || type == TokenTypes.LITERAL_NEW) {
117                break;
118            }
119        }
120
121        return returnValue;
122    }
123
124    /**
125     * Returns whether a node is directly contained within an annotation block.
126     *
127     * @param node the node to check if directly contained within an annotation block.
128     * @return a {@code boolean} value
129     */
130    public static boolean isInAnnotationBlock(DetailAST node) {
131        boolean returnValue = false;
132
133        // Loop up looking for a containing interface block
134        for (DetailAST token = node.getParent();
135             token != null && !returnValue;
136             token = token.getParent()) {
137            final int type = token.getType();
138            if (type == TokenTypes.ANNOTATION_DEF) {
139                returnValue = true;
140            }
141            else if (type == TokenTypes.CLASS_DEF
142                || type == TokenTypes.ENUM_DEF
143                || type == TokenTypes.INTERFACE_DEF
144                || type == TokenTypes.LITERAL_NEW) {
145                break;
146            }
147
148        }
149
150        return returnValue;
151    }
152
153    /**
154     * Returns whether a node is directly contained within an interface or
155     * annotation block.
156     *
157     * @param node the node to check if directly contained within an interface
158     *     or annotation block.
159     * @return a {@code boolean} value
160     */
161    public static boolean isInInterfaceOrAnnotationBlock(DetailAST node) {
162        return isInInterfaceBlock(node) || isInAnnotationBlock(node);
163    }
164
165    /**
166     * Returns whether a node is directly contained within an enum block.
167     *
168     * @param node the node to check if directly contained within an enum block.
169     * @return a {@code boolean} value
170     */
171    public static boolean isInEnumBlock(DetailAST node) {
172        boolean returnValue = false;
173
174        // Loop up looking for a containing interface block
175        for (DetailAST token = node.getParent();
176             token != null && !returnValue;
177             token = token.getParent()) {
178            final int type = token.getType();
179            if (type == TokenTypes.ENUM_DEF) {
180                returnValue = true;
181            }
182            else if (type == TokenTypes.INTERFACE_DEF
183                || type == TokenTypes.ANNOTATION_DEF
184                || type == TokenTypes.CLASS_DEF
185                || type == TokenTypes.LITERAL_NEW) {
186                break;
187            }
188        }
189
190        return returnValue;
191    }
192
193    /**
194     * Returns whether the scope of a node is restricted to a code block.
195     * A code block is a method or constructor body, an initializer block, or lambda body.
196     *
197     * @param node the node to check
198     * @return a {@code boolean} value
199     */
200    public static boolean isInCodeBlock(DetailAST node) {
201        boolean returnValue = false;
202
203        // Loop up looking for a containing code block
204        for (DetailAST token = node.getParent();
205             token != null;
206             token = token.getParent()) {
207            final int type = token.getType();
208            if (type == TokenTypes.METHOD_DEF
209                    || type == TokenTypes.CTOR_DEF
210                    || type == TokenTypes.INSTANCE_INIT
211                    || type == TokenTypes.STATIC_INIT
212                    || type == TokenTypes.LAMBDA) {
213                returnValue = true;
214                break;
215            }
216        }
217
218        return returnValue;
219    }
220
221    /**
222     * Returns whether a node is contained in the outer most type block.
223     *
224     * @param node the node to check
225     * @return a {@code boolean} value
226     */
227    public static boolean isOuterMostType(DetailAST node) {
228        boolean returnValue = true;
229        for (DetailAST parent = node.getParent();
230             parent != null;
231             parent = parent.getParent()) {
232            if (parent.getType() == TokenTypes.CLASS_DEF
233                || parent.getType() == TokenTypes.INTERFACE_DEF
234                || parent.getType() == TokenTypes.ANNOTATION_DEF
235                || parent.getType() == TokenTypes.ENUM_DEF) {
236                returnValue = false;
237                break;
238            }
239        }
240
241        return returnValue;
242    }
243
244    /**
245     * Determines whether a node is a local variable definition.
246     * I.e. if it is declared in a code block, a for initializer,
247     * or a catch parameter.
248     * @param node the node to check.
249     * @return whether aAST is a local variable definition.
250     */
251    public static boolean isLocalVariableDef(DetailAST node) {
252        boolean localVariableDef = false;
253        // variable declaration?
254        if (node.getType() == TokenTypes.VARIABLE_DEF) {
255            final DetailAST parent = node.getParent();
256            final int type = parent.getType();
257            localVariableDef = type == TokenTypes.SLIST
258                    || type == TokenTypes.FOR_INIT
259                    || type == TokenTypes.FOR_EACH_CLAUSE;
260        }
261        // catch parameter?
262        if (node.getType() == TokenTypes.PARAMETER_DEF) {
263            final DetailAST parent = node.getParent();
264            localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH;
265        }
266
267        if (node.getType() == TokenTypes.RESOURCE) {
268            localVariableDef = true;
269        }
270        return localVariableDef;
271    }
272
273    /**
274     * Determines whether a node is a class field definition.
275     * I.e. if a variable is not declared in a code block, a for initializer,
276     * or a catch parameter.
277     * @param node the node to check.
278     * @return whether a node is a class field definition.
279     */
280    public static boolean isClassFieldDef(DetailAST node) {
281        return node.getType() == TokenTypes.VARIABLE_DEF && !isLocalVariableDef(node);
282    }
283
284    /**
285     * Checks whether ast node is in a specific scope.
286     * @param ast the node to check.
287     * @param scope a {@code Scope} value.
288     * @return true if the ast node is in the scope.
289     */
290    public static boolean isInScope(DetailAST ast, Scope scope) {
291        final Scope surroundingScopeOfAstToken = getSurroundingScope(ast);
292        return surroundingScopeOfAstToken == scope;
293    }
294}