From af5b6085d7819f23a31dda12fac506136239d74d Mon Sep 17 00:00:00 2001
From: Thomas Florio <thomas.florio@suse.com>
Date: Mon, 26 Feb 2024 11:11:10 +0100
Subject: [PATCH 1/6] Made OptaPlanner logger static

---
 src/main/java/com/suse/matcher/OptaPlanner.java | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
index 4deb995..12938c5 100644
--- a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
+++ b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
@@ -47,7 +47,7 @@
 public class OptaPlanner {
 
     /** Logger instance. */
-    private final Logger logger = LogManager.getLogger(OptaPlanner.class);
+    private static final Logger LOGGER = LogManager.getLogger(OptaPlanner.class);
 
     /** The result. */
     Assignment result;
@@ -71,13 +71,13 @@ public OptaPlanner(Assignment unsolved, boolean testing) {
         // solve problem
         long start = System.currentTimeMillis();
         solver.solve(unsolved);
-        logger.info("Optimization phase took {}ms", System.currentTimeMillis() - start);
+        LOGGER.info("Optimization phase took {}ms", System.currentTimeMillis() - start);
         result = (Assignment) solver.getBestSolution();
-        logger.info("{} matches confirmed", result.getMatches().stream().filter(m -> m.confirmed).count());
+        LOGGER.info("{} matches confirmed", result.getMatches().stream().filter(m -> m.confirmed).count());
 
         // show Penalty facts generated in Scores.drl using DroolsScoreDirector and re-calculating
         // the score of the best solution because facts generated dynamically are not available outside of this object
-        if (logger.isDebugEnabled()) {
+        if (LOGGER.isDebugEnabled()) {
             DroolsScoreDirector scoreDirector = (DroolsScoreDirector) solver.getScoreDirectorFactory().buildScoreDirector();
             scoreDirector.setWorkingSolution(scoreDirector.cloneSolution(result));
             scoreDirector.calculateScore();
@@ -86,8 +86,8 @@ public OptaPlanner(Assignment unsolved, boolean testing) {
                     .filter(f -> f instanceof OneTwoPenalty)
                     .map(f -> (Penalty)f)
                     .collect(toList());
-            logger.debug("The best solution has " + penalties.size() + " penalties for 1-2 subscriptions.");
-            penalties.forEach(penalty -> logger.debug(penalty.toString()));
+            LOGGER.debug("The best solution has {} penalties for 1-2 subscriptions.", penalties.size());
+            penalties.forEach(penalty -> LOGGER.debug(penalty.toString()));
         }
     }
 

From a3a6de88f8be90dfc82f648c96182ed42467a6b1 Mon Sep 17 00:00:00 2001
From: Thomas Florio <thomas.florio@suse.com>
Date: Mon, 26 Feb 2024 11:11:31 +0100
Subject: [PATCH 2/6] Restrict field visibility

---
 src/main/java/com/suse/matcher/OptaPlanner.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
index 12938c5..325830c 100644
--- a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
+++ b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
@@ -50,7 +50,7 @@ public class OptaPlanner {
     private static final Logger LOGGER = LogManager.getLogger(OptaPlanner.class);
 
     /** The result. */
-    Assignment result;
+    private final Assignment result;
 
     /**
      * Instantiates an OptaPlanner instance with the specified unsolved problem.

From c0be8780d15f8886f47528b40e78514156e1632a Mon Sep 17 00:00:00 2001
From: Thomas Florio <thomas.florio@suse.com>
Date: Mon, 26 Feb 2024 11:19:53 +0100
Subject: [PATCH 3/6] Avoid double curly brace notation

---
 src/main/java/com/suse/matcher/OptaPlanner.java | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
index 325830c..35450d3 100644
--- a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
+++ b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
@@ -32,7 +32,6 @@
 import org.optaplanner.core.config.solver.SolverConfig;
 import org.optaplanner.core.config.solver.random.RandomType;
 import org.optaplanner.core.config.solver.termination.TerminationConfig;
-import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionFilter;
 import org.optaplanner.core.impl.score.director.drools.DroolsScoreDirector;
 
 import java.util.ArrayList;
@@ -117,14 +116,14 @@ private Solver initSolver(boolean testing) {
          * Declare solution and entity classes
          */
         config.setSolutionClass(Assignment.class);
-        config.setEntityClassList(new ArrayList<Class<?>>(){{ add(Match.class); }});
+        config.setEntityClassList(List.of(Match.class));
 
         /*
          * Declare score type and file location
          */
         ScoreDirectorFactoryConfig score = new ScoreDirectorFactoryConfig();
         score.setScoreDefinitionType(ScoreDefinitionType.HARD_SOFT);
-        score.setScoreDrlList(new ArrayList<String>() {{ add("com/suse/matcher/rules/optaplanner/Scores.drl"); }});
+        score.setScoreDrlList(List.of("com/suse/matcher/rules/optaplanner/Scores.drl"));
         config.setScoreDirectorFactoryConfig(score);
 
         /*
@@ -162,10 +161,8 @@ private Solver initSolver(boolean testing) {
         valueSelector.setSelectionOrder(SelectionOrder.ORIGINAL);
         changeMove.setValueSelectorConfig(valueSelector);
 
-        changeMove.setFilterClassList(new ArrayList<Class<? extends SelectionFilter>>() {{
-            add(ConflictMatchMoveFilter.class);
-        }});
-        entityPlacer.setMoveSelectorConfigList(new ArrayList<MoveSelectorConfig>() {{ add(changeMove); }});
+        changeMove.setFilterClassList(List.of(ConflictMatchMoveFilter.class));
+        entityPlacer.setMoveSelectorConfigList(List.of(changeMove));
         constructionHeuristic.setEntityPlacerConfig(entityPlacer);
         config.getPhaseConfigList().add(constructionHeuristic);
 

From a7ab3cff4a5ead47b85091e8f562f9185ade4053 Mon Sep 17 00:00:00 2001
From: Thomas Florio <thomas.florio@suse.com>
Date: Mon, 26 Feb 2024 11:21:07 +0100
Subject: [PATCH 4/6] Avoid usage of raw types

---
 .../java/com/suse/matcher/OptaPlanner.java     | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

diff --git a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
index 35450d3..3d7e482 100644
--- a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
+++ b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
@@ -65,19 +65,19 @@ public OptaPlanner(Assignment unsolved, boolean testing) {
         }
 
         // init solver
-        Solver solver = initSolver(testing);
+        Solver<Assignment> solver = initSolver(testing);
 
         // solve problem
         long start = System.currentTimeMillis();
         solver.solve(unsolved);
         LOGGER.info("Optimization phase took {}ms", System.currentTimeMillis() - start);
-        result = (Assignment) solver.getBestSolution();
+        result = solver.getBestSolution();
         LOGGER.info("{} matches confirmed", result.getMatches().stream().filter(m -> m.confirmed).count());
 
         // show Penalty facts generated in Scores.drl using DroolsScoreDirector and re-calculating
         // the score of the best solution because facts generated dynamically are not available outside of this object
         if (LOGGER.isDebugEnabled()) {
-            DroolsScoreDirector scoreDirector = (DroolsScoreDirector) solver.getScoreDirectorFactory().buildScoreDirector();
+            DroolsScoreDirector<Assignment> scoreDirector = (DroolsScoreDirector<Assignment>) solver.getScoreDirectorFactory().buildScoreDirector();
             scoreDirector.setWorkingSolution(scoreDirector.cloneSolution(result));
             scoreDirector.calculateScore();
             Collection<Penalty> penalties = scoreDirector.getKieSession().getObjects()
@@ -93,15 +93,14 @@ public OptaPlanner(Assignment unsolved, boolean testing) {
     /**
      * Configures and returns an OptaPlanner solver.
      *
-     * This method replaces the XML configuration file cited in Optaplanner's documentation.
+     * This method replaces the XML configuration file cited in OptaPlanner's documentation.
      *
      * @return the solver
      * @param testing true if running as a unit test, false otherwise
      */
-    @SuppressWarnings("rawtypes")
-    private Solver initSolver(boolean testing) {
+    private Solver<Assignment> initSolver(boolean testing) {
         // init basic objects
-        SolverFactory factory = SolverFactory.createEmpty();
+        SolverFactory<Assignment> factory = SolverFactory.createEmpty();
         SolverConfig config = factory.getSolverConfig();
         config.setPhaseConfigList(new ArrayList<>());
 
@@ -192,10 +191,7 @@ private Solver initSolver(boolean testing) {
          * both moves alternate within the step (in random fashion).
          */
         UnionMoveSelectorConfig unionMoveConfig = new UnionMoveSelectorConfig();
-        List<MoveSelectorConfig> selectors = new ArrayList<>();
-        selectors.add(move);
-        selectors.add(swapMove);
-        unionMoveConfig.setMoveSelectorConfigList(selectors);
+        unionMoveConfig.setMoveSelectorConfigList(List.of(move, swapMove));
 
         LocalSearchPhaseConfig search = new LocalSearchPhaseConfig();
         search.setMoveSelectorConfig(unionMoveConfig);

From e7d210f96b6253a60f6efd2039091943a656bfb1 Mon Sep 17 00:00:00 2001
From: Thomas Florio <thomas.florio@suse.com>
Date: Mon, 26 Feb 2024 11:39:39 +0100
Subject: [PATCH 5/6] Extracted method to log penalties

---
 .../java/com/suse/matcher/OptaPlanner.java    | 28 +++++++++++--------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
index 3d7e482..5c1208d 100644
--- a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
+++ b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
@@ -18,7 +18,6 @@
 import org.optaplanner.core.config.heuristic.selector.common.SelectionCacheType;
 import org.optaplanner.core.config.heuristic.selector.common.SelectionOrder;
 import org.optaplanner.core.config.heuristic.selector.entity.EntitySelectorConfig;
-import org.optaplanner.core.config.heuristic.selector.move.MoveSelectorConfig;
 import org.optaplanner.core.config.heuristic.selector.move.composite.UnionMoveSelectorConfig;
 import org.optaplanner.core.config.heuristic.selector.move.factory.MoveIteratorFactoryConfig;
 import org.optaplanner.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig;
@@ -77,19 +76,26 @@ public OptaPlanner(Assignment unsolved, boolean testing) {
         // show Penalty facts generated in Scores.drl using DroolsScoreDirector and re-calculating
         // the score of the best solution because facts generated dynamically are not available outside of this object
         if (LOGGER.isDebugEnabled()) {
-            DroolsScoreDirector<Assignment> scoreDirector = (DroolsScoreDirector<Assignment>) solver.getScoreDirectorFactory().buildScoreDirector();
-            scoreDirector.setWorkingSolution(scoreDirector.cloneSolution(result));
-            scoreDirector.calculateScore();
-            Collection<Penalty> penalties = scoreDirector.getKieSession().getObjects()
-                    .stream()
-                    .filter(f -> f instanceof OneTwoPenalty)
-                    .map(f -> (Penalty)f)
-                    .collect(toList());
-            LOGGER.debug("The best solution has {} penalties for 1-2 subscriptions.", penalties.size());
-            penalties.forEach(penalty -> LOGGER.debug(penalty.toString()));
+            logOneTwoPenalties(solver, result);
         }
     }
 
+    private static void logOneTwoPenalties(Solver<Assignment> solver, Assignment result) {
+        DroolsScoreDirector<Assignment> scoreDirector = (DroolsScoreDirector<Assignment>) solver.getScoreDirectorFactory().buildScoreDirector();
+
+        scoreDirector.setWorkingSolution(scoreDirector.cloneSolution(result));
+        scoreDirector.calculateScore();
+
+        Collection<Penalty> penalties = scoreDirector.getKieSession().getObjects()
+                .stream()
+                .filter(f -> f instanceof OneTwoPenalty)
+                .map(f -> (Penalty)f)
+                .collect(toList());
+
+        LOGGER.debug("The best solution has {} penalties for 1-2 subscriptions.", penalties.size());
+        penalties.forEach(penalty -> LOGGER.debug(penalty.toString()));
+    }
+
     /**
      * Configures and returns an OptaPlanner solver.
      *

From 8e93cf6f215c2f52ec0099583912f6c3be6e153d Mon Sep 17 00:00:00 2001
From: Thomas Florio <thomas.florio@suse.com>
Date: Mon, 26 Feb 2024 12:32:38 +0100
Subject: [PATCH 6/6] Initialize the score director consistently

---
 .../java/com/suse/matcher/OptaPlanner.java    | 30 ++++++++++++++-----
 1 file changed, 22 insertions(+), 8 deletions(-)

diff --git a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
index 5c1208d..3481368 100644
--- a/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
+++ b/subscription-matcher/src/main/java/com/suse/matcher/OptaPlanner.java
@@ -32,6 +32,8 @@
 import org.optaplanner.core.config.solver.random.RandomType;
 import org.optaplanner.core.config.solver.termination.TerminationConfig;
 import org.optaplanner.core.impl.score.director.drools.DroolsScoreDirector;
+import org.optaplanner.core.impl.score.director.drools.DroolsScoreDirectorFactory;
+import org.optaplanner.core.impl.solver.DefaultSolver;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -81,19 +83,31 @@ public OptaPlanner(Assignment unsolved, boolean testing) {
     }
 
     private static void logOneTwoPenalties(Solver<Assignment> solver, Assignment result) {
-        DroolsScoreDirector<Assignment> scoreDirector = (DroolsScoreDirector<Assignment>) solver.getScoreDirectorFactory().buildScoreDirector();
+        // Make sure the runtime instances are of the correct types
+        if (!(solver instanceof DefaultSolver) || !(solver.getScoreDirectorFactory() instanceof DroolsScoreDirectorFactory)) {
+            return;
+        }
+
+        DefaultSolver<Assignment> defaultSolver = (DefaultSolver<Assignment>) solver;
+        DroolsScoreDirectorFactory<Assignment> directorFactory = (DroolsScoreDirectorFactory<Assignment>) defaultSolver.getScoreDirectorFactory();
+        EnvironmentMode environmentMode = defaultSolver.getEnvironmentMode();
 
-        scoreDirector.setWorkingSolution(scoreDirector.cloneSolution(result));
-        scoreDirector.calculateScore();
+        // Build a new score director and re-evaluate the score
+        try (DroolsScoreDirector<Assignment> director = directorFactory.buildScoreDirector(true, environmentMode.isAsserted())) {
+            director.setWorkingSolution(director.cloneSolution(result));
+            director.calculateScore();
 
-        Collection<Penalty> penalties = scoreDirector.getKieSession().getObjects()
+            Collection<Penalty> penalties = director.getKieSession().getObjects(obj -> obj instanceof OneTwoPenalty)
                 .stream()
-                .filter(f -> f instanceof OneTwoPenalty)
-                .map(f -> (Penalty)f)
+                .map(f -> (Penalty) f)
                 .collect(toList());
 
-        LOGGER.debug("The best solution has {} penalties for 1-2 subscriptions.", penalties.size());
-        penalties.forEach(penalty -> LOGGER.debug(penalty.toString()));
+            LOGGER.debug("The best solution has {} penalties for 1-2 subscriptions.", penalties.size());
+            penalties.forEach(penalty -> LOGGER.debug(penalty.toString()));
+        }
+        catch (Exception ex) {
+            LOGGER.debug("Number of penalties for 1-2 subscriptions not available: {}", ex.getMessage());
+        }
     }
 
     /**
