Auto backport java util Map / Set / List of methods (#76038)

This introduces the infrastructure for auto backporting source to java 8 compatibility code by using the rewrite library (https://github.com/openrewrite/). We introduce a custom rewrite plugin as the original rewrite plugin uses tons of deprecated api and non efficient gradle api. Also we introduce a rewrite task per project rewrite task makes error analysis easier IMO.

We have the configuration of the backporting in a rewrite.yml file that contains three types of backports

java.util.Map.of(...) to org.elasticsearch.core.Map(...)
java.util.List.of(...) to org.elasticsearch.core.List(...)
java.util.Set.of(...) to org.elasticsearch.core.Set(...)
The configuration uses custom rules I wrote (https://github.com/breskeby/java-recipes) as the build-in rewrite rules shipped or provided with rewrite do not provide proper handling of dealing with classes of the same name but different package. These rules are resolved from maven central.

An example of how all this backporting works in action can be seen in the functional test provided as part of this PR.
This commit is contained in:
Rene Groeschke 2021-09-21 17:15:45 +02:00 committed by GitHub
parent f31e79d9e3
commit 194fafc582
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1722 additions and 2 deletions

View file

@ -123,6 +123,10 @@ gradlePlugin {
id = 'elasticsearch.rest-test'
implementationClass = 'org.elasticsearch.gradle.internal.test.RestTestPlugin'
}
rewrite {
id = 'elasticsearch.rewrite'
implementationClass = 'org.elasticsearch.gradle.internal.rewrite.RewritePlugin'
}
standaloneRestTest {
id = 'elasticsearch.standalone-rest-test'
implementationClass = 'org.elasticsearch.gradle.internal.test.StandaloneRestTestPlugin'
@ -197,13 +201,15 @@ repositories {
configurations {
integTestRuntimeOnly.extendsFrom(testRuntimeOnly)
}
dependencies {
api localGroovy()
api gradleApi()
api "org.elasticsearch:build-conventions:$version"
api "org.elasticsearch.gradle:build-tools:$version"
api "org.openrewrite:rewrite-java:7.10.0"
api 'org.openrewrite:plugin:5.6.0'
api 'commons-codec:commons-codec:1.12'
api 'org.apache.commons:commons-compress:1.19'
api 'org.apache.ant:ant:1.10.8'

View file

@ -0,0 +1,263 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.gradle.internal.rewrite;
import org.elasticsearch.gradle.fixtures.AbstractGradleFuncTest
class AutobackportFuncTest extends AbstractGradleFuncTest {
def setup() {
file("src/main/java/org/elasticsearch/core/List.java") << """
package org.elasticsearch.core;
public class List {
public static <T> java.util.List<T> of(T... entries) {
//impl does not matter
return new java.util.ArrayList();
}
}
"""
file("src/main/java/org/elasticsearch/core/Set.java") << """
package org.elasticsearch.core;
public class Set {
public static <T> java.util.Set<T> of(T... entries) {
//impl does not matter
return new java.util.HashSet();
}
}
"""
}
def "can run rewrite to backport java util methods"() {
when:
setupRewriteYamlConfig()
def sourceFile = file("src/main/java/org/acme/SomeClass.java")
sourceFile << """
package org.acme;
import java.util.List;
import java.util.Map;
import java.util.Set;
class SomeClass {
public void someMethod() {
List myList = List.of("some", "non", "java8", "code");
Set mySet = Set.of("some", "non", "java8", "code");
Map myMap = Map.of(List.of("some", "non"), Set.of("java8", "code"));
}
}
"""
buildFile.text = """
plugins {
id 'java'
id 'elasticsearch.rewrite'
}
rewrite {
rewriteVersion = "7.11.0"
activeRecipe("org.elasticsearch.java.backport.ListOfBackport",
"org.elasticsearch.java.backport.MapOfBackport",
"org.elasticsearch.java.backport.SetOfBackport")
configFile = rootProject.file("rewrite.yml")
}
repositories {
mavenCentral()
}
dependencies {
rewrite "org.openrewrite:rewrite-java-11"
rewrite "org.openrewrite:rewrite-java"
}
"""
then:
gradleRunner("rewrite", '--stacktrace').build()
sourceFile.text == """
package org.acme;
import java.util.List;
import java.util.Map;
import java.util.Set;
class SomeClass {
public void someMethod() {
List myList = org.elasticsearch.core.List.of("some", "non", "java8", "code");
Set mySet = org.elasticsearch.core.Set.of("some", "non", "java8", "code");
Map myMap = org.elasticsearch.core.Map.of(org.elasticsearch.core.List.of("some", "non"), org.elasticsearch.core.Set.of("java8", "code"));
}
}
"""
}
def "converts new full qualified usage of elastic util methods where possible"() {
when:
setupRewriteYamlConfig()
def sourceFile = file("src/main/java/org/acme/SomeClass.java")
sourceFile << """
package org.acme;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
class SomeClass {
public void someMethod() {
Collection myList = List.of("some", "non", "java8", "code");
Collection mySet = Set.of("some", "non", "java8", "code");
Map myMap = Map.of(List.of("some", "non"), Set.of("java8", "code"));
}
}
"""
buildFile.text = """
plugins {
id 'java'
id 'elasticsearch.rewrite'
}
rewrite {
rewriteVersion = "7.10.0"
activeRecipe("org.elasticsearch.java.backport.ListOfBackport",
"org.elasticsearch.java.backport.MapOfBackport",
"org.elasticsearch.java.backport.SetOfBackport")
configFile = rootProject.file("rewrite.yml")
}
repositories {
mavenCentral()
}
dependencies {
rewrite "org.openrewrite:rewrite-java-11"
rewrite "org.openrewrite:rewrite-java"
}
"""
then:
gradleRunner("rewrite", '--stacktrace').build()
sourceFile.text == """
package org.acme;
import org.elasticsearch.core.List;
import org.elasticsearch.core.Set;
import java.util.Collection;
import java.util.Map;
class SomeClass {
public void someMethod() {
Collection myList = List.of("some", "non", "java8", "code");
Collection mySet = Set.of("some", "non", "java8", "code");
Map myMap = org.elasticsearch.core.Map.of(List.of("some", "non"), Set.of("java8", "code"));
}
}
"""
}
def "compatible code is not changed"() {
when:
setupRewriteYamlConfig()
def sourceFile = file("src/main/java/org/acme/SomeClass.java")
sourceFile << """
package org.acme;
import java.util.Collection;
class SomeClass {
public void someMethod() {
Collection myList = org.elasticsearch.core.List.of("some", "non", "java8", "code");
Collection mySet = org.elasticsearch.core.Set.of("some", "non", "java8", "code");
}
}
"""
buildFile.text = """
plugins {
id 'java'
id 'elasticsearch.rewrite'
}
rewrite {
rewriteVersion = "7.10.0"
activeRecipe("org.elasticsearch.java.backport.ListOfBackport",
"org.elasticsearch.java.backport.MapOfBackport",
"org.elasticsearch.java.backport.SetOfBackport")
configFile = rootProject.file("rewrite.yml")
}
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
rewrite "org.openrewrite:rewrite-java-11"
rewrite "org.openrewrite:rewrite-java"
}
"""
then:
gradleRunner("rewrite", '--stacktrace').build()
sourceFile.text == """
package org.acme;
import java.util.Collection;
class SomeClass {
public void someMethod() {
Collection myList = org.elasticsearch.core.List.of("some", "non", "java8", "code");
Collection mySet = org.elasticsearch.core.Set.of("some", "non", "java8", "code");
}
}
"""
}
private File setupRewriteYamlConfig() {
file("rewrite.yml") << """
type: specs.openrewrite.org/v1beta/recipe
name: org.elasticsearch.java.backport.ListOfBackport
displayName: Use `org.elasticsearch.core.Lists#of(..)` not java.util.List.of#(..)
description: Java 8 does not support the `java.util.List#of(..)`.
tags:
- backport
recipeList:
- org.elasticsearch.gradle.internal.rewrite.rules.ChangeMethodOwnerRecipe:
originFullQualifiedClassname: java.util.List
targetFullQualifiedClassname: org.elasticsearch.core.List
methodName: of
---
type: specs.openrewrite.org/v1beta/recipe
name: org.elasticsearch.java.backport.MapOfBackport
displayName: Use `org.elasticsearch.core.Maps#of(..)` not java.util.Map.of#(..)
description: Java 8 does not support the `java.util.Map#of(..)`.
tags:
- backport
recipeList:
- org.elasticsearch.gradle.internal.rewrite.rules.ChangeMethodOwnerRecipe:
originFullQualifiedClassname: java.util.Map
targetFullQualifiedClassname: org.elasticsearch.core.Map
methodName: of
---
type: specs.openrewrite.org/v1beta/recipe
name: org.elasticsearch.java.backport.SetOfBackport
displayName: Use `org.elasticsearch.core.Sets#of(..)` not java.util.Set.of#(..)
description: Java 8 does not support the `java.util.Set#of(..)`.
tags:
- backport
recipeList:
- org.elasticsearch.gradle.internal.rewrite.rules.ChangeMethodOwnerRecipe:
originFullQualifiedClassname: java.util.Set
targetFullQualifiedClassname: org.elasticsearch.core.Set
methodName: of
"""
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import org.elasticsearch.gradle.internal.rewrite.RewritePlugin
allprojects {
apply plugin: 'elasticsearch.rewrite'
rewrite {
rewriteVersion = "7.11.0"
activeRecipe("org.elasticsearch.java.backport.ListOfBackport",
"org.elasticsearch.java.backport.MapOfBackport",
"org.elasticsearch.java.backport.SetOfBackport")
configFile = rootProject.file("rewrite.yml")
}
repositories {
mavenCentral()
}
configurations {
rewrite {
resolutionStrategy {
force 'org.jetbrains:annotations:21.0.1'
force 'org.slf4j:slf4j-api:1.7.31'
force 'org.jetbrains.kotlin:kotlin-stdlib:1.5.10'
force 'org.jetbrains.kotlin:kotlin-stdlib-common:1.5.10'
}
}
}
dependencies {
rewrite "org.openrewrite:rewrite-java-11"
rewrite "org.openrewrite:rewrite-java"
}
if (gradle.getStartParameter().getTaskNames().any { it.endsWith(RewritePlugin.REWRITE_TASKNAME) }) {
afterEvaluate {
def java = project.getExtensions().findByType(JavaPluginExtension.class);
if (java) {
java.setSourceCompatibility(JavaVersion.VERSION_11)
java.setTargetCompatibility(JavaVersion.VERSION_11)
}
}
}
tasks.named('rewrite').configure {rewrite ->
rewrite.getMaxHeapSize().set("2g")
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.gradle.internal.rewrite;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import static java.util.Arrays.asList;
public class RewriteExtension {
private final List<String> activeRecipes = new ArrayList<>();
private File configFile;
private String rewriteVersion = "7.10.0";
public void setConfigFile(File configFile) {
this.configFile = configFile;
}
public File getConfigFile() {
return configFile;
}
public void activeRecipe(String... recipes) {
activeRecipes.addAll(asList(recipes));
}
public void setActiveRecipes(List<String> activeRecipes) {
this.activeRecipes.clear();
this.activeRecipes.addAll(activeRecipes);
}
public List<String> getActiveRecipes() {
return activeRecipes;
}
public String getRewriteVersion() {
return rewriteVersion;
}
public void setRewriteVersion(String value) {
rewriteVersion = value;
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.gradle.internal.rewrite;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.workers.WorkParameters;
import java.io.File;
public interface RewriteParameters extends WorkParameters {
ListProperty<String> getActiveRecipes();
ListProperty<String> getActiveStyles();
ListProperty<File> getAllJavaPaths();
ListProperty<File> getAllDependencyPaths();
RegularFileProperty getProjectDirectory();
RegularFileProperty getConfigFile();
}

View file

@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.gradle.internal.rewrite;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.DependencyResolveDetails;
import org.gradle.api.artifacts.ModuleVersionSelector;
import org.gradle.api.file.ProjectLayout;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.ProviderFactory;
import javax.inject.Inject;
/**
* Adds the RewriteExtension to the current project and registers tasks per-sourceSet.
* Only needs to be applied to projects with java sources. No point in applying this to any project that does
* not have java sources of its own, such as the root project in a multi-project builds.
*/
public class RewritePlugin implements Plugin<Project> {
public static final String REWRITE_TASKNAME = "rewrite";
private ProviderFactory providerFactory;
private ProjectLayout projectLayout;
@Inject
public RewritePlugin(ProviderFactory providerFactory, ProjectLayout projectLayout) {
this.providerFactory = providerFactory;
this.projectLayout = projectLayout;
}
@Override
public void apply(Project project) {
final RewriteExtension extension = project.getExtensions().create("rewrite", RewriteExtension.class);
// Rewrite module dependencies put here will be available to all rewrite tasks
Configuration rewriteConf = project.getConfigurations().maybeCreate("rewrite");
rewriteConf.getResolutionStrategy().eachDependency(details -> {
ModuleVersionSelector requested = details.getRequested();
if (requested.getGroup().equals("org.openrewrite") && requested.getVersion().isBlank() || requested.getVersion() == null) {
details.useVersion(extension.getRewriteVersion());
}
});
RewriteTask rewriteTask = project.getTasks().create(REWRITE_TASKNAME, RewriteTask.class, rewriteConf, extension);
rewriteTask.getActiveRecipes().convention(providerFactory.provider(() -> extension.getActiveRecipes()));
rewriteTask.getConfigFile().convention(projectLayout.file(providerFactory.provider(() -> extension.getConfigFile())));
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> {
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
javaPluginExtension.getSourceSets().all( sourceSet -> {
rewriteTask.getSourceFiles().from(sourceSet.getAllSource());
rewriteTask.getDependencyFiles().from(sourceSet.getCompileClasspath());
});
});
}
}

View file

@ -0,0 +1,618 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.gradle.internal.rewrite;
import javax.annotation.Nullable;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
/**
* Provides access to Rewrite classes resolved and loaded from the supplied dependency configuration.
*/
@SuppressWarnings({"unchecked", "UnusedReturnValue", "InnerClassMayBeStatic"})
public class RewriteReflectiveFacade {
private ClassLoader getClassLoader() {
return getClass().getClassLoader();
}
private List<SourceFile> parseBase(Object real, Iterable<Path> sourcePaths, Path baseDir, RewriteReflectiveFacade.InMemoryExecutionContext ctx) {
try {
Class<?> executionContextClass = getClass().getClassLoader().loadClass("org.openrewrite.ExecutionContext");
List<Object> results = (List<Object>) real.getClass()
.getMethod("parse", Iterable.class, Path.class, executionContextClass)
.invoke(real, sourcePaths, baseDir, ctx.real);
return results.stream()
.map(RewriteReflectiveFacade.SourceFile::new)
.collect(toList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public class EnvironmentBuilder {
private final Object real;
private EnvironmentBuilder(Object real) {
this.real = real;
}
public RewriteReflectiveFacade.EnvironmentBuilder scanRuntimeClasspath(String... acceptPackages) {
try {
real.getClass().getMethod("scanRuntimeClasspath", String[].class).invoke(real, new Object[]{acceptPackages});
} catch (Exception e) {
throw new RuntimeException(e);
}
return this;
}
public RewriteReflectiveFacade.EnvironmentBuilder scanJar(Path jar, ClassLoader classLoader) {
try {
real.getClass().getMethod("scanJar", Path.class, ClassLoader.class).invoke(real, jar, classLoader);
} catch (Exception e) {
throw new RuntimeException(e);
}
return this;
}
public RewriteReflectiveFacade.EnvironmentBuilder scanJar(Path jar) {
try {
real.getClass().getMethod("scanJar", Path.class, ClassLoader.class).invoke(real, jar, getClassLoader());
} catch (Exception e) {
throw new RuntimeException(e);
}
return this;
}
public RewriteReflectiveFacade.EnvironmentBuilder scanUserHome() {
try {
real.getClass().getMethod("scanUserHome").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
return this;
}
public RewriteReflectiveFacade.EnvironmentBuilder load(RewriteReflectiveFacade.YamlResourceLoader yamlResourceLoader) {
try {
Class<?> resourceLoaderClass = getClassLoader().loadClass("org.openrewrite.config.ResourceLoader");
real.getClass().getMethod("load", resourceLoaderClass).invoke(real, yamlResourceLoader.real);
} catch (Exception e) {
throw new RuntimeException(e);
}
return this;
}
public RewriteReflectiveFacade.Environment build() {
try {
return new RewriteReflectiveFacade.Environment(real.getClass().getMethod("build").invoke(real));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class SourceFile {
private final Object real;
private SourceFile(Object real) {
this.real = real;
}
public Path getSourcePath() {
try {
return (Path) real.getClass().getMethod("getSourcePath").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String print() {
try {
return (String) real.getClass().getMethod("print").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class Result {
private final Object real;
private Result(Object real) {
this.real = real;
}
@Nullable
public RewriteReflectiveFacade.SourceFile getBefore() {
try {
return new RewriteReflectiveFacade.SourceFile(real.getClass().getMethod("getBefore").invoke(real));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Nullable
public RewriteReflectiveFacade.SourceFile getAfter() {
try {
return new RewriteReflectiveFacade.SourceFile(real.getClass().getMethod("getAfter").invoke(real));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public List<RewriteReflectiveFacade.Recipe> getRecipesThatMadeChanges() {
try {
Set<Object> result = (Set<Object>) real.getClass().getMethod("getRecipesThatMadeChanges").invoke(real);
return result.stream()
.map(RewriteReflectiveFacade.Recipe::new)
.collect(Collectors.toList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String diff() {
try {
return (String) real.getClass().getMethod("diff").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class Recipe {
private final Object real;
private Recipe(Object real) {
this.real = real;
}
public List<RewriteReflectiveFacade.Result> run(List<RewriteReflectiveFacade.SourceFile> sources) {
try {
List<Object> unwrappedSources = sources.stream().map(it -> it.real).collect(toList());
List<Object> result = (List<Object>) real.getClass().getMethod("run", List.class)
.invoke(real, unwrappedSources);
return result.stream()
.map(RewriteReflectiveFacade.Result::new)
.collect(toList());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public List<RewriteReflectiveFacade.Result> run(List<RewriteReflectiveFacade.SourceFile> sources,
RewriteReflectiveFacade.InMemoryExecutionContext ctx) {
try {
Class<?> executionContextClass = getClassLoader().loadClass("org.openrewrite.ExecutionContext");
List<Object> unwrappedSources = sources.stream().map(it -> it.real).collect(toList());
List<Object> result = (List<Object>) real.getClass().getMethod("run", List.class, executionContextClass)
.invoke(real, unwrappedSources, ctx.real);
return result.stream()
.map(RewriteReflectiveFacade.Result::new)
.collect(toList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getName() {
try {
return (String) real.getClass().getMethod("getName").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Collection<Validated> validateAll() {
try {
List<Object> results = (List<Object>) real.getClass().getMethod("validateAll").invoke(real);
return results.stream().map(r -> {
String canonicalName = r.getClass().getCanonicalName();
if (canonicalName.equals("org.openrewrite.Validated.Invalid")) {
return new RewriteReflectiveFacade.Validated.Invalid(r);
} else if (canonicalName.equals("org.openrewrite.Validated.Both")) {
return new RewriteReflectiveFacade.Validated.Both(r);
} else {
return null;
}
}).filter(Objects::nonNull).collect(toList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public interface Validated {
Object getReal();
default List<RewriteReflectiveFacade.Validated.Invalid> failures() {
try {
Object real = getReal();
List<Object> results = (List<Object>) real.getClass().getMethod("failures").invoke(real);
return results.stream().map(RewriteReflectiveFacade.Validated.Invalid::new).collect(toList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
class Invalid implements RewriteReflectiveFacade.Validated {
private final Object real;
public Invalid(Object real) {
this.real = real;
}
@Override
public Object getReal() {
return real;
}
public String getProperty() {
try {
return (String) real.getClass().getMethod("getProperty").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getMessage() {
try {
return (String) real.getClass().getMethod("getMessage").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Throwable getException() {
try {
return (Throwable) real.getClass().getMethod("getException").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class Both implements RewriteReflectiveFacade.Validated {
private final Object real;
public Both(Object real) {
this.real = real;
}
@Override
public Object getReal() {
return real;
}
}
}
public class NamedStyles {
private final Object real;
private NamedStyles(Object real) {
this.real = real;
}
public String getName() {
try {
return (String) real.getClass().getMethod("getName").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class Environment {
private final Object real;
private Environment(Object real) {
this.real = real;
}
public List<RewriteReflectiveFacade.NamedStyles> activateStyles(Iterable<String> activeStyles) {
try {
//noinspection unchecked
List<Object> raw = (List<Object>) real.getClass()
.getMethod("activateStyles", Iterable.class)
.invoke(real, activeStyles);
return raw.stream()
.map(RewriteReflectiveFacade.NamedStyles::new)
.collect(toList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public RewriteReflectiveFacade.Recipe activateRecipes(Iterable<String> activeRecipes) {
try {
return new RewriteReflectiveFacade.Recipe(real.getClass()
.getMethod("activateRecipes", Iterable.class)
.invoke(real, activeRecipes));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Collection<RewriteReflectiveFacade.RecipeDescriptor> listRecipeDescriptors() {
try {
Collection<Object> result = (Collection<Object>) real.getClass().getMethod("listRecipeDescriptors").invoke(real);
return result.stream()
.map(RewriteReflectiveFacade.RecipeDescriptor::new)
.collect(toList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Collection<RewriteReflectiveFacade.NamedStyles> listStyles() {
try {
List<Object> raw = (List<Object>) real.getClass().getMethod("listStyles").invoke(real);
return raw.stream()
.map(RewriteReflectiveFacade.NamedStyles::new)
.collect(toList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class RecipeDescriptor {
private final Object real;
private RecipeDescriptor(Object real) {
this.real = real;
}
public String getName() {
try {
return (String) real.getClass().getMethod("getName").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getDisplayName() {
try {
return (String) real.getClass().getMethod("getDisplayName").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getDescription() {
try {
return (String) real.getClass().getMethod("getDescription").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public List<RewriteReflectiveFacade.OptionDescriptor> getOptions() {
try {
List<Object> results = (List<Object>) real.getClass().getMethod("getOptions").invoke(real);
return results.stream().map(RewriteReflectiveFacade.OptionDescriptor::new).collect(toList());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class OptionDescriptor {
private final Object real;
private OptionDescriptor(Object real) {
this.real = real;
}
public String getName() {
try {
return (String) real.getClass().getMethod("getName").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getDisplayName() {
try {
return (String) real.getClass().getMethod("getDisplayName").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getDescription() {
try {
return (String) real.getClass().getMethod("getDescription").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getType() {
try {
return (String) real.getClass().getMethod("getType").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getExample() {
try {
return (String) real.getClass().getMethod("getExample").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public boolean isRequired() {
try {
return (boolean) real.getClass().getMethod("isRequired").invoke(real);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public RewriteReflectiveFacade.EnvironmentBuilder environmentBuilder(Properties properties) {
try {
return new RewriteReflectiveFacade.EnvironmentBuilder(getClassLoader()
.loadClass("org.openrewrite.config.Environment")
.getMethod("builder", Properties.class)
.invoke(null, properties)
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public class YamlResourceLoader {
private final Object real;
private YamlResourceLoader(Object real) {
this.real = real;
}
}
public RewriteReflectiveFacade.YamlResourceLoader yamlResourceLoader(InputStream yamlInput, URI source, Properties properties) {
try {
return new RewriteReflectiveFacade.YamlResourceLoader(getClassLoader()
.loadClass("org.openrewrite.config.YamlResourceLoader")
.getConstructor(InputStream.class, URI.class, Properties.class)
.newInstance(yamlInput, source, properties)
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public class InMemoryExecutionContext {
private final Object real;
private InMemoryExecutionContext(Object real) {
this.real = real;
}
}
public RewriteReflectiveFacade.InMemoryExecutionContext inMemoryExecutionContext(Consumer<Throwable> onError) {
try {
return new RewriteReflectiveFacade.InMemoryExecutionContext(getClassLoader()
.loadClass("org.openrewrite.InMemoryExecutionContext")
.getConstructor(Consumer.class)
.newInstance(onError));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public class JavaParserBuilder {
private final Object real;
private JavaParserBuilder(Object real) {
this.real = real;
}
public RewriteReflectiveFacade.JavaParserBuilder styles(List<RewriteReflectiveFacade.NamedStyles> styles) {
try {
List<Object> unwrappedStyles = styles.stream()
.map(it -> it.real)
.collect(toList());
real.getClass().getMethod("styles", Iterable.class).invoke(real, unwrappedStyles);
return this;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public RewriteReflectiveFacade.JavaParserBuilder classpath(Collection<Path> classpath) {
try {
real.getClass().getMethod("classpath", Collection.class).invoke(real, classpath);
return this;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public RewriteReflectiveFacade.JavaParserBuilder charset(Charset charset) {
try {
real.getClass().getMethod("charset", Charset.class).invoke(real, charset);
return this;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public RewriteReflectiveFacade.JavaParserBuilder logCompilationWarningsAndErrors(boolean logCompilationWarningsAndErrors) {
try {
real.getClass().getMethod("logCompilationWarningsAndErrors", boolean.class).invoke(real, logCompilationWarningsAndErrors);
return this;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public RewriteReflectiveFacade.JavaParserBuilder relaxedClassTypeMatching(boolean relaxedClassTypeMatching) {
try {
real.getClass().getMethod("relaxedClassTypeMatching", boolean.class).invoke(real, relaxedClassTypeMatching);
return this;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public RewriteReflectiveFacade.JavaParser build() {
try {
return new RewriteReflectiveFacade.JavaParser(real.getClass().getMethod("build").invoke(real));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class JavaParser {
private final Object real;
private JavaParser(Object real) {
this.real = real;
}
public List<RewriteReflectiveFacade.SourceFile> parse(Iterable<Path> sourcePaths, Path baseDir, RewriteReflectiveFacade.InMemoryExecutionContext ctx) {
return parseBase(real, sourcePaths, baseDir, ctx);
}
}
public RewriteReflectiveFacade.JavaParserBuilder javaParserFromJavaVersion() {
try {
return new RewriteReflectiveFacade.JavaParserBuilder(getClassLoader()
.loadClass("org.openrewrite.java.Java11Parser")
.getMethod("builder")
.invoke(null));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,114 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.gradle.internal.rewrite;
import org.gradle.api.DefaultTask;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.TaskAction;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;
import javax.inject.Inject;
import java.io.File;
import java.util.List;
import java.util.Set;
import static java.util.stream.Collectors.toList;
public abstract class RewriteTask extends DefaultTask {
private final Configuration configuration;
private WorkerExecutor workerExecutor;
protected final RewriteExtension extension;
@InputFiles
abstract ConfigurableFileCollection getSourceFiles();
@InputFiles
abstract ConfigurableFileCollection getDependencyFiles();
@Input
abstract SetProperty<String> getActiveRecipes();
@InputFile
abstract RegularFileProperty getConfigFile();
@Internal
abstract Property<String> getMaxHeapSize();
@Inject
public RewriteTask(
Configuration configuration,
RewriteExtension extension,
WorkerExecutor workerExecutor
) {
this.configuration = configuration;
this.extension = extension;
this.workerExecutor = workerExecutor;
this.setGroup("rewrite");
this.setDescription("Apply the active refactoring recipes");
}
@TaskAction
public void run() {
WorkQueue workQueue = workerExecutor.processIsolation(spec -> {
spec.getClasspath().from(configuration);
spec.getForkOptions().jvmArgs("--add-exports");
spec.getForkOptions().jvmArgs("jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED");
spec.getForkOptions().jvmArgs("--add-exports");
spec.getForkOptions().jvmArgs("jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED");
spec.getForkOptions().jvmArgs("--add-exports");
spec.getForkOptions().jvmArgs("jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED");
spec.getForkOptions().jvmArgs("--add-exports");
spec.getForkOptions().jvmArgs("jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED");
spec.getForkOptions().jvmArgs("--add-exports");
spec.getForkOptions().jvmArgs("jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED");
spec.getForkOptions().jvmArgs("--add-exports");
spec.getForkOptions().jvmArgs("jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED");
spec.getForkOptions().jvmArgs("--add-exports");
spec.getForkOptions().jvmArgs("jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED");
spec.getForkOptions().jvmArgs("--add-exports");
spec.getForkOptions().jvmArgs("jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED");
spec.getForkOptions().workingDir(getProject().getProjectDir());
spec.getForkOptions().setMaxHeapSize(getMaxHeapSize().convention("1g").get());
});
List<File> javaPaths = getSourceFiles().getFiles()
.stream()
.filter(it -> it.isFile() && it.getName().endsWith(".java"))
.collect(toList());
if (javaPaths.size() > 0) {
Set<File> dependencyPaths = getDependencyFiles().getFiles();
workQueue.submit(RewriteWorker.class, parameters -> {
parameters.getAllJavaPaths().addAll(javaPaths);
parameters.getAllDependencyPaths().addAll(dependencyPaths);
parameters.getActiveRecipes().addAll(getActiveRecipes().get());
parameters.getProjectDirectory().fileProvider(getProject().getProviders().provider(() -> getProject().getProjectDir()));
parameters.getConfigFile().value(getConfigFile());
});
}
}
}

View file

@ -0,0 +1,194 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.gradle.internal.rewrite;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.workers.WorkAction;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
public abstract class RewriteWorker implements WorkAction<RewriteParameters> {
private static Logger logger = Logging.getLogger(RewriteWorker.class);
RewriteReflectiveFacade rewrite;
@Override
public void execute() {
rewrite = new RewriteReflectiveFacade();
ResultsContainer results = listResults();
writeInPlaceChanges(results);
}
private void writeInPlaceChanges(ResultsContainer results) {
for (RewriteReflectiveFacade.Result result : results.refactoredInPlace) {
assert result.getBefore() != null;
try (BufferedWriter sourceFileWriter = Files.newBufferedWriter(
results.getProjectRoot().resolve(result.getBefore().getSourcePath()))) {
assert result.getAfter() != null;
sourceFileWriter.write(result.getAfter().print());
} catch (IOException e) {
e.printStackTrace();
}
}
}
protected RewriteWorker.ResultsContainer listResults() {
Path baseDir = getParameters().getProjectDirectory().get().getAsFile().toPath();
RewriteReflectiveFacade.Environment env = environment();
List<String> activeRecipes = getParameters().getActiveRecipes().get();
List<String> activeStyles = getParameters().getActiveStyles().get();
logger.lifecycle(String.format("Using active recipe(s) %s", activeRecipes));
logger.lifecycle(String.format("Using active styles(s) %s", activeStyles));
if (activeRecipes.isEmpty()) {
return new RewriteWorker.ResultsContainer(baseDir, emptyList());
}
List<RewriteReflectiveFacade.NamedStyles> styles = env.activateStyles(activeStyles);
RewriteReflectiveFacade.Recipe recipe = env.activateRecipes(activeRecipes);
logger.lifecycle("Validating active recipes");
Collection<RewriteReflectiveFacade.Validated> validated = recipe.validateAll();
List<RewriteReflectiveFacade.Validated.Invalid> failedValidations = validated.stream()
.map(RewriteReflectiveFacade.Validated::failures)
.flatMap(Collection::stream)
.collect(toList());
if (failedValidations.isEmpty() == false) {
failedValidations.forEach(
failedValidation -> logger.error(
"Recipe validation error in " + failedValidation.getProperty() + ": " + failedValidation.getMessage(),
failedValidation.getException()
)
);
logger.error(
"Recipe validation errors detected as part of one or more activeRecipe(s). Execution will continue regardless."
);
}
RewriteReflectiveFacade.InMemoryExecutionContext ctx = executionContext();
List<RewriteReflectiveFacade.SourceFile> sourceFiles = parse(
getParameters().getAllJavaPaths().get(),
getParameters().getAllDependencyPaths().get(),
styles,
ctx);
logger.lifecycle("Running recipe(s)...");
List<RewriteReflectiveFacade.Result> results = recipe.run(sourceFiles);
return new RewriteWorker.ResultsContainer(baseDir, results);
}
protected RewriteReflectiveFacade.InMemoryExecutionContext executionContext() {
return rewrite.inMemoryExecutionContext(t -> logger.warn(t.getMessage(), t));
}
protected List<RewriteReflectiveFacade.SourceFile> parse(
List<File> javaFiles,
List<File> dependencyFiles,
List<RewriteReflectiveFacade.NamedStyles> styles,
RewriteReflectiveFacade.InMemoryExecutionContext ctx
) {
try {
List<Path> javaPaths = javaFiles.stream().map(File::toPath).map(p -> p.toAbsolutePath()).toList();
List<Path> dependencyPaths = dependencyFiles.stream().map(File::toPath).map(p -> p.toAbsolutePath()).toList();
List<RewriteReflectiveFacade.SourceFile> sourceFiles = new ArrayList<>();
if (javaPaths.size() > 0) {
logger.lifecycle(
"Parsing " + javaPaths.size() + " Java files from " + getParameters().getProjectDirectory().getAsFile().get()
);
Instant start = Instant.now();
sourceFiles.addAll(
rewrite.javaParserFromJavaVersion()
.relaxedClassTypeMatching(true)
.styles(styles)
.classpath(dependencyPaths)
.charset(Charset.forName("UTF-8"))
.logCompilationWarningsAndErrors(false)
.build()
.parse(javaPaths, getParameters().getProjectDirectory().get().getAsFile().toPath(), ctx)
);
}
return sourceFiles;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected RewriteReflectiveFacade.Environment environment() {
Properties properties = new Properties();
RewriteReflectiveFacade.EnvironmentBuilder env =
rewrite.environmentBuilder(properties).scanRuntimeClasspath().scanUserHome();
File rewriteConfig = getParameters().getConfigFile().getAsFile().getOrNull();
if(rewriteConfig != null){
if (rewriteConfig.exists()) {
try (FileInputStream is = new FileInputStream(rewriteConfig)) {
RewriteReflectiveFacade.YamlResourceLoader resourceLoader =
rewrite.yamlResourceLoader(is, rewriteConfig.toURI(), properties);
env.load(resourceLoader);
} catch (IOException e) {
throw new RuntimeException("Unable to load rewrite configuration", e);
}
} else {
logger.warn("Rewrite configuration file " + rewriteConfig + " does not exist.");
}
}
return env.build();
}
public static class ResultsContainer {
final Path projectRoot;
final List<RewriteReflectiveFacade.Result> generated = new ArrayList<>();
final List<RewriteReflectiveFacade.Result> deleted = new ArrayList<>();
final List<RewriteReflectiveFacade.Result> moved = new ArrayList<>();
final List<RewriteReflectiveFacade.Result> refactoredInPlace = new ArrayList<>();
public ResultsContainer(Path projectRoot, Collection<RewriteReflectiveFacade.Result> results) {
this.projectRoot = projectRoot;
for (RewriteReflectiveFacade.Result result : results) {
if (result.getBefore() == null && result.getAfter() == null) {
// This situation shouldn't happen / makes no sense, log and skip
continue;
}
if (result.getBefore() == null && result.getAfter() != null) {
generated.add(result);
} else if (result.getBefore() != null && result.getAfter() == null) {
deleted.add(result);
} else if (result.getBefore() != null
&& result.getBefore().getSourcePath().equals(result.getAfter().getSourcePath()) == false) {
moved.add(result);
} else {
refactoredInPlace.add(result);
}
}
}
public Path getProjectRoot() {
return projectRoot;
}
public boolean isNotEmpty() {
return generated.isEmpty() == false
|| deleted.isEmpty() == false
|| moved.isEmpty() == false
|| refactoredInPlace.isEmpty() == false;
}
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.gradle.internal.rewrite.rules;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.openrewrite.Recipe;
import org.openrewrite.internal.lang.NonNull;
public class ChangeMethodOwnerRecipe extends Recipe {
@Override
public String getDisplayName() {
return "Change Method Owner Recipe chain";
}
@JsonCreator
public ChangeMethodOwnerRecipe(@NonNull @JsonProperty("originFullQualifiedClassname") String originFullQualifiedClassname,
@NonNull @JsonProperty("methodName") String methodName,
@NonNull @JsonProperty("targetFullQualifiedClassname") String targetFullQualifiedClassname) {
doNext(new FullQualifiedChangeMethodOwnerRecipe(originFullQualifiedClassname, methodName, targetFullQualifiedClassname));
doNext(new FixFullQualifiedReferenceRecipe(targetFullQualifiedClassname, true));
}
}

View file

@ -0,0 +1,121 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.gradle.internal.rewrite.rules;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Option;
import org.openrewrite.Recipe;
import org.openrewrite.internal.lang.NonNull;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import java.util.Collections;
import java.util.Set;
import static org.elasticsearch.gradle.internal.rewrite.rules.FullQualifiedChangeMethodOwnerRecipe.METHOD_CHANGE_PREFIX;
public class FixFullQualifiedReferenceRecipe extends Recipe {
@Option(
displayName = "Fully-qualified target type name",
description = "A fully-qualified class name we want to fix.",
example = "org.elasticsearch.core.List"
)
private String fullQualifiedClassname;
@Option(
displayName = "Only on change flag",
description = "A flag indicating if this rule should only be applied on changed methods.",
example = "true"
)
private boolean onlyOnChangedSource;
@JsonCreator
public FixFullQualifiedReferenceRecipe(
@NonNull @JsonProperty("fullQualifiedClassname") String fullQualifiedClassname,
@NonNull @JsonProperty("onlyOnChangedSource") boolean onlyOnChangedSource
) {
this.fullQualifiedClassname = fullQualifiedClassname;
this.onlyOnChangedSource = onlyOnChangedSource;
}
@Override
public String getDisplayName() {
return "FixFullQualifiedReferenceRecipe";
}
@Override
public String getDescription() {
return "Converts full qualified method calls to simple calls and an import if no clashing imports are found.";
}
@Override
protected JavaVisitor<ExecutionContext> getVisitor() {
String unqualifiedIdentifier = fullQualifiedClassname.substring(fullQualifiedClassname.lastIndexOf('.') + 1);
return new Visitor(fullQualifiedClassname, unqualifiedIdentifier, onlyOnChangedSource);
}
public static class Visitor extends JavaIsoVisitor<ExecutionContext> {
private String fullQualifiedClassname;
private String unqualifiedIdentifier;
private boolean hasOriginImport;
private boolean onlyOnChangedSource;
public Visitor(String fullQualifiedClassname, String unqualifiedIdentifier, boolean onlyOnChangedSource) {
this.fullQualifiedClassname = fullQualifiedClassname;
this.unqualifiedIdentifier = unqualifiedIdentifier;
this.onlyOnChangedSource = onlyOnChangedSource;
}
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
J.MethodInvocation m = super.visitMethodInvocation(method, executionContext);
if (canChange(method, executionContext)
&& hasOriginImport == false
&& m.getSelect() instanceof J.FieldAccess
&& ((J.FieldAccess) m.getSelect()).isFullyQualifiedClassReference(fullQualifiedClassname)) {
Expression select = m.getSelect();
JavaType javaType = JavaType.buildType(fullQualifiedClassname);
J.Identifier list = J.Identifier.build(
select.getId(),
select.getPrefix(),
select.getMarkers(),
unqualifiedIdentifier,
javaType
);
m = m.withSelect(list);
maybeAddImport(fullQualifiedClassname);
}
return m;
}
private boolean canChange(J.MethodInvocation method, ExecutionContext executionContext) {
if (onlyOnChangedSource == false) {
return true;
}
Set<Object> processed = executionContext.getMessage(METHOD_CHANGE_PREFIX + fullQualifiedClassname, Collections.emptySet());
return processed.contains(method.getId());
}
@Override
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext executionContext) {
hasOriginImport = cu.getImports().stream().anyMatch(i -> i.getClassName().equals(unqualifiedIdentifier));
return super.visitCompilationUnit(cu, executionContext);
}
}
}

View file

@ -0,0 +1,140 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.gradle.internal.rewrite.rules;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Option;
import org.openrewrite.Recipe;
import org.openrewrite.internal.lang.NonNull;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import java.util.Objects;
import java.util.UUID;
import static java.util.stream.Collectors.joining;
import static java.util.stream.IntStream.rangeClosed;
public class FullQualifiedChangeMethodOwnerRecipe extends Recipe {
public static final String METHOD_CHANGE_PREFIX = FullQualifiedChangeMethodOwnerRecipe.class.getSimpleName() + "-CHANGED";
@Option(
displayName = "Fully-qualified origin class name",
description = "A fully-qualified class name of the type upon which the method is defined.",
example = "java.util.List"
)
private String originFullQualifiedClassname;
@Option(
displayName = "method name",
description = "The name of the method we want to change the origin.",
example = "of"
)
private String originMethod;
@Option(
displayName = "Fully-qualified target type name",
description = "A fully-qualified class name of the type we want to change the method call to.",
example = "org.elasticsearch.core.List"
)
private String targetFullQualifiedClassname;
@JsonCreator
public FullQualifiedChangeMethodOwnerRecipe(
@NonNull @JsonProperty("originFullQualifiedClassname") String originFullQualifiedClassname,
@NonNull @JsonProperty("originMethod") String originMethod,
@NonNull @JsonProperty("targetFullQualifiedClassname") String targetFullQualifiedClassname
) {
this.originFullQualifiedClassname = originFullQualifiedClassname;
this.originMethod = originMethod;
this.targetFullQualifiedClassname = targetFullQualifiedClassname;
}
@Override
public String getDisplayName() {
return "FullQualifiedListOfBackportRecipe";
}
@Override
public String getDescription() {
return "Converts owner of a method call and uses full qualified type to avoid import conflicts.";
}
@Override
protected JavaVisitor<ExecutionContext> getVisitor() {
return new Visitor(originFullQualifiedClassname, originMethod, targetFullQualifiedClassname);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (super.equals(o) == false) return false;
FullQualifiedChangeMethodOwnerRecipe that = (FullQualifiedChangeMethodOwnerRecipe) o;
return Objects.equals(originFullQualifiedClassname, that.originFullQualifiedClassname)
&& Objects.equals(originMethod, that.originMethod)
&& Objects.equals(targetFullQualifiedClassname, that.targetFullQualifiedClassname);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), originFullQualifiedClassname, originMethod, targetFullQualifiedClassname);
}
public static class Visitor extends JavaIsoVisitor<ExecutionContext> {
private final MethodMatcher methodMatcher;
private String originFullQualifiedClassname;
private String originMethod;
private String targetFullQualifiedClassname;
public Visitor(MethodMatcher methodMatcher) {
this.methodMatcher = methodMatcher;
}
public Visitor(String originFullQualifiedClassname,
String originMethod,
String targetFullQualifiedClassname) {
this.originFullQualifiedClassname = originFullQualifiedClassname;
this.originMethod = originMethod;
this.targetFullQualifiedClassname = targetFullQualifiedClassname;
methodMatcher = new MethodMatcher(originFullQualifiedClassname + " " + originMethod + "(..)");
}
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
J.MethodInvocation mi = super.visitMethodInvocation(method, executionContext);
JavaType.Method type = method.getType();
if (type != null && methodMatcher.matches(method)) {
int paramCount = method.getArguments().size();
String code = targetFullQualifiedClassname
+ "."
+ originMethod
+ "("
+ rangeClosed(1, paramCount).mapToObj(i -> "#{any()}").collect(joining(", "))
+ ")";
JavaTemplate listOfUsage = JavaTemplate.builder(this::getCursor, code).build();
mi = method.withTemplate(listOfUsage, method.getCoordinates().replace(), method.getArguments().toArray());
maybeRemoveImport(originFullQualifiedClassname);
trackChange(executionContext, mi.getId());
}
return mi;
}
private void trackChange(ExecutionContext executionContext, UUID id) {
executionContext.putMessageInSet(METHOD_CHANGE_PREFIX + targetFullQualifiedClassname, id);
}
}
}

View file

@ -13,6 +13,7 @@ import org.elasticsearch.gradle.internal.BuildPlugin
import org.elasticsearch.gradle.Version
import org.elasticsearch.gradle.VersionProperties
import org.elasticsearch.gradle.internal.info.BuildParams
import org.elasticsearch.gradle.internal.rewrite.RewritePlugin
import org.elasticsearch.gradle.plugin.PluginBuildPlugin
import org.gradle.plugins.ide.eclipse.model.AccessRule
import org.gradle.util.DistributionLocator
@ -43,6 +44,7 @@ plugins {
id 'elasticsearch.internal-testclusters'
id 'elasticsearch.run'
id 'elasticsearch.release-tools'
id 'elasticsearch.auto-backporting'
id "com.diffplug.spotless" version "5.15.1" apply false
}

35
rewrite.yml Normal file
View file

@ -0,0 +1,35 @@
type: specs.openrewrite.org/v1beta/recipe
name: org.elasticsearch.java.backport.ListOfBackport
displayName: Use `org.elasticsearch.core.Lists#of(..)` not java.util.List.of#(..)
description: Java 8 does not support the `java.util.List#of(..)`.
tags:
- backport
recipeList:
- org.elasticsearch.gradle.internal.rewrite.rules.ChangeMethodOwnerRecipe:
originFullQualifiedClassname: java.util.List
targetFullQualifiedClassname: org.elasticsearch.core.List
methodName: of
---
type: specs.openrewrite.org/v1beta/recipe
name: org.elasticsearch.java.backport.MapOfBackport
displayName: Use `org.elasticsearch.core.Maps#of(..)` not java.util.Map.of#(..)
description: Java 8 does not support the `java.util.Map#of(..)`.
tags:
- backport
recipeList:
- org.elasticsearch.gradle.internal.rewrite.rules.ChangeMethodOwnerRecipe:
originFullQualifiedClassname: java.util.Map
targetFullQualifiedClassname: org.elasticsearch.core.Map
methodName: of
---
type: specs.openrewrite.org/v1beta/recipe
name: org.elasticsearch.java.backport.SetOfBackport
displayName: Use `org.elasticsearch.core.Sets#of(..)` not java.util.Set.of#(..)
description: Java 8 does not support the `java.util.Set#of(..)`.
tags:
- backport
recipeList:
- org.elasticsearch.gradle.internal.rewrite.rules.ChangeMethodOwnerRecipe:
originFullQualifiedClassname: java.util.Set
targetFullQualifiedClassname: org.elasticsearch.core.Set
methodName: of