diff --git a/core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java b/core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java
index 9aebf4641..375a3289b 100644
--- a/core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java
+++ b/core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java
@@ -122,18 +122,24 @@ private String reorderImports() throws FormatterException {
/**
* A {@link Comparator} that orders {@link Import}s by Google Style, defined at
* https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing.
+ *
+ *
Module imports are not allowed by Google Style, so we make an arbitrary choice about where
+ * to include them if they are present.
*/
private static final Comparator GOOGLE_IMPORT_COMPARATOR =
- Comparator.comparing(Import::isStatic, trueFirst()).thenComparing(Import::imported);
+ Comparator.comparing(Import::importType).thenComparing(Import::imported);
/**
* A {@link Comparator} that orders {@link Import}s by AOSP Style, defined at
* https://source.android.com/setup/contribute/code-style#order-import-statements and implemented
* in IntelliJ at
* https://android.googlesource.com/platform/development/+/master/ide/intellij/codestyles/AndroidStyle.xml.
+ *
+ *
Module imports are not mentioned by Android Style, so we make an arbitrary choice about
+ * where to include them if they are present.
*/
private static final Comparator AOSP_IMPORT_COMPARATOR =
- Comparator.comparing(Import::isStatic, trueFirst())
+ Comparator.comparing(Import::importType)
.thenComparing(Import::isAndroid, trueFirst())
.thenComparing(Import::isThirdParty, trueFirst())
.thenComparing(Import::isJava, trueFirst())
@@ -144,7 +150,7 @@ private String reorderImports() throws FormatterException {
* Import}s based on Google style.
*/
private static boolean shouldInsertBlankLineGoogle(Import prev, Import curr) {
- return prev.isStatic() && !curr.isStatic();
+ return !prev.importType().equals(curr.importType());
}
/**
@@ -152,7 +158,7 @@ private static boolean shouldInsertBlankLineGoogle(Import prev, Import curr) {
* Import}s based on AOSP style.
*/
private static boolean shouldInsertBlankLineAosp(Import prev, Import curr) {
- if (prev.isStatic() && !curr.isStatic()) {
+ if (!prev.importType().equals(curr.importType())) {
return true;
}
// insert blank line between "com.android" from "com.anythingelse"
@@ -183,16 +189,22 @@ private ImportOrderer(String text, ImmutableList toks, Style style) {
}
}
+ enum ImportType {
+ STATIC,
+ MODULE,
+ NORMAL
+ }
+
/** An import statement. */
class Import {
private final String imported;
- private final boolean isStatic;
private final String trailing;
+ private final ImportType importType;
- Import(String imported, String trailing, boolean isStatic) {
+ Import(String imported, String trailing, ImportType importType) {
this.imported = imported;
this.trailing = trailing;
- this.isStatic = isStatic;
+ this.importType = importType;
}
/** The name being imported, for example {@code java.util.List}. */
@@ -200,9 +212,9 @@ String imported() {
return imported;
}
- /** True if this is {@code import static}. */
- boolean isStatic() {
- return isStatic;
+ /** Returns the {@link ImportType}. */
+ ImportType importType() {
+ return importType;
}
/** The top-level package of the import. */
@@ -245,8 +257,10 @@ public boolean isThirdParty() {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("import ");
- if (isStatic()) {
- sb.append("static ");
+ switch (importType) {
+ case STATIC -> sb.append("static ");
+ case MODULE -> sb.append("module ");
+ case NORMAL -> {}
}
sb.append(imported()).append(';');
if (trailing().trim().isEmpty()) {
@@ -301,8 +315,13 @@ private ImportsAndIndex scanImports(int i) throws FormatterException {
if (isSpaceToken(i)) {
i++;
}
- boolean isStatic = tokenAt(i).equals("static");
- if (isStatic) {
+ ImportType importType =
+ switch (tokenAt(i)) {
+ case "static" -> ImportType.STATIC;
+ case "module" -> ImportType.MODULE;
+ default -> ImportType.NORMAL;
+ };
+ if (!importType.equals(ImportType.NORMAL)) {
i++;
if (isSpaceToken(i)) {
i++;
@@ -347,7 +366,7 @@ private ImportsAndIndex scanImports(int i) throws FormatterException {
// Extra semicolons are not allowed by the JLS but are accepted by javac.
i++;
}
- imports.add(new Import(importedName, trailing.toString(), isStatic));
+ imports.add(new Import(importedName, trailing.toString(), importType));
// Remember the position just after the import we just saw, before skipping blank lines.
// If the next thing after the blank lines is not another import then we don't want to
// include those blank lines in the text to be replaced.
diff --git a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java
index 8e8ac97cc..a2a32e79c 100644
--- a/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java
+++ b/core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java
@@ -157,6 +157,7 @@
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.tree.TreeScanner;
+import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@@ -1220,6 +1221,10 @@ public Void visitImport(ImportTree node, Void unused) {
sync(node);
token("import");
builder.space();
+ if (isModuleImport(node)) {
+ token("module");
+ builder.space();
+ }
if (node.isStatic()) {
token("static");
builder.space();
@@ -1231,6 +1236,27 @@ public Void visitImport(ImportTree node, Void unused) {
return null;
}
+ private static final @Nullable Method IS_MODULE_METHOD = getIsModuleMethod();
+
+ private static @Nullable Method getIsModuleMethod() {
+ try {
+ return ImportTree.class.getMethod("isModule");
+ } catch (NoSuchMethodException ignored) {
+ return null;
+ }
+ }
+
+ private static boolean isModuleImport(ImportTree importTree) {
+ if (IS_MODULE_METHOD == null) {
+ return false;
+ }
+ try {
+ return (boolean) IS_MODULE_METHOD.invoke(importTree);
+ } catch (ReflectiveOperationException e) {
+ throw new LinkageError(e.getMessage(), e);
+ }
+ }
+
private void checkForTypeAnnotation(ImportTree node) {
Name simpleName = getSimpleName(node);
Collection wellKnownAnnotations = TYPE_ANNOTATIONS.get(simpleName.toString());
diff --git a/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java b/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java
index 8c3cae319..4035ce319 100644
--- a/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java
+++ b/core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java
@@ -16,6 +16,7 @@
package com.google.googlejavaformat.java;
+import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.Math.max;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -67,6 +68,7 @@
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardLocation;
+import org.jspecify.annotations.Nullable;
/**
* Removes unused imports from a source file. Imports that are only used in javadoc are also
@@ -274,6 +276,9 @@ private static RangeMap buildReplacements(
Multimap> usedInJavadoc) {
RangeMap replacements = TreeRangeMap.create();
for (JCTree importTree : unit.getImports()) {
+ if (isModuleImport(importTree)) {
+ continue;
+ }
String simpleName = getSimpleName(importTree);
if (!isUnused(unit, usedNames, usedInJavadoc, importTree, simpleName)) {
continue;
@@ -322,10 +327,42 @@ private static boolean isUnused(
return true;
}
+ private static final Method GET_QUALIFIED_IDENTIFIER_METHOD = getQualifiedIdentifierMethod();
+
+ private static @Nullable Method getQualifiedIdentifierMethod() {
+ try {
+ return JCImport.class.getMethod("getQualifiedIdentifier");
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+
private static JCFieldAccess getQualifiedIdentifier(JCTree importTree) {
+ checkArgument(!isModuleImport(importTree));
// Use reflection because the return type is JCTree in some versions and JCFieldAccess in others
try {
- return (JCFieldAccess) JCImport.class.getMethod("getQualifiedIdentifier").invoke(importTree);
+ return (JCFieldAccess) GET_QUALIFIED_IDENTIFIER_METHOD.invoke(importTree);
+ } catch (ReflectiveOperationException e) {
+ throw new LinkageError(e.getMessage(), e);
+ }
+ }
+
+ private static final @Nullable Method IS_MODULE_METHOD = getIsModuleMethod();
+
+ private static @Nullable Method getIsModuleMethod() {
+ try {
+ return ImportTree.class.getMethod("isModule");
+ } catch (NoSuchMethodException ignored) {
+ return null;
+ }
+ }
+
+ private static boolean isModuleImport(JCTree importTree) {
+ if (IS_MODULE_METHOD == null) {
+ return false;
+ }
+ try {
+ return (boolean) IS_MODULE_METHOD.invoke(importTree);
} catch (ReflectiveOperationException e) {
throw new LinkageError(e.getMessage(), e);
}
diff --git a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java
index 8e863a28d..a406487f3 100644
--- a/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java
+++ b/core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java
@@ -59,6 +59,7 @@ public class FormatterIntegrationTest {
"I981",
"I1020",
"I1037")
+ .putAll(25, "ModuleImport")
.build();
@Parameters(name = "{index}: {0}")
diff --git a/core/src/test/java/com/google/googlejavaformat/java/ImportOrdererTest.java b/core/src/test/java/com/google/googlejavaformat/java/ImportOrdererTest.java
index 0b9dab26f..6772b42be 100644
--- a/core/src/test/java/com/google/googlejavaformat/java/ImportOrdererTest.java
+++ b/core/src/test/java/com/google/googlejavaformat/java/ImportOrdererTest.java
@@ -820,6 +820,23 @@ public static Collection