-
Notifications
You must be signed in to change notification settings - Fork 110
Remove limitations of recipe for making safe migration #810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
0ee60ac
eaf7a9a
7a1a854
f06bf29
f24be17
a4c807f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,6 @@ | |
| import lombok.NonNull; | ||
| import org.jspecify.annotations.Nullable; | ||
| import org.openrewrite.ExecutionContext; | ||
| import org.openrewrite.java.JavaTemplate; | ||
| import org.openrewrite.java.JavadocVisitor; | ||
| import org.openrewrite.java.migrate.joda.templates.AllTemplates; | ||
| import org.openrewrite.java.migrate.joda.templates.MethodTemplate; | ||
|
|
@@ -30,18 +29,14 @@ | |
| import java.util.LinkedList; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static org.openrewrite.java.migrate.joda.templates.TimeClassNames.*; | ||
|
|
||
| class JodaTimeVisitor extends ScopeAwareVisitor { | ||
|
|
||
| private final boolean safeMigration; | ||
| private final JodaTimeRecipe.Accumulator acc; | ||
|
|
||
| public JodaTimeVisitor(JodaTimeRecipe.Accumulator acc, boolean safeMigration, LinkedList<VariablesInScope> scopes) { | ||
| public JodaTimeVisitor(JodaTimeRecipe.Accumulator acc, boolean b, LinkedList<VariablesInScope> scopes) { | ||
| super(scopes); | ||
| this.acc = acc; | ||
| this.safeMigration = safeMigration; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -65,10 +60,25 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx) | |
| maybeRemoveImport(JODA_DATE_TIME_ZONE); | ||
| maybeRemoveImport(JODA_TIME_FORMAT); | ||
| maybeRemoveImport(JODA_DURATION); | ||
| maybeRemoveImport(JODA_PERIOD); | ||
| maybeRemoveImport(JODA_ABSTRACT_INSTANT); | ||
| maybeRemoveImport(JODA_INSTANT); | ||
| maybeRemoveImport(JODA_INTERVAL); | ||
| maybeRemoveImport("java.util.Locale"); | ||
| maybeRemoveImport(JODA_TIME_FORMATTER); | ||
| maybeRemoveImport(JAVA_UTIL_LOCALE); | ||
| maybeRemoveImport(JODA_LOCAL_DATE_TIME); | ||
| maybeRemoveImport(JODA_LOCAL_DATE); | ||
| maybeRemoveImport(JODA_LOCAL_TIME); | ||
| maybeRemoveImport(JODA_SECONDS); | ||
| maybeRemoveImport(JODA_MINUTES); | ||
| maybeRemoveImport(JODA_HOURS); | ||
| maybeRemoveImport(JODA_DAYS); | ||
| maybeRemoveImport(JODA_WEEKS); | ||
| maybeRemoveImport(JODA_MONTHS); | ||
| maybeRemoveImport(JODA_YEARS); | ||
| maybeRemoveImport(JODA_DATE_TIME_UTILS); | ||
| maybeRemoveImport(JODA_DATE_MIDNIGHT); | ||
| maybeRemoveImport(JODA_GEORGIAN_CHRONOLOGY); | ||
|
|
||
| maybeAddImport(JAVA_DATE_TIME); | ||
| maybeAddImport(JAVA_ZONE_OFFSET); | ||
|
|
@@ -77,13 +87,22 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx) | |
| maybeAddImport(JAVA_TIME_FORMATTER); | ||
| maybeAddImport(JAVA_TIME_FORMAT_STYLE); | ||
| maybeAddImport(JAVA_DURATION); | ||
| maybeAddImport(JAVA_PERIOD); | ||
| maybeAddImport(JAVA_LOCAL_DATE); | ||
| maybeAddImport(JAVA_LOCAL_DATE_TIME); | ||
| maybeAddImport(JAVA_LOCAL_TIME); | ||
| maybeAddImport(JAVA_TEMPORAL_ISO_FIELDS); | ||
| maybeAddImport(JAVA_CHRONO_FIELD); | ||
| maybeAddImport(JAVA_CHRONO_UNIT); | ||
| maybeAddImport(JAVA_UTIL_DATE); | ||
| maybeAddImport(JAVA_ISA_CHRONOLOGY); | ||
| maybeAddImport(THREE_TEN_EXTRA_INTERVAL); | ||
| maybeAddImport(THREE_TEN_EXTRA_DAYS); | ||
| maybeAddImport(THREE_TEN_EXTRA_WEEKS); | ||
| maybeAddImport(THREE_TEN_EXTRA_MONTHS); | ||
| maybeAddImport(THREE_TEN_EXTRA_YEARS); | ||
| } | ||
|
|
||
| return j; | ||
| } | ||
|
|
||
|
|
@@ -93,43 +112,66 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx) | |
| if (m.getReturnTypeExpression() == null || !m.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { | ||
| return m; | ||
| } | ||
| if (safeMigration && !acc.getSafeMethodMap().getOrDefault(m.getMethodType(), false)) { | ||
| return m; | ||
| } | ||
|
|
||
| JavaType.Class returnType = TimeClassMap.getJavaTimeType(((JavaType.Class) m.getType()).getFullyQualifiedName()); | ||
| J.Identifier returnExpr = TypeTree.build(returnType.getClassName()).withType(returnType).withPrefix(Space.format(" ")); | ||
| return m.withReturnTypeExpression(returnExpr) | ||
| .withMethodType(m.getMethodType().withReturnType(returnType)); | ||
|
|
||
| JavaType.Method methodType = m.getMethodType() | ||
| .withReturnType(returnType) | ||
| .withParameterTypes(m.getMethodType().getParameterTypes().stream().map(p -> visitTypeParameter(p, ctx)).collect(Collectors.toList())); | ||
|
|
||
| J.MethodDeclaration methodDeclaration = m | ||
| .withReturnTypeExpression(returnExpr) | ||
| .withMethodType(methodType); | ||
|
|
||
| return methodDeclaration; | ||
| } | ||
| JavaType visitTypeParameter(JavaType parameter, @NonNull ExecutionContext ctx){ | ||
| if (parameter instanceof JavaType.Class) {return visitClassTypeParameter((JavaType.Class)parameter, ctx);} | ||
| if (parameter instanceof JavaType.Array) {return ((JavaType.Array)parameter).withElemType(visitClassTypeParameter((JavaType.Class)((JavaType.Array) parameter).getElemType(), ctx));} | ||
| return parameter; | ||
| } | ||
| JavaType visitClassTypeParameter(JavaType.Class classParameter, @NonNull ExecutionContext ctx) { | ||
| if (!classParameter.isAssignableFrom(JODA_CLASS_PATTERN)) { | ||
| return classParameter; | ||
| } | ||
| return TimeClassMap.getJavaTimeType(classParameter.getFullyQualifiedName()); | ||
| } | ||
|
|
||
| @Override | ||
| public @NonNull J visitVariableDeclarations(@NonNull J.VariableDeclarations multiVariable, @NonNull ExecutionContext ctx) { | ||
| if (multiVariable.getTypeExpression() == null || !multiVariable.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { | ||
| return super.visitVariableDeclarations(multiVariable, ctx); | ||
| J.VariableDeclarations mv = (J.VariableDeclarations) super.visitVariableDeclarations(multiVariable, ctx); | ||
|
|
||
| if (!mv.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { | ||
| return mv; | ||
| } | ||
| if (multiVariable.getVariables().stream().anyMatch(acc.getUnsafeVars()::contains)) { | ||
| return multiVariable; | ||
|
|
||
| String fullyQualifiedName = ((JavaType.Class) mv.getType()).getFullyQualifiedName(); | ||
| JavaType.Class javaTimeType = TimeClassMap.getJavaTimeType(fullyQualifiedName); | ||
| String javaTimeShortName = TimeClassMap.getJavaTimeShortName(fullyQualifiedName); | ||
|
|
||
| J.Identifier typeExpression = (J.Identifier) mv.getTypeExpression(); | ||
|
|
||
| if(javaTimeShortName != null){ | ||
| typeExpression = typeExpression.withSimpleName(javaTimeShortName); | ||
| } | ||
| J.VariableDeclarations m = (J.VariableDeclarations) super.visitVariableDeclarations(multiVariable, ctx); | ||
| return VarTemplates.getTemplate(multiVariable).<J>map(t -> t.apply( | ||
| updateCursor(m), | ||
| m.getCoordinates().replace(), | ||
| VarTemplates.getTemplateArgs(m))).orElse(multiVariable); | ||
|
|
||
| return autoFormat(mv.withTypeExpression(typeExpression.withType(javaTimeType)), ctx); | ||
| } | ||
|
|
||
| @Override | ||
| public @NonNull J visitVariable(@NonNull J.VariableDeclarations.NamedVariable variable, @NonNull ExecutionContext ctx) { | ||
| if (!variable.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { | ||
| return super.visitVariable(variable, ctx); | ||
| J.VariableDeclarations.NamedVariable v = (J.VariableDeclarations.NamedVariable) super.visitVariable(variable, ctx); | ||
| if (v.getType() instanceof JavaType.Array && ((JavaType.Array)v.getType()).getElemType().isAssignableFrom(JODA_CLASS_PATTERN)) { | ||
| JavaType.Array type = (JavaType.Array) v.getType(); | ||
| JavaType.Class elemType = (JavaType.Class) type.getElemType(); | ||
| return v.withType(type.withElemType(TimeClassMap.getJavaTimeType(elemType.getFullyQualifiedName()))); | ||
| } | ||
| if (acc.getUnsafeVars().contains(variable) || !(variable.getType() instanceof JavaType.Class)) { | ||
| return variable; | ||
| if (v.getType() instanceof JavaType.Class && v.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { | ||
| return v.withType(TimeClassMap.getJavaTimeType(((JavaType.Class) variable.getType()).getFullyQualifiedName())); | ||
| } | ||
| JavaType.Class jodaType = (JavaType.Class) variable.getType(); | ||
| return variable | ||
| .withType(TimeClassMap.getJavaTimeType(jodaType.getFullyQualifiedName())) | ||
| .withInitializer((Expression) visit(variable.getInitializer(), ctx)); | ||
|
|
||
| return v; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -143,7 +185,7 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx) | |
| } | ||
| J.Identifier varName = (J.Identifier) a.getVariable(); | ||
| Optional<NamedVariable> mayBeVar = findVarInScope(varName.getSimpleName()); | ||
| if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) { | ||
| if (!mayBeVar.isPresent()) { | ||
| return assignment; | ||
| } | ||
| return VarTemplates.getTemplate(assignment).<J>map(t -> t.apply( | ||
|
|
@@ -183,34 +225,50 @@ public Javadoc visitReference(Javadoc.Reference reference, ExecutionContext ctx) | |
| @Override | ||
| public @NonNull J visitFieldAccess(@NonNull J.FieldAccess fieldAccess, @NonNull ExecutionContext ctx) { | ||
| J.FieldAccess f = (J.FieldAccess) super.visitFieldAccess(fieldAccess, ctx); | ||
| if (TypeUtils.isOfClassType(f.getType(), JODA_DATE_TIME_ZONE) && "UTC".equals(f.getSimpleName())) { | ||
| return JavaTemplate.builder("ZoneOffset.UTC") | ||
| .imports(JAVA_ZONE_OFFSET) | ||
| .build() | ||
| .apply(updateCursor(f), f.getCoordinates().replace()); | ||
| if (!isJodaVarRef(fieldAccess)) { | ||
| return f; | ||
| } | ||
| return f; | ||
|
|
||
| JavaType.Class fieldType = (JavaType.Class) f.getType(); | ||
| JavaType.Class javaTimeType = TimeClassMap.getJavaTimeType(fieldType.getFullyQualifiedName()); | ||
|
|
||
| return f.withType(javaTimeType); | ||
| } | ||
|
|
||
| @Override | ||
| public @NonNull J visitIdentifier(@NonNull J.Identifier ident, @NonNull ExecutionContext ctx) { | ||
| if (!isJodaVarRef(ident)) { | ||
| return super.visitIdentifier(ident, ctx); | ||
| } | ||
| if (this.safeMigration) { | ||
| Optional<NamedVariable> mayBeVar = findVarInScope(ident.getSimpleName()); | ||
| if (!mayBeVar.isPresent() || acc.getUnsafeVars().contains(mayBeVar.get())) { | ||
| return ident; | ||
| } | ||
| } | ||
| J.Identifier i = (J.Identifier) super.visitIdentifier(ident, ctx); | ||
| //i.getType().isAssignableFrom(JODA_CLASS_PATTERN) | ||
| if (!isJodaVarRef(i)) { | ||
| return i; | ||
| } | ||
| JavaType type = i.getType(); | ||
| if(type instanceof JavaType.Array) {return visitArrayIdentifier(i, ctx);} | ||
| if(type instanceof JavaType.Class) {return visitClassIdentifier(i, ctx);} | ||
|
|
||
| JavaType.FullyQualified jodaType = ((JavaType.Class) ident.getType()); | ||
| JavaType.FullyQualified fqType = TimeClassMap.getJavaTimeType(jodaType.getFullyQualifiedName()); | ||
| if (fqType == null) { | ||
| return ident; | ||
| return i; | ||
| } | ||
| private @NonNull J visitArrayIdentifier(J.Identifier arrayIdentifier, @NonNull ExecutionContext ctx) { | ||
| JavaType.Array at = (JavaType.Array)arrayIdentifier.getType(); | ||
| JavaType.Array javaTimeType = at.withElemType(TimeClassMap.getJavaTimeType(((JavaType.Class)at.getElemType()).getFullyQualifiedName())); | ||
|
|
||
| return arrayIdentifier.withType(javaTimeType) | ||
| .withFieldType(arrayIdentifier.getFieldType().withType(javaTimeType)); | ||
| } | ||
| private @NonNull J visitClassIdentifier(J.Identifier classIdentifier, @NonNull ExecutionContext ctx) { | ||
| JavaType.Class javaTimeType = TimeClassMap.getJavaTimeType(((JavaType.Class)classIdentifier.getType()).getFullyQualifiedName()); | ||
|
|
||
| return classIdentifier.withType(javaTimeType) | ||
| .withFieldType(classIdentifier.getFieldType().withType(javaTimeType)); | ||
| } | ||
|
|
||
| @Override | ||
| public J visitArrayAccess(J.ArrayAccess arrayAccess, @NonNull ExecutionContext ctx){ | ||
| J.ArrayAccess a = (J.ArrayAccess) super.visitArrayAccess(arrayAccess, ctx); | ||
| if (!a.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { | ||
| return a; | ||
| } | ||
| return ident.withType(fqType) | ||
| .withFieldType(ident.getFieldType().withType(fqType)); | ||
| return a.withType(TimeClassMap.getJavaTimeType(((JavaType.Class)a.getType()).getFullyQualifiedName())); | ||
| } | ||
|
|
||
| private J migrateMethodCall(MethodCall original, MethodCall updated) { | ||
|
|
@@ -219,38 +277,50 @@ private J migrateMethodCall(MethodCall original, MethodCall updated) { | |
| } | ||
| MethodTemplate template = AllTemplates.getTemplate(original); | ||
| if (template == null) { | ||
| System.out.println("Joda usage is found but mapping is missing: " + original); | ||
| return original; // unhandled case | ||
| } | ||
| if (template.getTemplate().getCode().equals(JODA_MULTIPLE_MAPPING_POSSIBLE)) { | ||
| System.out.println(JODA_MULTIPLE_MAPPING_POSSIBLE + ": " + original); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we want to report anything via stdout. If anything, for such a cases we place some Marker and attach to the source LST element. I am not fully if that's necessary in this specific case, maybe just refrain from making any change? I am not sure.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea is to report on missing mapping. It was extremely useful for me during development. And can be used for reporting later. |
||
| return original; // usage with no automated mapping | ||
| } | ||
| if (template.getTemplate().getCode().equals(JODA_NO_AUTOMATIC_MAPPING_POSSIBLE)) { | ||
| System.out.println(JODA_NO_AUTOMATIC_MAPPING_POSSIBLE + ": " + original); | ||
| return original; // usage with no automated mapping | ||
| } | ||
| Optional<J> maybeUpdated = applyTemplate(original, updated, template); | ||
| if (!maybeUpdated.isPresent()) { | ||
| System.out.println("Can not apply template: " + template + " to " + original); | ||
| return original; // unhandled case | ||
| } | ||
| Expression updatedExpr = (Expression) maybeUpdated.get(); | ||
| if (!safeMigration || !isArgument(original)) { | ||
| if (!isArgument(original)) { | ||
| return updatedExpr; | ||
| } | ||
| // this expression is an argument to a method call | ||
| MethodCall parentMethod = getCursor().getParentTreeCursor().getValue(); | ||
| if (parentMethod.getMethodType().getDeclaringType().isAssignableFrom(JODA_CLASS_PATTERN)) { | ||
| JavaType.Method parentMethodType = parentMethod.getMethodType(); | ||
| if (parentMethodType.getDeclaringType().isAssignableFrom(JODA_CLASS_PATTERN)) { | ||
| return updatedExpr; | ||
| } | ||
| int argPos = parentMethod.getArguments().indexOf(original); | ||
| JavaType paramType = parentMethod.getMethodType().getParameterTypes().get(argPos); | ||
| if (TypeUtils.isAssignableTo(paramType, updatedExpr.getType())) { | ||
| return updatedExpr; | ||
| } | ||
| String paramName = parentMethod.getMethodType().getParameterNames().get(argPos); | ||
| NamedVariable var = acc.getVarTable().getVarByName(parentMethod.getMethodType(), paramName); | ||
| if (var != null && !acc.getUnsafeVars().contains(var)) { | ||
| return updatedExpr; | ||
| List<JavaType> parameterTypes = parentMethodType.getParameterTypes(); | ||
| int parameterTypesSize = parameterTypes.size(); | ||
|
|
||
| //try to process method with variable arguments | ||
| if(argPos > parameterTypesSize) | ||
| { | ||
| //todo find better way to detect (...) in method arguments | ||
| if (parameterTypes.get(parameterTypesSize - 1).toString().endsWith("[]")){ | ||
| return updatedExpr; | ||
| } | ||
| return original; | ||
| } | ||
| return original; | ||
|
|
||
| return updatedExpr; | ||
| } | ||
|
|
||
| private J.MethodInvocation migrateNonJodaMethod(J.MethodInvocation original, J.MethodInvocation updated) { | ||
| if (safeMigration && !acc.getSafeMethodMap().getOrDefault(updated.getMethodType(), false)) { | ||
| return original; | ||
| } | ||
| JavaType.Class returnType = (JavaType.Class) updated.getMethodType().getReturnType(); | ||
| JavaType.Class updatedReturnType = TimeClassMap.getJavaTimeType(returnType.getFullyQualifiedName()); | ||
| if (updatedReturnType == null) { | ||
|
|
@@ -282,7 +352,18 @@ private Optional<J> applyTemplate(MethodCall original, MethodCall updated, Metho | |
| } | ||
|
|
||
| private boolean isJodaVarRef(@Nullable Expression expr) { | ||
| if (expr == null || expr.getType() == null || !expr.getType().isAssignableFrom(JODA_CLASS_PATTERN)) { | ||
| if (expr == null) { | ||
| return false; | ||
| } | ||
| JavaType type = expr.getType(); | ||
| if (type == null) { | ||
| return false; | ||
| } | ||
| if(type instanceof JavaType.Array){ | ||
| type = ((JavaType.Array) type).getElemType(); | ||
| } | ||
|
|
||
| if (!type.isAssignableFrom(JODA_CLASS_PATTERN )) { | ||
| return false; | ||
| } | ||
| if (expr instanceof J.FieldAccess) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.