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.coding;
21  
22  import java.io.File;
23  
24  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
25  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26  import com.puppycrawl.tools.checkstyle.api.DetailAST;
27  import com.puppycrawl.tools.checkstyle.api.FullIdent;
28  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29  
30  /**
31   * <p>
32   * Ensures that a class has a package declaration, and (optionally) whether
33   * the package name matches the directory name for the source file.
34   * </p>
35   * <p>
36   * Rationale: Classes that live in the null package cannot be imported.
37   * Many novice developers are not aware of this.
38   * </p>
39   * <p>
40   * Packages provide logical namespace to classes and should be stored in
41   * the form of directory levels to provide physical grouping to your classes.
42   * These directories are added to the classpath so that your classes
43   * are visible to JVM when it runs the code.
44   * </p>
45   * <ul>
46   * <li>
47   * Property {@code matchDirectoryStructure} - Control whether to check for
48   * directory and package name match.
49   * Default value is {@code true}.
50   * </li>
51   * </ul>
52   * <p>
53   * To configure the check:
54   * </p>
55   * <pre>
56   * &lt;module name=&quot;PackageDeclaration&quot;/&gt;
57   * </pre>
58   * <p>
59   * Let us consider the class AnnotationLocationCheck which is in the directory
60   * /com/puppycrawl/tools/checkstyle/checks/annotations/
61   * </p>
62   * <pre>
63   * package com.puppycrawl.tools.checkstyle.checks; //Violation
64   * public class AnnotationLocationCheck extends AbstractCheck {
65   *   //...
66   * }
67   * </pre>
68   * <p>
69   * Example of how the check works when matchDirectoryStructure option is set to false.
70   * Let us again consider the AnnotationLocationCheck class located at directory
71   * /com/puppycrawl/tools/checkstyle/checks/annotations/ along with the following setup,
72   * </p>
73   * <pre>
74   * &lt;module name=&quot;PackageDeclaration&quot;&gt;
75   * &lt;property name=&quot;matchDirectoryStructure&quot; value=&quot;false&quot;/&gt;
76   * &lt;/module&gt;
77   * </pre>
78   * <pre>
79   * package com.puppycrawl.tools.checkstyle.checks;  //No Violation
80   *
81   * public class AnnotationLocationCheck extends AbstractCheck {
82   *   //...
83   * }
84   * </pre>
85   *
86   * @since 3.2
87   */
88  @FileStatefulCheck
89  public final class PackageDeclarationCheck extends AbstractCheck {
90  
91      /**
92       * A key is pointing to the warning message text in "messages.properties"
93       * file.
94       */
95      public static final String MSG_KEY_MISSING = "missing.package.declaration";
96  
97      /**
98       * A key is pointing to the warning message text in "messages.properties"
99       * file.
100      */
101     public static final String MSG_KEY_MISMATCH = "mismatch.package.directory";
102 
103     /** Line number used to log violation when no AST nodes are present in file. */
104     private static final int DEFAULT_LINE_NUMBER = 1;
105 
106     /** Is package defined. */
107     private boolean defined;
108 
109     /** Control whether to check for directory and package name match. */
110     private boolean matchDirectoryStructure = true;
111 
112     /**
113      * Setter to control whether to check for directory and package name match.
114      * @param matchDirectoryStructure the new value.
115      */
116     public void setMatchDirectoryStructure(boolean matchDirectoryStructure) {
117         this.matchDirectoryStructure = matchDirectoryStructure;
118     }
119 
120     @Override
121     public int[] getDefaultTokens() {
122         return getRequiredTokens();
123     }
124 
125     @Override
126     public int[] getRequiredTokens() {
127         return new int[] {TokenTypes.PACKAGE_DEF};
128     }
129 
130     @Override
131     public int[] getAcceptableTokens() {
132         return getRequiredTokens();
133     }
134 
135     @Override
136     public void beginTree(DetailAST ast) {
137         defined = false;
138     }
139 
140     @Override
141     public void finishTree(DetailAST ast) {
142         if (!defined) {
143             int lineNumber = DEFAULT_LINE_NUMBER;
144             if (ast != null) {
145                 lineNumber = ast.getLineNo();
146             }
147             log(lineNumber, MSG_KEY_MISSING);
148         }
149     }
150 
151     @Override
152     public void visitToken(DetailAST ast) {
153         defined = true;
154 
155         if (matchDirectoryStructure) {
156             final DetailAST packageNameAst = ast.getLastChild().getPreviousSibling();
157             final FullIdent fullIdent = FullIdent.createFullIdent(packageNameAst);
158             final String packageName = fullIdent.getText().replace('.', File.separatorChar);
159 
160             final String directoryName = getDirectoryName();
161 
162             if (!directoryName.endsWith(packageName)) {
163                 log(fullIdent.getLineNo(), MSG_KEY_MISMATCH, packageName);
164             }
165         }
166     }
167 
168     /**
169      * Returns the directory name this file is in.
170      * @return Directory name.
171      */
172     private String getDirectoryName() {
173         final String fileName = getFileContents().getFileName();
174         final int lastSeparatorPos = fileName.lastIndexOf(File.separatorChar);
175         return fileName.substring(0, lastSeparatorPos);
176     }
177 
178 }