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