From 9b50db9beedb97be88faaa4bb77158b4c83996a7 Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 20 Feb 2026 11:26:27 -0300 Subject: [PATCH 1/9] feat: add new recipe for score classes --- .../ScoreClassesMigrationRecipe.java | 51 ++++++++ .../resources/META-INF/rewrite/ToLatest.yml | 5 +- .../ScoreClassesMigrationRecipeTest.java | 113 ++++++++++++++++++ 3 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 tools/migration/src/main/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipe.java create mode 100644 tools/migration/src/test/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipeTest.java diff --git a/tools/migration/src/main/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipe.java b/tools/migration/src/main/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipe.java new file mode 100644 index 0000000000..42a7be8923 --- /dev/null +++ b/tools/migration/src/main/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipe.java @@ -0,0 +1,51 @@ +package ai.timefold.solver.migration; + +import java.util.List; + +import org.openrewrite.Recipe; +import org.openrewrite.java.ChangeType; + +public class ScoreClassesMigrationRecipe extends AbstractRecipe { + @Override + public String getDisplayName() { + return "Migrate to the new score class structure"; + } + + @Override + public String getDescription() { + return "Migrate all legacy score classes to the new class structure."; + } + + @Override + public List getRecipeList() { + return List.of( + 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)); + } +} 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..fcb7067388 100644 --- a/tools/migration/src/main/resources/META-INF/rewrite/ToLatest.yml +++ b/tools/migration/src/main/resources/META-INF/rewrite/ToLatest.yml @@ -3,7 +3,6 @@ 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 @@ -20,4 +19,6 @@ recipeList: newMethodName: forEachIncludingUnassigned - org.openrewrite.java.ChangeMethodName: methodPattern: ai.timefold.solver.core.api.score.stream.ConstraintFactory fromUniquePair(..) - newMethodName: forEachUniquePair \ No newline at end of file + newMethodName: forEachUniquePair + - ai.timefold.solver.migration.ScoreClassesMigrationRecipe + - org.openrewrite.java.RemoveUnusedImports diff --git a/tools/migration/src/test/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipeTest.java b/tools/migration/src/test/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipeTest.java new file mode 100644 index 0000000000..d1eaa92cf6 --- /dev/null +++ b/tools/migration/src/test/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipeTest.java @@ -0,0 +1,113 @@ +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 ScoreClassesMigrationRecipeTest 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 ScoreClassesMigrationRecipe()) + .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.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 {}")); + } + + @Test + void migrate() { + rewriteRun(java( + """ + package timefold; + + 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 { + SimpleScore simpleScore; + 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.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; + + public class Test { + SimpleScore simpleScore; + SimpleScore simpleLongScore; + SimpleBigDecimalScore simpleBigDecimalScore; + HardSoftScore hardSoftScore; + HardSoftScore hardSoftLongScore; + HardSoftBigDecimalScore hardSoftBigDecimalScore; + HardMediumSoftScore hardMediumSoftScore; + HardMediumSoftScore hardMediumSoftLongScore; + HardMediumSoftBigDecimalScore hardMediumSoftBigDecimalScore; + BendableScore bendableScore; + BendableScore bendableLongScore; + BendableBigDecimalScore bendableBigDecimalScore; + }""")); + } + +} From 149aa30f7afd605fe1fe5905dfc6773c45e77d4d Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 20 Feb 2026 11:57:30 -0300 Subject: [PATCH 2/9] feat: improve the recipe --- ...ipe.java => GeneralChangeTypeMigrationRecipe.java} | 10 +++++++--- .../src/main/resources/META-INF/rewrite/ToLatest.yml | 2 +- ...java => GeneralChangeTypeMigrationRecipeTest.java} | 11 ++++++++--- 3 files changed, 16 insertions(+), 7 deletions(-) rename tools/migration/src/main/java/ai/timefold/solver/migration/{ScoreClassesMigrationRecipe.java => GeneralChangeTypeMigrationRecipe.java} (85%) rename tools/migration/src/test/java/ai/timefold/solver/migration/{ScoreClassesMigrationRecipeTest.java => GeneralChangeTypeMigrationRecipeTest.java} (92%) diff --git a/tools/migration/src/main/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipe.java b/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java similarity index 85% rename from tools/migration/src/main/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipe.java rename to tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java index 42a7be8923..9b47067e4b 100644 --- a/tools/migration/src/main/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipe.java +++ b/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java @@ -5,20 +5,24 @@ import org.openrewrite.Recipe; import org.openrewrite.java.ChangeType; -public class ScoreClassesMigrationRecipe extends AbstractRecipe { +public class GeneralChangeTypeMigrationRecipe extends AbstractRecipe { @Override public String getDisplayName() { - return "Migrate to the new score class structure"; + return "Migrate legacy code to the new class structure"; } @Override public String getDescription() { - return "Migrate all legacy score classes to the new class structure."; + 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", 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 fcb7067388..9b5714491f 100644 --- a/tools/migration/src/main/resources/META-INF/rewrite/ToLatest.yml +++ b/tools/migration/src/main/resources/META-INF/rewrite/ToLatest.yml @@ -20,5 +20,5 @@ recipeList: - org.openrewrite.java.ChangeMethodName: methodPattern: ai.timefold.solver.core.api.score.stream.ConstraintFactory fromUniquePair(..) newMethodName: forEachUniquePair - - ai.timefold.solver.migration.ScoreClassesMigrationRecipe + - ai.timefold.solver.migration.GeneralChangeTypeMigrationRecipe - org.openrewrite.java.RemoveUnusedImports diff --git a/tools/migration/src/test/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipeTest.java b/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java similarity index 92% rename from tools/migration/src/test/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipeTest.java rename to tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java index d1eaa92cf6..5a75ea6f7c 100644 --- a/tools/migration/src/test/java/ai/timefold/solver/migration/ScoreClassesMigrationRecipeTest.java +++ b/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java @@ -17,7 +17,7 @@ import org.openrewrite.test.TypeValidation; @Execution(ExecutionMode.CONCURRENT) -class ScoreClassesMigrationRecipeTest implements RewriteTest { +class GeneralChangeTypeMigrationRecipeTest implements RewriteTest { private static final class NoWildCardImportStyle extends NamedStyles { @@ -30,12 +30,13 @@ public NoWildCardImportStyle() { @Override public void defaults(RecipeSpec spec) { - spec.recipes(new ScoreClassesMigrationRecipe()) + 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.score.buildin.simple; public class SimpleScore {}", + .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 {}", @@ -55,6 +56,7 @@ void migrate() { """ package timefold; + import ai.timefold.solver.core.api.domain.lookup.PlanningId; 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; @@ -69,6 +71,7 @@ void migrate() { import ai.timefold.solver.core.api.score.buildin.bendablebigdecimal.BendableBigDecimalScore; public class Test { + @PlanningId SimpleScore simpleScore; SimpleLongScore simpleLongScore; SimpleBigDecimalScore simpleBigDecimalScore; @@ -85,6 +88,7 @@ public class Test { """ 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; @@ -95,6 +99,7 @@ public class Test { import ai.timefold.solver.core.api.score.SimpleScore; public class Test { + @PlanningId SimpleScore simpleScore; SimpleScore simpleLongScore; SimpleBigDecimalScore simpleBigDecimalScore; From 0debd13fa6fde5a6d7c5c9511135dfd6e0f65daf Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 20 Feb 2026 15:00:43 -0300 Subject: [PATCH 3/9] feat: add more recipes --- .../ConstraintStreamMigrationRecipe.java | 59 +++++++ .../resources/META-INF/rewrite/ToLatest.yml | 21 +-- .../ConstraintStreamMigrationRecipeTest.java | 153 ++++++++++++++++++ 3 files changed, 216 insertions(+), 17 deletions(-) create mode 100644 tools/migration/src/main/java/ai/timefold/solver/migration/ConstraintStreamMigrationRecipe.java create mode 100644 tools/migration/src/test/java/ai/timefold/solver/migration/ConstraintStreamMigrationRecipeTest.java 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/resources/META-INF/rewrite/ToLatest.yml b/tools/migration/src/main/resources/META-INF/rewrite/ToLatest.yml index 9b5714491f..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,22 +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: - - 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 - 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); + } + }""")); + } + +} From 46451ee3096c855429179eb27f87701d832cffbe Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 20 Feb 2026 15:05:47 -0300 Subject: [PATCH 4/9] feat: add more recipes --- .../solver/migration/GeneralChangeTypeMigrationRecipe.java | 5 ++++- .../migration/GeneralChangeTypeMigrationRecipeTest.java | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) 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 index 9b47067e4b..afa59c68cc 100644 --- a/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java +++ b/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java @@ -50,6 +50,9 @@ public List getRecipeList() { 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)); + "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/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java b/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java index 5a75ea6f7c..956d37e59b 100644 --- a/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java +++ b/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java @@ -47,7 +47,8 @@ public void defaults(RecipeSpec spec) { "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.score.buildin.bendablebigdecimal; public class BendableBigDecimalScore {}", + "package ai.timefold.solver.core.api.solver; public class ProblemFactChange {}")); } @Test @@ -57,6 +58,7 @@ void migrate() { 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; @@ -73,6 +75,7 @@ void migrate() { public class Test { @PlanningId SimpleScore simpleScore; + ProblemFactChange problemFactChange; SimpleLongScore simpleLongScore; SimpleBigDecimalScore simpleBigDecimalScore; HardSoftScore hardSoftScore; @@ -97,10 +100,12 @@ public class Test { 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; From 8ed35bc4dcf9768134ef61e16adb1fc0a2ab87f7 Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 20 Feb 2026 15:17:21 -0300 Subject: [PATCH 5/9] feat: add more recipes --- .../migration/GeneralChangeTypeMigrationRecipe.java | 8 +++++++- .../GeneralChangeTypeMigrationRecipeTest.java | 12 +++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) 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 index afa59c68cc..5d15a441c0 100644 --- a/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java +++ b/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java @@ -4,6 +4,7 @@ import org.openrewrite.Recipe; import org.openrewrite.java.ChangeType; +import org.openrewrite.java.ReplaceConstantWithAnotherConstant; public class GeneralChangeTypeMigrationRecipe extends AbstractRecipe { @Override @@ -53,6 +54,11 @@ public List getRecipeList() { "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)); + "ai.timefold.solver.core.api.solver.change.ProblemChange", true), + // Replace ENVIRONMENT values + new ReplaceConstantWithAnotherConstant("ai.timefold.solver.core.config.solver.EnvironmentMode.FAST_ASSERT", + "ai.timefold.solver.core.config.solver.EnvironmentMode.STEP_ASSERT"), + new ReplaceConstantWithAnotherConstant("ai.timefold.solver.core.config.solver.EnvironmentMode.REPRODUCIBLE", + "ai.timefold.solver.core.config.solver.EnvironmentMode.NO_ASSERT")); } } 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 index 956d37e59b..28c3265a75 100644 --- a/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java +++ b/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java @@ -48,7 +48,8 @@ public void defaults(RecipeSpec spec) { "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 {}")); + "package ai.timefold.solver.core.api.solver; public class ProblemFactChange {}", + "package ai.timefold.solver.core.config.solver; public enum EnvironmentMode {FAST_ASSERT, REPRODUCIBLE}")); } @Test @@ -59,6 +60,7 @@ void migrate() { import ai.timefold.solver.core.api.domain.lookup.PlanningId; import ai.timefold.solver.core.api.solver.ProblemFactChange; + import ai.timefold.solver.core.config.solver.EnvironmentMode; 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; @@ -87,10 +89,13 @@ public class Test { BendableScore bendableScore; BendableLongScore bendableLongScore; BendableBigDecimalScore bendableBigDecimalScore; + EnvironmentMode fast = EnvironmentMode.FAST_ASSERT; + EnvironmentMode reproducible = EnvironmentMode.REPRODUCIBLE; }""", """ package timefold; - + import ai.timefold.solver.core.api.solver.change.ProblemChange; + import ai.timefold.solver.core.config.solver.EnvironmentMode; 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; @@ -100,7 +105,6 @@ public class Test { 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 @@ -117,6 +121,8 @@ public class Test { BendableScore bendableScore; BendableScore bendableLongScore; BendableBigDecimalScore bendableBigDecimalScore; + EnvironmentMode fast = EnvironmentMode.STEP_ASSERT; + EnvironmentMode reproducible = EnvironmentMode.NO_ASSERT; }""")); } From d428b82b7b194f83449ad94b61c91797288a7fb7 Mon Sep 17 00:00:00 2001 From: fred Date: Fri, 20 Feb 2026 15:18:01 -0300 Subject: [PATCH 6/9] chore: minor fix --- .../solver/migration/GeneralChangeTypeMigrationRecipe.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 5d15a441c0..8ef396b18b 100644 --- a/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java +++ b/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java @@ -55,7 +55,7 @@ public List getRecipeList() { // Problem fact new ChangeType("ai.timefold.solver.core.api.solver.ProblemFactChange", "ai.timefold.solver.core.api.solver.change.ProblemChange", true), - // Replace ENVIRONMENT values + // ENVIRONMENT values new ReplaceConstantWithAnotherConstant("ai.timefold.solver.core.config.solver.EnvironmentMode.FAST_ASSERT", "ai.timefold.solver.core.config.solver.EnvironmentMode.STEP_ASSERT"), new ReplaceConstantWithAnotherConstant("ai.timefold.solver.core.config.solver.EnvironmentMode.REPRODUCIBLE", From 3695201a4c33031df6849a1937cf53524025db73 Mon Sep 17 00:00:00 2001 From: fred Date: Mon, 23 Feb 2026 08:40:27 -0300 Subject: [PATCH 7/9] chore: address comments --- .../solver/migration/GeneralChangeTypeMigrationRecipeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 28c3265a75..2ed057f647 100644 --- a/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java +++ b/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java @@ -49,7 +49,7 @@ public void defaults(RecipeSpec spec) { "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 {}", - "package ai.timefold.solver.core.config.solver; public enum EnvironmentMode {FAST_ASSERT, REPRODUCIBLE}")); + "package ai.timefold.solver.core.config.solver; public enum EnvironmentMode {FAST_ASSERT, STEP_ASSERT, NO_ASSERT, REPRODUCIBLE}")); } @Test From 4eb8a0039c494a79413bf32c505c7157f5de1b35 Mon Sep 17 00:00:00 2001 From: fred Date: Mon, 23 Feb 2026 17:40:05 -0300 Subject: [PATCH 8/9] chore: address comments --- .../migration/GeneralChangeTypeMigrationRecipe.java | 8 +------- .../GeneralChangeTypeMigrationRecipeTest.java | 12 +++--------- 2 files changed, 4 insertions(+), 16 deletions(-) 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 index 8ef396b18b..afa59c68cc 100644 --- a/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java +++ b/tools/migration/src/main/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipe.java @@ -4,7 +4,6 @@ import org.openrewrite.Recipe; import org.openrewrite.java.ChangeType; -import org.openrewrite.java.ReplaceConstantWithAnotherConstant; public class GeneralChangeTypeMigrationRecipe extends AbstractRecipe { @Override @@ -54,11 +53,6 @@ public List getRecipeList() { "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), - // ENVIRONMENT values - new ReplaceConstantWithAnotherConstant("ai.timefold.solver.core.config.solver.EnvironmentMode.FAST_ASSERT", - "ai.timefold.solver.core.config.solver.EnvironmentMode.STEP_ASSERT"), - new ReplaceConstantWithAnotherConstant("ai.timefold.solver.core.config.solver.EnvironmentMode.REPRODUCIBLE", - "ai.timefold.solver.core.config.solver.EnvironmentMode.NO_ASSERT")); + "ai.timefold.solver.core.api.solver.change.ProblemChange", true)); } } 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 index 2ed057f647..a35f2a0841 100644 --- a/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java +++ b/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java @@ -48,8 +48,7 @@ public void defaults(RecipeSpec spec) { "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 {}", - "package ai.timefold.solver.core.config.solver; public enum EnvironmentMode {FAST_ASSERT, STEP_ASSERT, NO_ASSERT, REPRODUCIBLE}")); + "package ai.timefold.solver.core.api.solver; public class ProblemFactChange {}")); } @Test @@ -60,7 +59,6 @@ void migrate() { import ai.timefold.solver.core.api.domain.lookup.PlanningId; import ai.timefold.solver.core.api.solver.ProblemFactChange; - import ai.timefold.solver.core.config.solver.EnvironmentMode; 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; @@ -89,13 +87,10 @@ public class Test { BendableScore bendableScore; BendableLongScore bendableLongScore; BendableBigDecimalScore bendableBigDecimalScore; - EnvironmentMode fast = EnvironmentMode.FAST_ASSERT; - EnvironmentMode reproducible = EnvironmentMode.REPRODUCIBLE; }""", """ package timefold; - import ai.timefold.solver.core.api.solver.change.ProblemChange; - import ai.timefold.solver.core.config.solver.EnvironmentMode; + 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; @@ -105,6 +100,7 @@ public class Test { 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 @@ -121,8 +117,6 @@ public class Test { BendableScore bendableScore; BendableScore bendableLongScore; BendableBigDecimalScore bendableBigDecimalScore; - EnvironmentMode fast = EnvironmentMode.STEP_ASSERT; - EnvironmentMode reproducible = EnvironmentMode.NO_ASSERT; }""")); } From 65c7ed5b444e0a48c59911fa70b7e9db83616c8c Mon Sep 17 00:00:00 2001 From: fred Date: Tue, 24 Feb 2026 08:09:14 -0300 Subject: [PATCH 9/9] chore: formatting --- .../solver/migration/GeneralChangeTypeMigrationRecipeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index a35f2a0841..956d37e59b 100644 --- a/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java +++ b/tools/migration/src/test/java/ai/timefold/solver/migration/GeneralChangeTypeMigrationRecipeTest.java @@ -90,7 +90,7 @@ public class Test { }""", """ 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;