001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 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.ArrayList;
023import java.util.List;
024import java.util.StringTokenizer;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
029import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
030import com.puppycrawl.tools.checkstyle.api.DetailAST;
031import com.puppycrawl.tools.checkstyle.api.FullIdent;
032import com.puppycrawl.tools.checkstyle.api.TokenTypes;
033import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
034
035/**
036 * <p>
037 * Checks that the groups of import declarations appear in the order specified
038 * by the user. If there is an import but its group is not specified in the
039 * configuration such an import should be placed at the end of the import list.
040 * </p>
041 * <p>
042 * The rule consists of:
043 * </p>
044 * <ol>
045 * <li>
046 * STATIC group. This group sets the ordering of static imports.
047 * </li>
048 * <li>
049 * SAME_PACKAGE(n) group. This group sets the ordering of the same package imports.
050 * Imports are considered on SAME_PACKAGE group if <b>n</b> first domains in package
051 * name and import name are identical:
052 * <pre>
053 * package java.util.concurrent.locks;
054 *
055 * import java.io.File;
056 * import java.util.*; //#1
057 * import java.util.List; //#2
058 * import java.util.StringTokenizer; //#3
059 * import java.util.concurrent.*; //#4
060 * import java.util.concurrent.AbstractExecutorService; //#5
061 * import java.util.concurrent.locks.LockSupport; //#6
062 * import java.util.regex.Pattern; //#7
063 * import java.util.regex.Matcher; //#8
064 * </pre>
065 * If we have SAME_PACKAGE(3) on configuration file, imports #4-6 will be considered as
066 * a SAME_PACKAGE group (java.util.concurrent.*, java.util.concurrent.AbstractExecutorService,
067 * java.util.concurrent.locks.LockSupport). SAME_PACKAGE(2) will include #1-8.
068 * SAME_PACKAGE(4) will include only #6. SAME_PACKAGE(5) will result in no imports assigned
069 * to SAME_PACKAGE group because actual package java.util.concurrent.locks has only 4 domains.
070 * </li>
071 * <li>
072 * THIRD_PARTY_PACKAGE group. This group sets ordering of third party imports.
073 * Third party imports are all imports except STATIC, SAME_PACKAGE(n), STANDARD_JAVA_PACKAGE and
074 * SPECIAL_IMPORTS.
075 * </li>
076 * <li>
077 * STANDARD_JAVA_PACKAGE group. By default, this group sets ordering of standard java/javax imports.
078 * </li>
079 * <li>
080 * SPECIAL_IMPORTS group. This group may contain some imports that have particular meaning for the
081 * user.
082 * </li>
083 * </ol>
084 * <p>
085 * Use the separator '###' between rules.
086 * </p>
087 * <p>
088 * To set RegExps for THIRD_PARTY_PACKAGE and STANDARD_JAVA_PACKAGE groups use
089 * thirdPartyPackageRegExp and standardPackageRegExp options.
090 * </p>
091 * <p>
092 * Pretty often one import can match more than one group. For example, static import from standard
093 * package or regular expressions are configured to allow one import match multiple groups.
094 * In this case, group will be assigned according to priorities:
095 * </p>
096 * <ol>
097 * <li>
098 * STATIC has top priority
099 * </li>
100 * <li>
101 * SAME_PACKAGE has second priority
102 * </li>
103 * <li>
104 * STANDARD_JAVA_PACKAGE and SPECIAL_IMPORTS will compete using "best match" rule: longer
105 * matching substring wins; in case of the same length, lower position of matching substring
106 * wins; if position is the same, order of rules in configuration solves the puzzle.
107 * </li>
108 * <li>
109 * THIRD_PARTY has the least priority
110 * </li>
111 * </ol>
112 * <p>
113 * Few examples to illustrate "best match":
114 * </p>
115 * <p>
116 * 1. patterns STANDARD_JAVA_PACKAGE = "Check", SPECIAL_IMPORTS="ImportOrderCheck" and input file:
117 * </p>
118 * <pre>
119 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck;
120 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck;
121 * </pre>
122 * <p>
123 * Result: imports will be assigned to SPECIAL_IMPORTS, because matching substring length is 16.
124 * Matching substring for STANDARD_JAVA_PACKAGE is 5.
125 * </p>
126 * <p>
127 * 2. patterns STANDARD_JAVA_PACKAGE = "Check", SPECIAL_IMPORTS="Avoid" and file:
128 * </p>
129 * <pre>
130 * import com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck;
131 * </pre>
132 * <p>
133 * Result: import will be assigned to SPECIAL_IMPORTS. Matching substring length is 5 for both
134 * patterns. However, "Avoid" position is lower than "Check" position.
135 * </p>
136 * <ul>
137 * <li>
138 * Property {@code customImportOrderRules} - Specify format of order declaration
139 * customizing by user.
140 * Type is {@code java.lang.String}.
141 * Default value is {@code ""}.
142 * </li>
143 * <li>
144 * Property {@code standardPackageRegExp} - Specify RegExp for STANDARD_JAVA_PACKAGE group imports.
145 * Type is {@code java.util.regex.Pattern}.
146 * Default value is {@code "^(java|javax)\."}.
147 * </li>
148 * <li>
149 * Property {@code thirdPartyPackageRegExp} - Specify RegExp for THIRD_PARTY_PACKAGE group imports.
150 * Type is {@code java.util.regex.Pattern}.
151 * Default value is {@code ".*"}.
152 * </li>
153 * <li>
154 * Property {@code specialImportsRegExp} - Specify RegExp for SPECIAL_IMPORTS group imports.
155 * Type is {@code java.util.regex.Pattern}.
156 * Default value is {@code "^$"}.
157 * </li>
158 * <li>
159 * Property {@code separateLineBetweenGroups} - Force empty line separator between
160 * import groups.
161 * Type is {@code boolean}.
162 * Default value is {@code true}.
163 * </li>
164 * <li>
165 * Property {@code sortImportsInGroupAlphabetically} - Force grouping alphabetically,
166 * in <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>.
167 * Type is {@code boolean}.
168 * Default value is {@code false}.
169 * </li>
170 * </ul>
171 * <p>
172 *     To configure the check :
173 * </p>
174 * <pre>
175 * &lt;module name=&quot;CustomImportOrder&quot;/&gt;
176 * </pre>
177 * <p>
178 * Example:
179 * </p>
180 * <pre>
181 * package com.company;
182 * import org.apache.commons.io.FileUtils; // OK
183 * import static java.util.*; // OK
184 * import java.time.*; // OK
185 * import static java.io.*; // OK
186 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK
187 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK
188 * </pre>
189 * <p>
190 * To configure the check so that it checks in the order
191 * (static imports,standard java packages,third party package):
192 * </p>
193 * <pre>
194 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
195 *   &lt;property name=&quot;customImportOrderRules&quot;
196 *     value=&quot;STATIC###STANDARD_JAVA_PACKAGE###THIRD_PARTY_PACKAGE&quot;/&gt;
197 * &lt;/module&gt;
198 * </pre>
199 * <p>
200 * Example:
201 * </p>
202 * <pre>
203 * package com.company;
204 *
205 * import static java.util.*; // OK
206 *
207 * import java.time.*; // OK
208 * import javax.net.*; // OK
209 * import static java.io.*; // violation as static imports should be in top
210 *
211 * import org.apache.commons.io.FileUtils; // OK
212 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK
213 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK
214 * </pre>
215 * <p>
216 * To configure the check such that only java packages are included in standard java packages
217 * </p>
218 * <pre>
219 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
220 *   &lt;property name=&quot;customImportOrderRules&quot;
221 *     value=&quot;STATIC###STANDARD_JAVA_PACKAGE###THIRD_PARTY_PACKAGE&quot;/&gt;
222 *   &lt;property name=&quot;standardPackageRegExp&quot; value=&quot;^java\.&quot;/&gt;
223 * &lt;/module&gt;
224 * </pre>
225 * <p>
226 * Example:
227 * </p>
228 * <pre>
229 * package com.company;
230 *
231 * import static java.util.*; // OK
232 * import static java.io.*; // OK
233 *
234 * import java.time.*; // OK
235 * import javax.net.*; // violation as it is not included in standard java package group.
236 *
237 * import org.apache.commons.io.FileUtils; // violation
238 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK
239 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK
240 * </pre>
241 * <p>
242 * To configure the check to include only "com" packages as third party group imports:
243 * </p>
244 * <pre>
245 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
246 *   &lt;property name=&quot;customImportOrderRules&quot;
247 *     value=&quot;STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE&quot;/&gt;
248 *   &lt;property name=&quot;thirdPartyPackageRegExp&quot; value=&quot;^com\.&quot;/&gt;
249 * &lt;/module&gt;
250 * </pre>
251 * <p>
252 * Example:
253 * </p>
254 * <pre>
255 * package com.company;
256 *
257 * import static java.util.*; // OK
258 * import static java.io.*; // OK
259 *
260 * import java.time.*; // OK
261 * import javax.net.*; // OK
262 *
263 * import org.apache.commons.io.FileUtils; // violation(should be in end)
264 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // violation
265 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK
266 * </pre>
267 * <p>
268 * To configure the check to force some packages in special import group:
269 * </p>
270 * <pre>
271 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
272 *   &lt;property name=&quot;customImportOrderRules&quot;
273 *     value=&quot;STATIC###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE&quot;/&gt;
274 *   &lt;property name=&quot;specialImportsRegExp&quot; value=&quot;^org\.&quot;/&gt;
275 * &lt;/module&gt;
276 * </pre>
277 * <p>
278 * Example:
279 * </p>
280 * <pre>
281 * package com.company;
282 *
283 * import static java.util.*; // OK
284 * import static java.io.*; // OK
285 *
286 * import org.json.JSONObject; // OK
287 *
288 * import java.time.*; // OK
289 * import javax.net.*; // OK
290 *
291 * import org.apache.commons.io.FileUtils; // violation
292 * </pre>
293 * <p>
294 * To configure the check such that empty line separator between two groups is enabled:
295 * </p>
296 * <pre>
297 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
298 *   &lt;property name=&quot;customImportOrderRules&quot;
299 *     value=&quot;STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE&quot;/&gt;
300 *   &lt;property name=&quot;specialImportsRegExp&quot; value=&quot;^org\.&quot;/&gt;
301 *   &lt;property name=&quot;thirdPartyPackageRegExp&quot; value=&quot;^com\.&quot;/&gt;
302 *   &lt;property name=&quot;separateLineBetweenGroups&quot; value=&quot;true&quot;/&gt;
303 * &lt;/module&gt;
304 * </pre>
305 * <p>
306 * Example:
307 * </p>
308 * <pre>
309 * package com.company;
310 *
311 * import static java.util.*; // OK
312 * import static java.io.*; // OK
313 *
314 * import java.time.*; // OK
315 * import javax.net.*; // OK
316 * import org.apache.commons.io.FileUtils; // violation
317 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // violation
318 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK
319 * </pre>
320 * <p>
321 * To configure the check such that import groups are forced to be sorted alphabetically:
322 * </p>
323 * <pre>
324 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
325 *   &lt;property name=&quot;customImportOrderRules&quot;
326 *     value=&quot;STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE&quot;/&gt;
327 *   &lt;property name=&quot;specialImportsRegExp&quot; value=&quot;^org\.&quot;/&gt;
328 *   &lt;property name=&quot;thirdPartyPackageRegExp&quot; value=&quot;^com\.&quot;/&gt;
329 *   &lt;property name=&quot;separateLineBetweenGroups&quot; value=&quot;false&quot;/&gt;
330 *   &lt;property name=&quot;sortImportsInGroupAlphabetically&quot; value=&quot;true&quot;/&gt;
331 * &lt;/module&gt;
332 * </pre>
333 * <p>
334 * Example:
335 * </p>
336 * <pre>
337 * package com.company;
338 *
339 * import static java.util.*; // OK
340 * import static java.io.*; // Violation since it should come before"java.util"
341 *
342 * import java.time.*; // OK
343 * import javax.net.*; // OK
344 * import org.apache.commons.io.FileUtils; // OK
345 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK
346 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK
347 * </pre>
348 * <p>
349 * To configure the check so that it matches default Eclipse formatter configuration
350 * (tested on Kepler and Luna releases):
351 * </p>
352 * <ul>
353 * <li>
354 * group of static imports is on the top
355 * </li>
356 * <li>
357 * groups of non-static imports: "java" and "javax" packages first, then "org" and then all other
358 * imports
359 * </li>
360 * <li>
361 * imports will be sorted in the groups
362 * </li>
363 * <li>
364 * groups are separated by single blank line
365 * </li>
366 * </ul>
367 * <p>
368 * Notes:
369 * </p>
370 * <ul>
371 * <li>
372 * "com" package is not mentioned on configuration, because it is ignored by Eclipse Kepler and Luna
373 * (looks like Eclipse defect)
374 * </li>
375 * <li>
376 * configuration below doesn't work in all 100% cases due to inconsistent behavior prior to Mars
377 * release, but covers most scenarios
378 * </li>
379 * </ul>
380 * <pre>
381 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
382 *   &lt;property name=&quot;customImportOrderRules&quot;
383 *     value=&quot;STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS&quot;/&gt;
384 *   &lt;property name=&quot;specialImportsRegExp&quot; value=&quot;^org\.&quot;/&gt;
385 *   &lt;property name=&quot;sortImportsInGroupAlphabetically&quot; value=&quot;true&quot;/&gt;
386 *   &lt;property name=&quot;separateLineBetweenGroups&quot; value=&quot;true&quot;/&gt;
387 * &lt;/module&gt;
388 * </pre>
389 * <p>
390 * Example:
391 * </p>
392 * <pre>
393 * package com.company;
394 *
395 * import static java.util.*; // OK
396 * import static java.io.*; // Violation since it should come before"java.util"
397 *
398 * import java.time.*; // OK
399 * import javax.net.*; // OK
400 * import org.apache.commons.io.FileUtils; // Violation should be separated by space
401 *
402 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK
403 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK
404 * </pre>
405 * <p>
406 * To configure the check so that it matches default Eclipse formatter configuration
407 * (tested on Mars release):
408 * </p>
409 * <ul>
410 * <li>
411 * group of static imports is on the top
412 * </li>
413 * <li>
414 * groups of non-static imports: "java" and "javax" packages first, then "org" and "com",
415 * then all other imports as one group
416 * </li>
417 * <li>
418 * imports will be sorted in the groups
419 * </li>
420 * <li>
421 * groups are separated by one blank line
422 * </li>
423 * </ul>
424 * <pre>
425 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
426 *   &lt;property name=&quot;customImportOrderRules&quot;
427 *     value=&quot;STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE&quot;/&gt;
428 *   &lt;property name=&quot;specialImportsRegExp&quot; value=&quot;^org\.&quot;/&gt;
429 *   &lt;property name=&quot;thirdPartyPackageRegExp&quot; value=&quot;^com\.&quot;/&gt;
430 *   &lt;property name=&quot;sortImportsInGroupAlphabetically&quot; value=&quot;true&quot;/&gt;
431 *   &lt;property name=&quot;separateLineBetweenGroups&quot; value=&quot;true&quot;/&gt;
432 * &lt;/module&gt;
433 * </pre>
434 * <p>
435 * Example:
436 * </p>
437 * <pre>
438 * package com.company;
439 *
440 * import static java.io.*; // OK
441 * import static java.util.*; // OK
442 *
443 * import java.time.*; // OK
444 * import javax.net.*; // OK
445 *
446 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // Violation
447 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // Violation
448 *
449 * import org.apache.commons.io.FileUtils;
450 * </pre>
451 * <p>
452 * To configure the check so that it matches default IntelliJ IDEA formatter configuration
453 * (tested on v14):
454 * </p>
455 * <ul>
456 * <li>
457 * group of static imports is on the bottom
458 * </li>
459 * <li>
460 * groups of non-static imports: all imports except of "javax" and "java", then "javax" and "java"
461 * </li>
462 * <li>
463 * imports will be sorted in the groups
464 * </li>
465 * <li>
466 * groups are separated by one blank line
467 * </li>
468 * </ul>
469 * <p>
470 * Note: "separated" option is disabled because IDEA default has blank line between "java" and
471 * static imports, and no blank line between "javax" and "java"
472 * </p>
473 * <pre>
474 * &lt;module name="CustomImportOrder"&gt;
475 *   &lt;property name="customImportOrderRules"
476 *     value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###STATIC"/&gt;
477 *   &lt;property name="specialImportsRegExp" value="^javax\."/&gt;
478 *   &lt;property name="standardPackageRegExp" value="^java\."/&gt;
479 *   &lt;property name="sortImportsInGroupAlphabetically" value="true"/&gt;
480 *   &lt;property name="separateLineBetweenGroups" value="false"/&gt;
481 * &lt;/module&gt;
482 * </pre>
483 * <p>
484 * Example:
485 * </p>
486 * <pre>
487 * package com.company;
488 *
489 * import static java.io.*; // OK
490 * import static java.util.*; // OK
491 *
492 * import java.time.*; // violation should be in standard package group
493 *                    // below special import
494 *
495 * import javax.net.*; // Violation should be in special import group
496 *
497 * import org.apache.commons.io.FileUtils; // Violation should be in
498 *                                        // THIRD PARTY PACKAGE GROUP
499 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // Violation
500 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // Violation
501 * </pre>
502 * <p>
503 * To configure the check so that it matches default NetBeans formatter configuration
504 * (tested on v8):
505 * </p>
506 * <ul>
507 * <li>
508 * groups of non-static imports are not defined, all imports will be sorted as a one group
509 * </li>
510 * <li>
511 * static imports are not separated, they will be sorted along with other imports
512 * </li>
513 * </ul>
514 * <pre>
515 * &lt;module name=&quot;CustomImportOrder&quot;/&gt;
516 * </pre>
517 * <p>
518 * Example:
519 * </p>
520 * <pre>
521 * package com.company;
522 *
523 * import static java.io.*; // OK
524 * import static java.util.*; // OK
525 * import java.time.*; // OK
526 * import javax.net.*; // OK
527 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK
528 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK
529 *
530 * import org.apache.commons.io.FileUtils; // should not be separated by line
531 * </pre>
532 * <p>
533 * To set RegExps for THIRD_PARTY_PACKAGE and STANDARD_JAVA_PACKAGE groups use
534 * thirdPartyPackageRegExp and standardPackageRegExp options.
535 * </p>
536 * <pre>
537 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
538 *   &lt;property name=&quot;customImportOrderRules&quot;
539 *     value=&quot;STATIC###SAME_PACKAGE(3)###THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE&quot;/&gt;
540 *   &lt;property name=&quot;thirdPartyPackageRegExp&quot; value=&quot;^(com|org)\.&quot;/&gt;
541 *   &lt;property name=&quot;standardPackageRegExp&quot; value=&quot;^(java|javax)\.&quot;/&gt;
542 * &lt;/module&gt;
543 * </pre>
544 * <p>
545 * Example:
546 * </p>
547 * <pre>
548 * package com.company;
549 *
550 * import static java.io.*; // OK
551 * import static java.util.*; // OK
552 * import java.time.*; // violation
553 * import javax.net.*; // violation
554 *
555 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK
556 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK
557 * import org.apache.commons.io.FileUtils; // OK
558 * </pre>
559 * <p>
560 * Also, this check can be configured to force empty line separator between
561 * import groups. For example.
562 * </p>
563 * <pre>
564 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
565 *   &lt;property name=&quot;separateLineBetweenGroups&quot; value=&quot;true&quot;/&gt;
566 * &lt;/module&gt;
567 * </pre>
568 * <p>
569 * Example:
570 * </p>
571 * <pre>
572 * package com.company;
573 *
574 * import static java.io.*; // OK
575 * import static java.util.*; // OK
576 * import java.time.*; // OK
577 * import javax.net.*; // OK
578 * import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck; // OK
579 * import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck; // OK
580 * import org.apache.commons.io.FileUtils; // OK
581 * </pre>
582 * <p>
583 * It is possible to enforce
584 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>
585 * of imports in groups using the following configuration:
586 * </p>
587 * <pre>
588 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
589 *   &lt;property name=&quot;sortImportsInGroupAlphabetically&quot; value=&quot;true&quot;/&gt;
590 * &lt;/module&gt;
591 * </pre>
592 * <p>
593 * Example of ASCII order:
594 * </p>
595 * <pre>
596 * import java.awt.Dialog;
597 * import java.awt.Window;
598 * import java.awt.color.ColorSpace;
599 * import java.awt.Frame; // violation here - in ASCII order 'F' should go before 'c',
600 *                        // as all uppercase come before lowercase letters
601 * </pre>
602 * <p>
603 * To force checking imports sequence such as:
604 * </p>
605 * <pre>
606 * package com.puppycrawl.tools.checkstyle.imports;
607 *
608 * import com.google.common.annotations.GwtCompatible;
609 * import com.google.common.annotations.Beta;
610 * import com.google.common.annotations.VisibleForTesting;
611 *
612 * import org.abego.treelayout.Configuration;
613 *
614 * import static sun.tools.util.ModifierFilter.ALL_ACCESS;
615 *
616 * import com.google.common.annotations.GwtCompatible; // violation here - should be in the
617 *                                                     // THIRD_PARTY_PACKAGE group
618 * import android.*;
619 * </pre>
620 * <p>
621 * configure as follows:
622 * </p>
623 * <pre>
624 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
625 *   &lt;property name=&quot;customImportOrderRules&quot;
626 *     value=&quot;SAME_PACKAGE(3)###THIRD_PARTY_PACKAGE###STATIC###SPECIAL_IMPORTS&quot;/&gt;
627 *   &lt;property name=&quot;specialImportsRegExp&quot; value=&quot;^android\.&quot;/&gt;
628 * &lt;/module&gt;
629 * </pre>
630 * <p>
631 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
632 * </p>
633 * <p>
634 * Violation Message Keys:
635 * </p>
636 * <ul>
637 * <li>
638 * {@code custom.import.order}
639 * </li>
640 * <li>
641 * {@code custom.import.order.lex}
642 * </li>
643 * <li>
644 * {@code custom.import.order.line.separator}
645 * </li>
646 * <li>
647 * {@code custom.import.order.nonGroup.expected}
648 * </li>
649 * <li>
650 * {@code custom.import.order.nonGroup.import}
651 * </li>
652 * <li>
653 * {@code custom.import.order.separated.internally}
654 * </li>
655 * </ul>
656 *
657 * @since 5.8
658 */
659@FileStatefulCheck
660public class CustomImportOrderCheck extends AbstractCheck {
661
662    /**
663     * A key is pointing to the warning message text in "messages.properties"
664     * file.
665     */
666    public static final String MSG_LINE_SEPARATOR = "custom.import.order.line.separator";
667
668    /**
669     * A key is pointing to the warning message text in "messages.properties"
670     * file.
671     */
672    public static final String MSG_SEPARATED_IN_GROUP = "custom.import.order.separated.internally";
673
674    /**
675     * A key is pointing to the warning message text in "messages.properties"
676     * file.
677     */
678    public static final String MSG_LEX = "custom.import.order.lex";
679
680    /**
681     * A key is pointing to the warning message text in "messages.properties"
682     * file.
683     */
684    public static final String MSG_NONGROUP_IMPORT = "custom.import.order.nonGroup.import";
685
686    /**
687     * A key is pointing to the warning message text in "messages.properties"
688     * file.
689     */
690    public static final String MSG_NONGROUP_EXPECTED = "custom.import.order.nonGroup.expected";
691
692    /**
693     * A key is pointing to the warning message text in "messages.properties"
694     * file.
695     */
696    public static final String MSG_ORDER = "custom.import.order";
697
698    /** STATIC group name. */
699    public static final String STATIC_RULE_GROUP = "STATIC";
700
701    /** SAME_PACKAGE group name. */
702    public static final String SAME_PACKAGE_RULE_GROUP = "SAME_PACKAGE";
703
704    /** THIRD_PARTY_PACKAGE group name. */
705    public static final String THIRD_PARTY_PACKAGE_RULE_GROUP = "THIRD_PARTY_PACKAGE";
706
707    /** STANDARD_JAVA_PACKAGE group name. */
708    public static final String STANDARD_JAVA_PACKAGE_RULE_GROUP = "STANDARD_JAVA_PACKAGE";
709
710    /** SPECIAL_IMPORTS group name. */
711    public static final String SPECIAL_IMPORTS_RULE_GROUP = "SPECIAL_IMPORTS";
712
713    /** NON_GROUP group name. */
714    private static final String NON_GROUP_RULE_GROUP = "NOT_ASSIGNED_TO_ANY_GROUP";
715
716    /** Pattern used to separate groups of imports. */
717    private static final Pattern GROUP_SEPARATOR_PATTERN = Pattern.compile("\\s*###\\s*");
718
719    /** Processed list of import order rules. */
720    private final List<String> customOrderRules = new ArrayList<>();
721
722    /** Contains objects with import attributes. */
723    private final List<ImportDetails> importToGroupList = new ArrayList<>();
724
725    /** Specify format of order declaration customizing by user. */
726    private String customImportOrderRules = "";
727
728    /** Specify RegExp for SAME_PACKAGE group imports. */
729    private String samePackageDomainsRegExp = "";
730
731    /** Specify RegExp for STANDARD_JAVA_PACKAGE group imports. */
732    private Pattern standardPackageRegExp = Pattern.compile("^(java|javax)\\.");
733
734    /** Specify RegExp for THIRD_PARTY_PACKAGE group imports. */
735    private Pattern thirdPartyPackageRegExp = Pattern.compile(".*");
736
737    /** Specify RegExp for SPECIAL_IMPORTS group imports. */
738    private Pattern specialImportsRegExp = Pattern.compile("^$");
739
740    /** Force empty line separator between import groups. */
741    private boolean separateLineBetweenGroups = true;
742
743    /**
744     * Force grouping alphabetically,
745     * in <a href="https://en.wikipedia.org/wiki/ASCII#Order"> ASCII sort order</a>.
746     */
747    private boolean sortImportsInGroupAlphabetically;
748
749    /** Number of first domains for SAME_PACKAGE group. */
750    private int samePackageMatchingDepth = 2;
751
752    /**
753     * Setter to specify RegExp for STANDARD_JAVA_PACKAGE group imports.
754     *
755     * @param regexp
756     *        user value.
757     */
758    public final void setStandardPackageRegExp(Pattern regexp) {
759        standardPackageRegExp = regexp;
760    }
761
762    /**
763     * Setter to specify RegExp for THIRD_PARTY_PACKAGE group imports.
764     *
765     * @param regexp
766     *        user value.
767     */
768    public final void setThirdPartyPackageRegExp(Pattern regexp) {
769        thirdPartyPackageRegExp = regexp;
770    }
771
772    /**
773     * Setter to specify RegExp for SPECIAL_IMPORTS group imports.
774     *
775     * @param regexp
776     *        user value.
777     */
778    public final void setSpecialImportsRegExp(Pattern regexp) {
779        specialImportsRegExp = regexp;
780    }
781
782    /**
783     * Setter to force empty line separator between import groups.
784     *
785     * @param value
786     *        user value.
787     */
788    public final void setSeparateLineBetweenGroups(boolean value) {
789        separateLineBetweenGroups = value;
790    }
791
792    /**
793     * Setter to force grouping alphabetically, in
794     * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>.
795     *
796     * @param value
797     *        user value.
798     */
799    public final void setSortImportsInGroupAlphabetically(boolean value) {
800        sortImportsInGroupAlphabetically = value;
801    }
802
803    /**
804     * Setter to specify format of order declaration customizing by user.
805     *
806     * @param inputCustomImportOrder
807     *        user value.
808     */
809    public final void setCustomImportOrderRules(final String inputCustomImportOrder) {
810        if (!customImportOrderRules.equals(inputCustomImportOrder)) {
811            for (String currentState : GROUP_SEPARATOR_PATTERN.split(inputCustomImportOrder)) {
812                addRulesToList(currentState);
813            }
814            customOrderRules.add(NON_GROUP_RULE_GROUP);
815        }
816        customImportOrderRules = inputCustomImportOrder;
817    }
818
819    @Override
820    public int[] getDefaultTokens() {
821        return getRequiredTokens();
822    }
823
824    @Override
825    public int[] getAcceptableTokens() {
826        return getRequiredTokens();
827    }
828
829    @Override
830    public int[] getRequiredTokens() {
831        return new int[] {
832            TokenTypes.IMPORT,
833            TokenTypes.STATIC_IMPORT,
834            TokenTypes.PACKAGE_DEF,
835        };
836    }
837
838    @Override
839    public void beginTree(DetailAST rootAST) {
840        importToGroupList.clear();
841    }
842
843    @Override
844    public void visitToken(DetailAST ast) {
845        if (ast.getType() == TokenTypes.PACKAGE_DEF) {
846            samePackageDomainsRegExp = createSamePackageRegexp(
847                    samePackageMatchingDepth, ast);
848        }
849        else {
850            final String importFullPath = getFullImportIdent(ast);
851            final boolean isStatic = ast.getType() == TokenTypes.STATIC_IMPORT;
852            importToGroupList.add(new ImportDetails(importFullPath,
853                    getImportGroup(isStatic, importFullPath), isStatic, ast));
854        }
855    }
856
857    @Override
858    public void finishTree(DetailAST rootAST) {
859        if (!importToGroupList.isEmpty()) {
860            finishImportList();
861        }
862    }
863
864    /** Examine the order of all the imports and log any violations. */
865    private void finishImportList() {
866        String currentGroup = getFirstGroup();
867        int currentGroupNumber = customOrderRules.lastIndexOf(currentGroup);
868        ImportDetails previousImportObjectFromCurrentGroup = null;
869        String previousImportFromCurrentGroup = null;
870
871        for (ImportDetails importObject : importToGroupList) {
872            final String importGroup = importObject.getImportGroup();
873            final String fullImportIdent = importObject.getImportFullPath();
874
875            if (importGroup.equals(currentGroup)) {
876                validateExtraEmptyLine(previousImportObjectFromCurrentGroup,
877                        importObject, fullImportIdent);
878                if (isAlphabeticalOrderBroken(previousImportFromCurrentGroup, fullImportIdent)) {
879                    log(importObject.getImportAST(), MSG_LEX,
880                            fullImportIdent, previousImportFromCurrentGroup);
881                }
882                else {
883                    previousImportFromCurrentGroup = fullImportIdent;
884                }
885                previousImportObjectFromCurrentGroup = importObject;
886            }
887            else {
888                // not the last group, last one is always NON_GROUP
889                if (customOrderRules.size() > currentGroupNumber + 1) {
890                    final String nextGroup = getNextImportGroup(currentGroupNumber + 1);
891                    if (importGroup.equals(nextGroup)) {
892                        validateMissedEmptyLine(previousImportObjectFromCurrentGroup,
893                                importObject, fullImportIdent);
894                        currentGroup = nextGroup;
895                        currentGroupNumber = customOrderRules.lastIndexOf(nextGroup);
896                        previousImportFromCurrentGroup = fullImportIdent;
897                    }
898                    else {
899                        logWrongImportGroupOrder(importObject.getImportAST(),
900                                importGroup, nextGroup, fullImportIdent);
901                    }
902                    previousImportObjectFromCurrentGroup = importObject;
903                }
904                else {
905                    logWrongImportGroupOrder(importObject.getImportAST(),
906                            importGroup, currentGroup, fullImportIdent);
907                }
908            }
909        }
910    }
911
912    /**
913     * Log violation if empty line is missed.
914     *
915     * @param previousImport previous import from current group.
916     * @param importObject current import.
917     * @param fullImportIdent full import identifier.
918     */
919    private void validateMissedEmptyLine(ImportDetails previousImport,
920                                         ImportDetails importObject, String fullImportIdent) {
921        if (isEmptyLineMissed(previousImport, importObject)) {
922            log(importObject.getImportAST(), MSG_LINE_SEPARATOR, fullImportIdent);
923        }
924    }
925
926    /**
927     * Log violation if extra empty line is present.
928     *
929     * @param previousImport previous import from current group.
930     * @param importObject current import.
931     * @param fullImportIdent full import identifier.
932     */
933    private void validateExtraEmptyLine(ImportDetails previousImport,
934                                        ImportDetails importObject, String fullImportIdent) {
935        if (isSeparatedByExtraEmptyLine(previousImport, importObject)) {
936            log(importObject.getImportAST(), MSG_SEPARATED_IN_GROUP, fullImportIdent);
937        }
938    }
939
940    /**
941     * Get first import group.
942     *
943     * @return
944     *        first import group of file.
945     */
946    private String getFirstGroup() {
947        final ImportDetails firstImport = importToGroupList.get(0);
948        return getImportGroup(firstImport.isStaticImport(),
949                firstImport.getImportFullPath());
950    }
951
952    /**
953     * Examine alphabetical order of imports.
954     *
955     * @param previousImport
956     *        previous import of current group.
957     * @param currentImport
958     *        current import.
959     * @return
960     *        true, if previous and current import are not in alphabetical order.
961     */
962    private boolean isAlphabeticalOrderBroken(String previousImport,
963                                              String currentImport) {
964        return sortImportsInGroupAlphabetically
965                && previousImport != null
966                && compareImports(currentImport, previousImport) < 0;
967    }
968
969    /**
970     * Examine empty lines between groups.
971     *
972     * @param previousImportObject
973     *        previous import in current group.
974     * @param currentImportObject
975     *        current import.
976     * @return
977     *        true, if current import NOT separated from previous import by empty line.
978     */
979    private boolean isEmptyLineMissed(ImportDetails previousImportObject,
980                                      ImportDetails currentImportObject) {
981        return separateLineBetweenGroups
982                && getCountOfEmptyLinesBetween(
983                     previousImportObject.getEndLineNumber(),
984                     currentImportObject.getStartLineNumber()) != 1;
985    }
986
987    /**
988     * Examine that imports separated by more than one empty line.
989     *
990     * @param previousImportObject
991     *        previous import in current group.
992     * @param currentImportObject
993     *        current import.
994     * @return
995     *        true, if current import separated from previous by more than one empty line.
996     */
997    private boolean isSeparatedByExtraEmptyLine(ImportDetails previousImportObject,
998                                                ImportDetails currentImportObject) {
999        return previousImportObject != null
1000                && getCountOfEmptyLinesBetween(
1001                     previousImportObject.getEndLineNumber(),
1002                     currentImportObject.getStartLineNumber()) > 0;
1003    }
1004
1005    /**
1006     * Log wrong import group order.
1007     *
1008     * @param importAST
1009     *        import ast.
1010     * @param importGroup
1011     *        import group.
1012     * @param currentGroupNumber
1013     *        current group number we are checking.
1014     * @param fullImportIdent
1015     *        full import name.
1016     */
1017    private void logWrongImportGroupOrder(DetailAST importAST, String importGroup,
1018            String currentGroupNumber, String fullImportIdent) {
1019        if (NON_GROUP_RULE_GROUP.equals(importGroup)) {
1020            log(importAST, MSG_NONGROUP_IMPORT, fullImportIdent);
1021        }
1022        else if (NON_GROUP_RULE_GROUP.equals(currentGroupNumber)) {
1023            log(importAST, MSG_NONGROUP_EXPECTED, importGroup, fullImportIdent);
1024        }
1025        else {
1026            log(importAST, MSG_ORDER, importGroup, currentGroupNumber, fullImportIdent);
1027        }
1028    }
1029
1030    /**
1031     * Get next import group.
1032     *
1033     * @param currentGroupNumber
1034     *        current group number.
1035     * @return
1036     *        next import group.
1037     */
1038    private String getNextImportGroup(int currentGroupNumber) {
1039        int nextGroupNumber = currentGroupNumber;
1040
1041        while (customOrderRules.size() > nextGroupNumber + 1) {
1042            if (hasAnyImportInCurrentGroup(customOrderRules.get(nextGroupNumber))) {
1043                break;
1044            }
1045            nextGroupNumber++;
1046        }
1047        return customOrderRules.get(nextGroupNumber);
1048    }
1049
1050    /**
1051     * Checks if current group contains any import.
1052     *
1053     * @param currentGroup
1054     *        current group.
1055     * @return
1056     *        true, if current group contains at least one import.
1057     */
1058    private boolean hasAnyImportInCurrentGroup(String currentGroup) {
1059        boolean result = false;
1060        for (ImportDetails currentImport : importToGroupList) {
1061            if (currentGroup.equals(currentImport.getImportGroup())) {
1062                result = true;
1063                break;
1064            }
1065        }
1066        return result;
1067    }
1068
1069    /**
1070     * Get import valid group.
1071     *
1072     * @param isStatic
1073     *        is static import.
1074     * @param importPath
1075     *        full import path.
1076     * @return import valid group.
1077     */
1078    private String getImportGroup(boolean isStatic, String importPath) {
1079        RuleMatchForImport bestMatch = new RuleMatchForImport(NON_GROUP_RULE_GROUP, 0, 0);
1080        if (isStatic && customOrderRules.contains(STATIC_RULE_GROUP)) {
1081            bestMatch.group = STATIC_RULE_GROUP;
1082            bestMatch.matchLength = importPath.length();
1083        }
1084        else if (customOrderRules.contains(SAME_PACKAGE_RULE_GROUP)) {
1085            final String importPathTrimmedToSamePackageDepth =
1086                    getFirstDomainsFromIdent(samePackageMatchingDepth, importPath);
1087            if (samePackageDomainsRegExp.equals(importPathTrimmedToSamePackageDepth)) {
1088                bestMatch.group = SAME_PACKAGE_RULE_GROUP;
1089                bestMatch.matchLength = importPath.length();
1090            }
1091        }
1092        for (String group : customOrderRules) {
1093            if (STANDARD_JAVA_PACKAGE_RULE_GROUP.equals(group)) {
1094                bestMatch = findBetterPatternMatch(importPath,
1095                        STANDARD_JAVA_PACKAGE_RULE_GROUP, standardPackageRegExp, bestMatch);
1096            }
1097            if (SPECIAL_IMPORTS_RULE_GROUP.equals(group)) {
1098                bestMatch = findBetterPatternMatch(importPath,
1099                        group, specialImportsRegExp, bestMatch);
1100            }
1101        }
1102
1103        if (NON_GROUP_RULE_GROUP.equals(bestMatch.group)
1104                && customOrderRules.contains(THIRD_PARTY_PACKAGE_RULE_GROUP)
1105                && thirdPartyPackageRegExp.matcher(importPath).find()) {
1106            bestMatch.group = THIRD_PARTY_PACKAGE_RULE_GROUP;
1107        }
1108        return bestMatch.group;
1109    }
1110
1111    /**
1112     * Tries to find better matching regular expression:
1113     * longer matching substring wins; in case of the same length,
1114     * lower position of matching substring wins.
1115     *
1116     * @param importPath
1117     *      Full import identifier
1118     * @param group
1119     *      Import group we are trying to assign the import
1120     * @param regExp
1121     *      Regular expression for import group
1122     * @param currentBestMatch
1123     *      object with currently best match
1124     * @return better match (if found) or the same (currentBestMatch)
1125     */
1126    private static RuleMatchForImport findBetterPatternMatch(String importPath, String group,
1127            Pattern regExp, RuleMatchForImport currentBestMatch) {
1128        RuleMatchForImport betterMatchCandidate = currentBestMatch;
1129        final Matcher matcher = regExp.matcher(importPath);
1130        while (matcher.find()) {
1131            final int matchStart = matcher.start();
1132            final int length = matcher.end() - matchStart;
1133            if (length > betterMatchCandidate.matchLength
1134                    || length == betterMatchCandidate.matchLength
1135                        && matchStart < betterMatchCandidate.matchPosition) {
1136                betterMatchCandidate = new RuleMatchForImport(group, length, matchStart);
1137            }
1138        }
1139        return betterMatchCandidate;
1140    }
1141
1142    /**
1143     * Checks compare two import paths.
1144     *
1145     * @param import1
1146     *        current import.
1147     * @param import2
1148     *        previous import.
1149     * @return a negative integer, zero, or a positive integer as the
1150     *        specified String is greater than, equal to, or less
1151     *        than this String, ignoring case considerations.
1152     */
1153    private static int compareImports(String import1, String import2) {
1154        int result = 0;
1155        final String separator = "\\.";
1156        final String[] import1Tokens = import1.split(separator);
1157        final String[] import2Tokens = import2.split(separator);
1158        for (int i = 0; i != import1Tokens.length && i != import2Tokens.length; i++) {
1159            final String import1Token = import1Tokens[i];
1160            final String import2Token = import2Tokens[i];
1161            result = import1Token.compareTo(import2Token);
1162            if (result != 0) {
1163                break;
1164            }
1165        }
1166        if (result == 0) {
1167            result = Integer.compare(import1Tokens.length, import2Tokens.length);
1168        }
1169        return result;
1170    }
1171
1172    /**
1173     * Counts empty lines between given parameters.
1174     *
1175     * @param fromLineNo
1176     *        One-based line number of previous import.
1177     * @param toLineNo
1178     *        One-based line number of current import.
1179     * @return count of empty lines between given parameters, exclusive,
1180     *        eg., (fromLineNo, toLineNo).
1181     */
1182    private int getCountOfEmptyLinesBetween(int fromLineNo, int toLineNo) {
1183        int result = 0;
1184        final String[] lines = getLines();
1185
1186        for (int i = fromLineNo + 1; i <= toLineNo - 1; i++) {
1187            // "- 1" because the numbering is one-based
1188            if (CommonUtil.isBlank(lines[i - 1])) {
1189                result++;
1190            }
1191        }
1192        return result;
1193    }
1194
1195    /**
1196     * Forms import full path.
1197     *
1198     * @param token
1199     *        current token.
1200     * @return full path or null.
1201     */
1202    private static String getFullImportIdent(DetailAST token) {
1203        String ident = "";
1204        if (token != null) {
1205            ident = FullIdent.createFullIdent(token.findFirstToken(TokenTypes.DOT)).getText();
1206        }
1207        return ident;
1208    }
1209
1210    /**
1211     * Parses ordering rule and adds it to the list with rules.
1212     *
1213     * @param ruleStr
1214     *        String with rule.
1215     * @throws IllegalArgumentException when SAME_PACKAGE rule parameter is not positive integer
1216     * @throws IllegalStateException when ruleStr is unexpected value
1217     */
1218    private void addRulesToList(String ruleStr) {
1219        if (STATIC_RULE_GROUP.equals(ruleStr)
1220                || THIRD_PARTY_PACKAGE_RULE_GROUP.equals(ruleStr)
1221                || STANDARD_JAVA_PACKAGE_RULE_GROUP.equals(ruleStr)
1222                || SPECIAL_IMPORTS_RULE_GROUP.equals(ruleStr)) {
1223            customOrderRules.add(ruleStr);
1224        }
1225        else if (ruleStr.startsWith(SAME_PACKAGE_RULE_GROUP)) {
1226            final String rule = ruleStr.substring(ruleStr.indexOf('(') + 1,
1227                    ruleStr.indexOf(')'));
1228            samePackageMatchingDepth = Integer.parseInt(rule);
1229            if (samePackageMatchingDepth <= 0) {
1230                throw new IllegalArgumentException(
1231                        "SAME_PACKAGE rule parameter should be positive integer: " + ruleStr);
1232            }
1233            customOrderRules.add(SAME_PACKAGE_RULE_GROUP);
1234        }
1235        else {
1236            throw new IllegalStateException("Unexpected rule: " + ruleStr);
1237        }
1238    }
1239
1240    /**
1241     * Creates samePackageDomainsRegExp of the first package domains.
1242     *
1243     * @param firstPackageDomainsCount
1244     *        number of first package domains.
1245     * @param packageNode
1246     *        package node.
1247     * @return same package regexp.
1248     */
1249    private static String createSamePackageRegexp(int firstPackageDomainsCount,
1250             DetailAST packageNode) {
1251        final String packageFullPath = getFullImportIdent(packageNode);
1252        return getFirstDomainsFromIdent(firstPackageDomainsCount, packageFullPath);
1253    }
1254
1255    /**
1256     * Extracts defined amount of domains from the left side of package/import identifier.
1257     *
1258     * @param firstPackageDomainsCount
1259     *        number of first package domains.
1260     * @param packageFullPath
1261     *        full identifier containing path to package or imported object.
1262     * @return String with defined amount of domains or full identifier
1263     *        (if full identifier had less domain than specified)
1264     */
1265    private static String getFirstDomainsFromIdent(
1266            final int firstPackageDomainsCount, final String packageFullPath) {
1267        final StringBuilder builder = new StringBuilder(256);
1268        final StringTokenizer tokens = new StringTokenizer(packageFullPath, ".");
1269        int count = firstPackageDomainsCount;
1270
1271        while (count > 0 && tokens.hasMoreTokens()) {
1272            builder.append(tokens.nextToken()).append('.');
1273            count--;
1274        }
1275        return builder.toString();
1276    }
1277
1278    /**
1279     * Contains import attributes as line number, import full path, import
1280     * group.
1281     */
1282    private static final class ImportDetails {
1283
1284        /** Import full path. */
1285        private final String importFullPath;
1286
1287        /** Import group. */
1288        private final String importGroup;
1289
1290        /** Is static import. */
1291        private final boolean staticImport;
1292
1293        /** Import AST. */
1294        private final DetailAST importAST;
1295
1296        /**
1297         * Initialise importFullPath, importGroup, staticImport, importAST.
1298         *
1299         * @param importFullPath
1300         *        import full path.
1301         * @param importGroup
1302         *        import group.
1303         * @param staticImport
1304         *        if import is static.
1305         * @param importAST
1306         *        import ast
1307         */
1308        private ImportDetails(String importFullPath, String importGroup, boolean staticImport,
1309                                    DetailAST importAST) {
1310            this.importFullPath = importFullPath;
1311            this.importGroup = importGroup;
1312            this.staticImport = staticImport;
1313            this.importAST = importAST;
1314        }
1315
1316        /**
1317         * Get import full path variable.
1318         *
1319         * @return import full path variable.
1320         */
1321        public String getImportFullPath() {
1322            return importFullPath;
1323        }
1324
1325        /**
1326         * Get import start line number from ast.
1327         *
1328         * @return import start line from ast.
1329         */
1330        public int getStartLineNumber() {
1331            return importAST.getLineNo();
1332        }
1333
1334        /**
1335         * Get import end line number from ast.
1336         * <p>
1337         * <b>Note:</b> It can be different from <b>startLineNumber</b> when import statement span
1338         * multiple lines.
1339         * </p>
1340         *
1341         * @return import end line from ast.
1342         */
1343        public int getEndLineNumber() {
1344            return importAST.getLastChild().getLineNo();
1345        }
1346
1347        /**
1348         * Get import group.
1349         *
1350         * @return import group.
1351         */
1352        public String getImportGroup() {
1353            return importGroup;
1354        }
1355
1356        /**
1357         * Checks if import is static.
1358         *
1359         * @return true, if import is static.
1360         */
1361        public boolean isStaticImport() {
1362            return staticImport;
1363        }
1364
1365        /**
1366         * Get import ast.
1367         *
1368         * @return import ast.
1369         */
1370        public DetailAST getImportAST() {
1371            return importAST;
1372        }
1373
1374    }
1375
1376    /**
1377     * Contains matching attributes assisting in definition of "best matching"
1378     * group for import.
1379     */
1380    private static final class RuleMatchForImport {
1381
1382        /** Position of matching string for current best match. */
1383        private final int matchPosition;
1384        /** Length of matching string for current best match. */
1385        private int matchLength;
1386        /** Import group for current best match. */
1387        private String group;
1388
1389        /**
1390         * Constructor to initialize the fields.
1391         *
1392         * @param group
1393         *        Matched group.
1394         * @param length
1395         *        Matching length.
1396         * @param position
1397         *        Matching position.
1398         */
1399        private RuleMatchForImport(String group, int length, int position) {
1400            this.group = group;
1401            matchLength = length;
1402            matchPosition = position;
1403        }
1404
1405    }
1406
1407}