001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 java.util.Optional;
023
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.Scope;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027
028/**
029 * Contains utility methods for working on scope.
030 *
031 */
032public final class ScopeUtil {
033
034    /** Prevent instantiation. */
035    private ScopeUtil() {
036    }
037
038    /**
039     * Returns the {@code Scope} explicitly specified by the modifier set.
040     * Returns {@code null} if there are no modifiers.
041     *
042     * @param aMods root node of a modifier set
043     * @return a {@code Scope} value or {@code null}
044     */
045    public static Scope getDeclaredScopeFromMods(DetailAST aMods) {
046        Scope result = null;
047        for (DetailAST token = aMods.getFirstChild(); token != null;
048                token = token.getNextSibling()) {
049            if ("public".equals(token.getText())) {
050                result = Scope.PUBLIC;
051            }
052            else if ("protected".equals(token.getText())) {
053                result = Scope.PROTECTED;
054            }
055            else if ("private".equals(token.getText())) {
056                result = Scope.PRIVATE;
057            }
058        }
059        return result;
060    }
061
062    /**
063     * Returns the {@code Scope} for a given {@code DetailAST}.
064     *
065     * @param ast the DetailAST to examine
066     * @return a {@code Scope} value
067     */
068    public static Scope getScope(DetailAST ast) {
069        return Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
070                .map(ScopeUtil::getDeclaredScopeFromMods)
071                .orElseGet(() -> getDefaultScope(ast));
072    }
073
074    /**
075     * Returns the {@code Scope} specified by the modifier set. If no modifiers are present,
076     * the default scope is used.
077     *
078     * @param aMods root node of a modifier set
079     * @return a {@code Scope} value
080     * @see #getDefaultScope(DetailAST)
081     */
082    public static Scope getScopeFromMods(DetailAST aMods) {
083        return Optional.ofNullable(getDeclaredScopeFromMods(aMods))
084                .orElseGet(() -> getDefaultScope(aMods.getParent()));
085    }
086
087    /**
088     * Returns the default {@code Scope} for a {@code DetailAST}.
089     * <p>The following rules are taken into account:</p>
090     * <ul>
091     *     <li>enum constants are public</li>
092     *     <li>enum constructors are private</li>
093     *     <li>interface members are public</li>
094     *     <li>everything else is package private</li>
095     * </ul>
096     *
097     * @param ast DetailAST to process
098     * @return a {@code Scope} value
099     */
100    private static Scope getDefaultScope(DetailAST ast) {
101        final Scope result;
102        if (isInEnumBlock(ast)) {
103            if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
104                result = Scope.PUBLIC;
105            }
106            else if (ast.getType() == TokenTypes.CTOR_DEF) {
107                result = Scope.PRIVATE;
108            }
109            else {
110                result = Scope.PACKAGE;
111            }
112        }
113        else if (isInInterfaceOrAnnotationBlock(ast)) {
114            result = Scope.PUBLIC;
115        }
116        else {
117            result = Scope.PACKAGE;
118        }
119        return result;
120    }
121
122    /**
123     * Returns the scope of the surrounding "block".
124     *
125     * @param node the node to return the scope for
126     * @return the Scope of the surrounding block
127     */
128    public static Scope getSurroundingScope(DetailAST node) {
129        Scope returnValue = null;
130        for (DetailAST token = node.getParent();
131             token != null;
132             token = token.getParent()) {
133            final int type = token.getType();
134            if (TokenUtil.isTypeDeclaration(type)) {
135                final Scope tokenScope = getScope(token);
136                if (returnValue == null || returnValue.isIn(tokenScope)) {
137                    returnValue = tokenScope;
138                }
139            }
140            else if (type == TokenTypes.LITERAL_NEW) {
141                returnValue = Scope.ANONINNER;
142                // because Scope.ANONINNER is not in any other Scope
143                break;
144            }
145        }
146
147        return returnValue;
148    }
149
150    /**
151     * Returns whether a node is directly contained within a class block.
152     *
153     * @param node the node to check if directly contained within a class block.
154     * @return a {@code boolean} value
155     */
156    public static boolean isInClassBlock(DetailAST node) {
157        return isInBlockOf(node, TokenTypes.CLASS_DEF);
158    }
159
160    /**
161     * Returns whether a node is directly contained within a record block.
162     *
163     * @param node the node to check if directly contained within a record block.
164     * @return a {@code boolean} value
165     */
166    public static boolean isInRecordBlock(DetailAST node) {
167        return isInBlockOf(node, TokenTypes.RECORD_DEF);
168    }
169
170    /**
171     * Returns whether a node is directly contained within an interface block.
172     *
173     * @param node the node to check if directly contained within an interface block.
174     * @return a {@code boolean} value
175     */
176    public static boolean isInInterfaceBlock(DetailAST node) {
177        return isInBlockOf(node, TokenTypes.INTERFACE_DEF);
178    }
179
180    /**
181     * Returns whether a node is directly contained within an annotation block.
182     *
183     * @param node the node to check if directly contained within an annotation block.
184     * @return a {@code boolean} value
185     */
186    public static boolean isInAnnotationBlock(DetailAST node) {
187        return isInBlockOf(node, TokenTypes.ANNOTATION_DEF);
188    }
189
190    /**
191     * Returns whether a node is directly contained within a specified block.
192     *
193     * @param node the node to check if directly contained within a specified block.
194     * @param tokenType type of token.
195     * @return a {@code boolean} value
196     */
197    private static boolean isInBlockOf(DetailAST node, int tokenType) {
198        boolean returnValue = false;
199
200        // Loop up looking for a containing interface block
201        for (DetailAST token = node.getParent();
202             token != null && !returnValue;
203             token = token.getParent()) {
204            if (token.getType() == tokenType) {
205                returnValue = true;
206            }
207            else if (token.getType() == TokenTypes.LITERAL_NEW
208                    || TokenUtil.isTypeDeclaration(token.getType())) {
209                break;
210            }
211        }
212
213        return returnValue;
214    }
215
216    /**
217     * Returns whether a node is directly contained within an interface or
218     * annotation block.
219     *
220     * @param node the node to check if directly contained within an interface
221     *     or annotation block.
222     * @return a {@code boolean} value
223     */
224    public static boolean isInInterfaceOrAnnotationBlock(DetailAST node) {
225        return isInInterfaceBlock(node) || isInAnnotationBlock(node);
226    }
227
228    /**
229     * Returns whether a node is directly contained within an enum block.
230     *
231     * @param node the node to check if directly contained within an enum block.
232     * @return a {@code boolean} value
233     */
234    public static boolean isInEnumBlock(DetailAST node) {
235        boolean returnValue = false;
236
237        // Loop up looking for a containing interface block
238        for (DetailAST token = node.getParent();
239             token != null && !returnValue;
240             token = token.getParent()) {
241            if (token.getType() == TokenTypes.ENUM_DEF) {
242                returnValue = true;
243            }
244            else if (TokenUtil.isOfType(token, TokenTypes.INTERFACE_DEF,
245                TokenTypes.ANNOTATION_DEF, TokenTypes.CLASS_DEF,
246                TokenTypes.LITERAL_NEW)) {
247                break;
248            }
249        }
250
251        return returnValue;
252    }
253
254    /**
255     * Returns whether the scope of a node is restricted to a code block.
256     * A code block is a method or constructor body, an initializer block, or lambda body.
257     *
258     * @param node the node to check
259     * @return a {@code boolean} value
260     */
261    public static boolean isInCodeBlock(DetailAST node) {
262        boolean returnValue = false;
263        final int[] tokenTypes = {
264            TokenTypes.METHOD_DEF,
265            TokenTypes.CTOR_DEF,
266            TokenTypes.INSTANCE_INIT,
267            TokenTypes.STATIC_INIT,
268            TokenTypes.LAMBDA,
269            TokenTypes.COMPACT_CTOR_DEF,
270        };
271
272        // Loop up looking for a containing code block
273        for (DetailAST token = node.getParent();
274             token != null;
275             token = token.getParent()) {
276            if (TokenUtil.isOfType(token, tokenTypes)) {
277                returnValue = true;
278                break;
279            }
280        }
281
282        return returnValue;
283    }
284
285    /**
286     * Returns whether a node is contained in the outer most type block.
287     *
288     * @param node the node to check
289     * @return a {@code boolean} value
290     */
291    public static boolean isOuterMostType(DetailAST node) {
292        boolean returnValue = true;
293        for (DetailAST parent = node.getParent();
294             parent != null;
295             parent = parent.getParent()) {
296            if (TokenUtil.isTypeDeclaration(parent.getType())) {
297                returnValue = false;
298                break;
299            }
300        }
301
302        return returnValue;
303    }
304
305    /**
306     * Determines whether a node is a local variable definition.
307     * I.e. if it is declared in a code block, a for initializer,
308     * or a catch parameter.
309     *
310     * @param node the node to check.
311     * @return whether aAST is a local variable definition.
312     */
313    public static boolean isLocalVariableDef(DetailAST node) {
314        boolean localVariableDef = false;
315        // variable declaration?
316        if (node.getType() == TokenTypes.VARIABLE_DEF) {
317            final DetailAST parent = node.getParent();
318            localVariableDef = TokenUtil.isOfType(parent, TokenTypes.SLIST,
319                                TokenTypes.FOR_INIT, TokenTypes.FOR_EACH_CLAUSE);
320        }
321        // catch parameter?
322        if (node.getType() == TokenTypes.PARAMETER_DEF) {
323            final DetailAST parent = node.getParent();
324            localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH;
325        }
326
327        if (node.getType() == TokenTypes.RESOURCE) {
328            localVariableDef = node.getChildCount() > 1;
329        }
330        return localVariableDef;
331    }
332
333    /**
334     * Determines whether a node is a class field definition.
335     * I.e. if a variable is not declared in a code block, a for initializer,
336     * or a catch parameter.
337     *
338     * @param node the node to check.
339     * @return whether a node is a class field definition.
340     */
341    public static boolean isClassFieldDef(DetailAST node) {
342        return node.getType() == TokenTypes.VARIABLE_DEF
343                && !isLocalVariableDef(node);
344    }
345
346    /**
347     * Checks whether ast node is in a specific scope.
348     *
349     * @param ast the node to check.
350     * @param scope a {@code Scope} value.
351     * @return true if the ast node is in the scope.
352     */
353    public static boolean isInScope(DetailAST ast, Scope scope) {
354        final Scope surroundingScopeOfAstToken = getSurroundingScope(ast);
355        return surroundingScopeOfAstToken == scope;
356    }
357
358}