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}