diff --git a/src/main/java/org/openrewrite/java/apache/httpclient5/AddTimeUnitArgument.java b/src/main/java/org/openrewrite/java/apache/httpclient5/AddTimeUnitArgument.java new file mode 100644 index 000000000..ec1f082ab --- /dev/null +++ b/src/main/java/org/openrewrite/java/apache/httpclient5/AddTimeUnitArgument.java @@ -0,0 +1,98 @@ +/* + * Copyright 2023 the original author or 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 + *

+ * https://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 org.openrewrite.java.apache.httpclient5; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.StringUtils; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.J; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + + +@Value +@EqualsAndHashCode(callSuper = true) +public class AddTimeUnitArgument extends Recipe { + + @Option(displayName = "Method pattern", + description = "A method pattern that is used to find matching method invocations.", + example = "org.apache.http.client.config.RequestConfig.Builder setConnectionRequestTimeout(int)") + String methodPattern; + + @Option(displayName = "Time Unit", + description = "The TimeUnit enum value we want to add to the method invocation. Defaults to `MILLISECONDS`.", + example = "MILLISECONDS", + required = false) + @Nullable + TimeUnit timeUnit; + + @Override + public String getDisplayName() { + return "Adds a TimeUnit argument to the matched method invocations"; + } + + @Override + public String getDescription() { + return "In Apache Http Client 5.x migration, an extra TimeUnit argument is required in the timeout and duration methods. " + + "Previously in 4.x, all these methods were implicitly having the timeout or duration expressed in milliseconds, " + + "but in 5.x the unit of the timeout or duration is required. So, by default this recipe adds " + + "`TimeUnit.MILLISECONDS`, it is possible to specify this as a parameter. Since all affected methods of " + + "the Apache Http Client 5.x migration only have one integer/long argument, the recipe applies with matched method " + + "invocations of exactly one parameter."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + final MethodMatcher matcher = new MethodMatcher(methodPattern); + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) { + J.MethodInvocation m = super.visitMethodInvocation(method, executionContext); + if (matcher.matches(m)) { + JavaTemplate template = JavaTemplate + .builder(StringUtils.repeat("#{any()}, ", m.getArguments().size()) + "TimeUnit.#{}") + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpath("httpclient5", "httpcore5")) + .imports("java.util.concurrent.TimeUnit") + .build(); + + List arguments = new ArrayList<>(m.getArguments()); + arguments.add(timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS); + + m = template.apply( + updateCursor(m), + m.getCoordinates().replaceArguments(), + arguments.toArray(new Object[0]) + ); + maybeAddImport("java.util.concurrent.TimeUnit"); + } + return m; + } + }; + } +} diff --git a/src/main/java/org/openrewrite/java/apache/httpclient5/NewStatusLine.java b/src/main/java/org/openrewrite/java/apache/httpclient5/NewStatusLine.java index efe660e97..6e37cd306 100644 --- a/src/main/java/org/openrewrite/java/apache/httpclient5/NewStatusLine.java +++ b/src/main/java/org/openrewrite/java/apache/httpclient5/NewStatusLine.java @@ -38,7 +38,6 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { return new JavaVisitor() { - final MethodMatcher matcher = new MethodMatcher("org.apache.hc.core5.http.HttpResponse getStatusLine()"); final JavaTemplate template = JavaTemplate.builder("new StatusLine(#{any(org.apache.hc.core5.http.HttpResponse)})") .javaParser(JavaParser.fromJavaVersion().classpath("httpcore5")) diff --git a/src/main/java/org/openrewrite/java/apache/httpclient5/UseTimeValue.java b/src/main/java/org/openrewrite/java/apache/httpclient5/UseTimeValue.java deleted file mode 100644 index 1458f06d2..000000000 --- a/src/main/java/org/openrewrite/java/apache/httpclient5/UseTimeValue.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2023 the original author or 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 - *

- * https://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 org.openrewrite.java.apache.httpclient5; - -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.JavaParser; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesType; -import org.openrewrite.java.tree.J; - -import java.util.Arrays; -import java.util.List; - -public class UseTimeValue extends Recipe { - @Override - public String getDisplayName() { - return "Use `TimeValue` class to define time values (duration)"; - } - - @Override - public String getDescription() { - return "Use `TimeValue` class to define time values (duration)."; - } - - @Override - public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.apache.hc..*", true), new JavaIsoVisitor() { - - List methodMatchers = Arrays.asList( - new MethodMatcher("org.apache.hc.core5.http.io.SocketConfig.Builder setSoLinger(int)") - ); - - JavaTemplate template = JavaTemplate.builder("TimeValue.ofMilliseconds(#{})") - .contextSensitive() - .javaParser(JavaParser.fromJavaVersion().classpath( - "httpclient5", "httpcore5" - )) - .imports("org.apache.hc.core5.util.TimeValue") - .build(); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - - if (methodMatches(m)) { - m = template.apply(updateCursor(m), m.getCoordinates().replaceArguments(), m.getArguments().get(0)); - maybeAddImport("org.apache.hc.core5.util.TimeValue"); - } - return m; - } - - private boolean methodMatches(J.MethodInvocation method) { - for (MethodMatcher matcher : methodMatchers) { - if (matcher.matches(method)) { - return true; - } - } - return false; - } - }); - } -} diff --git a/src/main/java/org/openrewrite/java/apache/httpclient5/UseTimeout.java b/src/main/java/org/openrewrite/java/apache/httpclient5/UseTimeout.java deleted file mode 100644 index de17e1c5c..000000000 --- a/src/main/java/org/openrewrite/java/apache/httpclient5/UseTimeout.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2023 the original author or 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 - *

- * https://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 org.openrewrite.java.apache.httpclient5; - -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.JavaParser; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesType; -import org.openrewrite.java.tree.J; - -import java.util.Arrays; -import java.util.List; - - -public class UseTimeout extends Recipe { - @Override - public String getDisplayName() { - return "Use `Timeout` class to define timeouts"; - } - - @Override - public String getDescription() { - return "Use Timeout class to define timeouts."; - } - - @Override - public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.apache.hc..*", true), new JavaIsoVisitor() { - - final List methodMatchers = Arrays.asList( - new MethodMatcher("org.apache.hc.client5.http.config.RequestConfig.Builder setConnectionRequestTimeout(int)"), - new MethodMatcher("org.apache.hc.client5.http.config.RequestConfig.Builder setConnectTimeout(int)"), - new MethodMatcher("org.apache.hc.client5.http.config.RequestConfig.Builder setResponseTimeout(int)"), - new MethodMatcher("org.apache.hc.core5.http.io.SocketConfig.Builder setSoTimeout(int)") - ); - - final JavaTemplate template = JavaTemplate.builder("Timeout.ofMilliseconds(#{})") - .contextSensitive() - .javaParser(JavaParser.fromJavaVersion().classpath( - "httpclient5", "httpcore5" - )) - .imports("org.apache.hc.core5.util.Timeout") - .build(); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = super.visitMethodInvocation(method, ctx); - - if (methodMatches(m)) { - m = template.apply(updateCursor(m), m.getCoordinates().replaceArguments(), m.getArguments().get(0)); - maybeAddImport("org.apache.hc.core5.util.Timeout"); - } - return m; - } - - private boolean methodMatches(J.MethodInvocation method) { - for (MethodMatcher matcher : methodMatchers) { - if (matcher.matches(method)) { - return true; - } - } - return false; - } - }); - } -} diff --git a/src/main/resources/META-INF/rewrite/apache-httpclient-5.yml b/src/main/resources/META-INF/rewrite/apache-httpclient-5.yml index ba4190c99..40689c480 100644 --- a/src/main/resources/META-INF/rewrite/apache-httpclient-5.yml +++ b/src/main/resources/META-INF/rewrite/apache-httpclient-5.yml @@ -37,10 +37,8 @@ recipeList: overrideManagedVersion: true - org.openrewrite.java.apache.httpclient5.UpgradeApacheHttpClient_5_ClassMapping - org.openrewrite.java.apache.httpclient5.UpgradeApacheHttpClient_5_DeprecatedMethods - - org.openrewrite.java.apache.httpclient5.UseTimeout - - org.openrewrite.java.apache.httpclient5.UseTimeValue + - org.openrewrite.java.apache.httpclient5.UpgradeApacheHttpClient_5_TimeUnit - org.openrewrite.java.apache.httpclient5.StatusLine - --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.apache.httpclient5.UpgradeApacheHttpClient_5_ClassMapping @@ -415,6 +413,22 @@ recipeList: newMethodName: setResponseTimeout --- type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.apache.httpclient5.UpgradeApacheHttpClient_5_TimeUnit +displayName: Adds `TimeUnit` to timeouts and duration methods +description: Apache HttpClient 5.x Timeout and duration methods need an extra the TimeUnit argument. This recipe uses milliseconds as a default unit. +recipeList: + - org.openrewrite.java.apache.httpclient5.AddTimeUnitArgument: + methodPattern: org.apache.hc.client5.http.config.RequestConfig.Builder setConnectionRequestTimeout(int) + - org.openrewrite.java.apache.httpclient5.AddTimeUnitArgument: + methodPattern: org.apache.hc.client5.http.config.RequestConfig.Builder setConnectTimeout(int) + - org.openrewrite.java.apache.httpclient5.AddTimeUnitArgument: + methodPattern: org.apache.hc.client5.http.config.RequestConfig.Builder setResponseTimeout(int) + - org.openrewrite.java.apache.httpclient5.AddTimeUnitArgument: + methodPattern: org.apache.hc.core5.http.io.SocketConfig.Builder setSoLinger(int) + - org.openrewrite.java.apache.httpclient5.AddTimeUnitArgument: + methodPattern: org.apache.hc.core5.http.io.SocketConfig.Builder setSoTimeout(int) +--- +type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.apache.httpclient5.StatusLine displayName: Migrate to ApacheHttpClient 5.x deprecated methods from 4.x description: Migrates deprecated methods to their equivalent ones in 5.x diff --git a/src/test/java/org/openrewrite/java/apache/httpclient5/AddTimeUnitArgumentTest.java b/src/test/java/org/openrewrite/java/apache/httpclient5/AddTimeUnitArgumentTest.java new file mode 100644 index 000000000..91d6df699 --- /dev/null +++ b/src/test/java/org/openrewrite/java/apache/httpclient5/AddTimeUnitArgumentTest.java @@ -0,0 +1,142 @@ +/* + * Copyright 2023 the original author or 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 + *

+ * https://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 org.openrewrite.java.apache.httpclient5; + +import org.junit.jupiter.api.Test; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpecs; + +import java.util.concurrent.TimeUnit; + +import static org.openrewrite.java.Assertions.java; + +public class AddTimeUnitArgumentTest implements RewriteTest { + //language=java + private static final SourceSpecs stubCode = java(""" + import java.util.concurrent.TimeUnit; + + class A { + private long value; + private float foo; + private TimeUnit timeunit; + + A method(int value) { + this.value = value; + this.timeunit = TimeUnit.MILLISECONDS; + return this; + } + + A method(long value, TimeUnit timeunit) { + this.value = value; + this.timeunit = timeunit; + return this; + } + + A method(int value, float foo) { + this.value = value; + this.foo = foo; + this.timeunit = TimeUnit.MILLISECONDS; + return this; + } + + A method(long value, float foo, TimeUnit timeunit) { + this.value = value; + this.foo = foo; + this.timeunit = timeunit; + return this; + } + } + """); + + @Test + void addTimeUnitDefaultMilliseconds() { + rewriteRun( + spec -> spec.recipe(new AddTimeUnitArgument("A method(int)", null)), + stubCode, + //language=java + java(""" + class B { + void test() { + A a = new A(); + a.method(100); + } + } + """, """ + import java.util.concurrent.TimeUnit; + + class B { + void test() { + A a = new A(); + a.method(100, TimeUnit.MILLISECONDS); + } + } + """) + ); + } + + @Test + void addTimeUnitSpecificTimeUnit() { + rewriteRun( + spec -> spec.recipe(new AddTimeUnitArgument("A method(int)", TimeUnit.SECONDS)), + stubCode, + //language=java + java(""" + class B { + void test() { + A a = new A(); + a.method(100); + } + } + """, """ + import java.util.concurrent.TimeUnit; + + class B { + void test() { + A a = new A(); + a.method(100, TimeUnit.SECONDS); + } + } + """) + ); + } + + @Test + void doesModifyMethodsWithMoreThanOneArgument() { + rewriteRun( + spec -> spec.recipe(new AddTimeUnitArgument("A method(int, float)", null)), + //language=java + stubCode, + //language=java + java(""" + class B { + void test() { + A a = new A(); + a.method(100, 1.0f); + } + } + """, """ + import java.util.concurrent.TimeUnit; + + class B { + void test() { + A a = new A(); + a.method(100, 1.0f, TimeUnit.MILLISECONDS); + } + } + """) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/apache/httpclient5/UpgradeApacheHttpClient5Test.java b/src/test/java/org/openrewrite/java/apache/httpclient5/UpgradeApacheHttpClient5Test.java index 29ea21985..69a062bd3 100644 --- a/src/test/java/org/openrewrite/java/apache/httpclient5/UpgradeApacheHttpClient5Test.java +++ b/src/test/java/org/openrewrite/java/apache/httpclient5/UpgradeApacheHttpClient5Test.java @@ -115,7 +115,7 @@ void method(HttpEntity entity, String urlStr) throws Exception { } @Test - void useTimeoutClass() { + void addTimeUnitsToTimeoutAndDurationMethods() { rewriteRun( //language=java java(""" @@ -124,56 +124,36 @@ void useTimeoutClass() { class A { void method() { + int connectTimeout = 500; RequestConfig.custom() .setConnectionRequestTimeout(300) - .setConnectTimeout(500) - .setSocketTimeout(1500); + .setConnectTimeout(connectTimeout) + .setSocketTimeout(connectTimeout * 10); + long linger = 420; SocketConfig.custom() - .setSoTimeout(1000); + .setSoTimeout(1000) + .setSoLinger((int) linger); } } """, """ import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.core5.http.io.SocketConfig; - import org.apache.hc.core5.util.Timeout; + + import java.util.concurrent.TimeUnit; class A { void method() { + int connectTimeout = 500; RequestConfig.custom() - .setConnectionRequestTimeout(Timeout.ofMilliseconds(300)) - .setConnectTimeout(Timeout.ofMilliseconds(500)) - .setResponseTimeout(Timeout.ofMilliseconds(1500)); + .setConnectionRequestTimeout(300, TimeUnit.MILLISECONDS) + .setConnectTimeout(connectTimeout, TimeUnit.MILLISECONDS) + .setResponseTimeout(connectTimeout * 10, TimeUnit.MILLISECONDS); + long linger = 420; SocketConfig.custom() - .setSoTimeout(Timeout.ofMilliseconds(1000)); - } - } - """) - ); - } - - @Test - void useTimeValueClass() { - rewriteRun( - //language=java - java(""" - import org.apache.http.config.SocketConfig; - - class A { - void method() { - SocketConfig.custom() - .setSoLinger(500); - } - } - """, """ - import org.apache.hc.core5.http.io.SocketConfig; - import org.apache.hc.core5.util.TimeValue; - - class A { - void method() { - SocketConfig.custom() - .setSoLinger(TimeValue.ofMilliseconds(500)); + .setSoTimeout(1000, TimeUnit.MILLISECONDS) + .setSoLinger((int) linger, TimeUnit.MILLISECONDS); } } """)