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