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