Skip to content

Commit 1b71438

Browse files
authored
Merge pull request #218 from openjavaformat/abashev/replace-guava-immutablelist-immutablemap-immutab
Replace Guava's immutable collections with java.util and ban them outside the SPI
2 parents 7904376 + ef69118 commit 1b71438

53 files changed

Lines changed: 438 additions & 372 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
caches/*/transforms
5252
5353
- name: Build
54-
run: ./gradlew test
54+
run: ./gradlew check
5555

5656
- name: Publish Test Report
5757
uses: mikepenz/action-junit-report@4a26df3e2ae132254ba12a53f65a388d0961e786 # v6.7.0

‎CLAUDE.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# Working in this repository
22

33
Building and testing: the README's "Building and testing locally" section. Gradle runs on the Temurin 21 that
4-
`mise install` provides, `./gradlew test` is what CI runs, and `./gradlew formatDiff` formats the files you changed.
4+
`mise install` provides, `./gradlew check` is what CI runs, and `./gradlew formatDiff` formats the files you changed.
55

66
## Conventions for new code
77

88
- **Collections.** Plain `java.util` types. `List.of`, `List.copyOf`, `Map.of` and `Stream.toList()` give
9-
unmodifiable collections, so new code does not need Guava's `ImmutableList` or `ImmutableMap`; use them only
10-
where an existing API already hands one over.
9+
unmodifiable collections; unlike Guava's, `Set.of` and `Map.of` do not keep insertion order, and `contains(null)`
10+
on them throws. Guava's `ImmutableList`, `ImmutableMap` and `ImmutableSet` are banned by forbidden-apis
11+
(`config/forbidden-apis`), which fails the build, in every module but the SPI: `FormatterService` keeps returning
12+
`ImmutableList` for compatibility, and the methods that implement it carry their module's `@SuppressForbidden`.
1113
- **Nullability.** One annotation, JSpecify's `org.jspecify.annotations.Nullable`; do not add another `Nullable`,
1214
nor a `NotNull` or `NonNull`: what is not `@Nullable` is non-null, and NullAway checks it. It is a type-use
1315
annotation, so on a nested type it goes before the simple name (`Input.@Nullable Token`), and on an array before

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ fixes made the output differ from upstream, that numbering lost its meaning.
134134

135135
```bash
136136
mise trust && mise install
137-
./gradlew test # what the CI build job runs
137+
./gradlew check # what the CI build job runs: the tests, Checkstyle and forbidden-apis
138138
```
139139

140140
`-PjavaRuntime=25` runs the same tests on JDK 25, as CI's `jdk` jobs do for 25, 26 and 27, and for

‎build.gradle‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ subprojects {
8383
// Java toolchains, javac and test settings; Error Prone. Both in buildSrc/src/main/groovy.
8484
apply plugin: 'open-java-format.java-conventions'
8585
apply plugin: 'open-java-format.error-prone-conventions'
86+
// Bans Guava's immutable collections, but not in the SPI: FormatterService.getFormatReplacements keeps
87+
// returning ImmutableList, so that code compiled against an older SPI still links.
88+
if (path != ':open-java-format-spi') {
89+
apply plugin: 'open-java-format.forbidden-apis-conventions'
90+
}
8691

8792
dependencies {
8893
// Gives a version to the JUnit artifacts that useJUnitJupiter() adds without one.

‎buildSrc/build.gradle‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ repositories {
1313
dependencies {
1414
implementation 'net.ltgt.gradle:gradle-errorprone-plugin:5.2.0'
1515
implementation 'net.ltgt.gradle:gradle-nullaway-plugin:3.3.0'
16+
implementation 'de.thetaphi:forbiddenapis:3.11'
1617
implementation 'org.jreleaser:org.jreleaser.gradle.plugin:1.26.0'
1718
}

‎buildSrc/src/main/groovy/open-java-format.error-prone-conventions.gradle‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ tasks.withType(JavaCompile).configureEach {
2727
}
2828
error('CanonicalAnnotationSyntax')
2929
error('CollectorMutability')
30+
error('DefaultCharset')
3031
error('DirectReturn')
3132
error('EffectivelyPrivate')
3233
error('FormatStringConcatenation')
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Bans Guava's immutable collections in favour of java.util ones; the list is config/forbidden-apis.
2+
plugins {
3+
id 'java'
4+
id 'de.thetaphi.forbiddenapis'
5+
}
6+
7+
forbiddenApis {
8+
signaturesFiles = files(rootProject.file('config/forbidden-apis/guava-immutables.txt'))
9+
// A module without Guava on its classpath cannot use the classes anyway.
10+
ignoreSignaturesOfMissingClasses = true
11+
// A module's own package-private SuppressForbidden, so the code does not depend on forbidden-apis.
12+
suppressAnnotations = ['**.SuppressForbidden']
13+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@defaultMessage Use java.util collections: List.of, List.copyOf, Map.of, Stream.toList()
2+
com.google.common.collect.ImmutableList
3+
com.google.common.collect.ImmutableMap
4+
com.google.common.collect.ImmutableSet

‎gradle-open-java-format/src/main/groovy/com/palantir/javaformat/gradle/PalantirJavaFormatIdeaPlugin.java‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package com.palantir.javaformat.gradle;
1818

1919
import com.google.common.base.Preconditions;
20-
import com.google.common.collect.ImmutableList;
2120
import java.util.stream.Stream;
2221
import org.gradle.StartParameter;
2322
import org.gradle.api.Plugin;
@@ -80,9 +79,8 @@ public void apply(Project rootProject) {
8079
// Adds the tasks to the Gradle start parameters, so they run with whatever the build was asked to do.
8180
private static void runWithEveryBuild(Project rootProject, TaskProvider<?>... tasks) {
8281
StartParameter startParameter = rootProject.getGradle().getStartParameter();
83-
startParameter.setTaskNames(ImmutableList.<String>builder()
84-
.addAll(startParameter.getTaskNames())
85-
.addAll(Stream.of(tasks).map(task -> ":" + task.getName()).toList())
86-
.build());
82+
startParameter.setTaskNames(Stream.concat(
83+
startParameter.getTaskNames().stream(), Stream.of(tasks).map(task -> ":" + task.getName()))
84+
.toList());
8785
}
8886
}

‎gradle-open-java-format/src/main/groovy/com/palantir/javaformat/gradle/XmlUtils.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package com.palantir.javaformat.gradle;
1818

19-
import com.google.common.collect.ImmutableMap;
2019
import com.google.common.io.Files;
2120
import groovy.util.Node;
2221
import groovy.xml.XmlNodePrinter;
@@ -26,6 +25,7 @@
2625
import java.io.IOException;
2726
import java.io.PrintWriter;
2827
import java.nio.charset.Charset;
28+
import java.util.Map;
2929
import java.util.function.Consumer;
3030
import javax.xml.parsers.ParserConfigurationException;
3131
import org.xml.sax.SAXException;
@@ -41,7 +41,7 @@ public static void updateIdeaXmlFile(File configurationFile, Consumer<Node> conf
4141
throw new RuntimeException("Couldn't parse existing configuration file: " + configurationFile, e);
4242
}
4343
} else {
44-
rootNode = new Node(null, "project", ImmutableMap.of("version", "4"));
44+
rootNode = new Node(null, "project", Map.of("version", "4"));
4545
}
4646

4747
configure.accept(rootNode);

0 commit comments

Comments
 (0)