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