Source code: com/puppycrawl/tools/checkstyle/checks/imports/ImportOrderCheck.java
1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2003 Oliver Burn
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 com.puppycrawl.tools.checkstyle.api.Check;
23 import com.puppycrawl.tools.checkstyle.api.DetailAST;
24 import com.puppycrawl.tools.checkstyle.api.FullIdent;
25 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26
27 /**
28 * Class to check the ordering/grouping of imports. Ensures that
29 * groups of imports come in a specific order (e.g., java. comes
30 * first, javax. comes first, then everything else) and imports
31 * within each group are in lexicographic order.
32 *
33 * <p>
34 * Example:
35 * <pre>
36 * <module name="ImportOrder">
37 * <property name="groups" value="java,javax"/>
38 * <property name="ordered" value="true"/>
39 * </module>
40 * </pre>
41 *
42 * There is always an additional, implied "everything else" package
43 * group. If no "groups" property is supplied, all imports belong in
44 * this "everything else" group. </p>
45 *
46 * <p>
47 * ordered defaults to true.
48 * </p>
49 *
50 * <p>
51 * separated defaults to false.
52 * </p>
53 *
54 * @author Bill Schneider
55 * @author o_sukhodolsky
56 */
57 public class ImportOrderCheck extends Check
58 {
59 /** List of import groups specified by the user. */
60 private String[] mGroups = new String[0];
61
62 /** Require imports in group. */
63 private boolean mOrdered = true;
64
65 /** Require imports in group be separated. */
66 private boolean mSeparated;
67
68 /** Last imported group. */
69 private int mLastGroup;
70 /** Line number of last import. */
71 private int mLastImportLine;
72 /** Name of last import. */
73 private String mLastImport;
74 /** Whether there was any imports. */
75 private boolean mBeforeFirstImport;
76
77 /**
78 * Default constructor.
79 */
80 public ImportOrderCheck()
81 {
82 }
83
84 /**
85 * sets the list of package groups and the order they should
86 * occur in the file.
87 *
88 * @param aGroups a comma-separated list of package names/prefixes
89 */
90 public void setGroups(String[] aGroups)
91 {
92 mGroups = new String[ aGroups.length ];
93
94 for (int i = 0; i < aGroups.length; i++) {
95 String pkg = aGroups[i];
96
97 if (!pkg.endsWith(".")) {
98 pkg = pkg + ".";
99 }
100
101 mGroups[i] = pkg;
102 }
103 }
104
105 /**
106 * Sets whether or not imports should be ordered within any one
107 * group of imports.
108 *
109 * @param aOrdered whether lexicographic ordering of imports within
110 * a group required or not.
111 */
112 public void setOrdered(boolean aOrdered)
113 {
114 mOrdered = aOrdered;
115 }
116
117 /**
118 * Sets whether or not groups of imports must be separated from
119 * one another by at least one blank line.
120 *
121 * @param aSeparated whehter groups should be separated by blank line.
122 */
123 public void setSeparated(boolean aSeparated)
124 {
125 mSeparated = aSeparated;
126 }
127
128 /** {@inheritDoc} */
129 public int[] getDefaultTokens()
130 {
131 return new int[]{TokenTypes.IMPORT};
132 }
133
134 /** {@inheritDoc} */
135 public int[] getRequiredTokens()
136 {
137 return getDefaultTokens();
138 }
139
140 /**
141 * @param aName import name to check.
142 * @return group number for given import name.
143 */
144 private int getGroupNumber(String aName)
145 {
146 int i = 0;
147
148 // find out what group this belongs in
149 // loop over mGroups and get index
150 for (; i < mGroups.length; i++) {
151 if (aName.startsWith(mGroups[i])) {
152 break;
153 }
154 }
155
156 return i;
157 }
158
159 /** {@inheritDoc} */
160 public void beginTree(DetailAST aRootAST)
161 {
162 mLastGroup = Integer.MIN_VALUE;
163 mLastImportLine = Integer.MIN_VALUE;
164 mLastImport = "";
165 mBeforeFirstImport = true;
166 }
167
168 /** {@inheritDoc} */
169 public void visitToken(DetailAST aAST)
170 {
171 final FullIdent ident = FullIdent.createFullIdentBelow(aAST);
172
173 if (ident != null) {
174 String name = ident.getText();
175 int groupIdx = getGroupNumber(name);
176 int line = ident.getLineNo();
177
178 if (groupIdx > mLastGroup) {
179 if (!mBeforeFirstImport && mSeparated) {
180 // This check should be made more robust to handle
181 // comments and imports that span more than one line.
182 if (line - mLastImportLine < 2) {
183 log(line, "import.separation", name);
184 }
185 }
186 }
187 else if (groupIdx == mLastGroup) {
188 if (mOrdered) {
189 if (mLastImport.compareTo(name) >= 0) {
190 log(line, "import.ordering", name);
191 }
192 }
193 }
194 else {
195 log(line, "import.ordering", name);
196 }
197
198 mLastGroup = groupIdx;
199 mLastImport = name;
200 mLastImportLine = aAST.findFirstToken(TokenTypes.SEMI).getLineNo();
201 mBeforeFirstImport = false;
202 }
203 }
204 }