All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrewrite.gradle.UpdateJavaCompatibility Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2022 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.gradle; import lombok.EqualsAndHashCode; import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.util.ChangeStringLiteral; import org.openrewrite.groovy.GroovyVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; import org.openrewrite.marker.SearchResult; import java.util.Collections; import java.util.List; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static org.openrewrite.Tree.randomId; @Value @EqualsAndHashCode(callSuper = false) public class UpdateJavaCompatibility extends Recipe { @Option(displayName = "Java version", description = "The Java version to upgrade to.", example = "11") Integer version; @Option(displayName = "Compatibility type", description = "The compatibility type to change", valid = {"source", "target"}, required = false) @Nullable CompatibilityType compatibilityType; @Option(displayName = "Declaration style", description = "The desired style to write the new version as when being written to the `sourceCompatibility` " + "or `targetCompatibility` variables. Default, match current source style. " + "(ex. Enum: `JavaVersion.VERSION_11`, Number: 11, or String: \"11\")", valid = {"Enum", "Number", "String"}, required = false) @Nullable DeclarationStyle declarationStyle; @Option(displayName = "Allow downgrade", description = "Allow downgrading the Java version.", required = false) @Nullable Boolean allowDowngrade; @Option(displayName = "Add compatibility type if missing", description = "Adds the specified compatibility type if one is not found.", required = false) @Nullable Boolean addIfMissing; private static final String SOURCE_COMPATIBILITY_FOUND = "SOURCE_COMPATIBILITY_FOUND"; private static final String TARGET_COMPATIBILITY_FOUND = "TARGET_COMPATIBILITY_FOUND"; @Override public String getDisplayName() { return "Update Gradle project Java compatibility"; } @Override public String getDescription() { return "Find and updates the Java compatibility for the Gradle project."; } @Override public Validated validate() { return super.validate().and(Validated.test("version", "Version must be > 0.", version, v -> v > 0)); } @Override public TreeVisitor getVisitor() { return Preconditions.check(new IsBuildGradle<>(), new GroovyVisitor() { final MethodMatcher sourceCompatibilityDsl = new MethodMatcher("RewriteGradleProject setSourceCompatibility(..)"); final MethodMatcher targetCompatibilityDsl = new MethodMatcher("RewriteGradleProject setTargetCompatibility(..)"); final MethodMatcher javaLanguageVersionMatcher = new MethodMatcher("org.gradle.jvm.toolchain.JavaLanguageVersion of(int)"); final MethodMatcher javaVersionToVersionMatcher = new MethodMatcher("org.gradle.api.JavaVersion toVersion(..)"); @Override public J visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) { G.CompilationUnit c = (G.CompilationUnit) super.visitCompilationUnit(cu, ctx); if (getCursor().pollMessage(SOURCE_COMPATIBILITY_FOUND) == null) { c = addCompatibilityTypeToSourceFile(c, "source"); } if (getCursor().pollMessage(TARGET_COMPATIBILITY_FOUND) == null) { c = addCompatibilityTypeToSourceFile(c, "target"); } return c; } @Override public J visitAssignment(J.Assignment assignment, ExecutionContext ctx) { J.Assignment a = (J.Assignment) super.visitAssignment(assignment, ctx); if (a.getVariable() instanceof J.Identifier) { J.Identifier variable = (J.Identifier) a.getVariable(); if ("sourceCompatibility".equals(variable.getSimpleName())) { getCursor().putMessageOnFirstEnclosing(G.CompilationUnit.class, SOURCE_COMPATIBILITY_FOUND, a.getAssignment()); } if ("targetCompatibility".equals(variable.getSimpleName())) { getCursor().putMessageOnFirstEnclosing(G.CompilationUnit.class, TARGET_COMPATIBILITY_FOUND, a.getAssignment()); } if (compatibilityType == null) { if (!("sourceCompatibility".equals(variable.getSimpleName()) || "targetCompatibility".equals(variable.getSimpleName()))) { return a; } } else if (!(compatibilityType.toString().toLowerCase() + "Compatibility").equals(variable.getSimpleName())) { return a; } } else if (a.getVariable() instanceof J.FieldAccess) { J.FieldAccess fieldAccess = (J.FieldAccess) a.getVariable(); if (compatibilityType == null) { if (!("sourceCompatibility".equals(fieldAccess.getSimpleName()) || "targetCompatibility".equals(fieldAccess.getSimpleName()))) { return a; } } else if (!(compatibilityType.toString().toLowerCase() + "Compatibility").equals(fieldAccess.getSimpleName())) { return a; } } else { return a; } DeclarationStyle currentStyle = getCurrentStyle(a.getAssignment()); int currentMajor = getMajorVersion(a.getAssignment()); if (shouldUpdateVersion(currentMajor) || shouldUpdateStyle(currentStyle)) { DeclarationStyle actualStyle = declarationStyle == null ? currentStyle : declarationStyle; return a.withAssignment(changeExpression(a.getAssignment(), actualStyle)); } return a; } private boolean shouldUpdateVersion(int currentMajor) { return currentMajor < version || currentMajor > version && Boolean.TRUE.equals(allowDowngrade); } private boolean shouldUpdateStyle(@Nullable DeclarationStyle currentStyle) { return declarationStyle != null && declarationStyle != currentStyle; } @Override public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation m = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); if ("sourceCompatibility".equals(m.getSimpleName())) { getCursor().putMessageOnFirstEnclosing(G.CompilationUnit.class, SOURCE_COMPATIBILITY_FOUND, true); } if ("targetCompatibility".equals(m.getSimpleName())) { getCursor().putMessageOnFirstEnclosing(G.CompilationUnit.class, TARGET_COMPATIBILITY_FOUND, true); } if (javaLanguageVersionMatcher.matches(m)) { List args = m.getArguments(); if (args.size() == 1 && args.get(0) instanceof J.Literal) { J.Literal versionArg = (J.Literal) args.get(0); if (versionArg.getValue() instanceof Integer) { Integer versionNumber = (Integer) versionArg.getValue(); if (shouldUpdateVersion(versionNumber)) { return m.withArguments( Collections.singletonList(versionArg.withValue(version) .withValueSource(version.toString()))); } else { return m; } } } return SearchResult.found(m, "Attempted to update to Java version to " + version + " but was unsuccessful, please update manually"); } if (sourceCompatibilityDsl.matches(m) || targetCompatibilityDsl.matches(m)) { if (compatibilityType != null && ( (compatibilityType == CompatibilityType.source && !sourceCompatibilityDsl.matches(m)) || (compatibilityType == CompatibilityType.target && !targetCompatibilityDsl.matches(m)))) { return m; } if (m.getArguments().size() == 1 && (m.getArguments().get(0) instanceof J.Literal || m.getArguments().get(0) instanceof J.FieldAccess)) { DeclarationStyle currentStyle = getCurrentStyle(m.getArguments().get(0)); int currentMajor = getMajorVersion(m.getArguments().get(0)); if (shouldUpdateVersion(currentMajor) || shouldUpdateStyle(declarationStyle)) { DeclarationStyle actualStyle = declarationStyle == null ? currentStyle : declarationStyle; return m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> changeExpression(arg, actualStyle))); } else { return m; } } return SearchResult.found(m, "Attempted to update to Java version to " + version + " but was unsuccessful, please update manually"); } return m; } private int getMajorVersion(@Nullable String version) { if(version == null) { return -1; } try { return Integer.parseInt(normalize(version)); } catch (NumberFormatException e) { return -1; } } private int getMajorVersion(Expression expression) { if (expression instanceof J.Literal) { J.Literal argument = (J.Literal) expression; JavaType.Primitive type = argument.getType(); if (type == JavaType.Primitive.String) { return getMajorVersion((String) argument.getValue()); } else if (type == JavaType.Primitive.Int) { return (int) requireNonNull(argument.getValue()); } else if (type == JavaType.Primitive.Double) { return getMajorVersion(requireNonNull(argument.getValue()).toString()); } } else if (expression instanceof J.FieldAccess) { J.FieldAccess field = (J.FieldAccess) expression; J.Identifier identifier = field.getName(); return getMajorVersion(identifier.getSimpleName()); } else if (expression instanceof J.MethodInvocation && javaVersionToVersionMatcher.matches((J.MethodInvocation) expression)) { J.MethodInvocation method = (J.MethodInvocation) expression; if (method.getArguments().get(0) instanceof J.Literal) { return getMajorVersion(method.getArguments().get(0)); } } return -1; } private @Nullable DeclarationStyle getCurrentStyle(Expression expression) { if (expression instanceof J.Literal) { J.Literal argument = (J.Literal) expression; JavaType.Primitive type = argument.getType(); if (type == JavaType.Primitive.String) { return DeclarationStyle.String; } else if (type == JavaType.Primitive.Int) { return DeclarationStyle.Number; } else if (type == JavaType.Primitive.Double) { return DeclarationStyle.Number; } } else if (expression instanceof J.FieldAccess) { return DeclarationStyle.Enum; } return null; } private String normalize(String version) { if (version.contains("\"") || version.contains("'")) { version = version.replace("\"", "").replace("'", ""); } if (!version.contains(".") && !version.contains("_")) { return version; } if (version.contains("_")) { String removePrefix = version.substring(version.indexOf("_") + 1); if (removePrefix.startsWith("1_")) { return removePrefix.substring(removePrefix.indexOf("_") + 1); } else { return removePrefix; } } else { return version.substring(version.indexOf(".") + 1); } } private Expression changeExpression(Expression expression, @Nullable DeclarationStyle style) { if (expression instanceof J.Literal) { J.Literal literal = (J.Literal) expression; if (style == DeclarationStyle.String) { String newVersion = version <= 8 ? "1." + version : String.valueOf(version); if (literal.getType() == JavaType.Primitive.String) { expression = ChangeStringLiteral.withStringValue(literal, newVersion); } else { expression = literal.withType(JavaType.Primitive.String).withValue(newVersion).withValueSource("'" + newVersion + "'"); } } else if (style == DeclarationStyle.Enum) { String name = version <= 8 ? "VERSION_1_" + version : "VERSION_" + version; expression = new J.FieldAccess( randomId(), literal.getPrefix(), literal.getMarkers(), new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), "JavaVersion", JavaType.ShallowClass.build("org.gradle.api.JavaVersion"), null), new JLeftPadded<>(Space.EMPTY, new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), name, null, null), Markers.EMPTY), JavaType.ShallowClass.build("org.gradle.api.JavaVersion") ); } else if (style == DeclarationStyle.Number) { if (version <= 8) { double doubleValue = Double.parseDouble("1." + version); expression = literal.withType(JavaType.Primitive.Double).withValue(doubleValue).withValueSource("1." + version); } else { expression = literal.withType(JavaType.Primitive.Int).withValue(version).withValueSource(String.valueOf(version)); } } } else if (expression instanceof J.FieldAccess) { J.FieldAccess fieldAccess = (J.FieldAccess) expression; if (style == DeclarationStyle.String) { String newVersion = version <= 8 ? "1." + version : String.valueOf(version); expression = new J.Literal(randomId(), fieldAccess.getPrefix(), fieldAccess.getMarkers(), newVersion, "'" + newVersion + "'", emptyList(), JavaType.Primitive.String); } else if (style == DeclarationStyle.Enum) { String name = version <= 8 ? "VERSION_1_" + version : "VERSION_" + version; expression = fieldAccess.withName(fieldAccess.getName().withSimpleName(name)); } else if (style == DeclarationStyle.Number) { if (version <= 8) { double doubleValue = Double.parseDouble("1." + version); expression = new J.Literal(randomId(), fieldAccess.getPrefix(), fieldAccess.getMarkers(), doubleValue, String.valueOf(doubleValue), emptyList(), JavaType.Primitive.Double); } else { expression = new J.Literal(randomId(), fieldAccess.getPrefix(), fieldAccess.getMarkers(), version, String.valueOf(version), emptyList(), JavaType.Primitive.Int); } } } else if (expression instanceof J.MethodInvocation && javaVersionToVersionMatcher.matches((J.MethodInvocation) expression)) { J.MethodInvocation m = (J.MethodInvocation) expression; if (style == null) { expression = m.withArguments(ListUtils.mapFirst(m.getArguments(), arg -> { if (arg instanceof J.Literal) { if (arg.getType() == JavaType.Primitive.String) { String newVersion = version <= 8 ? "1." + version : String.valueOf(version); return ChangeStringLiteral.withStringValue((J.Literal) arg, newVersion); } else if (arg.getType() == JavaType.Primitive.Int) { return ((J.Literal) arg).withValue(version).withValueSource(String.valueOf(version)); } else if (arg.getType() == JavaType.Primitive.Double) { if (version <= 8) { double doubleValue = Double.parseDouble("1." + version); return ((J.Literal) arg).withValue(doubleValue).withValueSource(String.valueOf(doubleValue)); } else { return ((J.Literal) arg).withType(JavaType.Primitive.Int).withValue(version).withValueSource(String.valueOf(version)); } } } return arg; })); } else if (style == DeclarationStyle.String) { String newVersion = version <= 8 ? "1." + version : String.valueOf(version); expression = new J.Literal(randomId(), m.getPrefix(), m.getMarkers(), newVersion, "'" + newVersion + "'", emptyList(), JavaType.Primitive.String); } else if (style == DeclarationStyle.Enum) { String name = version <= 8 ? "VERSION_1_" + version : "VERSION_" + version; expression = new J.FieldAccess( randomId(), m.getPrefix(), m.getMarkers(), new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), "JavaVersion", JavaType.ShallowClass.build("org.gradle.api.JavaVersion"), null), new JLeftPadded<>(Space.EMPTY, new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), name, null, null), Markers.EMPTY), JavaType.ShallowClass.build("org.gradle.api.JavaVersion") ); } else if (style == DeclarationStyle.Number) { if (version <= 8) { double doubleValue = Double.parseDouble("1." + version); expression = new J.Literal(randomId(), m.getPrefix(), m.getMarkers(), doubleValue, String.valueOf(doubleValue), emptyList(), JavaType.Primitive.Double); } else { expression = new J.Literal(randomId(), m.getPrefix(), m.getMarkers(), version, String.valueOf(version), emptyList(), JavaType.Primitive.Int); } } } return expression; } }); } private G.CompilationUnit addCompatibilityTypeToSourceFile(G.CompilationUnit c, String compatibilityType) { if ((this.compatibilityType == null || compatibilityType.equals(this.compatibilityType.toString())) && Boolean.TRUE.equals(addIfMissing)) { G.CompilationUnit sourceFile = (G.CompilationUnit) GradleParser.builder().build().parse("\n" + compatibilityType + "Compatibility = " + styleMissingCompatibilityVersion()) .findFirst() .orElseThrow(() -> new IllegalStateException("Unable to parse compatibility type as a Gradle file")); sourceFile.getStatements(); c = c.withStatements(ListUtils.concatAll(c.getStatements(), sourceFile.getStatements())); } return c; } private String styleMissingCompatibilityVersion() { if (declarationStyle == DeclarationStyle.String) { return version <= 8 ? "'1." + version + "'" : "'" + version + "'"; } else if (declarationStyle == DeclarationStyle.Enum) { return version <= 8 ? "JavaVersion.VERSION_1_" + version : "JavaVersion.VERSION_" + version; } else if (version <= 8) { return "1." + version; } return String.valueOf(version); } public enum CompatibilityType { source, target } public enum DeclarationStyle { Enum, Number, String } }