001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.CommonUtils;
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 * The rule consists of:
042 *
043 * <p>
044 * 1. STATIC group. This group sets the ordering of static imports.
045 * </p>
046 *
047 * <p>
048 * 2. SAME_PACKAGE(n) group. This group sets the ordering of the same package imports.
049 * Imports are considered on SAME_PACKAGE group if <b>n</b> first domains in package name
050 * and import name are identical.
051 * </p>
052 *
053 * <pre>
054 *{@code
055 *package java.util.concurrent.locks;
056 *
057 *import java.io.File;
058 *import java.util.*; //#1
059 *import java.util.List; //#2
060 *import java.util.StringTokenizer; //#3
061 *import java.util.concurrent.*; //#4
062 *import java.util.concurrent.AbstractExecutorService; //#5
063 *import java.util.concurrent.locks.LockSupport; //#6
064 *import java.util.regex.Pattern; //#7
065 *import java.util.regex.Matcher; //#8
066 *}
067 * </pre>
068 *
069 * <p>
070 * If we have SAME_PACKAGE(3) on configuration file,
071 * imports #4-6 will be considered as a SAME_PACKAGE group (java.util.concurrent.*,
072 * java.util.concurrent.AbstractExecutorService, java.util.concurrent.locks.LockSupport).
073 * SAME_PACKAGE(2) will include #1-8. SAME_PACKAGE(4) will include only #6.
074 * SAME_PACKAGE(5) will result in no imports assigned to SAME_PACKAGE group because
075 * actual package java.util.concurrent.locks has only 4 domains.
076 * </p>
077 *
078 * <p>
079 * 3. THIRD_PARTY_PACKAGE group. This group sets ordering of third party imports.
080 * Third party imports are all imports except STATIC,
081 * SAME_PACKAGE(n), STANDARD_JAVA_PACKAGE and SPECIAL_IMPORTS.
082 * </p>
083 *
084 * <p>
085 * 4. STANDARD_JAVA_PACKAGE group. By default this group sets ordering of standard java/javax
086 * imports.
087 * </p>
088 *
089 * <p>
090 * 5. SPECIAL_IMPORTS group. This group may contains some imports
091 * that have particular meaning for the user.
092 * </p>
093 *
094 * <p>
095 * NOTE!
096 * </p>
097 * <p>
098 * Use the separator '###' between rules.
099 * </p>
100 * <p>
101 * To set RegExps for THIRD_PARTY_PACKAGE and STANDARD_JAVA_PACKAGE groups use
102 * thirdPartyPackageRegExp and standardPackageRegExp options.
103 * </p>
104 * <p>
105 * Pretty often one import can match more than one group. For example, static import from standard
106 * package or regular expressions are configured to allow one import match multiple groups.
107 * In this case, group will be assigned according to priorities:
108 * </p>
109 * <ol>
110 * <li>
111 *    STATIC has top priority
112 * </li>
113 * <li>
114 *    SAME_PACKAGE has second priority
115 * </li>
116 * <li>
117 *    STANDARD_JAVA_PACKAGE and SPECIAL_IMPORTS will compete using "best match" rule: longer
118 *    matching substring wins; in case of the same length, lower position of matching substring
119 *    wins; if position is the same, order of rules in configuration solves the puzzle.
120 * </li>
121 * <li>
122 *    THIRD_PARTY has the least priority
123 * </li>
124 * </ol>
125 * <p>
126 *    Few examples to illustrate "best match":
127 * </p>
128 * <p>
129 *    1. patterns STANDARD_JAVA_PACKAGE = "Check", SPECIAL_IMPORTS="ImportOrderCheck" and input
130 *    file:
131 * </p>
132 * <pre>
133 *{@code
134 *import com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck;
135 *import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck;}
136 * </pre>
137 * <p>
138 *    Result: imports will be assigned to SPECIAL_IMPORTS, because matching substring length is 16.
139 *    Matching substring for STANDARD_JAVA_PACKAGE is 5.
140 * </p>
141 * <p>
142 *    2. patterns STANDARD_JAVA_PACKAGE = "Check", SPECIAL_IMPORTS="Avoid" and file:
143 * </p>
144 * <pre>
145 *{@code
146 *import com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck;}
147 * </pre>
148 * <p>
149 *   Result: import will be assigned to SPECIAL_IMPORTS. Matching substring length is 5 for both
150 *   patterns. However, "Avoid" position is lower then "Check" position.
151 * </p>
152 *
153 * <pre>
154 *    Properties:
155 * </pre>
156 * <table summary="Properties" border="1">
157 *     <tr><th>name</th><th>Description</th><th>type</th><th>default value</th></tr>
158 *      <tr><td>customImportOrderRules</td><td>List of order declaration customizing by user.</td>
159 *          <td>string</td><td>null</td></tr>
160 *      <tr><td>standardPackageRegExp</td><td>RegExp for STANDARD_JAVA_PACKAGE group imports.</td>
161 *          <td>regular expression</td><td>^(java|javax)\.</td></tr>
162 *      <tr><td>thirdPartyPackageRegExp</td><td>RegExp for THIRD_PARTY_PACKAGE group imports.</td>
163 *          <td>regular expression</td><td>.*</td></tr>
164 *      <tr><td>specialImportsRegExp</td><td>RegExp for SPECIAL_IMPORTS group imports.</td>
165 *          <td>regular expression</td><td>^$</td></tr>
166 *      <tr><td>separateLineBetweenGroups</td><td>Force empty line separator between import groups.
167 *          </td><td>boolean</td><td>true</td></tr>
168 *      <tr><td>sortImportsInGroupAlphabetically</td><td>Force grouping alphabetically,
169 *          in ASCII sort order.</td><td>boolean</td><td>false</td></tr>
170 * </table>
171 *
172 * <p>
173 * For example:
174 * </p>
175 *        <p>To configure the check so that it matches default Eclipse formatter configuration
176 *        (tested on Kepler, Luna and Mars):</p>
177 *        <ul>
178 *          <li>group of static imports is on the top</li>
179 *          <li>groups of non-static imports: &quot;java&quot; and &quot;javax&quot; packages
180 *          first, then &quot;org&quot; and then all other imports</li>
181 *          <li>imports will be sorted in the groups</li>
182 *          <li>groups are separated by, at least, one blank line</li>
183 *        </ul>
184 * <pre>
185 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
186 *    &lt;property name=&quot;customImportOrderRules&quot;
187 *        value=&quot;STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS&quot;/&gt;
188 *    &lt;property name=&quot;specialImportsRegExp&quot; value=&quot;org&quot;/&gt;
189 *    &lt;property name=&quot;sortImportsInGroupAlphabetically&quot; value=&quot;true&quot;/&gt;
190 *    &lt;property name=&quot;separateLineBetweenGroups&quot; value=&quot;true&quot;/&gt;
191 * &lt;/module&gt;
192 * </pre>
193 *
194 *        <p>To configure the check so that it matches default IntelliJ IDEA formatter
195 *        configuration (tested on v14):</p>
196 *        <ul>
197 *          <li>group of static imports is on the bottom</li>
198 *          <li>groups of non-static imports: all imports except of &quot;javax&quot;
199 *          and &quot;java&quot;, then &quot;javax&quot; and &quot;java&quot;</li>
200 *          <li>imports will be sorted in the groups</li>
201 *          <li>groups are separated by, at least, one blank line</li>
202 *        </ul>
203 *
204 *        <p>
205 *        Note: &quot;separated&quot; option is disabled because IDEA default has blank line
206 *        between &quot;java&quot; and static imports, and no blank line between
207 *        &quot;javax&quot; and &quot;java&quot;
208 *        </p>
209 *
210 * <pre>
211 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
212 *    &lt;property name=&quot;customImportOrderRules&quot;
213 *        value=&quot;THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE
214 *        ###STATIC&quot;/&gt;
215 *    &lt;property name=&quot;specialImportsRegExp&quot; value=&quot;^javax\.&quot;/&gt;
216 *    &lt;property name=&quot;standardPackageRegExp&quot; value=&quot;^java\.&quot;/&gt;
217 *    &lt;property name=&quot;sortImportsInGroupAlphabetically&quot; value=&quot;true&quot;/&gt;
218 *    &lt;property name=&quot;separateLineBetweenGroups&quot; value=&quot;false&quot;/&gt;
219 *&lt;/module&gt;
220 * </pre>
221 *
222 * <p>To configure the check so that it matches default NetBeans formatter
223 *    configuration (tested on v8):</p>
224 * <ul>
225 *     <li>groups of non-static imports are not defined, all imports will be sorted as a one
226 *         group</li>
227 *     <li>static imports are not separated, they will be sorted along with other imports</li>
228 * </ul>
229 *
230 * <pre>
231 *&lt;module name=&quot;CustomImportOrder&quot;/&gt;
232 * </pre>
233 * <p>To set RegExps for THIRD_PARTY_PACKAGE and STANDARD_JAVA_PACKAGE groups use
234 *         thirdPartyPackageRegExp and standardPackageRegExp options.</p>
235 * <pre>
236 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
237 *    &lt;property name=&quot;customImportOrderRules&quot;
238 *    value=&quot;STATIC###SAME_PACKAGE(3)###THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE&quot;/&gt;
239 *    &lt;property name=&quot;thirdPartyPackageRegExp&quot; value=&quot;com|org&quot;/&gt;
240 *    &lt;property name=&quot;standardPackageRegExp&quot; value=&quot;^(java|javax)\.&quot;/&gt;
241 * &lt;/module&gt;
242 * </pre>
243 * <p>
244 * Also, this check can be configured to force empty line separator between
245 * import groups. For example
246 * </p>
247 *
248 * <pre>
249 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
250 *    &lt;property name=&quot;separateLineBetweenGroups&quot; value=&quot;true&quot;/&gt;
251 * &lt;/module&gt;
252 * </pre>
253 * <p>
254 * It is possible to enforce
255 * <a href="https://en.wikipedia.org/wiki/ASCII#Order">ASCII sort order</a>
256 * of imports in groups using the following configuration:
257 * </p>
258 * <pre>
259 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
260 *    &lt;property name=&quot;sortImportsInGroupAlphabetically&quot; value=&quot;true&quot;/&gt;
261 * &lt;/module&gt;
262 * </pre>
263 * <p>
264 * Example of ASCII order:
265 * </p>
266 * <pre>
267 * {@code
268 *import java.awt.Dialog;
269 *import java.awt.Window;
270 *import java.awt.color.ColorSpace;
271 *import java.awt.Frame; // violation here - in ASCII order 'F' should go before 'c',
272 *                       // as all uppercase come before lowercase letters}
273 * </pre>
274 * <p>
275 * To force checking imports sequence such as:
276 * </p>
277 *
278 * <pre>
279 * {@code
280 * package com.puppycrawl.tools.checkstyle.imports;
281 *
282 * import com.google.common.annotations.GwtCompatible;
283 * import com.google.common.annotations.Beta;
284 * import com.google.common.annotations.VisibleForTesting;
285 *
286 * import org.abego.treelayout.Configuration;
287 *
288 * import static sun.tools.util.ModifierFilter.ALL_ACCESS;
289 *
290 * import com.google.common.annotations.GwtCompatible; // violation here - should be in the
291 *                                                     // THIRD_PARTY_PACKAGE group
292 * import android.*;}
293 * </pre>
294 * configure as follows:
295 * <pre>
296 * &lt;module name=&quot;CustomImportOrder&quot;&gt;
297 *    &lt;property name=&quot;customImportOrderRules&quot;
298 *    value=&quot;SAME_PACKAGE(3)###THIRD_PARTY_PACKAGE###STATIC###SPECIAL_IMPORTS&quot;/&gt;
299 *    &lt;property name=&quot;specialImportsRegExp&quot; value=&quot;android.*&quot;/&gt;
300 * &lt;/module&gt;
301 * </pre>
302 *
303 * @author maxvetrenko
304 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
305 */
306@FileStatefulCheck
307public class CustomImportOrderCheck extends AbstractCheck {
308
309    /**
310     * A key is pointing to the warning message text in "messages.properties"
311     * file.
312     */
313    public static final String MSG_LINE_SEPARATOR = "custom.import.order.line.separator";
314
315    /**
316     * A key is pointing to the warning message text in "messages.properties"
317     * file.
318     */
319    public static final String MSG_LEX = "custom.import.order.lex";
320
321    /**
322     * A key is pointing to the warning message text in "messages.properties"
323     * file.
324     */
325    public static final String MSG_NONGROUP_IMPORT = "custom.import.order.nonGroup.import";
326
327    /**
328     * A key is pointing to the warning message text in "messages.properties"
329     * file.
330     */
331    public static final String MSG_NONGROUP_EXPECTED = "custom.import.order.nonGroup.expected";
332
333    /**
334     * A key is pointing to the warning message text in "messages.properties"
335     * file.
336     */
337    public static final String MSG_ORDER = "custom.import.order";
338
339    /** STATIC group name. */
340    public static final String STATIC_RULE_GROUP = "STATIC";
341
342    /** SAME_PACKAGE group name. */
343    public static final String SAME_PACKAGE_RULE_GROUP = "SAME_PACKAGE";
344
345    /** THIRD_PARTY_PACKAGE group name. */
346    public static final String THIRD_PARTY_PACKAGE_RULE_GROUP = "THIRD_PARTY_PACKAGE";
347
348    /** STANDARD_JAVA_PACKAGE group name. */
349    public static final String STANDARD_JAVA_PACKAGE_RULE_GROUP = "STANDARD_JAVA_PACKAGE";
350
351    /** SPECIAL_IMPORTS group name. */
352    public static final String SPECIAL_IMPORTS_RULE_GROUP = "SPECIAL_IMPORTS";
353
354    /** NON_GROUP group name. */
355    private static final String NON_GROUP_RULE_GROUP = "NOT_ASSIGNED_TO_ANY_GROUP";
356
357    /** Pattern used to separate groups of imports. */
358    private static final Pattern GROUP_SEPARATOR_PATTERN = Pattern.compile("\\s*###\\s*");
359
360    /** List of order declaration customizing by user. */
361    private final List<String> customImportOrderRules = new ArrayList<>();
362
363    /** Contains objects with import attributes. */
364    private final List<ImportDetails> importToGroupList = new ArrayList<>();
365
366    /** RegExp for SAME_PACKAGE group imports. */
367    private String samePackageDomainsRegExp = "";
368
369    /** RegExp for STANDARD_JAVA_PACKAGE group imports. */
370    private Pattern standardPackageRegExp = Pattern.compile("^(java|javax)\\.");
371
372    /** RegExp for THIRD_PARTY_PACKAGE group imports. */
373    private Pattern thirdPartyPackageRegExp = Pattern.compile(".*");
374
375    /** RegExp for SPECIAL_IMPORTS group imports. */
376    private Pattern specialImportsRegExp = Pattern.compile("^$");
377
378    /** Force empty line separator between import groups. */
379    private boolean separateLineBetweenGroups = true;
380
381    /** Force grouping alphabetically, in ASCII order. */
382    private boolean sortImportsInGroupAlphabetically;
383
384    /** Number of first domains for SAME_PACKAGE group. */
385    private int samePackageMatchingDepth = 2;
386
387    /**
388     * Sets standardRegExp specified by user.
389     * @param regexp
390     *        user value.
391     */
392    public final void setStandardPackageRegExp(Pattern regexp) {
393        standardPackageRegExp = regexp;
394    }
395
396    /**
397     * Sets thirdPartyRegExp specified by user.
398     * @param regexp
399     *        user value.
400     */
401    public final void setThirdPartyPackageRegExp(Pattern regexp) {
402        thirdPartyPackageRegExp = regexp;
403    }
404
405    /**
406     * Sets specialImportsRegExp specified by user.
407     * @param regexp
408     *        user value.
409     */
410    public final void setSpecialImportsRegExp(Pattern regexp) {
411        specialImportsRegExp = regexp;
412    }
413
414    /**
415     * Sets separateLineBetweenGroups specified by user.
416     * @param value
417     *        user value.
418     */
419    public final void setSeparateLineBetweenGroups(boolean value) {
420        separateLineBetweenGroups = value;
421    }
422
423    /**
424     * Sets sortImportsInGroupAlphabetically specified by user.
425     * @param value
426     *        user value.
427     */
428    public final void setSortImportsInGroupAlphabetically(boolean value) {
429        sortImportsInGroupAlphabetically = value;
430    }
431
432    /**
433     * Sets a custom import order from the rules in the string format specified
434     * by user.
435     * @param inputCustomImportOrder
436     *        user value.
437     */
438    public final void setCustomImportOrderRules(final String inputCustomImportOrder) {
439        customImportOrderRules.clear();
440        for (String currentState : GROUP_SEPARATOR_PATTERN.split(inputCustomImportOrder)) {
441            addRulesToList(currentState);
442        }
443        customImportOrderRules.add(NON_GROUP_RULE_GROUP);
444    }
445
446    @Override
447    public int[] getDefaultTokens() {
448        return getRequiredTokens();
449    }
450
451    @Override
452    public int[] getAcceptableTokens() {
453        return getRequiredTokens();
454    }
455
456    @Override
457    public int[] getRequiredTokens() {
458        return new int[] {
459            TokenTypes.IMPORT,
460            TokenTypes.STATIC_IMPORT,
461            TokenTypes.PACKAGE_DEF,
462        };
463    }
464
465    @Override
466    public void beginTree(DetailAST rootAST) {
467        importToGroupList.clear();
468    }
469
470    @Override
471    public void visitToken(DetailAST ast) {
472        if (ast.getType() == TokenTypes.PACKAGE_DEF) {
473            if (customImportOrderRules.contains(SAME_PACKAGE_RULE_GROUP)) {
474                samePackageDomainsRegExp = createSamePackageRegexp(
475                        samePackageMatchingDepth, ast);
476            }
477        }
478        else {
479            final String importFullPath = getFullImportIdent(ast);
480            final int lineNo = ast.getLineNo();
481            final boolean isStatic = ast.getType() == TokenTypes.STATIC_IMPORT;
482            importToGroupList.add(new ImportDetails(importFullPath,
483                    lineNo, getImportGroup(isStatic, importFullPath),
484                    isStatic));
485        }
486    }
487
488    @Override
489    public void finishTree(DetailAST rootAST) {
490
491        if (!importToGroupList.isEmpty()) {
492            finishImportList();
493        }
494    }
495
496    /** Examine the order of all the imports and log any violations. */
497    private void finishImportList() {
498        final ImportDetails firstImport = importToGroupList.get(0);
499        String currentGroup = getImportGroup(firstImport.isStaticImport(),
500                firstImport.getImportFullPath());
501        int currentGroupNumber = customImportOrderRules.indexOf(currentGroup);
502        String previousImportFromCurrentGroup = null;
503
504        for (ImportDetails importObject : importToGroupList) {
505            final String importGroup = importObject.getImportGroup();
506            final String fullImportIdent = importObject.getImportFullPath();
507
508            if (getCountOfEmptyLinesBefore(importObject.getLineNumber()) > 1) {
509                log(importObject.getLineNumber(), MSG_LINE_SEPARATOR, fullImportIdent);
510            }
511            if (importGroup.equals(currentGroup)) {
512                if (sortImportsInGroupAlphabetically
513                        && previousImportFromCurrentGroup != null
514                        && compareImports(fullImportIdent, previousImportFromCurrentGroup) < 0) {
515                    log(importObject.getLineNumber(), MSG_LEX,
516                            fullImportIdent, previousImportFromCurrentGroup);
517                }
518                else {
519                    previousImportFromCurrentGroup = fullImportIdent;
520                }
521            }
522            else {
523                //not the last group, last one is always NON_GROUP
524                if (customImportOrderRules.size() > currentGroupNumber + 1) {
525                    final String nextGroup = getNextImportGroup(currentGroupNumber + 1);
526                    if (importGroup.equals(nextGroup)) {
527                        if (separateLineBetweenGroups
528                                && getCountOfEmptyLinesBefore(importObject.getLineNumber()) == 0) {
529                            log(importObject.getLineNumber(), MSG_LINE_SEPARATOR, fullImportIdent);
530                        }
531                        currentGroup = nextGroup;
532                        currentGroupNumber = customImportOrderRules.indexOf(nextGroup);
533                        previousImportFromCurrentGroup = fullImportIdent;
534                    }
535                    else {
536                        logWrongImportGroupOrder(importObject.getLineNumber(),
537                                importGroup, nextGroup, fullImportIdent);
538                    }
539                }
540                else {
541                    logWrongImportGroupOrder(importObject.getLineNumber(),
542                            importGroup, currentGroup, fullImportIdent);
543                }
544            }
545        }
546    }
547
548    /**
549     * Log wrong import group order.
550     * @param currentImportLine
551     *        line number of current import current import.
552     * @param importGroup
553     *        import group.
554     * @param currentGroupNumber
555     *        current group number we are checking.
556     * @param fullImportIdent
557     *        full import name.
558     */
559    private void logWrongImportGroupOrder(int currentImportLine, String importGroup,
560            String currentGroupNumber, String fullImportIdent) {
561        if (NON_GROUP_RULE_GROUP.equals(importGroup)) {
562            log(currentImportLine, MSG_NONGROUP_IMPORT, fullImportIdent);
563        }
564        else if (NON_GROUP_RULE_GROUP.equals(currentGroupNumber)) {
565            log(currentImportLine, MSG_NONGROUP_EXPECTED, importGroup, fullImportIdent);
566        }
567        else {
568            log(currentImportLine, MSG_ORDER, importGroup, currentGroupNumber, fullImportIdent);
569        }
570    }
571
572    /**
573     * Get next import group.
574     * @param currentGroupNumber
575     *        current group number.
576     * @return
577     *        next import group.
578     */
579    private String getNextImportGroup(int currentGroupNumber) {
580        int nextGroupNumber = currentGroupNumber;
581
582        while (customImportOrderRules.size() > nextGroupNumber + 1) {
583            if (hasAnyImportInCurrentGroup(customImportOrderRules.get(nextGroupNumber))) {
584                break;
585            }
586            nextGroupNumber++;
587        }
588        return customImportOrderRules.get(nextGroupNumber);
589    }
590
591    /**
592     * Checks if current group contains any import.
593     * @param currentGroup
594     *        current group.
595     * @return
596     *        true, if current group contains at least one import.
597     */
598    private boolean hasAnyImportInCurrentGroup(String currentGroup) {
599        boolean result = false;
600        for (ImportDetails currentImport : importToGroupList) {
601            if (currentGroup.equals(currentImport.getImportGroup())) {
602                result = true;
603                break;
604            }
605        }
606        return result;
607    }
608
609    /**
610     * Get import valid group.
611     * @param isStatic
612     *        is static import.
613     * @param importPath
614     *        full import path.
615     * @return import valid group.
616     */
617    private String getImportGroup(boolean isStatic, String importPath) {
618        RuleMatchForImport bestMatch = new RuleMatchForImport(NON_GROUP_RULE_GROUP, 0, 0);
619        if (isStatic && customImportOrderRules.contains(STATIC_RULE_GROUP)) {
620            bestMatch.group = STATIC_RULE_GROUP;
621            bestMatch.matchLength = importPath.length();
622        }
623        else if (customImportOrderRules.contains(SAME_PACKAGE_RULE_GROUP)) {
624            final String importPathTrimmedToSamePackageDepth =
625                    getFirstDomainsFromIdent(samePackageMatchingDepth, importPath);
626            if (samePackageDomainsRegExp.equals(importPathTrimmedToSamePackageDepth)) {
627                bestMatch.group = SAME_PACKAGE_RULE_GROUP;
628                bestMatch.matchLength = importPath.length();
629            }
630        }
631        if (bestMatch.group.equals(NON_GROUP_RULE_GROUP)) {
632            for (String group : customImportOrderRules) {
633                if (STANDARD_JAVA_PACKAGE_RULE_GROUP.equals(group)) {
634                    bestMatch = findBetterPatternMatch(importPath,
635                            STANDARD_JAVA_PACKAGE_RULE_GROUP, standardPackageRegExp, bestMatch);
636                }
637                if (SPECIAL_IMPORTS_RULE_GROUP.equals(group)) {
638                    bestMatch = findBetterPatternMatch(importPath,
639                            SPECIAL_IMPORTS_RULE_GROUP, specialImportsRegExp, bestMatch);
640                }
641            }
642        }
643        if (bestMatch.group.equals(NON_GROUP_RULE_GROUP)
644                && customImportOrderRules.contains(THIRD_PARTY_PACKAGE_RULE_GROUP)
645                && thirdPartyPackageRegExp.matcher(importPath).find()) {
646            bestMatch.group = THIRD_PARTY_PACKAGE_RULE_GROUP;
647        }
648        return bestMatch.group;
649    }
650
651    /** Tries to find better matching regular expression:
652     * longer matching substring wins; in case of the same length,
653     * lower position of matching substring wins.
654     * @param importPath
655     *      Full import identifier
656     * @param group
657     *      Import group we are trying to assign the import
658     * @param regExp
659     *      Regular expression for import group
660     * @param currentBestMatch
661     *      object with currently best match
662     * @return better match (if found) or the same (currentBestMatch)
663     */
664    private static RuleMatchForImport findBetterPatternMatch(String importPath, String group,
665            Pattern regExp, RuleMatchForImport currentBestMatch) {
666        RuleMatchForImport betterMatchCandidate = currentBestMatch;
667        final Matcher matcher = regExp.matcher(importPath);
668        while (matcher.find()) {
669            final int length = matcher.end() - matcher.start();
670            if (length > betterMatchCandidate.matchLength
671                    || length == betterMatchCandidate.matchLength
672                        && matcher.start() < betterMatchCandidate.matchPosition) {
673                betterMatchCandidate = new RuleMatchForImport(group, length, matcher.start());
674            }
675        }
676        return betterMatchCandidate;
677    }
678
679    /**
680     * Checks compare two import paths.
681     * @param import1
682     *        current import.
683     * @param import2
684     *        previous import.
685     * @return a negative integer, zero, or a positive integer as the
686     *        specified String is greater than, equal to, or less
687     *        than this String, ignoring case considerations.
688     */
689    private static int compareImports(String import1, String import2) {
690        int result = 0;
691        final String separator = "\\.";
692        final String[] import1Tokens = import1.split(separator);
693        final String[] import2Tokens = import2.split(separator);
694        for (int i = 0; i < import1Tokens.length && i != import2Tokens.length; i++) {
695            final String import1Token = import1Tokens[i];
696            final String import2Token = import2Tokens[i];
697            result = import1Token.compareTo(import2Token);
698            if (result != 0) {
699                break;
700            }
701        }
702        return result;
703    }
704
705    /**
706     * Counts empty lines before given.
707     * @param lineNo
708     *        Line number of current import.
709     * @return count of empty lines before given.
710     */
711    private int getCountOfEmptyLinesBefore(int lineNo) {
712        int result = 0;
713        final String[] lines = getLines();
714        //  [lineNo - 2] is the number of the previous line
715        //  because the numbering starts from zero.
716        int lineBeforeIndex = lineNo - 2;
717        while (lineBeforeIndex >= 0
718                && CommonUtils.isBlank(lines[lineBeforeIndex])) {
719            lineBeforeIndex--;
720            result++;
721        }
722        return result;
723    }
724
725    /**
726     * Forms import full path.
727     * @param token
728     *        current token.
729     * @return full path or null.
730     */
731    private static String getFullImportIdent(DetailAST token) {
732        String ident = "";
733        if (token != null) {
734            ident = FullIdent.createFullIdent(token.findFirstToken(TokenTypes.DOT)).getText();
735        }
736        return ident;
737    }
738
739    /**
740     * Parses ordering rule and adds it to the list with rules.
741     * @param ruleStr
742     *        String with rule.
743     */
744    private void addRulesToList(String ruleStr) {
745        if (STATIC_RULE_GROUP.equals(ruleStr)
746                || THIRD_PARTY_PACKAGE_RULE_GROUP.equals(ruleStr)
747                || STANDARD_JAVA_PACKAGE_RULE_GROUP.equals(ruleStr)
748                || SPECIAL_IMPORTS_RULE_GROUP.equals(ruleStr)) {
749            customImportOrderRules.add(ruleStr);
750
751        }
752        else if (ruleStr.startsWith(SAME_PACKAGE_RULE_GROUP)) {
753
754            final String rule = ruleStr.substring(ruleStr.indexOf('(') + 1,
755                    ruleStr.indexOf(')'));
756            samePackageMatchingDepth = Integer.parseInt(rule);
757            if (samePackageMatchingDepth <= 0) {
758                throw new IllegalArgumentException(
759                        "SAME_PACKAGE rule parameter should be positive integer: " + ruleStr);
760            }
761            customImportOrderRules.add(SAME_PACKAGE_RULE_GROUP);
762
763        }
764        else {
765            throw new IllegalStateException("Unexpected rule: " + ruleStr);
766        }
767    }
768
769    /**
770     * Creates samePackageDomainsRegExp of the first package domains.
771     * @param firstPackageDomainsCount
772     *        number of first package domains.
773     * @param packageNode
774     *        package node.
775     * @return same package regexp.
776     */
777    private static String createSamePackageRegexp(int firstPackageDomainsCount,
778             DetailAST packageNode) {
779        final String packageFullPath = getFullImportIdent(packageNode);
780        return getFirstDomainsFromIdent(firstPackageDomainsCount, packageFullPath);
781    }
782
783    /**
784     * Extracts defined amount of domains from the left side of package/import identifier.
785     * @param firstPackageDomainsCount
786     *        number of first package domains.
787     * @param packageFullPath
788     *        full identifier containing path to package or imported object.
789     * @return String with defined amount of domains or full identifier
790     *        (if full identifier had less domain then specified)
791     */
792    private static String getFirstDomainsFromIdent(
793            final int firstPackageDomainsCount, final String packageFullPath) {
794        final StringBuilder builder = new StringBuilder(256);
795        final StringTokenizer tokens = new StringTokenizer(packageFullPath, ".");
796        int count = firstPackageDomainsCount;
797
798        while (count > 0 && tokens.hasMoreTokens()) {
799            builder.append(tokens.nextToken()).append('.');
800            count--;
801        }
802        return builder.toString();
803    }
804
805    /**
806     * Contains import attributes as line number, import full path, import
807     * group.
808     * @author max
809     */
810    private static class ImportDetails {
811        /** Import full path. */
812        private final String importFullPath;
813
814        /** Import line number. */
815        private final int lineNumber;
816
817        /** Import group. */
818        private final String importGroup;
819
820        /** Is static import. */
821        private final boolean staticImport;
822
823        /**
824         * Initialise importFullPath, lineNumber, importGroup, staticImport.
825         * @param importFullPath
826         *        import full path.
827         * @param lineNumber
828         *        import line number.
829         * @param importGroup
830         *        import group.
831         * @param staticImport
832         *        if import is static.
833         */
834        ImportDetails(String importFullPath,
835                int lineNumber, String importGroup, boolean staticImport) {
836            this.importFullPath = importFullPath;
837            this.lineNumber = lineNumber;
838            this.importGroup = importGroup;
839            this.staticImport = staticImport;
840        }
841
842        /**
843         * Get import full path variable.
844         * @return import full path variable.
845         */
846        public String getImportFullPath() {
847            return importFullPath;
848        }
849
850        /**
851         * Get import line number.
852         * @return import line.
853         */
854        public int getLineNumber() {
855            return lineNumber;
856        }
857
858        /**
859         * Get import group.
860         * @return import group.
861         */
862        public String getImportGroup() {
863            return importGroup;
864        }
865
866        /**
867         * Checks if import is static.
868         * @return true, if import is static.
869         */
870        public boolean isStaticImport() {
871            return staticImport;
872        }
873    }
874
875    /**
876     * Contains matching attributes assisting in definition of "best matching"
877     * group for import.
878     * @author ivanov-alex
879     */
880    private static class RuleMatchForImport {
881        /** Position of matching string for current best match. */
882        private final int matchPosition;
883        /** Length of matching string for current best match. */
884        private int matchLength;
885        /** Import group for current best match. */
886        private String group;
887
888        /** Constructor to initialize the fields.
889         * @param group
890         *        Matched group.
891         * @param length
892         *        Matching length.
893         * @param position
894         *        Matching position.
895         */
896        RuleMatchForImport(String group, int length, int position) {
897            this.group = group;
898            matchLength = length;
899            matchPosition = position;
900        }
901    }
902}