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}