1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.imports;
21  
22  import java.util.Deque;
23  import java.util.LinkedList;
24  
25  /**
26   * Represents a tree of import rules for controlling whether packages or
27   * classes are allowed to be used. Each instance must have a single parent or
28   * be the root node.
29   */
30  abstract class AbstractImportControl {
31  
32      /** List of {@link AbstractImportRule} objects to check. */
33      private final Deque<AbstractImportRule> rules = new LinkedList<>();
34      /** The parent. Null indicates we are the root node. */
35      private final AbstractImportControl parent;
36      /** Strategy in a case if matching allow/disallow rule was not found. */
37      private final MismatchStrategy strategyOnMismatch;
38  
39      /**
40       * Construct a child node.
41       * @param parent the parent node.
42       * @param strategyOnMismatch strategy in a case if matching allow/disallow rule was not found.
43       */
44      protected AbstractImportControl(AbstractImportControl parent,
45              MismatchStrategy strategyOnMismatch) {
46          this.parent = parent;
47          this.strategyOnMismatch = strategyOnMismatch;
48      }
49  
50      /**
51       * Search down the tree to locate the finest match for a supplied package.
52       * @param forPkg the package to search for.
53       * @param forFileName the file name to search for.
54       * @return the finest match, or null if no match at all.
55       */
56      public abstract AbstractImportControl locateFinest(String forPkg, String forFileName);
57  
58      /**
59       * Check for equality of this with pkg.
60       * @param pkg the package to compare with.
61       * @param fileName the file name to compare with.
62       * @return if it matches.
63       */
64      protected abstract boolean matchesExactly(String pkg, String fileName);
65  
66      /**
67       * Adds an {@link AbstractImportRule} to the node.
68       * @param rule the rule to be added.
69       */
70      protected void addImportRule(AbstractImportRule rule) {
71          rules.addLast(rule);
72      }
73  
74      /**
75       * Returns whether a package or class is allowed to be imported.
76       * The algorithm checks with the current node for a result, and if none is
77       * found then calls its parent looking for a match. This will recurse
78       * looking for match. If there is no clear result then
79       * {@link AccessResult#UNKNOWN} is returned.
80       * @param inPkg the package doing the import.
81       * @param inFileName the file name doing the import.
82       * @param forImport the import to check on.
83       * @return an {@link AccessResult}.
84       */
85      public AccessResult checkAccess(String inPkg, String inFileName, String forImport) {
86          final AccessResult result;
87          final AccessResult returnValue = localCheckAccess(inPkg, inFileName, forImport);
88          if (returnValue != AccessResult.UNKNOWN) {
89              result = returnValue;
90          }
91          else if (parent == null) {
92              if (strategyOnMismatch == MismatchStrategy.ALLOWED) {
93                  result = AccessResult.ALLOWED;
94              }
95              else {
96                  result = AccessResult.DISALLOWED;
97              }
98          }
99          else {
100             if (strategyOnMismatch == MismatchStrategy.ALLOWED) {
101                 result = AccessResult.ALLOWED;
102             }
103             else if (strategyOnMismatch == MismatchStrategy.DISALLOWED) {
104                 result = AccessResult.DISALLOWED;
105             }
106             else {
107                 result = parent.checkAccess(inPkg, inFileName, forImport);
108             }
109         }
110         return result;
111     }
112 
113     /**
114      * Checks whether any of the rules for this node control access to
115      * a specified package or file.
116      * @param inPkg the package doing the import.
117      * @param inFileName the file name doing the import.
118      * @param forImport the import to check on.
119      * @return an {@link AccessResult}.
120      */
121     private AccessResult localCheckAccess(String inPkg, String inFileName, String forImport) {
122         AccessResult localCheckAccessResult = AccessResult.UNKNOWN;
123         for (AbstractImportRule importRule : rules) {
124             // Check if an import rule is only meant to be applied locally.
125             if (!importRule.isLocalOnly() || matchesExactly(inPkg, inFileName)) {
126                 final AccessResult result = importRule.verifyImport(forImport);
127                 if (result != AccessResult.UNKNOWN) {
128                     localCheckAccessResult = result;
129                     break;
130                 }
131             }
132         }
133         return localCheckAccessResult;
134     }
135 
136 }