001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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 com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.Scope;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025
026/**
027 * Contains utility methods for working on scope.
028 *
029 */
030public final class ScopeUtil {
031
032    /** Prevent instantiation. */
033    private ScopeUtil() {
034    }
035
036    /**
037     * Returns the Scope specified by the modifier set.
038     *
039     * @param aMods root node of a modifier set
040     * @return a {@code Scope} value
041     */
042    public static Scope getScopeFromMods(DetailAST aMods) {
043        // default scope
044        Scope returnValue = Scope.PACKAGE;
045        for (DetailAST token = aMods.getFirstChild(); token != null
046                && returnValue == Scope.PACKAGE;
047                token = token.getNextSibling()) {
048            if ("public".equals(token.getText())) {
049                returnValue = Scope.PUBLIC;
050            }
051            else if ("protected".equals(token.getText())) {
052                returnValue = Scope.PROTECTED;
053            }
054            else if ("private".equals(token.getText())) {
055                returnValue = Scope.PRIVATE;
056            }
057        }
058        return returnValue;
059    }
060
061    /**
062     * Returns the scope of the surrounding "block".
063     * @param node the node to return the scope for
064     * @return the Scope of the surrounding block
065     */
066    public static Scope getSurroundingScope(DetailAST node) {
067        Scope returnValue = null;
068        for (DetailAST token = node.getParent();
069             token != null;
070             token = token.getParent()) {
071            final int type = token.getType();
072            if (type == TokenTypes.CLASS_DEF
073                || type == TokenTypes.INTERFACE_DEF
074                || type == TokenTypes.ANNOTATION_DEF
075                || type == TokenTypes.ENUM_DEF) {
076                final DetailAST mods =
077                    token.findFirstToken(TokenTypes.MODIFIERS);
078                final Scope modScope = getScopeFromMods(mods);
079                if (returnValue == null || returnValue.isIn(modScope)) {
080                    returnValue = modScope;
081                }
082            }
083            else if (type == TokenTypes.LITERAL_NEW) {
084                returnValue = Scope.ANONINNER;
085                // because Scope.ANONINNER is not in any other Scope
086                break;
087            }
088        }
089
090        return returnValue;
091    }
092
093    /**
094     * Returns whether a node is directly contained within a class block.
095     *
096     * @param node the node to check if directly contained within a class block.
097     * @return a {@code boolean} value
098     */
099    public static boolean isInClassBlock(DetailAST node) {
100        return isInBlockOf(node, TokenTypes.CLASS_DEF);
101    }
102
103    /**
104     * Returns whether a node is directly contained within an interface block.
105     *
106     * @param node the node to check if directly contained within an interface block.
107     * @return a {@code boolean} value
108     */
109    public static boolean isInInterfaceBlock(DetailAST node) {
110        return isInBlockOf(node, TokenTypes.INTERFACE_DEF);
111    }
112
113    /**
114     * Returns whether a node is directly contained within an annotation block.
115     *
116     * @param node the node to check if directly contained within an annotation block.
117     * @return a {@code boolean} value
118     */
119    public static boolean isInAnnotationBlock(DetailAST node) {
120        return isInBlockOf(node, TokenTypes.ANNOTATION_DEF);
121    }
122
123    /**
124     * Returns whether a node is directly contained within a specified block.
125     *
126     * @param node the node to check if directly contained within a specified block.
127     * @param tokenType type of token.
128     * @return a {@code boolean} value
129     */
130    private static boolean isInBlockOf(DetailAST node, int tokenType) {
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 == tokenType) {
139                returnValue = true;
140            }
141            else if (type == TokenTypes.CLASS_DEF
142                || type == TokenTypes.ENUM_DEF
143                || type == TokenTypes.INTERFACE_DEF
144                || type == TokenTypes.ANNOTATION_DEF
145                || type == TokenTypes.LITERAL_NEW) {
146                break;
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
295}