001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.checks.imports;
021
022import java.util.ArrayDeque;
023import java.util.Deque;
024
025/**
026 * Represents a tree of import rules for controlling whether packages or
027 * classes are allowed to be used. Each instance must have a single parent or be the root node.
028 */
029public abstract class AbstractImportControl {
030
031    /** List of {@link AbstractImportRule} objects to check. */
032    private final Deque<AbstractImportRule> rules = new ArrayDeque<>();
033    /** The parent. Null indicates we are the root node. */
034    private final AbstractImportControl parent;
035    /** Strategy in a case if matching allow/disallow rule was not found. */
036    private final MismatchStrategy strategyOnMismatch;
037
038    /**
039     * Construct a child node.
040     *
041     * @param parent the parent node.
042     * @param strategyOnMismatch strategy in a case if matching allow/disallow rule was not found.
043     */
044    protected AbstractImportControl(AbstractImportControl parent,
045            MismatchStrategy strategyOnMismatch) {
046        this.parent = parent;
047        this.strategyOnMismatch = strategyOnMismatch;
048    }
049
050    /**
051     * Search down the tree to locate the finest match for a supplied package.
052     *
053     * @param forPkg the package to search for.
054     * @param forFileName the file name to search for.
055     * @return the finest match, or null if no match at all.
056     */
057    public abstract AbstractImportControl locateFinest(String forPkg, String forFileName);
058
059    /**
060     * Check for equality of this with pkg.
061     *
062     * @param pkg the package to compare with.
063     * @param fileName the file name to compare with.
064     * @return if it matches.
065     */
066    protected abstract boolean matchesExactly(String pkg, String fileName);
067
068    /**
069     * Adds an {@link AbstractImportRule} to the node.
070     *
071     * @param rule the rule to be added.
072     */
073    protected void addImportRule(AbstractImportRule rule) {
074        rules.addLast(rule);
075    }
076
077    /**
078     * Returns whether a package or class is allowed to be imported.
079     * The algorithm checks with the current node for a result, and if none is
080     * found then calls its parent looking for a match. This will recurse
081     * looking for match. If there is no clear result then
082     * {@link AccessResult#UNKNOWN} is returned.
083     *
084     * @param inPkg the package doing the import.
085     * @param inFileName the file name doing the import.
086     * @param forImport the import to check on.
087     * @return an {@link AccessResult}.
088     */
089    public AccessResult checkAccess(String inPkg, String inFileName, String forImport) {
090        final AccessResult result;
091        final AccessResult returnValue = localCheckAccess(inPkg, inFileName, forImport);
092        if (returnValue != AccessResult.UNKNOWN) {
093            result = returnValue;
094        }
095        else if (parent == null) {
096            if (strategyOnMismatch == MismatchStrategy.ALLOWED) {
097                result = AccessResult.ALLOWED;
098            }
099            else {
100                result = AccessResult.DISALLOWED;
101            }
102        }
103        else {
104            if (strategyOnMismatch == MismatchStrategy.ALLOWED) {
105                result = AccessResult.ALLOWED;
106            }
107            else if (strategyOnMismatch == MismatchStrategy.DISALLOWED) {
108                result = AccessResult.DISALLOWED;
109            }
110            else {
111                result = parent.checkAccess(inPkg, inFileName, forImport);
112            }
113        }
114        return result;
115    }
116
117    /**
118     * Checks whether any of the rules for this node control access to a specified package or file.
119     *
120     * @param inPkg the package doing the import.
121     * @param inFileName the file name doing the import.
122     * @param forImport the import to check on.
123     * @return an {@link AccessResult}.
124     */
125    private AccessResult localCheckAccess(String inPkg, String inFileName, String forImport) {
126        AccessResult localCheckAccessResult = AccessResult.UNKNOWN;
127        for (AbstractImportRule importRule : rules) {
128            // Check if an import rule is only meant to be applied locally.
129            if (!importRule.isLocalOnly() || matchesExactly(inPkg, inFileName)) {
130                final AccessResult result = importRule.verifyImport(forImport);
131                if (result != AccessResult.UNKNOWN) {
132                    localCheckAccessResult = result;
133                    break;
134                }
135            }
136        }
137        return localCheckAccessResult;
138    }
139
140}