Skip to content

Commit

Permalink
Rewrite Stream.collect(Collectors.toUnmodifiableList()) to `Stream.…
Browse files Browse the repository at this point in the history
…toList()` (#293)

* Add recipe to rewrite java 11 `Stream.collect(Collectors.toUnmodifiableList())` and optionally `Stream.collect(Collectors.toList())` to java 16 `Stream.toList()`

* Polish

* Apply suggestions from code review

---------

Co-authored-by: Westerlaken, H.L. (Laurens) <laurens.westerlaken@devolksbank.nl>
Co-authored-by: Tim te Beek <tim@moderne.io>
Co-authored-by: Tim te Beek <timtebeek@gmail.com>
  • Loading branch information
4 people authored Sep 15, 2023
1 parent c3f924f commit 467c10f
Show file tree
Hide file tree
Showing 3 changed files with 290 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 org.openrewrite.java.migrate.util;

import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.openrewrite.*;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.search.UsesJavaVersion;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;

import java.time.Duration;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

@Value
public class ReplaceStreamCollectWithToList extends Recipe {

private static final MethodMatcher STREAM_COLLECT = new MethodMatcher("java.util.stream.Stream collect(java.util.stream.Collector)");
private static final MethodMatcher COLLECT_TO_UNMODIFIABLE_LIST = new MethodMatcher("java.util.stream.Collectors toUnmodifiableList()");
private static final MethodMatcher COLLECT_TO_LIST = new MethodMatcher("java.util.stream.Collectors toList()");

@Option(displayName = "Convert mutable `Collectors.toList()` to immutable",
description = "Also replace `Stream.collect(Collectors.toList())` with `Stream.toList()`. " +
"*BEWARE*: Attempts to modify the returned list, result in an `UnsupportedOperationException`!",
required = false)
@Nullable
Boolean convertToList;

@Override
public String getDisplayName() {
return "Replace `Stream.collect(Collectors.toUnmodifiableList())` with `Stream.toList()`";
}

@Override
public String getDescription() {
return "Replace `Stream.collect(Collectors.toUnmodifiableList())` with Java 16+ `Stream.toList()`. " +
"Also replaces `Stream.collect(Collectors.toList())` if `convertToList` is set to `true`.";
}

@Override
public Duration getEstimatedEffortPerOccurrence() {
return Duration.ofMinutes(1);
}

@Override
public Set<String> getTags() {
return new HashSet<>(Collections.singletonList("RSPEC-6204"));
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
Preconditions.and(
new UsesJavaVersion<>(16),
new UsesMethod<>(STREAM_COLLECT),
Preconditions.or(
new UsesMethod<>(COLLECT_TO_UNMODIFIABLE_LIST),
new UsesMethod<>(COLLECT_TO_LIST))
),
new ReplaceCollectorToListVisitor(Boolean.TRUE.equals(convertToList)));
}

@RequiredArgsConstructor
private static final class ReplaceCollectorToListVisitor extends JavaIsoVisitor<ExecutionContext> {
private static final JavaTemplate template = JavaTemplate
.builder("#{any(java.util.stream.Stream)}.toList()")
.build();
private final boolean convertToList;

@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
J.MethodInvocation result = super.visitMethodInvocation(method, ctx);
if (!STREAM_COLLECT.matches(method)) {
return result;
}
Expression command = method.getArguments().get(0);
if (COLLECT_TO_UNMODIFIABLE_LIST.matches(command)
|| convertToList && COLLECT_TO_LIST.matches(command)) {
maybeRemoveImport("java.util.stream.Collectors");
J.MethodInvocation toList = template.apply(updateCursor(result), result.getCoordinates().replace(), result.getSelect());
return toList.getPadding().withSelect(result.getPadding().getSelect());
}
return result;
}
}
}
1 change: 1 addition & 0 deletions src/main/resources/META-INF/rewrite/java-util-apis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ recipeList:
- org.openrewrite.java.migrate.util.MigrateCollectionsSingletonSet
- org.openrewrite.java.migrate.util.MigrateCollectionsUnmodifiableList
- org.openrewrite.java.migrate.util.MigrateCollectionsUnmodifiableSet
- org.openrewrite.java.migrate.util.ReplaceStreamCollectWithToList
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 org.openrewrite.java.migrate.util;

import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.java.Assertions.java;
import static org.openrewrite.java.Assertions.javaVersion;

class ReplaceStreamCollectWithToListTest implements RewriteTest {

@Override
public void defaults(RecipeSpec spec) {
spec
.recipe(new ReplaceStreamCollectWithToList(false))
.allSources(s -> s.markers(javaVersion(17)));
}

@Test
@DocumentExample
void replacesToUnmodifiableList() {
rewriteRun(
//language=java
java(
"""
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.List;
class Example {
List<String> test(Stream<String> stream) {
return stream.collect(Collectors.toUnmodifiableList());
}
}
""",
"""
import java.util.stream.Stream;
import java.util.List;
class Example {
List<String> test(Stream<String> stream) {
return stream.toList();
}
}
"""
)
);
}

@Test
void doesNotReplaceToListByDefault() {
rewriteRun(
//language=java
java(
"""
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.List;
class Example {
List<String> test(Stream<String> stream) {
return stream.collect(Collectors.toList());
}
}
"""
)
);
}

@Test
void doesReplaceToListWhenFlagSetToTrue() {
rewriteRun(
recipeSpec -> recipeSpec.recipe(new ReplaceStreamCollectWithToList(true)),
//language=java
java(
"""
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.List;
class Example {
List<String> test(Stream<String> stream) {
return stream.collect(Collectors.toList());
}
}
""",
"""
import java.util.stream.Stream;
import java.util.List;
class Example {
List<String> test(Stream<String> stream) {
return stream.toList();
}
}
"""
)
);
}

@Test
void retainWhitespace() {
rewriteRun(
//language=java
java(
"""
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.List;
class Example {
List<String> test(Stream<String> stream) {
return stream
.collect(Collectors.toUnmodifiableList());
}
}
""",
"""
import java.util.stream.Stream;
import java.util.List;
class Example {
List<String> test(Stream<String> stream) {
return stream
.toList();
}
}
"""
)
);
}

@Test
void retainComment() {
rewriteRun(
//language=java
java(
"""
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.List;
class Example {
List<String> test(Stream<String> stream) {
return stream
// Convert to list
.collect(Collectors.toUnmodifiableList());
}
}
""",
"""
import java.util.stream.Stream;
import java.util.List;
class Example {
List<String> test(Stream<String> stream) {
return stream
// Convert to list
.toList();
}
}
"""
)
);
}

}

0 comments on commit 467c10f

Please sign in to comment.