diff --git a/tools/migration/src/main/java/ai/timefold/solver/migration/ConstraintStreamMigrationRecipe.java b/tools/migration/src/main/java/ai/timefold/solver/migration/ConstraintStreamMigrationRecipe.java new file mode 100644 index 0000000000..5eda1f23fd --- /dev/null +++ b/tools/migration/src/main/java/ai/timefold/solver/migration/ConstraintStreamMigrationRecipe.java @@ -0,0 +1,59 @@ +package ai.timefold.solver.migration; + +import java.util.List; + +import org.openrewrite.Recipe; +import org.openrewrite.java.ChangeMethodName; + +public class ConstraintStreamMigrationRecipe extends AbstractRecipe { + @Override + public String getDisplayName() { + return "Migrate legacy constraint stream code to the new methods"; + } + + @Override + public String getDescription() { + return "Migrate all legacy constraint stream methods to the new methods."; + } + + @Override + public List getRecipeList() { + return List.of( + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.uni.UniConstraintStream penalizeLong(..)", + "penalize", true, false), + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.uni.UniConstraintStream rewardLong(..)", + "reward", true, false), + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.uni.UniConstraintStream impactLong(..)", + "impact", true, false), + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream penalizeLong(..)", + "penalize", true, false), + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream rewardLong(..)", + "reward", true, false), + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream impactLong(..)", + "impact", true, false), + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream penalizeLong(..)", + "penalize", true, false), + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream rewardLong(..)", + "reward", true, false), + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream impactLong(..)", + "impact", true, false), + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.quad.QuadConstraintStream penalizeLong(..)", + "penalize", true, false), + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.quad.QuadConstraintStream rewardLong(..)", + "reward", true, false), + new ChangeMethodName( + "ai.timefold.solver.core.api.score.stream.quad.QuadConstraintStream impactLong(..)", + "impact", true, false)); + } +} diff --git a/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java b/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java new file mode 100644 index 0000000000..afa59c68cc --- /dev/null +++ b/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java @@ -0,0 +1,58 @@ +package ai.timefold.solver.migration; + +import java.util.List; + +import org.openrewrite.Recipe; +import org.openrewrite.java.ChangeType; + +public class GeneralChangeTypeMigrationRecipe extends AbstractRecipe { + @Override + public String getDisplayName() { + return "Migrate legacy code to the new class structure"; + } + + @Override + public String getDescription() { + return "Migrate all legacy classes to the new class structure."; + } + + @Override + public List getRecipeList() { + return List.of( + // Planning Id + new ChangeType("ai.timefold.solver.core.api.domain.lookup.PlanningId", + "ai.timefold.solver.core.api.domain.common.PlanningId", true), + // Score classes + new ChangeType("ai.timefold.solver.core.api.score.buildin.simple.SimpleScore", + "ai.timefold.solver.core.api.score.SimpleScore", true), + new ChangeType("ai.timefold.solver.core.api.score.buildin.simplelong.SimpleLongScore", + "ai.timefold.solver.core.api.score.SimpleScore", true), + new ChangeType("ai.timefold.solver.core.api.score.buildin.simplebigdecimal.SimpleBigDecimalScore", + "ai.timefold.solver.core.api.score.SimpleBigDecimalScore", true), + + new ChangeType("ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore", + "ai.timefold.solver.core.api.score.HardSoftScore", true), + new ChangeType("ai.timefold.solver.core.api.score.buildin.hardsoftlong.HardSoftLongScore", + "ai.timefold.solver.core.api.score.HardSoftScore", true), + new ChangeType("ai.timefold.solver.core.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScore", + "ai.timefold.solver.core.api.score.HardSoftBigDecimalScore", true), + + new ChangeType("ai.timefold.solver.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore", + "ai.timefold.solver.core.api.score.HardMediumSoftScore", true), + new ChangeType("ai.timefold.solver.core.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScore", + "ai.timefold.solver.core.api.score.HardMediumSoftScore", true), + new ChangeType( + "ai.timefold.solver.core.api.score.buildin.hardmediumsoftbigdecimal.HardMediumSoftBigDecimalScore", + "ai.timefold.solver.core.api.score.HardMediumSoftBigDecimalScore", true), + + new ChangeType("ai.timefold.solver.core.api.score.buildin.bendable.BendableScore", + "ai.timefold.solver.core.api.score.BendableScore", true), + new ChangeType("ai.timefold.solver.core.api.score.buildin.bendablelong.BendableLongScore", + "ai.timefold.solver.core.api.score.BendableScore", true), + new ChangeType("ai.timefold.solver.core.api.score.buildin.bendablebigdecimal.BendableBigDecimalScore", + "ai.timefold.solver.core.api.score.BendableBigDecimalScore", true), + // Problem fact + new ChangeType("ai.timefold.solver.core.api.solver.ProblemFactChange", + "ai.timefold.solver.core.api.solver.change.ProblemChange", true)); + } +} diff --git a/tools/migration/src/main/resources/META-INF/rewrite/ToLatest.yml b/tools/migration/src/main/resources/META-INF/rewrite/ToLatest.yml index ddd3d95c97..8ca8fa4205 100644 --- a/tools/migration/src/main/resources/META-INF/rewrite/ToLatest.yml +++ b/tools/migration/src/main/resources/META-INF/rewrite/ToLatest.yml @@ -3,21 +3,9 @@ name: ai.timefold.solver.migration.ToLatest displayName: Upgrade to the latest Timefold Solver description: 'Replace all your calls to deleted/deprecated types and methods of Timefold Solver with their proper alternatives.' recipeList: - - ai.timefold.solver.migration.FromOptaPlannerToTimefoldSolver - - org.openrewrite.properties.ChangePropertyKey: - oldPropertyKey: timefold.solver.solve-length - newPropertyKey: timefold.solver.solve.duration - fileMatcher: '**/application.properties' - - org.openrewrite.properties.ChangePropertyKey: - oldPropertyKey: quarkus.timefold.solver.solve-length - newPropertyKey: quarkus.timefold.solver.solve.duration - fileMatcher: '**/application.properties' - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.api.score.stream.ConstraintFactory from(Class) - newMethodName: forEach - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.api.score.stream.ConstraintFactory fromUnfiltered(Class) - newMethodName: forEachIncludingUnassigned - - org.openrewrite.java.ChangeMethodName: - methodPattern: ai.timefold.solver.core.api.score.stream.ConstraintFactory fromUniquePair(..) - newMethodName: forEachUniquePair \ No newline at end of file + - ai.timefold.solver.migration.GeneralChangeTypeMigrationRecipe + - ai.timefold.solver.migration.ConstraintStreamMigrationRecipe + - org.openrewrite.java.DeleteMethodArgument: + methodPattern: ai.timefold.solver.core.api.score.constraint.ConstraintRef of(String, String) + argumentIndex: 0 + - org.openrewrite.java.RemoveUnusedImports diff --git a/tools/migration/src/test/java/ai/timefold/solver/migration/ConstraintStreamMigrationRecipeTest.java b/tools/migration/src/test/java/ai/timefold/solver/migration/ConstraintStreamMigrationRecipeTest.java new file mode 100644 index 0000000000..470eb4a48e --- /dev/null +++ b/tools/migration/src/test/java/ai/timefold/solver/migration/ConstraintStreamMigrationRecipeTest.java @@ -0,0 +1,153 @@ +package ai.timefold.solver.migration; + +import static org.openrewrite.java.Assertions.java; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.search.FindMissingTypes; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +@Execution(ExecutionMode.CONCURRENT) +class ConstraintStreamMigrationRecipeTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipes(new FindMissingTypes(false), new ConstraintStreamMigrationRecipe()) + .parser(JavaParser.fromJavaVersion() + // We must add all old classes as stubs to the JavaTemplate + .dependsOn( + """ + package ai.timefold.solver.core.api.score.stream.uni; + + import java.util.function.ToLongFunction; + + public class UniConstraintStream { + public void penalizeLong(Object constraintWeight) {} + public void penalizeLong(Object constraintWeight, ToLongFunction matchWeigher) {} + public void rewardLong(Object constraintWeight) {} + public void rewardLong(Object constraintWeight, ToLongFunction matchWeigher) {} + public void impactLong(Object constraintWeight) {} + public void impactLong(Object constraintWeight, ToLongFunction matchWeigher) {} + }""", + """ + package ai.timefold.solver.core.api.score.stream.bi; + + import java.util.function.ToLongFunction; + + public class BiConstraintStream { + public void penalizeLong(Object constraintWeight) {} + public void penalizeLong(Object constraintWeight, ToLongFunction matchWeigher) {} + public void rewardLong(Object constraintWeight) {} + public void rewardLong(Object constraintWeight, ToLongFunction matchWeigher) {} + public void impactLong(Object constraintWeight) {} + public void impactLong(Object constraintWeight, ToLongFunction matchWeigher) {} + }""", + """ + package ai.timefold.solver.core.api.score.stream.tri; + + import java.util.function.ToLongFunction; + + public class TriConstraintStream { + public void penalizeLong(Object constraintWeight) {} + public void penalizeLong(Object constraintWeight, ToLongFunction matchWeigher) {} + public void rewardLong(Object constraintWeight) {} + public void rewardLong(Object constraintWeight, ToLongFunction matchWeigher) {} + public void impactLong(Object constraintWeight) {} + public void impactLong(Object constraintWeight, ToLongFunction matchWeigher) {} + }""", + """ + package ai.timefold.solver.core.api.score.stream.quad; + + import java.util.function.ToLongFunction; + + public class QuadConstraintStream { + public void penalizeLong(Object constraintWeight) {} + public void penalizeLong(Object constraintWeight, ToLongFunction matchWeigher) {} + public void rewardLong(Object constraintWeight) {} + public void rewardLong(Object constraintWeight, ToLongFunction matchWeigher) {} + public void impactLong(Object constraintWeight) {} + public void impactLong(Object constraintWeight, ToLongFunction matchWeigher) {} + }""")); + } + + @Test + void migrate() { + rewriteRun(java( + """ + package timefold; + + import ai.timefold.solver.core.api.score.stream.uni.UniConstraintStream; + import ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream; + import ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream; + import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintStream; + + public class Test { + void validate(UniConstraintStream stream, BiConstraintStream stream2, TriConstraintStream stream3, QuadConstraintStream stream4) { + stream.penalizeLong(null); + stream.penalizeLong(null, null); + stream.rewardLong(null); + stream.rewardLong(null, null); + stream.impactLong(null); + stream.impactLong(null, null); + stream2.penalizeLong(null); + stream2.penalizeLong(null, null); + stream2.rewardLong(null); + stream2.rewardLong(null, null); + stream2.impactLong(null); + stream2.impactLong(null, null); + stream3.penalizeLong(null); + stream3.penalizeLong(null, null); + stream3.rewardLong(null); + stream3.rewardLong(null, null); + stream3.impactLong(null); + stream3.impactLong(null, null); + stream4.penalizeLong(null); + stream4.penalizeLong(null, null); + stream4.rewardLong(null); + stream4.rewardLong(null, null); + stream4.impactLong(null); + stream4.impactLong(null, null); + } + }""", + """ + package timefold; + + import ai.timefold.solver.core.api.score.stream.uni.UniConstraintStream; + import ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream; + import ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream; + import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintStream; + + public class Test { + void validate(UniConstraintStream stream, BiConstraintStream stream2, TriConstraintStream stream3, QuadConstraintStream stream4) { + stream.penalize(null); + stream.penalize(null, null); + stream.reward(null); + stream.reward(null, null); + stream.impact(null); + stream.impact(null, null); + stream2.penalize(null); + stream2.penalize(null, null); + stream2.reward(null); + stream2.reward(null, null); + stream2.impact(null); + stream2.impact(null, null); + stream3.penalize(null); + stream3.penalize(null, null); + stream3.reward(null); + stream3.reward(null, null); + stream3.impact(null); + stream3.impact(null, null); + stream4.penalize(null); + stream4.penalize(null, null); + stream4.reward(null); + stream4.reward(null, null); + stream4.impact(null); + stream4.impact(null, null); + } + }""")); + } + +} diff --git a/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java b/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java new file mode 100644 index 0000000000..956d37e59b --- /dev/null +++ b/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java @@ -0,0 +1,123 @@ +package ai.timefold.solver.migration; + +import static org.openrewrite.java.Assertions.java; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.style.ImportLayoutStyle; +import org.openrewrite.style.NamedStyles; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; + +@Execution(ExecutionMode.CONCURRENT) +class GeneralChangeTypeMigrationRecipeTest implements RewriteTest { + + private static final class NoWildCardImportStyle extends NamedStyles { + + public NoWildCardImportStyle() { + super(UUID.randomUUID(), "ImportStyle", "ImportStyle", "ImportStyle", Collections.emptySet(), + List.of(ImportLayoutStyle.builder().classCountToUseStarImport(9999999).importStaticAllOthers() + .importAllOthers().build())); + } + } + + @Override + public void defaults(RecipeSpec spec) { + spec.recipes(new GeneralChangeTypeMigrationRecipe()) + .typeValidationOptions(TypeValidation.builder().allowMissingType(ignore -> true).build()) + .parser(JavaParser.fromJavaVersion() + .styles(List.of(new NoWildCardImportStyle())) + // We must add all old classes as stubs to the JavaTemplate + .dependsOn("package ai.timefold.solver.core.api.domain.lookup; public class PlanningId {}", + "package ai.timefold.solver.core.api.score.buildin.simple; public class SimpleScore {}", + "package ai.timefold.solver.core.api.score.buildin.simplelong; public class SimpleLongScore {}", + "package ai.timefold.solver.core.api.score.buildin.simplebigdecimal; public class SimpleBigDecimalScore {}", + "package ai.timefold.solver.core.api.score.buildin.hardsoft; public class HardSoftScore {}", + "package ai.timefold.solver.core.api.score.buildin.hardsoftlong; public class HardSoftLongScore {}", + "package ai.timefold.solver.core.api.score.buildin.hardsoftbigdecimal; public class HardSoftBigDecimalScore {}", + "package ai.timefold.solver.core.api.score.buildin.hardmediumsoft; public class HardMediumSoftScore {}", + "package ai.timefold.solver.core.api.score.buildin.hardmediumsoftlong; public class HardMediumSoftLongScore {}", + "package ai.timefold.solver.core.api.score.buildin.hardmediumsoftbigdecimal; public class HardMediumSoftBigDecimalScore {}", + "package ai.timefold.solver.core.api.score.buildin.bendable; public class BendableScore {}", + "package ai.timefold.solver.core.api.score.buildin.bendablelong; public class BendableLongScore {}", + "package ai.timefold.solver.core.api.score.buildin.bendablebigdecimal; public class BendableBigDecimalScore {}", + "package ai.timefold.solver.core.api.solver; public class ProblemFactChange {}")); + } + + @Test + void migrate() { + rewriteRun(java( + """ + package timefold; + + import ai.timefold.solver.core.api.domain.lookup.PlanningId; + import ai.timefold.solver.core.api.solver.ProblemFactChange; + import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; + import ai.timefold.solver.core.api.score.buildin.simplelong.SimpleLongScore; + import ai.timefold.solver.core.api.score.buildin.simplebigdecimal.SimpleBigDecimalScore; + import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; + import ai.timefold.solver.core.api.score.buildin.hardsoftlong.HardSoftLongScore; + import ai.timefold.solver.core.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScore; + import ai.timefold.solver.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore; + import ai.timefold.solver.core.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScore; + import ai.timefold.solver.core.api.score.buildin.hardmediumsoftbigdecimal.HardMediumSoftBigDecimalScore; + import ai.timefold.solver.core.api.score.buildin.bendable.BendableScore; + import ai.timefold.solver.core.api.score.buildin.bendablelong.BendableLongScore; + import ai.timefold.solver.core.api.score.buildin.bendablebigdecimal.BendableBigDecimalScore; + + public class Test { + @PlanningId + SimpleScore simpleScore; + ProblemFactChange problemFactChange; + SimpleLongScore simpleLongScore; + SimpleBigDecimalScore simpleBigDecimalScore; + HardSoftScore hardSoftScore; + HardSoftLongScore hardSoftLongScore; + HardSoftBigDecimalScore hardSoftBigDecimalScore; + HardMediumSoftScore hardMediumSoftScore; + HardMediumSoftLongScore hardMediumSoftLongScore; + HardMediumSoftBigDecimalScore hardMediumSoftBigDecimalScore; + BendableScore bendableScore; + BendableLongScore bendableLongScore; + BendableBigDecimalScore bendableBigDecimalScore; + }""", + """ + package timefold; + + import ai.timefold.solver.core.api.domain.common.PlanningId; + import ai.timefold.solver.core.api.score.BendableBigDecimalScore; + import ai.timefold.solver.core.api.score.BendableScore; + import ai.timefold.solver.core.api.score.HardMediumSoftBigDecimalScore; + import ai.timefold.solver.core.api.score.HardMediumSoftScore; + import ai.timefold.solver.core.api.score.HardSoftBigDecimalScore; + import ai.timefold.solver.core.api.score.HardSoftScore; + import ai.timefold.solver.core.api.score.SimpleBigDecimalScore; + import ai.timefold.solver.core.api.score.SimpleScore; + import ai.timefold.solver.core.api.solver.change.ProblemChange; + + public class Test { + @PlanningId + SimpleScore simpleScore; + ProblemChange problemFactChange; + SimpleScore simpleLongScore; + SimpleBigDecimalScore simpleBigDecimalScore; + HardSoftScore hardSoftScore; + HardSoftScore hardSoftLongScore; + HardSoftBigDecimalScore hardSoftBigDecimalScore; + HardMediumSoftScore hardMediumSoftScore; + HardMediumSoftScore hardMediumSoftLongScore; + HardMediumSoftBigDecimalScore hardMediumSoftBigDecimalScore; + BendableScore bendableScore; + BendableScore bendableLongScore; + BendableBigDecimalScore bendableBigDecimalScore; + }""")); + } + +}