Skip to content

Commit

Permalink
Adopt SequencedCollection First and Last methods
Browse files Browse the repository at this point in the history
  • Loading branch information
timtebeek committed Nov 3, 2023
1 parent 9e2457f commit 8c1b8dc
Show file tree
Hide file tree
Showing 2 changed files with 321 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.openrewrite.java.migrate.util;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
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.util.ArrayList;
import java.util.List;

public class SequencedCollectionFirstAndLast extends Recipe {

private static final MethodMatcher ADD_MATCHER = new MethodMatcher("java.util.List add(int, Object)", true);
private static final MethodMatcher GET_MATCHER = new MethodMatcher("java.util.List get(int)", true);
private static final MethodMatcher REMOVE_MATCHER = new MethodMatcher("java.util.List remove(int)", true);
private static final MethodMatcher SIZE_MATCHER = new MethodMatcher("java.util.List size()", true);

@Override
public String getDisplayName() {
return "Adopt `First` and `Last` methods for `SequencedCollections`";
}

@Override
public String getDescription() {
return "Replace `sc.get(0)` with `sc.getFirst()`, `sc.get(sc.size() - 1)` with `sc.getLast()`, and similar for `add(int, E)` and `remove(int)`.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
Preconditions.and(
new UsesJavaVersion<>(21),
Preconditions.or(
new UsesMethod<>(ADD_MATCHER),
new UsesMethod<>(GET_MATCHER),
new UsesMethod<>(REMOVE_MATCHER)
)
),
new FirstLastVisitor());
}

private static class FirstLastVisitor extends JavaIsoVisitor<ExecutionContext> {
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
J.MethodInvocation mi = super.visitMethodInvocation(method, executionContext);
Expression select = mi.getSelect();
if (select == null || !(select instanceof J.Identifier)) {
return mi;
}
J.Identifier sequencedCollection = (J.Identifier) select;

final String operation;
if (ADD_MATCHER.matches(mi)) {
operation = "add";
} else if (GET_MATCHER.matches(mi)) {
operation = "get";
} else if (REMOVE_MATCHER.matches(mi)) {
operation = "remove";
} else {
return mi;
}

final String firstOrLast;
Expression expression = mi.getArguments().get(0);
if (J.Literal.isLiteralValue(expression, 0)) {
firstOrLast = "First";
} else if (lastElementOfSequencedCollection(sequencedCollection, expression)) {
firstOrLast = "Last";
} else {
return mi;
}

List<Object> arguments = new ArrayList<>();
if ("add".equals(operation)) {
arguments.add(mi.getArguments().get(1));
}
return JavaTemplate.builder(operation + firstOrLast + (arguments.isEmpty() ? "()" : "(#{})")).build()
.apply(updateCursor(mi), mi.getCoordinates().replaceMethod(), arguments.toArray());
}

/**
* @param sequencedCollection
* @param expression
* @return true, if we're calling `sequencedCollection.size() - 1` in expression
*/
private static boolean lastElementOfSequencedCollection(J.Identifier sequencedCollection, Expression expression) {
if (expression instanceof J.Binary) {
J.Binary binary = (J.Binary) expression;
if (binary.getOperator() == J.Binary.Type.Subtraction) {
if (J.Literal.isLiteralValue(binary.getRight(), 1) && SIZE_MATCHER.matches(binary.getLeft())) {
Expression sizeSelect = ((J.MethodInvocation) binary.getLeft()).getSelect();
if (sizeSelect instanceof J.Identifier) {
if (sequencedCollection.getSimpleName().equals(((J.Identifier) sizeSelect).getSimpleName())) {
return true;
}
}
}
}
}
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* 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.Nested;
import org.junit.jupiter.api.Test;
import org.openrewrite.Issue;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

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


@Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/243")
class SequencedCollectionFirstAndLastTest implements RewriteTest {

@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new SequencedCollectionFirstAndLast())
.allSources(src -> src.markers(javaVersion(21)));
}

@Nested
class First {
@Test
void getFirst() {
rewriteRun(
//language=java
java(
"""
import java.util.*;
class Foo {
String bar(List<String> collection) {
return collection.get(0);
}
}
""",
"""
import java.util.*;
class Foo {
String bar(List<String> collection) {
return collection.getFirst();
}
}
"""
)
);
}

@Test
void addFirst() {
rewriteRun(
//language=java
java(
"""
import java.util.*;
class Foo {
void bar(List<String> collection) {
collection.add(0, "first");
}
}
""",
"""
import java.util.*;
class Foo {
void bar(List<String> collection) {
collection.addFirst("first");
}
}
"""
)
);
}

@Test
void removeFirst() {
rewriteRun(
//language=java
java(
"""
import java.util.*;
class Foo {
String bar(List<String> collection) {
return collection.remove(0);
}
}
""",
"""
import java.util.*;
class Foo {
String bar(List<String> collection) {
return collection.removeFirst();
}
}
"""
)
);
}
}

@Nested
class Last {
@Test
void getLast() {
rewriteRun(
//language=java
java(
"""
import java.util.*;
class Foo {
String bar(List<String> collection) {
return collection.get(collection.size() - 1);
}
}
""",
"""
import java.util.*;
class Foo {
String bar(List<String> collection) {
return collection.getLast();
}
}
"""
)
);
}

@Test
void addLast() {
rewriteRun(
//language=java
java(
"""
import java.util.*;
class Foo {
void bar(List<String> collection) {
collection.add(collection.size() - 1, "last");
}
}
""",
"""
import java.util.*;
class Foo {
void bar(List<String> collection) {
collection.addLast("last");
}
}
"""
)
);
}

@Test
void removeLast() {
rewriteRun(
//language=java
java(
"""
import java.util.*;
class Foo {
String bar(List<String> collection) {
return collection.remove(collection.size() - 1);
}
}
""",
"""
import java.util.*;
class Foo {
String bar(List<String> collection) {
return collection.removeLast();
}
}
"""
)
);
}
}

@Nested
class NoChange {

// TODO SortedSet throws UnsupportedOperationException on addFirst / addLast
// TODO last of different collection
}
}

0 comments on commit 8c1b8dc

Please sign in to comment.