diff --git a/config/suppressions.xml b/config/suppressions.xml index 2eca0a12bcc..69e91b82b95 100644 --- a/config/suppressions.xml +++ b/config/suppressions.xml @@ -158,7 +158,7 @@ - + diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/imports/ImportOrderCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/imports/ImportOrderCheck.java index 431953b26a4..fdb2661752f 100644 --- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/imports/ImportOrderCheck.java +++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/imports/ImportOrderCheck.java @@ -64,6 +64,8 @@ * Case sensitive sorting is in ASCII sort orderBooleantrue * sortStaticImportsAlphabeticallywhether static imports grouped by top or * bottom option are sorted alphabetically or notBooleanfalse + * useContainerOrderingForStaticwhether to use container ordering + * (Eclipse IDE term) for static imports or notBooleanfalse * * *

@@ -176,6 +178,7 @@ * @author David DIDIER * @author Steve McKay * @author Aleksey Nesterenko + * @author Andrei Selkin */ public class ImportOrderCheck extends AbstractCheck { @@ -219,6 +222,8 @@ public class ImportOrderCheck private boolean beforeFirstImport; /** Whether static imports should be sorted alphabetically or not. */ private boolean sortStaticImportsAlphabetically; + /** Whether to use container ordering (Eclipse IDE term) for static imports or not. */ + private boolean useContainerOrderingForStatic; /** The policy to enforce. */ private ImportOrderOption option = ImportOrderOption.UNDER; @@ -317,6 +322,14 @@ public void setSortStaticImportsAlphabetically(boolean sortAlphabetically) { sortStaticImportsAlphabetically = sortAlphabetically; } + /** + * Sets whether to use container ordering (Eclipse IDE term) for static imports or not. + * @param useContainerOrdering whether to use container ordering for static imports or not. + */ + public void setUseContainerOrderingForStatic(boolean useContainerOrdering) { + useContainerOrderingForStatic = useContainerOrdering; + } + @Override public int[] getDefaultTokens() { return getAcceptableTokens(); @@ -459,8 +472,7 @@ private void doVisitTokenInSameGroup(boolean isStatic, boolean previous, String name, int line) { if (ordered) { if (option == ImportOrderOption.INFLOW) { - // out of lexicographic order - if (compare(lastImport, name, caseSensitive) > 0) { + if (isWrongOrder(name, isStatic)) { log(line, MSG_ORDERING, name); } } @@ -474,9 +486,7 @@ private void doVisitTokenInSameGroup(boolean isStatic, // current and previous static or current and // previous non-static lastImportStatic == isStatic - && - // and out of lexicographic order - compare(lastImport, name, caseSensitive) > 0; + && isWrongOrder(name, isStatic); if (shouldFireError) { log(line, MSG_ORDERING, name); @@ -485,6 +495,90 @@ private void doVisitTokenInSameGroup(boolean isStatic, } } + /** + * Checks whether import name is in wrong order. + * @param name import name. + * @param isStatic whether it is a static import name. + * @return true if import name is in wrong order. + */ + private boolean isWrongOrder(String name, boolean isStatic) { + final boolean result; + if (isStatic && useContainerOrderingForStatic) { + result = compareContainerOrder(lastImport, name, caseSensitive) > 0; + } + else { + // out of lexicographic order + result = compare(lastImport, name, caseSensitive) > 0; + } + return result; + } + + /** + * Compares two import strings. + * We first compare the container of the static import, container being the type enclosing + * the static element being imported. When this returns 0, we compare the qualified + * import name. For e.g. this is what is considered to be container names: + *

+ * import static HttpConstants.COLON => HttpConstants + * import static HttpHeaders.addHeader => HttpHeaders + * import static HttpHeaders.setHeader => HttpHeaders + * import static HttpHeaders.Names.DATE => HttpHeaders.Names + *

+ *

+ * According to this logic, HttpHeaders.Names would come after HttpHeaders. + * + * For more details, see + * static imports comparison method in Eclipse. + *

+ * + * @param importName1 first import name. + * @param importName2 second import name. + * @param caseSensitive whether the comparison of fully qualified import names is case + * sensitive. + * @return the value {@code 0} if str1 is equal to str2; a value + * less than {@code 0} if str is less than the str2 (container order + * or lexicographical); and a value greater than {@code 0} if str1 is greater than str2 + * (container order or lexicographically). + */ + private static int compareContainerOrder(String importName1, String importName2, + boolean caseSensitive) { + final String container1 = getImportContainer(importName1); + final String container2 = getImportContainer(importName2); + final int compareContainersOrderResult; + if (caseSensitive) { + compareContainersOrderResult = container1.compareTo(container2); + } + else { + compareContainersOrderResult = container1.compareToIgnoreCase(container2); + } + final int result; + if (compareContainersOrderResult == 0) { + result = compare(importName1, importName2, caseSensitive); + } + else { + result = compareContainersOrderResult; + } + return result; + } + + /** + * Extracts import container name from fully qualified import name. + * An import container name is the type which encloses the static element being imported. + * For example, HttpConstants, HttpHeaders, HttpHeaders.Names are import container names: + *

+ * import static HttpConstants.COLON => HttpConstants + * import static HttpHeaders.addHeader => HttpHeaders + * import static HttpHeaders.setHeader => HttpHeaders + * import static HttpHeaders.Names.DATE => HttpHeaders.Names + *

+ * @param qualifiedImportName fully qualified import name. + * @return import container name. + */ + private static String getImportContainer(String qualifiedImportName) { + final int lastDotIndex = qualifiedImportName.lastIndexOf('.'); + return qualifiedImportName.substring(0, lastDotIndex); + } + /** * Finds out what group the specified import belongs to. * diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/imports/ImportOrderCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/imports/ImportOrderCheckTest.java index de7147e3d59..0f66b011a51 100644 --- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/imports/ImportOrderCheckTest.java +++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/imports/ImportOrderCheckTest.java @@ -483,4 +483,51 @@ public void testEclipseDefaultNegative() throws Exception { verify(checkConfig, getPath("InputImportOrder_EclipseDefaultNegative.java"), expected); } + + @Test + public void testUseContainerOrderingForStaticTrue() throws Exception { + final DefaultConfiguration checkConfig = createCheckConfig(ImportOrderCheck.class); + checkConfig.addAttribute("groups", "/^javax?\\./,org"); + checkConfig.addAttribute("ordered", "true"); + checkConfig.addAttribute("separated", "true"); + checkConfig.addAttribute("option", "top"); + checkConfig.addAttribute("caseSensitive", "false"); + checkConfig.addAttribute("sortStaticImportsAlphabetically", "true"); + checkConfig.addAttribute("useContainerOrderingForStatic", "true"); + final String[] expected = CommonUtils.EMPTY_STRING_ARRAY; + verify(checkConfig, getNonCompilablePath("InputEclipseStaticImportsOrder.java"), expected); + } + + @Test + public void testUseContainerOrderingForStaticFalse() throws Exception { + final DefaultConfiguration checkConfig = createCheckConfig(ImportOrderCheck.class); + checkConfig.addAttribute("groups", "/^javax?\\./,org"); + checkConfig.addAttribute("ordered", "true"); + checkConfig.addAttribute("separated", "true"); + checkConfig.addAttribute("option", "top"); + checkConfig.addAttribute("caseSensitive", "false"); + checkConfig.addAttribute("sortStaticImportsAlphabetically", "true"); + checkConfig.addAttribute("useContainerOrderingForStatic", "false"); + final String[] expected = { + "6: " + getCheckMessage(MSG_ORDERING, + "io.netty.handler.codec.http.HttpHeaders.Names.addDate"), + }; + verify(checkConfig, getNonCompilablePath("InputEclipseStaticImportsOrder.java"), expected); + } + + @Test + public void testUseContainerOrderingForStaticTrueCaseSensitive() throws Exception { + final DefaultConfiguration checkConfig = createCheckConfig(ImportOrderCheck.class); + checkConfig.addAttribute("groups", "/^javax?\\./,org"); + checkConfig.addAttribute("ordered", "true"); + checkConfig.addAttribute("separated", "true"); + checkConfig.addAttribute("option", "top"); + checkConfig.addAttribute("sortStaticImportsAlphabetically", "true"); + checkConfig.addAttribute("useContainerOrderingForStatic", "true"); + final String[] expected = { + "7: " + getCheckMessage(MSG_ORDERING, + "io.netty.handler.codec.http.HttpHeaders.Names.DATE"), + }; + verify(checkConfig, getNonCompilablePath("InputEclipseStaticImportsOrder.java"), expected); + } } diff --git a/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/imports/InputEclipseStaticImportsOrder.java b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/imports/InputEclipseStaticImportsOrder.java new file mode 100644 index 00000000000..6a934edf5c3 --- /dev/null +++ b/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/checks/imports/InputEclipseStaticImportsOrder.java @@ -0,0 +1,10 @@ +package com.puppycrawl.tools.checkstyle.checks.imports; + +import static io.netty.handler.codec.http.HttpConstants.COLON; +import static io.netty.handler.codec.http.HttpHeaders.addHeader; +import static io.netty.handler.codec.http.HttpHeaders.setHeader; +import static io.netty.handler.codec.http.HttpHeaders.Names.addDate; +import static io.netty.handler.codec.http.HttpHeaders.Names.DATE; + +public class InputEclipseStaticImportsOrder { +} \ No newline at end of file diff --git a/src/xdocs/config_imports.xml b/src/xdocs/config_imports.xml index b054dc78458..60b6442a2e2 100644 --- a/src/xdocs/config_imports.xml +++ b/src/xdocs/config_imports.xml @@ -878,6 +878,12 @@ import android.*; Boolean false + + useContainerOrderingForStatic + whether to use container ordering (Eclipse IDE term) for static imports or not + Boolean + false + tokens @@ -1007,6 +1013,56 @@ import java.util.Set; // Wrong order for 'java.util.Set' import. public class SomeClass { ... } +

+ The following example shows the idea of 'useContainerOrderingForStatic' option that is + useful for Eclipse IDE users to match ordering validation. + This is how the import comparison works for static imports: we first compare + the container of the static import, container is the type enclosing the static element + being imported. When the result of the comparison is 0 (containers are equal), + we compare the fully qualified import names. + For e.g. this is what is considered to be container names for the given example: + + import static HttpConstants.COLON => HttpConstants + import static HttpHeaders.addHeader => HttpHeaders + import static HttpHeaders.setHeader => HttpHeaders + import static HttpHeaders.Names.DATE => HttpHeaders.Names + + According to this logic, HttpHeaders.Names should come after HttpHeaders. +

+ +<module name="ImportOrder"> + <property name="useContainerOrderingForStatic" value="true"/> + <property name="ordered" value="true"/> + <property name="option" value="top"/> + <property name="caseSensitive" value="false"/> + <property name="sortStaticImportsAlphabetically" value="true"/> +</module> + + +import static io.netty.handler.codec.http.HttpConstants.COLON; +import static io.netty.handler.codec.http.HttpHeaders.addHeader; +import static io.netty.handler.codec.http.HttpHeaders.setHeader; +import static io.netty.handler.codec.http.HttpHeaders.Names.DATE; + +public class InputEclipseStaticImportsOrder { } + + +<module name="ImportOrder"> + <property name="useContainerOrderingForStatic" value="false"/> + <property name="ordered" value="true"/> + <property name="option" value="top"/> + <property name="caseSensitive" value="false"/> + <property name="sortStaticImportsAlphabetically" value="true"/> +</module> + + +import static io.netty.handler.codec.http.HttpConstants.COLON; +import static io.netty.handler.codec.http.HttpHeaders.addHeader; +import static io.netty.handler.codec.http.HttpHeaders.setHeader; +import static io.netty.handler.codec.http.HttpHeaders.Names.DATE; // violation + +public class InputEclipseStaticImportsOrder { } +