diff --git a/check_api/src/main/java/com/google/errorprone/ImportOrderParser.java b/check_api/src/main/java/com/google/errorprone/ImportOrderParser.java
index 064bb690e4b..9928460238f 100644
--- a/check_api/src/main/java/com/google/errorprone/ImportOrderParser.java
+++ b/check_api/src/main/java/com/google/errorprone/ImportOrderParser.java
@@ -15,6 +15,7 @@
*/
package com.google.errorprone;
+import com.google.errorprone.apply.CustomImportOrganizer;
import com.google.errorprone.apply.ImportOrganizer;
/** Parse import order strings. */
@@ -39,6 +40,8 @@ public static ImportOrganizer getImportOrganizer(String importOrder) {
case "idea":
return ImportOrganizer.IDEA_ORGANIZER;
default:
+ if (importOrder.startsWith("custom:"))
+ return new CustomImportOrganizer(importOrder.substring(7));
throw new IllegalStateException("Unknown import order: '" + importOrder + "'");
}
}
diff --git a/check_api/src/main/java/com/google/errorprone/apply/CustomImportOrganizer.java b/check_api/src/main/java/com/google/errorprone/apply/CustomImportOrganizer.java
new file mode 100644
index 00000000000..597da6c31e4
--- /dev/null
+++ b/check_api/src/main/java/com/google/errorprone/apply/CustomImportOrganizer.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2017 The Error Prone Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.errorprone.apply;
+
+import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.errorprone.InvalidCommandLineOptionException;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+/**
+ * Organizes imports based on a specified custom Code Style.
+ *
+ *
The custom ordering is specified using a -XepPatchImportOrder
with the format:
+ * custom:static={first|last},order={':'-separated prefixes}
+ *
+ * The order
parameter is a colon-separated list indicating the desired order of import groups by their package prefixes.
+ * The special OTHER
prefix is used to place imports that do not match any specified prefix.
+ *
+ *
The imports are partitioned into groups in two steps, each step sub-partitions all groups from
+ * the previous step. The steps are:
+ *
+ *
+ * - Split into static and non-static, the static imports come before or after the non static
+ * imports depending on the
static
parameter.
+ * - Further partitioning within each group based on the package prefix order defined in
order
.
+ * For example,
"-XepPatchImportOrder=custom:static=first,order=java:javax:OTHER:org.a:org.b:com"
+ * results in the following order:
+ *
+ * - {@code java.}
+ * - {@code javax.}
+ * - Imports not matching any other specified prefix.
+ * - {@code org.a.}
+ * - {@code org.b.}
+ * - {@code com.}
+ *
+ *
+ *
+ * Each group is separated from the previous/next groups with a blank line.
+ */
+public class CustomImportOrganizer implements ImportOrganizer {
+
+ private static final String OTHER = "OTHER";
+ private static final String STATIC_PREFIX = "static=";
+ private static final String ORDER_PREFIX = "order=";
+
+ private StaticOrder staticOrder;
+ private ImmutableList roots;
+
+ /**
+ * Creates a custom import organizer according to the provided specification.
+ *
+ * @param spec import order specified as: static={first|last},order={':'-separated prefixes}
+ */
+ public CustomImportOrganizer(String spec) {
+ for (String keyValue: spec.split(",")) {
+ if (keyValue.startsWith(STATIC_PREFIX)) {
+ String value = keyValue.substring(STATIC_PREFIX.length());
+ if (value.equals("first")) {
+ staticOrder = StaticOrder.STATIC_FIRST;
+ } else if (value.equals("last")) {
+ staticOrder = StaticOrder.STATIC_LAST;
+ }
+ } else if (keyValue.startsWith(ORDER_PREFIX)) {
+ String value = keyValue.substring(ORDER_PREFIX.length());
+ roots = ImmutableList.copyOf(value.split(":"));
+ }
+ }
+ if (staticOrder == null) {
+ throw new InvalidCommandLineOptionException("missing \"" + STATIC_PREFIX + "\" parameter: " + spec);
+ }
+ if (roots == null) {
+ throw new InvalidCommandLineOptionException("missing \"" + ORDER_PREFIX + "\" parameter: " + spec);
+ }
+ if (!roots.contains(OTHER)) {
+ throw new InvalidCommandLineOptionException("missing \"" + OTHER + "\" order prefix: " + spec);
+ }
+ }
+
+ @Override
+ public OrganizedImports organizeImports(List imports) {
+ OrganizedImports organized = new OrganizedImports();
+
+ // Group into static and non-static.
+ Map> partionedByStatic =
+ imports.stream().collect(Collectors.partitioningBy(Import::isStatic));
+
+ for (Boolean key : staticOrder.groupOrder()) {
+ organizePartition(organized, partionedByStatic.get(key));
+ }
+
+ return organized;
+ }
+
+ private void organizePartition(OrganizedImports organized, List imports) {
+
+ Map> groupedByRoot =
+ imports.stream()
+ .collect(
+ Collectors.groupingBy(
+ // Group by root package.
+ anImport -> rootPackage(anImport),
+ // Ensure that the results are sorted.
+ TreeMap::new,
+ // Each group is a set sorted by type.
+ toImmutableSortedSet(Comparator.comparing(Import::getType))));
+
+ organized.addGroups(groupedByRoot, roots);
+ }
+
+ private String rootPackage(Import anImport) {
+ String type = anImport.getType();
+
+ for (String root: roots) {
+ if (type.startsWith(root + ".")) {
+ return root;
+ }
+ }
+
+ return OTHER;
+ }
+}
diff --git a/check_api/src/test/java/com/google/errorprone/ErrorProneOptionsTest.java b/check_api/src/test/java/com/google/errorprone/ErrorProneOptionsTest.java
index 6465bd2d57b..ee1b36c37b7 100644
--- a/check_api/src/test/java/com/google/errorprone/ErrorProneOptionsTest.java
+++ b/check_api/src/test/java/com/google/errorprone/ErrorProneOptionsTest.java
@@ -25,6 +25,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.ErrorProneOptions.Severity;
+import com.google.errorprone.apply.CustomImportOrganizer;
import com.google.errorprone.apply.ImportOrganizer;
import java.util.Arrays;
import java.util.Collection;
@@ -303,6 +304,14 @@ public void importOrder_androidStaticLast() {
assertThat(options.patchingOptions().importOrganizer())
.isSameInstanceAs(ImportOrganizer.ANDROID_STATIC_LAST_ORGANIZER);
}
+
+ @Test
+ public void importOrder_custom() {
+ ErrorProneOptions options =
+ ErrorProneOptions.processArgs(new String[] {"-XepPatchImportOrder:custom:static=first,order=java:javax:OTHER:org"});
+ assertThat(options.patchingOptions().importOrganizer())
+ .isInstanceOf(CustomImportOrganizer.class);
+ }
@Test
public void noSuchXepFlag() {
diff --git a/check_api/src/test/java/com/google/errorprone/apply/CustomImportOrganizerTest.java b/check_api/src/test/java/com/google/errorprone/apply/CustomImportOrganizerTest.java
new file mode 100644
index 00000000000..1bbec13596c
--- /dev/null
+++ b/check_api/src/test/java/com/google/errorprone/apply/CustomImportOrganizerTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2018 The Error Prone Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.errorprone.apply;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import com.google.common.collect.ImmutableList;
+
+/** {@link CustomImportOrganizer}Test */
+@RunWith(JUnit4.class)
+public class CustomImportOrganizerTest {
+
+ private static final ImmutableList IMPORTS =
+ Stream.of(
+ "import com.acme",
+ "import org.a.example",
+ "import org.b.example",
+ "import unknown.fred",
+ "import java.ping",
+ "import org.a.sample",
+ "import org.b.sample",
+ "import javax.pong",
+ "import unknown.barney",
+ "import net.wilma",
+ "import static com.acme.bla",
+ "import static java.ping.bla",
+ "import java.util",
+ "import static unknown.fred.bla")
+ .map(ImportOrganizer.Import::importOf)
+ .collect(toImmutableList());
+
+ @Test
+ public void staticFirstOtherLast() {
+ ImportOrganizer organizer = new CustomImportOrganizer("static=first,order=java:javax:org.a:org.b:com:OTHER");
+ ImportOrganizer.OrganizedImports organized = organizer.organizeImports(IMPORTS);
+ assertThat(organized.asImportBlock())
+ .isEqualTo(
+ "import static java.ping.bla;\n"
+ + "\n"
+ + "import static com.acme.bla;\n"
+ + "\n"
+ + "import static unknown.fred.bla;\n"
+ + "\n"
+ + "import java.ping;\n"
+ + "import java.util;\n"
+ + "\n"
+ + "import javax.pong;\n"
+ + "\n"
+ + "import org.a.example;\n"
+ + "import org.a.sample;\n"
+ + "\n"
+ + "import org.b.example;\n"
+ + "import org.b.sample;\n"
+ + "\n"
+ + "import com.acme;\n"
+ + "\n"
+ + "import net.wilma;\n"
+ + "import unknown.barney;\n"
+ + "import unknown.fred;\n"
+ );
+ }
+
+ @Test
+ public void staticLastOtherFirst() {
+ ImportOrganizer organizer = new CustomImportOrganizer("static=last,order=OTHER:java:javax:org.a:org.b:com");
+ ImportOrganizer.OrganizedImports organized = organizer.organizeImports(IMPORTS);
+ assertThat(organized.asImportBlock())
+ .isEqualTo(
+ "import net.wilma;\n"
+ + "import unknown.barney;\n"
+ + "import unknown.fred;\n"
+ + "\n"
+ + "import java.ping;\n"
+ + "import java.util;\n"
+ + "\n"
+ + "import javax.pong;\n"
+ + "\n"
+ + "import org.a.example;\n"
+ + "import org.a.sample;\n"
+ + "\n"
+ + "import org.b.example;\n"
+ + "import org.b.sample;\n"
+ + "\n"
+ + "import com.acme;\n"
+ + "\n"
+ + "import static unknown.fred.bla;\n"
+ + "\n"
+ + "import static java.ping.bla;\n"
+ + "\n"
+ + "import static com.acme.bla;\n"
+ );
+ }
+
+ @Test
+ public void staticFirstOtherMiddle() {
+ ImportOrganizer organizer = new CustomImportOrganizer("static=first,order=java:javax:OTHER:org.a:org.b:com");
+ ImportOrganizer.OrganizedImports organized = organizer.organizeImports(IMPORTS);
+ assertThat(organized.asImportBlock())
+ .isEqualTo(
+ "import static java.ping.bla;\n"
+ + "\n"
+ + "import static unknown.fred.bla;\n"
+ + "\n"
+ + "import static com.acme.bla;\n"
+ + "\n"
+ + "import java.ping;\n"
+ + "import java.util;\n"
+ + "\n"
+ + "import javax.pong;\n"
+ + "\n"
+ + "import net.wilma;\n"
+ + "import unknown.barney;\n"
+ + "import unknown.fred;\n"
+ + "\n"
+ + "import org.a.example;\n"
+ + "import org.a.sample;\n"
+ + "\n"
+ + "import org.b.example;\n"
+ + "import org.b.sample;\n"
+ + "\n"
+ + "import com.acme;\n"
+ );
+ }
+}