PLANNER-1238 Didn't make 7.11.0.Final

PLANNER-1238 Didn't make 7.11.0.Final

= Upgrade recipe 7
:awestruct-description: Upgrade to OptaPlanner 7 from a previous version.
:awestruct-layout: upgradeRecipeBase
:awestruct-priority: 0.5
:awestruct-upgrade_recipe_version: 7

== From 6.5.0.Final to 7.0.0.Beta1

=== Backwards incompatible changes to the public API in 7.0

Because this is a new major version number (7.0), which is the foundation for the 7.x series for the next few years,
it allows us to make backwards incompatible changes to the public API _for the long term benefit of this project_.

We kept these backwards incompatible changes to a strict minimum
(by favoring deprecation over removal) and will not introduce any additional ones during the 7.x era.

Any backwards incompatible changes are annotated with a [.label.label-danger.label-as-badge.label-public-api]#Public API# badge.

[.upgrade-recipe-major.upgrade-recipe-public-api]
=== Java 8 or higher required

If you're using JRE or JDK 6 or 7, upgrade to JDK 8 or higher.

We currently intend to support a minimal version of Java 8 throughout the entire 7.x series.

[.upgrade-recipe-minor.upgrade-recipe-public-api]
===  Deprecated methods removed

The following long term deprecated methods have been finally removed:

* Setters on `ScoreHolder` implementations, such as `HardSoftScoreHolder.setHardScore(int)` and `setSoftScore(int)`.
Use `addHardConstraintMatch(...)` and `addSoftConstraintMatch(...)` in your score rules instead.
See link:./upgradeRecipe6.0.html[this upgrade recipe].

* The experimental, deprecated, hybrid metaheuristic called `LATE_SIMULATED_ANNEALING`
(which was inspired by both Late Acceptance and Simulated Annealing) has been removed.

* The dead, deprecated code of `DeciderScoreComparatorFactory` has been removed.
See link:./upgradeRecipe6.2.html[this upgrade recipe].

* The deprecated `SolverBenchmarkBluePrintType.ALL_CONSTRUCTION_HEURISTIC_TYPES` has been removed.
See link:./upgradeRecipe6.3.html[this upgrade recipe].

[.upgrade-recipe-major]
=== `Solution` interface removed (deprecated)

Your solution class no longer needs to have both the `@PlanningSolution` annotation and implement the `Solution` interface,
only the annotation suffices. The `Solution` interface has been deprecated and will be removed in a future version.

Remove the `Solution` interface, annotate the `getScore()` method with `@PlanningScore`
and replace the `getProblemFacts()` method with a `@ProblemFactCollectionProperty` annotation directly on every problem fact getter (or field).

Before in `*.java`:
[source, java]
----
@PlanningSolution
public class CloudBalance implements Solution<HardSoftScore> {

    private List<CloudComputer> computerList;
    ...

    private HardSoftScore score;

    @ValueRangeProvider(id = "computerRange")
    public List<CloudComputer> getComputerList() {...}

    public HardSoftScore getScore() {...}
    public void setScore(HardSoftScore score) {...}

    public Collection<? extends Object> getProblemFacts() {
        List<Object> facts = new ArrayList<Object>();
        facts.addAll(computerList);
        ...
        return facts;
    }

}
----

After in `*.java`:
[source, java]
----
@PlanningSolution
public class CloudBalance {

    private List<CloudComputer> computerList;
    ...

    private HardSoftScore score;

    @ValueRangeProvider(id = "computerRange")
    @ProblemFactCollectionProperty
    public List<CloudComputer> getComputerList() {...}

    @PlanningScore
    public HardSoftScore getScore() {...}
    public void setScore(HardSoftScore score) {...}

}
----

For a single problem fact (which is not wrapped in a `Collection`), use the `@ProblemFactProperty` annotation,
as shown below (with field annotations this time).

Before in `*.java`:
[source, java]
----
@PlanningSolution
public class CloudBalance implements Solution<HardSoftScore> {

    private CloudParametrization parametrization;
    private List<CloudBuilding> buildingList;
    @ValueRangeProvider(id = "computerRange")
    private List<CloudComputer> computerList;
    ...

    public Collection<? extends Object> getProblemFacts() {
        List<Object> facts = new ArrayList<Object>();
        facts.add(parametrization); // not a Collection
        facts.addAll(buildingList);
        facts.addAll(computerList);
        ...
        return facts;
    }

}
----

After in `*.java`:
[source, java]
----
@PlanningSolution
public class CloudBalance {

    @ProblemFactProperty
    private CloudParametrization parametrization;
    @ProblemFactCollectionProperty
    private List<CloudBuilding> buildingList;
    @ValueRangeProvider(id = "computerRange")
    @ProblemFactCollectionProperty
    private List<CloudComputer> computerList;
    ...

}
----

Don't add the `@ProblemFactCollectionProperty` annotation on getters (or fields)
that have a `@PlanningEntityCollectionProperty` annotation.

[.upgrade-recipe-minor.upgrade-recipe-public-api]
===  `Solver`: return values no longer implement `Solution`

Because the `Solution` interface was deprecated (see the section below to upgrade from 6.4.0.Final to 7.0.0.Beta1),
the `Solver.solve(...)` and `Solver.getBestSolution()` methods now return an `Object` instead of a `Solution` instance
(if and only if no type parameter is specified for the `Solver`).

*This only applies if you're still using a `Solver` without a type parameter
and if you're not casting the return value immediately to your solution implementation (which is unlikely).*

Before in `*.java`:
[source, java]
----
Solution s = solver.solve(problem);
CloudBalance solution = (CloudBalance) s;
----

After in `*.java` (quick and dirty fix):
[source, java]
----
CloudBalance solution = (CloudBalance) solver.solve(problem);
----

After in `*.java` (recommended fix):
[source, java]
----
SolverFactory<CloudBalance> factory = SolverFactory.createFromXmlResource(...);
Solver<CloudBalance> solver = factory.buildSolver();
...
CloudBalance solution = solver.solve(problem);
----

[.upgrade-recipe-minor.upgrade-recipe-public-api]
===  `BestSolutionChangedEvent.getNewBestSolution()`: return value no longer implements `Solution`

Because the `Solution` interface was deprecated (see the section below to upgrade from 6.4.0.Final to 7.0.0.Beta1),
the `BestSolutionChangedEvent.getNewBestSolution()` method now returns an `Object`
(if and only if no type parameter is specified for the `SolverEventListener`).

*This only applies if you're still using a `SolverEventListener` without a type parameter
and if you're not casting the return value immediately to your solution implementation (which is unlikely).*

Before in `*.java`:
[source, java]
----
SolverFactory factory = SolverFactory.createFromXmlResource(...);
Solver solver = factory.buildSolver();
solver.addEventListener(new SolverEventListener() {
    @Override
    public void bestSolutionChanged(BestSolutionChangedEvent event) {
        Solution s = event.getNewBestSolution();
        CloudBalance solution = (CloudBalance) s;
        ...
    }
});
----

After in `*.java`:
[source, java]
----
SolverFactory<CloudBalance> factory = SolverFactory.createFromXmlResource(...);
Solver<CloudBalance> solver = factory.buildSolver();
solver.addEventListener(new SolverEventListener<CloudBalance>() {
    @Override
    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        CloudBalance solution = event.getNewBestSolution();
        ...
    }
});
----

And you'll probably want to use a lambda here:

[source, java]
----
SolverFactory<CloudBalance> factory = SolverFactory.createFromXmlResource(...);
Solver<CloudBalance> solver = factory.buildSolver();
solver.addEventListener(event -> {
    CloudBalance solution = event.getNewBestSolution();
    ...
});
----

[.upgrade-recipe-major]
=== `SolutionFileIO`: added optional generic type parameter

To avoid the awkward cast to your `Solution` implementation and to get rid of that deprecated interface,
`SolutionFileIO` now optionally supports a generic type parameter (which is the solution class).

Before in `*.java`:
[source, java]
----
public class TspFileIO implements SolutionFileIO {
    ...

    public Solution read(File inputSolutionFile) {...}

    public void write(Solution solution, File outputSolutionFile) {
        TspSolution tspSolution = (TspSolution) solution;
        ...
    }

}
----

After in `*.java`:
[source, java]
----
public class TspFileIO implements SolutionFileIO<TspSolution> {
    ...

    public TspSolution read(File inputSolutionFile) {...}

    public void write(TspSolution tspSolution, File outputSolutionFile) {
        ...
    }

}
----

[.upgrade-recipe-minor]
=== `XStreamSolutionFileIO`: added optional generic type parameter

To avoid the awkward cast to your `Solution` implementation and to get rid of that deprecated interface,
`XStreamSolutionFileIO` now optionally supports a generic type parameter (which is the solution class).

Before in `*.java`:
[source, java]
----
SolutionFileIO solutionFileIO = new XStreamSolutionFileIO(CloudBalance.class);
----

After in `*.java`:
[source, java]
----
SolutionFileIO<CloudBalance> solutionFileIO = new XStreamSolutionFileIO<>(CloudBalance.class);
----

[.upgrade-recipe-minor]
=== `SelectionFilter`: added generic type parameter

To avoid the awkward cast to your `Solution` implementation,
a `SelectionFilter` now also has a generic type parameter for the solution, not just the selection type.

Before in `*.java`:
[source, java]
----
public class LectureFilter implements SelectionFilter<Lecture> {

    public boolean accept(ScoreDirector scoreDirector, Lecture lecture) {
        ...
    }

}
----

After in `*.java`:
[source, java]
----
public class LectureFilter implements SelectionFilter<CourseSchedule, Lecture> {

    @Override
    public boolean accept(ScoreDirector<CourseSchedule> scoreDirector, Lecture lecture) {
        ...
    }

}
----

[.upgrade-recipe-minor]
=== `CustomPhaseCommand`: added optional generic type parameter

To avoid the awkward cast to your `Solution` implementation and to get rid of that deprecated interface,
`CustomPhaseCommand` now optionally supports a generic type parameter (which is the solution class).

Before in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand {

    public void changeWorkingSolution(ScoreDirector scoreDirector) {
        DinnerParty dinnerParty = (DinnerParty) scoreDirector.getWorkingSolution();
        ...
    }

}
----

After in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {

    public void changeWorkingSolution(ScoreDirector<DinnerParty> scoreDirector) {
        DinnerParty dinnerParty = scoreDirector.getWorkingSolution();
        ...
    }

}
----

[.upgrade-recipe-major]
=== `ProblemFactChange`: added optional generic type parameter

To avoid the awkward cast to your `Solution` implementation and to get rid of that deprecated interface,
`ProblemFactChange` now optionally supports a generic type parameter (which is the solution class).

Before in `*.java`:
[source, java]
----
        solver.addProblemFactChange(new ProblemFactChange() {
            public void doChange(ScoreDirector scoreDirector) {
                CloudBalance cloudBalance = (CloudBalance) scoreDirector.getWorkingSolution();
                ...
            }
        });
----

After in `*.java`:
[source, java]
----
        solver.addProblemFactChange(new ProblemFactChange<CloudBalance>() {
            public void doChange(ScoreDirector<CloudBalance> scoreDirector) {
                CloudBalance cloudBalance = scoreDirector.getWorkingSolution();
                ...
            }
        });
----

After in `*.java` (with lambda):
[source, java]
----
        solver.addProblemFactChange(scoreDirector -> {
            CloudBalance cloudBalance = scoreDirector.getWorkingSolution();
            ...
        });
----

[.upgrade-recipe-minor]
=== `Bendable*Score`: `toString()` changed

A bendable score (`BendableScore`, `BendableLongScore` or `BendableBigDecimalScore`)'s `String`
has changed so it can be parsed without the ScoreDefinition.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
      <termination>
        <bestScoreLimit>0/0/-1/-2/-3</bestScoreLimit>
      </termination>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
      <termination>
        <bestScoreLimit>[0/0]hard/[-1/-2/-3]soft</bestScoreLimit>
      </termination>
----

Before in XStream `*.xml` output with `optaplanner-persistence-xstream`:
[source, xml]
----
      <score>0/0/-1/-2/-3</score>
----

After in in XStream `*.xml` output with `optaplanner-persistence-xstream`:
[source, xml]
----
      <score>[0/0]hard/[-1/-2/-3]soft</score>
----

[.upgrade-recipe-major]
=== `EnvironmentMode`: `PRODUCTION` renamed

The `EnvironmentMode` `PRODUCTION` has been renamed to `NON_REPRODUCIBLE`
because most enterprises use `REPRODUCIBLE` in production and that's fine.
For backwards compatibility, `PRODUCTION` still exists, but it's deprecated and it will be removed in a future version.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
<solver>
  <environmentMode>PRODUCTION</environmentMode>
  ...
</solver>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
<solver>
  <environmentMode>NON_REPRODUCIBLE</environmentMode>
  ...
</solver>
----

[.upgrade-recipe-readme]
=== Average calculate count renamed to score calculation speed

In the logs and the benchmark report, the _average calculate count per second_ has been renamed to _score calculation speed_.

[.upgrade-recipe-minor]
=== `Termination`: `calculateCountLimit` renamed

The termination configuration property `calculateCountLimit` has been renamed to `scoreCalculationCountLimit`.
The property `calculateCountLimit`  has been deprecated and will be removed in a future version.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <termination>
    <calculateCountLimit>100000</calculateCountLimit>
  </termination>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <termination>
    <scoreCalculationCountLimit>100000</scoreCalculationCountLimit>
  </termination>
----

[.upgrade-recipe-minor]
=== `ProblemStatisticType`: `CALCULATE_COUNT_PER_SECOND` renamed

The benchmark ProblemStatisticType `CALCULATE_COUNT_PER_SECOND` has been renamed to `SCORE_CALCULATION_SPEED`.

Before in `*BenchmarkConfig.xml`:
[source, xml]
----
      <problemStatisticType>CALCULATE_COUNT_PER_SECOND</problemStatisticType>
----

After in `*BenchmarkConfig.xml`:
[source, xml]
----
      <problemStatisticType>SCORE_CALCULATION_SPEED</problemStatisticType>
----

[.upgrade-recipe-readme]
=== `Score`: uninitialized variable count

A solution's `Score` now also contains the number of uninitialized variables (usually `0`) as a negative `getInitScore()`.
This is useful in exotic cases with multiple phases to fully initialize a solution.
It also prevents bugs in multithreaded use cases.

With `Score.isSolutionInitialized()`, it's now possible to quickly and reliably determine if a solution is fully initialized.
The method `FeasibilityScore.isFeasible()` now also checks if the solution was fully initialized during score calculation.

[.upgrade-recipe-major.upgrade-recipe-reverted]
=== `EasyScoreCalculator`: `calculateScore()` changed

*This change has been reverted in version 7.0.0.Beta6. Ignore this item if you're upgrading directly to that version or higher.*

The `EasyScoreCalculator` interface method `calculateScore(solution)` has been changed to `calculateScore(solution, initScore)`.
Change the method signature to add the `initScore` and then pass it to the `Score.valueOf()` method.

Before in `*.java`:
[source, java]
----
public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance) {
        ...
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}
----

After in `*.java`:
[source, java]
----
public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance, int initScore) {
        ...
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}
----

OptaPlanner keeps track of the `initScore` internally, but it needs to be passed into the `Score` creation because a `Score` is immutable by design.

[.upgrade-recipe-minor.upgrade-recipe-reverted]
=== `IncrementalScoreCalculator`: `calculateScore()` changed

*This change has been reverted in version 7.0.0.Beta6. Ignore this item if you're upgrading directly to that version or higher.*

The `IncrementalScoreCalculator` interface method `calculateScore()` has been changed to `calculateScore(initScore)`.
Change the method signature to add the `initScore` and then pass it to the `Score.valueOf()` method.

Before in `*.java`:
[source, java]
----
public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore() {
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}
----

After in `*.java`:
[source, java]
----
public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(int initScore) {
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}
----

[.upgrade-recipe-major.upgrade-recipe-public-api.upgrade-recipe-reverted]
=== `Score`: `valueOf(...)` changed and `valueOfInitialized(...)` added

*This change has been reverted in version 7.0.0.Beta6. Ignore this item if you're upgrading directly to that version or higher.
Instead, the method `valueOfUninitialized(...)` has been added, but that doesn't affect your code.*

Each `Score` implementation now requires an `initScore` parameter.
Inside a `ScoreCalculator`, the `initScore` must be passed from the `calculateScore()` method (see the 2 previous notes above).

Outside of a `ScoreCalculator`, if you're constructing a score for an initialized solution,
just replace `valueOf()` with `valueOfInitialized()`:

Before in `*.java`:
[source, java]
----
        SimpleScore score = SimpleScore.valueOf(1234);
----

After in `*.java`:
[source, java]
----
        SimpleScore score = SimpleScore.valueOfInitialized(1234);
----

Or with a `HardSoftScore`:

Before in `*.java`:
[source, java]
----
        HardSoftScore score = HardSoftScore.valueOf(1200, 34);
----

After in `*.java`:
[source, java]
----
        HardSoftScore score = HardSoftScore.valueOfInitialized(1200, 34);
----

It is intentional that `valueOfInitialized()` doesn't just overload `valueOf()`,
to avoid that an `EasyScoreCalculator` implementation forgets to pass the `initScore` parameter.

[.upgrade-recipe-major]
=== `BestSolutionChangedEvent`: `isNewBestSolutionInitialized()` replaced

The method `BestSolutionChangedEvent.isNewBestSolutionInitialized()`
has been deprecated in favor of `BestSolutionChangedEvent.getNewBestSolution().getScore().isSolutionInitialized()`.

Before in `*.java`:
[source, java]
----
    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        if (event.isEveryProblemFactChangeProcessed()
                && event.isNewBestSolutionInitialized()) {
            ...
        }
    }
----

After in `*.java`:
[source, java]
----
    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        if (event.isEveryProblemFactChangeProcessed()
                && event.getNewBestSolution().getScore().isSolutionInitialized()) {
            ...
        }
    }
----

However, if you also check `isFeasible()`, that's enough because it now also checks if the solution is initialized.

After in `*.java` for a `FeasibleScore`:
[source, java]
----
    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        if (event.isEveryProblemFactChangeProcessed()
                // isFeasible() checks isSolutionInitialized() too
                && event.getNewBestSolution().getScore().isFeasible()) {
            ...
        }
    }
----

[.upgrade-recipe-minor.upgrade-recipe-reverted]
=== Custom initializer: `Score.compareTo()` behaviour changed

*This change has been reverted in version 7.0.0.Beta6. Ignore this item if you're upgrading directly to that version or higher.*

The `Score.compareTo()` now also takes the uninitialized variable count into account.
If you have a `CustomPhaseCommand` that implements a custom solution initializer (instead of using a Construction Heuristic),
it will need to transform all scores with `Score.toInitializedScore()` before comparison to avoid making the wrong decision:

Before in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOf(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.compareTo(bestScore) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}
----

After in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOfInitialized(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.toInitializedScore().compareTo(bestScore.toInitializedScore()) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}
----

[.upgrade-recipe-minor]
=== `Score` and `ScoreDefinition`: methods changed

The `ScoreDefinition.fromLevelNumbers(...)` method now requires an `initScore` parameter.

Before in `*.java`:
[source, java]
----
Score score = scoreDefinition.fromLevelNumbers(new int[]{0, -200});
----

After in `*.java` (quick and dirty fix):
[source, java]
----
Score score = scoreDefinition.fromLevelNumbers(0, new int[]{0, -200});
----

[.upgrade-recipe-minor]
=== Custom `Score`: methods added

If you have a custom `Score`:
The `Score` interface has several new methods: `getInitScore()`, `isSolutionInitialized()`, `toInitializedScore()` and `withInitScore()`.
The first two methods are implemented by `AbstractScore`, but the last two methods need to be specifically implemented.

Before in `*.java`:
[source, java]
----
public final class HardSoftScore extends AbstractScore<HardSoftScore> ... {
    ...
}
----

After in `*.java`:
[source, java]
----
public final class HardSoftScore extends AbstractScore<HardSoftScore> ... {
    ...

    public HardSoftScore toInitializedScore() {
        return initScore == 0 ? this : new HardSoftScore(0, hardScore, softScore);
    }

    public HardSoftScore withInitScore(int newInitScore) {
        assertNoInitScore();
        return new HardSoftScore(newInitScore, hardScore, softScore);
    }

}
----

Furthermore, a score that implements `FeasibleScore` needs to take the `initScore` into account in the `isFeasible()` method implementation.

[.upgrade-recipe-minor]
=== Hibernate integration: extra `@Column` needed

Because a `Score` now also contains an `initScore` of type `int` (regardless of the type of the other fields),
add an extra `@Column` annotation to the beginning of the `@Columns` list to map that field to a database column.

Set it to `0` for all existing records (unless you have reason to believe that some scores weren't calculated on a fully initialized solution).

Before in `*.java`:
[source, java]
----
        @Columns(columns = {
                @Column(name = "hardScore"),
                @Column(name = "softScore")})
        public HardSoftScore getScore() {
            return score;
        }
----

After in `*.java`:
[source, java]
----
        @Columns(columns = {
                @Column(name = "initScore"),
                @Column(name = "hardScore"),
                @Column(name = "softScore")})
        public HardSoftScore getScore() {
            return score;
        }
----

[.upgrade-recipe-impl-detail]
=== `XStreamSolutionFileIO`: no-arg constructor removed

The no-arg constructor of `XStreamSolutionFileIO` has been removed because it's useless.

[.upgrade-recipe-minor]
=== JAXB support added

If you're using JAXB, take advantage the new JAXB Score bindings etc.
See the reference manual, chapter _Integration_.

These new `ScoreJaxbXmlAdapter` implementations have been promoted to the public API,
so they are guaranteed to be backwards compatible in future versions.

[.upgrade-recipe-minor]
=== Jackson support added

If you're using Jackson, take advantage the new Jackson Score bindings etc.
See the reference manual, chapter _Integration_.

These new `ScoreJacksonJsonSerializer` and `ScoreJacksonJsonDeserializer` implementations have been promoted to the public API,
so they are guaranteed to be backwards compatible in future versions.

[.upgrade-recipe-major]
=== `XStreamScoreConverter` replaced

The general purpose `XStreamScoreConverter` to bind `Score` implementations
has been replaced by specific implementations, such as `HardSoftScoreXStreamConverter` and `SimpleScoreXStreamConverter`
that are easier to use.

Furthermore, these  new `ScoreXStreamConverter` implementations have been promoted to the public API,
so they are now guaranteed to be backwards compatible in future versions.

Before in `*.java`:
[source, java]
----
public class CloudBalance {

    @XStreamConverter(value = XStreamScoreConverter.class, types = {HardSoftScoreDefinition.class})
    private HardSoftScore score;

    ...
}
----

After in `*.java`:
[source, java]
----
public class CloudBalance {

    @XStreamConverter(HardSoftScoreXStreamConverter.class)
    private HardSoftScore score;

    ...
}
----

For a bendable score, it's no longer needed to configure the `hardLevelSize` and `softLevelSize`.

Before in `*.java`:
[source, java]
----
public class Schedule {

    @XStreamConverter(value = XStreamScoreConverter.class, types = {BendableScoreDefinition.class}, ints = {1, 2})
    private BendableScore score;

    ...
}
----

After in `*.java`:
[source, java]
----
public class Schedule {

    @XStreamConverter(BendableScoreXStreamConverter.class)
    private BendableScore score;

    ...
}
----

[.upgrade-recipe-major.upgrade-recipe-public-api]
===  `@CustomShadowVariable`: `sources` type changed

A shadow variable annotated with `@CustomShadowVariable`
now expects that the `sources` parameter is of type `@PlanningVariableReference`
instead of `@CustomShadowVariable.Source`.

This way it's consistent with the `variableListenerRef` parameter.

Before in `*.java`:
[source, java]
----
        @CustomShadowVariable(variableListenerClass = ArrivalTimeUpdatingVariableListener.class,
                sources = {@CustomShadowVariable.Source(variableName = "previousStandstill")})
        public Long getArrivalTime() {
            return arrivalTime;
        }
----

After in `*.java`:
[source, java]
----
        @CustomShadowVariable(variableListenerClass = ArrivalTimeUpdatingVariableListener.class,
                sources = {@PlanningVariableReference(variableName = "previousStandstill")})
        public Long getArrivalTime() {
            return arrivalTime;
        }
----

== From 7.0.0.Beta1 to 7.0.0.Beta2

[.upgrade-recipe-minor]
===  `ProblemFactChange`: `before/afterProblemFactChanged` renamed

The `ScoreDirector` methods `beforeProblemFactChanged()` and `afterProblemFactChanged()`
have been renamed to `beforeProblemPropertyChanged()` and `afterProblemPropertyChanged()`.
This can affect your `ProblemFactChange` implementations.

A problem fact is a class that doesn't change during planning.
A problem property is a property on a problem fact or a planning entity that doesn't change during planning
(so it's not a planning variable).

Before in `*.java`:
[source, java]
----
        scoreDirector.beforeProblemFactChanged(computer);
        computer.setMemory(newMemoryCapacity);
        scoreDirector.afterProblemFactChanged(computer);
----

After in `*.java`:
[source, java]
----
        scoreDirector.beforeProblemPropertyChanged(computer);
        computer.setMemory(newMemoryCapacity);
        scoreDirector.afterProblemPropertyChanged(computer);
----

[.upgrade-recipe-major]
=== Solver configuration: `<scoreDefinitionType>` removed

Don't specify the `scoreDefinitionType` in the solver configuration any more
because OptaPlanner will now figure it out automatically from the domain.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <scoreDrl>org/optaplanner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <scoreDirectorFactory>
    <scoreDrl>org/optaplanner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>
----

For a bendable score, also move the `bendableHardLevelsSize` and `bendableSoftLevelsSize` lines from the solver configuration XML
into the `@PlanningScore` annotation on your domain class.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <scoreDirectorFactory>
    <scoreDefinitionType>BENDABLE</scoreDefinitionType>
    <bendableHardLevelsSize>1</bendableHardLevelsSize>
    <bendableSoftLevelsSize>2</bendableSoftLevelsSize>
    <scoreDrl>org/optaplanner/examples/projectjobscheduling/solver/projectJobSchedulingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <scoreDirectorFactory>
    <scoreDrl>org/optaplanner/examples/projectjobscheduling/solver/projectJobSchedulingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>
----

Before in `*.java`:
[source, java]
----
    @PlanningScore
    private BendableScore score;
----

After in `*.java`:
[source, java]
----
    @PlanningScore(bendableHardLevelsSize = 1, bendableSoftLevelsSize = 2)
    private BendableScore score;
----

In the rare case that you're using a custom score, also move its declaration into the domain:

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
    <scoreDefinitionClass>...MyCustomScoreDefinition</scoreDefinitionClass>
----

After in `*.java`:
[source, java]
----
    @PlanningScore(scoreDefinitionClass = MyCustomScoreDefinition.class)
----

[.upgrade-recipe-minor.upgrade-recipe-public-api]
===  `@PlanningVariable` on primitive types: no longer supported

A `@PlanningVariable` annotation on a primitive type such as `int` or `long` (instead of `Integer` or `Long`)
now fails fast instead of causing an inferior result.
The use of a primitive type caused the Construction Heuristics to presume the variable is already initialized
(because it's not null and it might be form of Repeated Planning),
which lead to inferior results.
It was hard to diagnose the cause of that issue for many users, so now this inferior approach fails fast with a clear message.

Before in `*.java`:
[source, java]
----
    private int delay;

    @PlanningVariable(valueRangeProviderRefs = {"delayRange"})
    public int getDelay() {
        return delay;
    }

    public void setDelay(int delay) {
        this.delay = delay;
    }
----

After in `*.java`:
[source, java]
----
    private Integer delay;

    @PlanningVariable(valueRangeProviderRefs = {"delayRange"})
    public Integer getDelay() {
        return delay;
    }

    public void setDelay(Integer delay) {
        this.delay = delay;
    }
----

[.upgrade-recipe-minor]
===  `VariableListener` events are no longer unique

OptaPlanner might call the `before...` and `after...` methods on your `VariableListener` implementation
twice with the exact same parameters.
Most `VariableListener` implementations can deal with this,
getting a small performance boost because OptaPlanner doesn't have to guarantee uniqueness.
If your implementation can't deal with it, then overwrite the `requiresUniqueEntityEvents()` method.

Before in `*.java`:
[source, java]
----
public class StartTimeUpdatingVariableListener implements VariableListener<Task> {

    ...
}
----

After in `*.java` (optional):
[source, java]
----
public class StartTimeUpdatingVariableListener implements VariableListener<Task> {

    @Override
    public boolean requiresUniqueEntityEvents() {
        // If you don't need to overwrite this method, you get a small performance gain
        return true;
    }

    ...
}
----

[.upgrade-recipe-recommended]
===  Faster and nicer `accumulate()` in `drl`

Drools now uses typed `sum()`, `min()`, `max()` and `avg()` functions in `accumulate()` patterns.
This means that a sum of ints is now an int (instead of a double) and a sum of BigDecimals is now a BigDecimal (without rounding errors).
This is faster and it also gets rid of the `intValue()` conversions.

Meanwhile, also take advantage of migrating to the clearer `accumulate` form, if you haven't already.

Before in `*.drl`:
[source, drl]
----
rule "requiredCpuPowerTotal"
    when
        $c : CloudComputer($capacity : cpuPower)
        $total : Number(intValue > $capacity) from accumulate(
            CloudProcess(
                computer == $c,
                $required : requiredCpuPower),
            sum($required)
        )
    then
        scoreHolder.addHardConstraintMatch(kcontext, $capacity - $total.intValue());
end
----

After in `*.drl`:
[source, drl]
----
rule "requiredCpuPowerTotal"
    when
        $c : CloudComputer($capacity : cpuPower)
        accumulate(
            CloudProcess(
                computer == $c,
                $required : requiredCpuPower);
            $total : sum($required);
            $total > $capacity
        )
    then
        scoreHolder.addHardConstraintMatch(kcontext, $capacity - $total);
end
----

Notice that the pattern, the function list and the DRL constraint list in the `accumulate()`
are recommended to be separated by a `;` character instead of a `,` character.

[.upgrade-recipe-minor]
===  Custom `Score`: implement `isCompatibleArithmeticArgument()`

An `AbstractScore` no longer implements the `Score` interface method `isCompatibleArithmeticArgument()` (which is still there).
Now, your custom `Score` implementation needs to implement it itself.

This way, `Score` instances can be reused by GWT and other JavaScript generating code.

Before in `*.java`:
[source, java]
----
public final class HardSoftScore extends AbstractScore<HardSoftScore> {
    ...

}
----

After in `*.java`:
[source, java]
----
public final class HardSoftScore extends AbstractScore<HardSoftScore> {
    ...

    @Override
    public boolean isCompatibleArithmeticArgument(Score otherScore) {
        return otherScore instanceof HardSoftScore;
    }

}
----

== From 7.0.0.Beta2 to 7.0.0.Beta3

[.upgrade-recipe-minor]
===  `Solver.getScoreDirectorFactory`: call `ScoreDirector.dispose()`

Every `ScoreDirector` needs to be disposed to avoid a potential memory leak.
The old docs didn't clearly mention that, so your code might not do that.

Before in `*.java`:
[source, java]
----
ScoreDirectorFactory<CloudBalance> scoreDirectorFactory = solver.getScoreDirectorFactory();
ScoreDirector<CloudBalance> guiScoreDirector = scoreDirectorFactory.buildScoreDirector();
...
----

After in `*.java`:
[source, java]
----
ScoreDirectorFactory<CloudBalance> scoreDirectorFactory = solver.getScoreDirectorFactory();
ScoreDirector<CloudBalance> guiScoreDirector = scoreDirectorFactory.buildScoreDirector();
...
guiScoreDirector.dispose();
----

[.upgrade-recipe-minor]
=== Custom cloning: `PlanningCloneable` replaced

The interface `PlanningCloneable` has been removed,
use a `SolutionCloner` instead.

Before in `*.java`:
[source, java]
----
public class NQueens implements PlanningCloneable<NQueens> {
    ...

    public NQueens planningClone() {
        ...
    }

}
----

After in `*.java`:
[source, java]
----
public class NQueensSolutionCloner implements SolutionCloner<NQueens> {

    @Override
    public NQueens cloneSolution(CloneLedger ledger, NQueens original) {
        ...
    }

}
----

[source, java]
----
@PlanningSolution(solutionCloner = NQueensSolutionCloner.class)
public class NQueens {
    ...
}
----

== From 7.0.0.Beta3 to 7.0.0.Beta4

[.upgrade-recipe-recommended]
=== Add `@PlanningId` annotation

It is recommended to add a `@PlanningId` annotation
on the unique ID of every planning entity and on most problem fact classes
(especially on each class that is a planning value class).
The ID must never be null and must be unique per class (no need to be globally unique).

This enables the use of multithreaded solvers (such as Partitioned Search)
and makes it easier to implement a real-time planning `ProblemFactChange` by using `ScoreDirector.lookUpWorkingObject()`.

Before in `*.java`:
[source, java]
----
public abstract class AbstractPersistable ... {

    protected Long id; // Can also be a String, Integer, ...

    public Long getId() {
        return id;
    }

    ...
}
----

After in `*.java`:
[source, java]
----
public abstract class AbstractPersistable ... {

    protected Long id; // Can also be a String, Integer, ...

    @PlanningId
    public Long getId() {
        return id;
    }

    ...
}
----

You can also put the `@PlanningId` annotation on the field instead.

[.upgrade-recipe-minor]
=== `ProblemFactChange`: Use `lookUpWorkingObject()`

Use the new method `ScoreDirector.lookUpWorkingObject(Object)` to translate a planning entity or problem fact
to its working instance planning clone more efficiently.

This requires that the class has a `@PlanningId` annotation on one of its getters or fields.

Before in `*.java`:
[source, java]
----
public class EditComputerProblemFactChange implements ProblemFactChange<CloudBalance> {

    private final CloudComputer changedComputer;
    ...

    public void doChange(ScoreDirector<CloudBalance> scoreDirector) {
        CloudComputer workingComputer = null;
        for (CloudComputer computer : cloudBalance.getComputerList()) {
            if (changedComputer.getId().equals(computer.getId())) {
                workingComputer = computer;
                break;
            }
        }

        scoreDirector.beforeProblemPropertyChanged(workingComputer);
        workingComputer.setCpuPower(changedComputer.getCpuPower());
        scoreDirector.afterProblemPropertyChanged(workingComputer);
        ...
        scoreDirector.triggerVariableListeners();
    }

}
----

After in `*.java`:
[source, java]
----
public class EditComputerProblemFactChange implements ProblemFactChange<CloudBalance> {

    private final CloudComputer changedComputer;
    ...

    public void doChange(ScoreDirector<CloudBalance> scoreDirector) {
        CloudComputer workingComputer = scoreDirector.lookUpWorkingObject(changedComputer);

        scoreDirector.beforeProblemPropertyChanged(workingComputer);
        workingComputer.setCpuPower(changedComputer.getCpuPower());
        scoreDirector.afterProblemPropertyChanged(workingComputer);
        ...
        scoreDirector.triggerVariableListeners();
    }

}
----

== From 7.0.0.Beta5 to 7.0.0.Beta6

[.upgrade-recipe-minor]
=== Benchmarker warms up by default

It is no longer needed to explicitly configure a warm up time for the benchmarks.
It now warms up for 30 seconds by default.

Before in `*BenchmarkConfig.xml`:
[source, xml]
----
<plannerBenchmark>
  ...
  <warmUpSecondsSpentLimit>30</warmUpSecondsSpentLimit>
  ...
</plannerBenchmark>
----

After in `*BenchmarkConfig.xml`:
[source, xml]
----
<plannerBenchmark>
  ...
</plannerBenchmark>
----

To disable the warm up, explicitly set it to 0:
[source, xml]
----
<plannerBenchmark>
  ...
  <warmUpSecondsSpentLimit>0</warmUpSecondsSpentLimit>
  ...
</plannerBenchmark>
----

[.upgrade-recipe-minor]
=== `CustomPhaseCommand`: method `applyCustomProperties()` replaced

The `CustomPhaseCommand` no longer has the method `applyCustomProperties()`.
If you have custom properties in your solver configuration,
simply implement a public setter for each custom property.
The supported types for a setter currently include booleans, numbers and string.

Similar custom properties support is available on some other custom classes (such as `SolutionPartitioner`).

Before in `*.java`:
[source, java]
----
public class MyCustomPhaseCommand extends AbstractCustomPhaseCommand {

    private int mySelectionSize;

    @Override
    public void applyCustomProperties(Map<String, String> customPropertyMap) {
        String mySelectionSizeString = customPropertyMap.get("mySelectionSize");
        try {
            mySelectionSize = mySelectionSizeString == null ? 10 : Integer.parseInt(mySelectionSizeString);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("The mySelectionSize (" + mySelectionSizeString + ") cannot be parsed.", e);
        }
        ...
    }

    ...
}
----

After in `*.java`:
[source, java]
----
public class MyCustomPhaseCommand extends AbstractCustomPhaseCommand {

    private int mySelectionSize = 10;

    @SuppressWarnings("unused")
    public void setMySelectionSize(int mySelectionSize) {
        this.mySelectionSize = mySelectionSize;
    }

    ...
}
----

[.upgrade-recipe-minor]
=== `Bendable*Score`: method `isFeasible()` fixed

A bendable score with at least 2 hard score levels is now infeasible
if any of those hard levels is negative, even if one of them is positive (1 or higher).

[.upgrade-recipe-minor]
=== `EasyScoreCalculator`: `calculateScore()` reverted to 6.x style

*This change reverts a change of 7.0.0.Beta1. Ignore this item if you're upgrading directly from version 6.*

Before in `*.java`:
[source, java]
----
public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance, int initScore) {
        ...
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}
----

After in `*.java`:
[source, java]
----
public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance) {
        ...
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}
----

OptaPlanner still keeps track of the `initScore` internally.

[.upgrade-recipe-minor]
=== `IncrementalScoreCalculator`: `calculateScore()` reverted to 6.x style

*This change reverts a change of 7.0.0.Beta1. Ignore this item if you're upgrading directly from version 6.*

Before in `*.java`:
[source, java]
----
public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(int initScore) {
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}
----

After in `*.java`:
[source, java]
----
public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore() {
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}
----

[.upgrade-recipe-minor]
=== `Score`: `valueOf(...)` and `valueOfInitialized(...)` reverted to 6.x style

*This change reverts a change of 7.0.0.Beta1. Ignore this item if you're upgrading directly from version 6.*

Before in `*.java`:
[source, java]
----
        SimpleScore score = SimpleScore.valueOfInitialized(1234);
----

After in `*.java`:
[source, java]
----
        SimpleScore score = SimpleScore.valueOf(1234);
----

Or with a `HardSoftScore`:

Before in `*.java`:
[source, java]
----
        HardSoftScore score = HardSoftScore.valueOfInitialized(1200, 34);
----

After in `*.java`:
[source, java]
----
        HardSoftScore score = HardSoftScore.valueOf(1200, 34);
----

[.upgrade-recipe-minor]
=== Custom initializer: `Score.compareTo()` behaviour reverted to 6.x style

*This change reverts a change of 7.0.0.Beta1. Ignore this item if you're upgrading directly from version 6.*

Before in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOfInitialized(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.toInitializedScore().compareTo(bestScore.toInitializedScore()) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}
----

After in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOf(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.compareTo(bestScore) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}
----

[.upgrade-recipe-minor]
=== Custom `Move`: added optional generic type parameter

To avoid the awkward cast to your `Solution` implementation,
`Move` and `AbstractMove` now optionally support a generic type parameter (which is the solution class).

Before in `*.java`:
[source, java]
----
public class CloudComputerChangeMove extends AbstractMove {

    @Override
    public boolean isMoveDoable(ScoreDirector scoreDirector) {
        return !Objects.equals(cloudProcess.getComputer(), toCloudComputer);
    }

    @Override
    public Move createUndoMove(ScoreDirector scoreDirector) {
        return new CloudComputerChangeMove(cloudProcess, cloudProcess.getComputer());
    }

    @Override
    protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) {
        scoreDirector.beforeVariableChanged(cloudProcess, "computer");
        cloudProcess.setComputer(toCloudComputer);
        scoreDirector.afterVariableChanged(cloudProcess, "computer");
    }

    ...
}
----

After in `*.java`:
[source, java]
----
public class CloudComputerChangeMove extends AbstractMove<CloudBalance> {

    @Override
    public boolean isMoveDoable(ScoreDirector<CloudBalance> scoreDirector) {
        return !Objects.equals(cloudProcess.getComputer(), toCloudComputer);
    }

    @Override
    public CloudComputerChangeMove createUndoMove(ScoreDirector<CloudBalance> scoreDirector) {
        return new CloudComputerChangeMove(cloudProcess, cloudProcess.getComputer());
    }

    @Override
    protected void doMoveOnGenuineVariables(ScoreDirector<CloudBalance> scoreDirector) {
        scoreDirector.beforeVariableChanged(cloudProcess, "computer");
        cloudProcess.setComputer(toCloudComputer);
        scoreDirector.afterVariableChanged(cloudProcess, "computer");
    }

    ...
}
----

== From 7.0.0.Beta6 to 7.0.0.Beta7

[.upgrade-recipe-major.upgrade-recipe-public-api]
=== `ConstraintMatch(Total)`: `getWeightTotal()` and `getWeight()` replaced

When explaining a score through `ScoreDirector.getConstraintMatchTotals()`,
the `ConstraintMatchTotal` and `ConstraintMatch` instances now have a `Score`
instead of an `int scoreLevel` and a `Number weight`.

This simplifies the API and allows the reuse of `ConstraintMatch` in the indictment API.

Before in `*.java`:
[source, java]
----
    for (ConstraintMatchTotal constraintMatchTotal : guiScoreDirector.getConstraintMatchTotals()) {
        int scoreLevel = constraintMatchTotal.getScoreLevel();
        Integer weightTotal = (Integer) constraintMatchTotal.getWeightTotalAsNumber();
        String text = weightTotal.toString() + (scoreLevel == 0 ? "hard" : "soft");
        ...
    }
----

After in `*.java`:
[source, java]
----
    for (ConstraintMatchTotal constraintMatchTotal : guiScoreDirector.getConstraintMatchTotals()) {
        HardSoftScore scoreTotal = (HardSoftScore) constraintMatchTotal.getScoreTotal();
        String text = scoreTotal.toShortString();
        ...
    }
----

[.upgrade-recipe-minor.upgrade-recipe-public-api]
=== `ConstraintMatchAwareIncrementalScoreCalculator`: `getConstraintMatchTotals()` impact

When implementing the interface `ConstraintMatchAwareIncrementalScoreCalculator`'s method `getConstraintMatchTotals()`,
the constructor of `ConstraintMatchTotal` and the method `addConstraintMatch(...)`
now use `Score` instances instead of numbers.

Before in `*.java`:
[source, java]
----
    public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
        LongConstraintMatchTotal maximumCapacityMatchTotal = new LongConstraintMatchTotal(
                CONSTRAINT_PACKAGE, "maximumCapacity", 0);
        ...
        serviceLocationSpreadMatchTotal.addConstraintMatch(
                ..., - weight);
        ...
    }
----

After in `*.java`:
[source, java]
----
    public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
        ConstraintMatchTotal maximumCapacityMatchTotal = new ConstraintMatchTotal(
                CONSTRAINT_PACKAGE, "maximumCapacity", HardSoftLongScore.ZERO);
        ...
        serviceLocationSpreadMatchTotal.addConstraintMatch(
                ..., HardSoftLongScore.valueOf(- weight, 0));
        ...
    }
----

[.upgrade-recipe-minor.upgrade-recipe-public-api]
=== Score rule that changes 2 score levels: call `addMultiConstraintMatch()`

A score rule that changes 2 score levels in its RHS,
must now call `addMultiConstraintMatch()` instead of 2 separate `add*ConstraintMatch()` calls.

Before in `*.drl`:
[source, drl]
----
rule "Costly and unfair"
when
    // Complex pattern
then
    scoreHolder.addMediumConstraintMatch(kcontext, -3); // Financial cost
    scoreHolder.addSoftConstraintMatch(kcontext, -4); // Employee happiness cost
end
----

After in `*.drl`:
[source, drl]
----
rule "Costly and unfair"
when
    // Complex pattern
then
    scoreHolder.addMultiConstraintMatch(kcontext, 0, -3, -4);
end
----

When calling `guiScoreDirector.getConstraintMatchTotals()`,
there is now also only one `ConstraintMatchTotal` instance for this score rule
and only one `ConstraintMatch` instance per fired rule.

[.upgrade-recipe-minor]
=== Custom `Score`: `toShortString()` added

If you have a custom `ScoreDefinition`: the `Score` interface has another new method `toShortString()`.

After in `*.java`:
[source, java]
----
public final class HardSoftScore extends AbstractScore<HardSoftScore> ... {
    ...
    @Override
    public String toShortString() {
        return buildShortString((n) -> ((Integer) n).intValue() != 0, HARD_LABEL, SOFT_LABEL);
    }
}
----

[.upgrade-recipe-minor]
=== `Move`: `createUndoMove()` and `doMove()` changed

The `Move` interface has changed: `doMove()` now returns the undo move, so `createUndoMove()` has been removed.
This was needed to fix a bug in `CompositeMove`.
However, `AbstractMove` completely deals with this change, so *your custom move implementation stays the same*.

In the very rare case that you're actually calling `createUndoMove()` yourself,
use the return type of `doMove()` instead.

Before in `*.java`:
[source, java]
----
        Move<Solution_> move = ...;
        Move<Solution_> undoMove = move.createUndoMove(scoreDirector);
        move.doMove(scoreDirector);
----

After in `*.java`:
[source, java]
----
        Move<Solution_> move = ...;
        Move<Solution_> undoMove = move.doMove(scoreDirector);
----

[.upgrade-recipe-minor]
=== Custom `ScoreDefinition`: `getZeroScore()` added

If you have a custom `ScoreDefinition`: the `ScoreDefinition` interface has another new method `getZeroScore()`.

After in `*.java`:
[source, java]
----
public class HardSoftScoreDefinition extends AbstractFeasibilityScoreDefinition<HardSoftScore> {
    ...

    @Override
    public HardSoftScore getZeroScore() {
        return HardSoftScore.ZERO;
    }
}
----

== From 7.0.0.Beta7 to 7.0.0.CR1

[.upgrade-recipe-minor]
=== `ConstraintMatchAwareIncrementalScoreCalculator`: `getIndictmentMap()` added

If you're using a Java incremental score calculator that is also `ConstraintMatch` aware,
it now needs to also implement the method `getIndictmentMap()`.
Simply `return null` to have it calculated automatically from the return value of `getConstraintMatchTotals()`.

Before in `*.java`:
[source, java]
----
public class MachineReassignmentIncrementalScoreCalculator
        implements ConstraintMatchAwareIncrementalScoreCalculator<MachineReassignment> {

    ...

    @Override
    public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
        ...
    }
}
----

After in `*.java`:
[source, java]
----
public class MachineReassignmentIncrementalScoreCalculator
        implements ConstraintMatchAwareIncrementalScoreCalculator<MachineReassignment> {

    ...

    @Override
    public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
        ...
    }

    @Override
    public Map<Object, Indictment> getIndictmentMap() {
        return null; // Calculate it non-incrementally from getConstraintMatchTotals()
    }
}
----

[.upgrade-recipe-minor]
=== Custom `MoveListFactory` and `MoveIteratorFactory`: method return `Move<Solution_>`

The `MoveListFactory` and `MoveIteratorFactory`'s methods now use `Move<Solution_>` instead of a raw-typed `Move`.
This way your `MoveListFactory` can return `List<ChangeMove<MySolution>>` instead of `List<ChangeMove>`.

Before in `*.java`:
[source, java]
----
public class CloudComputerChangeMoveFactory implements MoveListFactory<CloudBalance> {

    @Override
    public List<Move> createMoveList(CloudBalance solution) {
        ...
    }

}
----

After in `*.java` (if it creates generic `ChangeMove` instances):
[source, java]
----
public class CloudComputerChangeMoveFactory implements MoveListFactory<CloudBalance> {

    @Override
    public List<ChangeMove<CloudBalance>> createMoveList(CloudBalance nQueens) {
        ...
    }

}
----

After in `*.java` (if it creates `CloudComputerChangeMove` instances and that implements `Move<CloudBalance>`):

[source, java]
----
public class RowChangeMoveFactory implements MoveListFactory<CloudBalance> {

    @Override
    public List<CloudComputerChangeMove> createMoveList(CloudBalance nQueens) {
        ...
    }

}
----

Before in `*.java`:
[source, java]
----
public class CheapTimePillarSlideMoveIteratorFactory implements MoveIteratorFactory<CheapTimeSolution> {

    public Iterator<Move> createOriginalMoveIterator(...) {...}
    public Iterator<Move> createRandomMoveIterator(...) {...}

}
----

After in `*.java`:
[source, java]
----
public class CheapTimePillarSlideMoveIteratorFactory implements MoveIteratorFactory<CheapTimeSolution> {

    public Iterator<CheapTimePillarSlideMove> createOriginalMoveIterator(...) {...}
    public Iterator<CheapTimePillarSlideMove> createRandomMoveIterator(...) {...}

}
----

== From 7.0.0.CR3 to 7.0.0.Final

[.upgrade-recipe-impl-detail]
=== Workbench's `AbstractSolution` deprecated

The implementation class `AbstractSolution`, used only by workbench 6,
has been deprecated and replaced by the `autoDiscoverMemberType` feature.

Before in `*.java`:
[source, java]
----
@PlanningSolution
public class Mysolution extends AbstractSolution<HardSoftScore> {

    private List<FooFact> fooFactList;
    private List<BarFact> barFactList;

    ...
}
----

After in `*.java`:
[source, java]
----
@PlanningSolution(autoDiscoverMemberType = AutoDiscoverMemberType.FIELD)
public class Mysolution {

    private List<FooFact> fooFactList;
    private List<BarFact> barFactList;

    private HardSoftScore score;

    ...
}
----

== From 7.1.0.Beta2 to 7.1.0.Beta3

[.upgrade-recipe-minor]
=== `<valueSelector>`: `variableName` is now an attribute

When power tweaking move selectors, such as `<changeMoveSelector>`,
in a use case with multiple planning variables,
the `<variableName>` XML element has been replaced by a `variableName="..."` XML attribute.
This reduces the solver configuration verbosity.
For backwards compatibility, the old way is still supported in the 7.x series.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <valueSelector>
    <variableName>room</variableName>
  </valueSelector>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <valueSelector variableName="room"/>
----

[.upgrade-recipe-minor]
=== Construction Heuristic: multiple variable power tweaking simplified

It's now easier to configure construction heuristics that scale better for multiple variables,
but assigning one variable at a time.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <constructionHeuristic>
    <queuedEntityPlacer>
      <entitySelector id="placerEntitySelector"/>
      <changeMoveSelector>
        <entitySelector mimicSelectorRef="placerEntitySelector"/>
        <valueSelector variableName="period"/>
      </changeMoveSelector>
      <changeMoveSelector>
        <entitySelector mimicSelectorRef="placerEntitySelector"/>
        <valueSelector variableName="room"/>
      </changeMoveSelector>
    </queuedEntityPlacer>
  </constructionHeuristic>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <constructionHeuristic>
    <constructionHeuristicType>FIRST_FIT</constructionHeuristicType>
    <changeMoveSelector>
      <valueSelector variableName="period"/>
    </changeMoveSelector>
    <changeMoveSelector>
      <valueSelector variableName="room"/>
    </changeMoveSelector>
  </constructionHeuristic>
----

== From 7.1.0.Final to 7.2.0.Final

There is no impact on your code.

== From 7.2.0.Final to 7.3.0.Final

[.upgrade-recipe-minor]
=== `SolutionFileIO`: `getOutputFileExtension()` is now defaulted

It's no longer needed to implement `getOutputFileExtension()` of the `SolutionFileIO` interface
if it returns the same as `getInputFileExtension()`.

Before in `*.java`:
[source, java]
----
public class VehicleRoutingFileIO implements SolutionFileIO<VehicleRoutingSolution> {

    @Override
    public String getInputFileExtension() {
        return "vrp";
    }

    @Override
    public String getOutputFileExtension() {
        return "vrp";
    }

    ...
}
----

After in `*.java`:
[source, java]
----
public class VehicleRoutingFileIO implements SolutionFileIO<VehicleRoutingSolution> {

    @Override
    public String getInputFileExtension() {
        return "vrp";
    }

    ...
}
----

[.upgrade-recipe-minor]
=== Benchmarker: direct POJO input

The benchmarker now also accepts problem instances directly, without reading them from disk.
If you're generating your problems or fetching them from a database,
it might be interesting to switch to this approach (otherwise stick with the old approach because it works offline).

Before in `*.java`:
[source, java]
----
    CloudBalance problem1 = readFromDatabase(...);
    CloudBalance problem2 = readFromDatabase(...);
    ...
    CloudBalanceFileIO solutionFileIO = new CloudBalanceFileIO();
    solutionFileIO.write(problem1, new File("tmp/problem1.xml"));
    solutionFileIO.write(problem2, new File("tmp/problem1.xml"));
    ...
    PlannerBenchmark plannerBenchmark = benchmarkFactory.buildPlannerBenchmark();
    plannerBenchmark.benchmark();
----

Before in `*BenchmarkConfig.xml`:
[source, xml]
----
    <problemBenchmarks>
      <solutionFileIOClass>...CloudBalanceFileIO</solutionFileIOClass>
      <inputSolutionFile>tmp/problem1.xml</inputSolutionFile>
      <inputSolutionFile>tmp/problem2.xml</inputSolutionFile>
      ...
    </problemBenchmarks>
----

After in `*.java`:
[source, java]
----
    CloudBalance problem1 = readFromDatabase(...);
    CloudBalance problem2 = readFromDatabase(...);
    ...
    PlannerBenchmark plannerBenchmark = benchmarkFactory.buildPlannerBenchmark(problem1, problem2, ...);
    plannerBenchmark.benchmark();
----

After in `*BenchmarkConfig.xml`:
[source, xml]
----
    <problemBenchmarks>
    </problemBenchmarks>
----

[.upgrade-recipe-minor]
=== Benchmarker: `BEST_SCORE` statistic by default

The benchmarker now includes the `BEST_SCORE` statistic by default.
It no longer needs to be explicitly configured.

Before in `*BenchmarkConfig.xml`:
[source, xml]
----
    <problemBenchmarks>
      ...
      <problemStatisticType>BEST_SCORE</problemStatisticType>
    </problemBenchmarks>
----

After in `*BenchmarkConfig.xml`:
[source, xml]
----
    <problemBenchmarks>
      ...
    </problemBenchmarks>
----

To disable the `BEST_SCORE` statistic, use `<problemStatisticEnabled>` in `*BenchmarkConfig.xml`:
[source, xml]
----
    <problemBenchmarks>
      ...
      <problemStatisticEnabled>false</problemStatisticEnabled>
    </problemBenchmarks>
----

[.upgrade-recipe-major]
=== `ScoreDirector`: `dispose()` replaced by `close()`

`ScoreDirector` now implements `AutoCloseable`,
so the `dispose()` method has been deprecated and replaced by `close()`.

Before in `*.java`:
[source, java]
----
    ScoreDirector<VehicleRoutingSolution> scoreDirector = scoreDirectorFactory.buildScoreDirector();
    ...
    scoreDirector.dispose();
----

After in `*.java`:
[source, java]
----
    ScoreDirector<VehicleRoutingSolution> scoreDirector = scoreDirectorFactory.buildScoreDirector();
    ...
    scoreDirector.close();
----

After in `*.java` (with ARM usage):
[source, java]
----
    try (ScoreDirector<VehicleRoutingSolution> scoreDirector = scoreDirectorFactory.buildScoreDirector()) {
        ...
    }
----

== From 7.3.0.Final to 7.4.0.Final

[.upgrade-recipe-minor]
=== `movableEntitySelectionFilter` is now inherited

An entity's `movableEntitySelectionFilter` is now inherited by child entities.
The workaround of configuring the filter twice, is now obsolete.

Before in `*.java`:
[source, java]
----
@PlanningEntity(movableEntitySelectionFilter = ParentFilter.class)
public class Animal {
   ...
}

@PlanningEntity(movableEntitySelectionFilter = ParentFilter.class)
public class Dog extends Animal {
   ...
}
----

After in `*.java`:
[source, java]
----
@PlanningEntity(movableEntitySelectionFilter = ParentFilter.class)
public class Animal {
   ...
}

@PlanningEntity()
public class Dog extends Animal {
   ...
}
----

== From 7.4.0.Final to 7.5.0.Final

[.upgrade-recipe-minor]
=== `Indictment`: natural comparison changed

An `Indictment` is now naturally sorted by its justification.
To sort it based on its score, use `IndictmentScoreTotalComparator`.

Before in `*.java`:
[source, java]
----
Collections.sort(indictmentList);
----

After in `*.java`:
[source, java]
----
Collections.sort(indictmentList, new IndictmentScoreTotalComparator());
----

== From 7.5.0.Final to 7.6.0.Final

[.upgrade-recipe-minor]
=== `PlannerBenchmark`: new method `benchmarkAndShowReportInBrowser()`

If you're running local benchmarks, this new method will save time by opening the report automatically.

Before in `*.java`:
[source, java]
----
plannerBenchmark.benchmark();
// Afterwards manually find the benchmark dir to open the report
----

After in `*.java`:
[source, java]
----
plannerBenchmark.benchmarkAndShowReportInBrowser();
----

[.upgrade-recipe-minor]
=== `ConstraintMatchAwareIncrementalScoreCalculator`: `Indictment.addConstraintMatch()` changed

This only applies if you're extending `ConstraintMatchAwareIncrementalScoreCalculator`
and you do not simply return `null` in `getIndictmentMap()`.

The method `Indictment.addConstraintMatch(ConstraintMatch)` now returns void instead of a boolean.
If the same `ConstraintMatch` is added twice, it now fails fast instead of returning false.
If the same `ConstraintMatch` has the same justification twice,
it must now be added to that justification's `Indictment` only once.

== From 7.6.0.Final to 7.7.0.Final

[.upgrade-recipe-major]
=== Replace `movableEntitySelectionFilter` with `@PlanningPin` when possible

In many cases, the complex use of a `movableEntitySelectionFilter` to pin down planning entities
can by simplified by a `@PlanningPin` annotation on a field or method
that returns true if the entity is immovable.

Before in `*.java`:
[source, java]
----
@PlanningEntity(movableEntitySelectionFilter = MovableLectureSelectionFilter.class)
public class Lecture {
    private boolean pinned;

    public boolean isPinned() {
        return pinned;
    }
}
----

[source, java]
----
public class MovableLectureSelectionFilter implements SelectionFilter<CourseSchedule, Lecture> {

    @Override
    public boolean accept(ScoreDirector<CourseSchedule> scoreDirector, Lecture lecture) {
        return !lecture.isPinned();
    }

}
----

After in `*.java`:
[source, java]
----
@PlanningEntity
public class Lecture {
    private boolean pinned;

    @PlanningPin
    public boolean isPinned() {
        return pinned;
    }
}
----

[.upgrade-recipe-minor]
=== Jackson integration: use `OptaPlannerJacksonModule`

Instead of using `@JsonSerialize` and `@JsonDeserialize` Jackson annotations on every `Score` field,
just register `OptaPlannerJacksonModule` once instead.

Before in `*.java`:

[source, java]
----
    ObjectMapper objectMapper = new ObjectMapper();
----

[source, java]
----
@PlanningSolution
public class MySolution {

    @JsonSerialize(using = ScoreJacksonJsonSerializer.class)
    @JsonDeserialize(using = HardSoftScoreJacksonJsonDeserializer.class)
    private HardSoftScore score;

    ...
}
----

After in `*.java`:

[source, java]
----
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(OptaPlannerJacksonModule.createModule());
----

[source, java]
----
@PlanningSolution
public class MySolution {

    private HardSoftScore score;

    ...
}
----

[.upgrade-recipe-minor]
=== Jackson integration: replace `ScoreJacksonJsonSerializer`

If you do prefer to use `@JsonSerialize` and `@JsonDeserialize` Jackson annotations,
instead of registering `OptaPlannerJacksonModule`,
replace `ScoreJacksonJsonSerializer` with a specific serializer,
such as `HardSoftScoreJacksonJsonSerializer`.
This won't affect the json output.

`ScoreJacksonJsonSerializer` is deprecated.

Before in `*.java`:
[source, java]
----
@JsonSerialize(using = ScoreJacksonJsonSerializer.class)
@JsonDeserialize(using = HardSoftScoreJacksonJsonDeserializer.class)
private HardSoftScore score;
----

After in `*.java`:
[source, java]
----
@JsonSerialize(using = HardSoftScoreJacksonJsonSerializer.class)
@JsonDeserialize(using = HardSoftScoreJacksonJsonDeserializer.class)
private HardSoftScore score;
----

== From 7.7.0.Final to 7.8.0.Final

[.upgrade-recipe-minor]
=== Partitioned Search: `threadFactoryClass` moved

Now that `<solver>` directly supports a `<threadFactoryClass>` element,
the `<threadFactoryClass>` element under `<partitionedSearch>` has been deprecated.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <solver>
    ...
    <partitionedSearch>
      <threadFactoryClass>...MyAppServerThreadFactory</threadFactoryClass>
      ...
    </partitionedSearch>
  </solver>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <solver>
    <threadFactoryClass>...MyAppServerThreadFactory</threadFactoryClass>
    ...
    <partitionedSearch>
      ...
    </partitionedSearch>
  </solver>
----

[.upgrade-recipe-minor]
=== `ConstraintMatchTotal` and `Indictment`: `getScoreTotal()` renamed to `getScore()`

The `getScoreTotal()` methods on `ConstraintMatchTotal` and `Indictment`
have been deprecated and replaced by `getScore()`.
Those deprecated methods will be removed in 8.0.

Before in `*.java`:
[source, java]
----
Score score = constraintMatchTotal.getScoreTotal();
----

After in `*.java`:
[source, java]
----
Score score = constraintMatchTotal.getScore();
----

Before in `*.java`:
[source, java]
----
Score score = indictment.getScoreTotal();
----

After in `*.java`:
[source, java]
----
Score score = indictment.getScore();
----

[.upgrade-recipe-minor]
=== `IndictmentScoreTotalComparator` renamed to `IndictmentScoreComparator`

The comparator `IndictmentScoreTotalComparator` has been deprecated
and replaced by `IndictmentScoreComparator`.
The deprecated class will be removed in 8.0.

Before in `*.java`:
[source, java]
----
indictmentList.sort(new IndictmentScoreTotalComparator());
----

After in `*.java`:
[source, java]
----
indictmentList.sort(new IndictmentScoreComparator());
----

== From 7.11.0.Final to 7.12.0.Final

[.upgrade-recipe-major]
=== Chained ChangeMove: cache type `PHASE` no longer supported

To work correctly with multithreaded solving,
`ChainedChangeMove` and `ChainedSwapMove` aren't `PHASE` cacheable any longer.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
      <changeMoveSelector><!-- On at least 1 chained variable -->
        <cacheType>PHASE</cacheType>
        ...
      </changeMoveSelector>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
      <changeMoveSelector>
        <cacheType>STEP</cacheType>
        ...
      </changeMoveSelector>
----

= Upgrade recipe 7
:awestruct-description: Upgrade to OptaPlanner 7 from a previous version.
:awestruct-layout: upgradeRecipeBase
:awestruct-priority: 0.5
:awestruct-upgrade_recipe_version: 7

== From 6.5.0.Final to 7.0.0.Beta1

=== Backwards incompatible changes to the public API in 7.0

Because this is a new major version number (7.0), which is the foundation for the 7.x series for the next few years,
it allows us to make backwards incompatible changes to the public API _for the long term benefit of this project_.

We kept these backwards incompatible changes to a strict minimum
(by favoring deprecation over removal) and will not introduce any additional ones during the 7.x era.

Any backwards incompatible changes are annotated with a [.label.label-danger.label-as-badge.label-public-api]#Public API# badge.

[.upgrade-recipe-major.upgrade-recipe-public-api]
=== Java 8 or higher required

If you're using JRE or JDK 6 or 7, upgrade to JDK 8 or higher.

We currently intend to support a minimal version of Java 8 throughout the entire 7.x series.

[.upgrade-recipe-minor.upgrade-recipe-public-api]
===  Deprecated methods removed

The following long term deprecated methods have been finally removed:

* Setters on `ScoreHolder` implementations, such as `HardSoftScoreHolder.setHardScore(int)` and `setSoftScore(int)`.
Use `addHardConstraintMatch(...)` and `addSoftConstraintMatch(...)` in your score rules instead.
See link:./upgradeRecipe6.0.html[this upgrade recipe].

* The experimental, deprecated, hybrid metaheuristic called `LATE_SIMULATED_ANNEALING`
(which was inspired by both Late Acceptance and Simulated Annealing) has been removed.

* The dead, deprecated code of `DeciderScoreComparatorFactory` has been removed.
See link:./upgradeRecipe6.2.html[this upgrade recipe].

* The deprecated `SolverBenchmarkBluePrintType.ALL_CONSTRUCTION_HEURISTIC_TYPES` has been removed.
See link:./upgradeRecipe6.3.html[this upgrade recipe].

[.upgrade-recipe-major]
=== `Solution` interface removed (deprecated)

Your solution class no longer needs to have both the `@PlanningSolution` annotation and implement the `Solution` interface,
only the annotation suffices. The `Solution` interface has been deprecated and will be removed in a future version.

Remove the `Solution` interface, annotate the `getScore()` method with `@PlanningScore`
and replace the `getProblemFacts()` method with a `@ProblemFactCollectionProperty` annotation directly on every problem fact getter (or field).

Before in `*.java`:
[source, java]
----
@PlanningSolution
public class CloudBalance implements Solution<HardSoftScore> {

    private List<CloudComputer> computerList;
    ...

    private HardSoftScore score;

    @ValueRangeProvider(id = "computerRange")
    public List<CloudComputer> getComputerList() {...}

    public HardSoftScore getScore() {...}
    public void setScore(HardSoftScore score) {...}

    public Collection<? extends Object> getProblemFacts() {
        List<Object> facts = new ArrayList<Object>();
        facts.addAll(computerList);
        ...
        return facts;
    }

}
----

After in `*.java`:
[source, java]
----
@PlanningSolution
public class CloudBalance {

    private List<CloudComputer> computerList;
    ...

    private HardSoftScore score;

    @ValueRangeProvider(id = "computerRange")
    @ProblemFactCollectionProperty
    public List<CloudComputer> getComputerList() {...}

    @PlanningScore
    public HardSoftScore getScore() {...}
    public void setScore(HardSoftScore score) {...}

}
----

For a single problem fact (which is not wrapped in a `Collection`), use the `@ProblemFactProperty` annotation,
as shown below (with field annotations this time).

Before in `*.java`:
[source, java]
----
@PlanningSolution
public class CloudBalance implements Solution<HardSoftScore> {

    private CloudParametrization parametrization;
    private List<CloudBuilding> buildingList;
    @ValueRangeProvider(id = "computerRange")
    private List<CloudComputer> computerList;
    ...

    public Collection<? extends Object> getProblemFacts() {
        List<Object> facts = new ArrayList<Object>();
        facts.add(parametrization); // not a Collection
        facts.addAll(buildingList);
        facts.addAll(computerList);
        ...
        return facts;
    }

}
----

After in `*.java`:
[source, java]
----
@PlanningSolution
public class CloudBalance {

    @ProblemFactProperty
    private CloudParametrization parametrization;
    @ProblemFactCollectionProperty
    private List<CloudBuilding> buildingList;
    @ValueRangeProvider(id = "computerRange")
    @ProblemFactCollectionProperty
    private List<CloudComputer> computerList;
    ...

}
----

Don't add the `@ProblemFactCollectionProperty` annotation on getters (or fields)
that have a `@PlanningEntityCollectionProperty` annotation.

[.upgrade-recipe-minor.upgrade-recipe-public-api]
===  `Solver`: return values no longer implement `Solution`

Because the `Solution` interface was deprecated (see the section below to upgrade from 6.4.0.Final to 7.0.0.Beta1),
the `Solver.solve(...)` and `Solver.getBestSolution()` methods now return an `Object` instead of a `Solution` instance
(if and only if no type parameter is specified for the `Solver`).

*This only applies if you're still using a `Solver` without a type parameter
and if you're not casting the return value immediately to your solution implementation (which is unlikely).*

Before in `*.java`:
[source, java]
----
Solution s = solver.solve(problem);
CloudBalance solution = (CloudBalance) s;
----

After in `*.java` (quick and dirty fix):
[source, java]
----
CloudBalance solution = (CloudBalance) solver.solve(problem);
----

After in `*.java` (recommended fix):
[source, java]
----
SolverFactory<CloudBalance> factory = SolverFactory.createFromXmlResource(...);
Solver<CloudBalance> solver = factory.buildSolver();
...
CloudBalance solution = solver.solve(problem);
----

[.upgrade-recipe-minor.upgrade-recipe-public-api]
===  `BestSolutionChangedEvent.getNewBestSolution()`: return value no longer implements `Solution`

Because the `Solution` interface was deprecated (see the section below to upgrade from 6.4.0.Final to 7.0.0.Beta1),
the `BestSolutionChangedEvent.getNewBestSolution()` method now returns an `Object`
(if and only if no type parameter is specified for the `SolverEventListener`).

*This only applies if you're still using a `SolverEventListener` without a type parameter
and if you're not casting the return value immediately to your solution implementation (which is unlikely).*

Before in `*.java`:
[source, java]
----
SolverFactory factory = SolverFactory.createFromXmlResource(...);
Solver solver = factory.buildSolver();
solver.addEventListener(new SolverEventListener() {
    @Override
    public void bestSolutionChanged(BestSolutionChangedEvent event) {
        Solution s = event.getNewBestSolution();
        CloudBalance solution = (CloudBalance) s;
        ...
    }
});
----

After in `*.java`:
[source, java]
----
SolverFactory<CloudBalance> factory = SolverFactory.createFromXmlResource(...);
Solver<CloudBalance> solver = factory.buildSolver();
solver.addEventListener(new SolverEventListener<CloudBalance>() {
    @Override
    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        CloudBalance solution = event.getNewBestSolution();
        ...
    }
});
----

And you'll probably want to use a lambda here:

[source, java]
----
SolverFactory<CloudBalance> factory = SolverFactory.createFromXmlResource(...);
Solver<CloudBalance> solver = factory.buildSolver();
solver.addEventListener(event -> {
    CloudBalance solution = event.getNewBestSolution();
    ...
});
----

[.upgrade-recipe-major]
=== `SolutionFileIO`: added optional generic type parameter

To avoid the awkward cast to your `Solution` implementation and to get rid of that deprecated interface,
`SolutionFileIO` now optionally supports a generic type parameter (which is the solution class).

Before in `*.java`:
[source, java]
----
public class TspFileIO implements SolutionFileIO {
    ...

    public Solution read(File inputSolutionFile) {...}

    public void write(Solution solution, File outputSolutionFile) {
        TspSolution tspSolution = (TspSolution) solution;
        ...
    }

}
----

After in `*.java`:
[source, java]
----
public class TspFileIO implements SolutionFileIO<TspSolution> {
    ...

    public TspSolution read(File inputSolutionFile) {...}

    public void write(TspSolution tspSolution, File outputSolutionFile) {
        ...
    }

}
----

[.upgrade-recipe-minor]
=== `XStreamSolutionFileIO`: added optional generic type parameter

To avoid the awkward cast to your `Solution` implementation and to get rid of that deprecated interface,
`XStreamSolutionFileIO` now optionally supports a generic type parameter (which is the solution class).

Before in `*.java`:
[source, java]
----
SolutionFileIO solutionFileIO = new XStreamSolutionFileIO(CloudBalance.class);
----

After in `*.java`:
[source, java]
----
SolutionFileIO<CloudBalance> solutionFileIO = new XStreamSolutionFileIO<>(CloudBalance.class);
----

[.upgrade-recipe-minor]
=== `SelectionFilter`: added generic type parameter

To avoid the awkward cast to your `Solution` implementation,
a `SelectionFilter` now also has a generic type parameter for the solution, not just the selection type.

Before in `*.java`:
[source, java]
----
public class LectureFilter implements SelectionFilter<Lecture> {

    public boolean accept(ScoreDirector scoreDirector, Lecture lecture) {
        ...
    }

}
----

After in `*.java`:
[source, java]
----
public class LectureFilter implements SelectionFilter<CourseSchedule, Lecture> {

    @Override
    public boolean accept(ScoreDirector<CourseSchedule> scoreDirector, Lecture lecture) {
        ...
    }

}
----

[.upgrade-recipe-minor]
=== `CustomPhaseCommand`: added optional generic type parameter

To avoid the awkward cast to your `Solution` implementation and to get rid of that deprecated interface,
`CustomPhaseCommand` now optionally supports a generic type parameter (which is the solution class).

Before in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand {

    public void changeWorkingSolution(ScoreDirector scoreDirector) {
        DinnerParty dinnerParty = (DinnerParty) scoreDirector.getWorkingSolution();
        ...
    }

}
----

After in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {

    public void changeWorkingSolution(ScoreDirector<DinnerParty> scoreDirector) {
        DinnerParty dinnerParty = scoreDirector.getWorkingSolution();
        ...
    }

}
----

[.upgrade-recipe-major]
=== `ProblemFactChange`: added optional generic type parameter

To avoid the awkward cast to your `Solution` implementation and to get rid of that deprecated interface,
`ProblemFactChange` now optionally supports a generic type parameter (which is the solution class).

Before in `*.java`:
[source, java]
----
        solver.addProblemFactChange(new ProblemFactChange() {
            public void doChange(ScoreDirector scoreDirector) {
                CloudBalance cloudBalance = (CloudBalance) scoreDirector.getWorkingSolution();
                ...
            }
        });
----

After in `*.java`:
[source, java]
----
        solver.addProblemFactChange(new ProblemFactChange<CloudBalance>() {
            public void doChange(ScoreDirector<CloudBalance> scoreDirector) {
                CloudBalance cloudBalance = scoreDirector.getWorkingSolution();
                ...
            }
        });
----

After in `*.java` (with lambda):
[source, java]
----
        solver.addProblemFactChange(scoreDirector -> {
            CloudBalance cloudBalance = scoreDirector.getWorkingSolution();
            ...
        });
----

[.upgrade-recipe-minor]
=== `Bendable*Score`: `toString()` changed

A bendable score (`BendableScore`, `BendableLongScore` or `BendableBigDecimalScore`)'s `String`
has changed so it can be parsed without the ScoreDefinition.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
      <termination>
        <bestScoreLimit>0/0/-1/-2/-3</bestScoreLimit>
      </termination>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
      <termination>
        <bestScoreLimit>[0/0]hard/[-1/-2/-3]soft</bestScoreLimit>
      </termination>
----

Before in XStream `*.xml` output with `optaplanner-persistence-xstream`:
[source, xml]
----
      <score>0/0/-1/-2/-3</score>
----

After in in XStream `*.xml` output with `optaplanner-persistence-xstream`:
[source, xml]
----
      <score>[0/0]hard/[-1/-2/-3]soft</score>
----

[.upgrade-recipe-major]
=== `EnvironmentMode`: `PRODUCTION` renamed

The `EnvironmentMode` `PRODUCTION` has been renamed to `NON_REPRODUCIBLE`
because most enterprises use `REPRODUCIBLE` in production and that's fine.
For backwards compatibility, `PRODUCTION` still exists, but it's deprecated and it will be removed in a future version.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
<solver>
  <environmentMode>PRODUCTION</environmentMode>
  ...
</solver>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
<solver>
  <environmentMode>NON_REPRODUCIBLE</environmentMode>
  ...
</solver>
----

[.upgrade-recipe-readme]
=== Average calculate count renamed to score calculation speed

In the logs and the benchmark report, the _average calculate count per second_ has been renamed to _score calculation speed_.

[.upgrade-recipe-minor]
=== `Termination`: `calculateCountLimit` renamed

The termination configuration property `calculateCountLimit` has been renamed to `scoreCalculationCountLimit`.
The property `calculateCountLimit`  has been deprecated and will be removed in a future version.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <termination>
    <calculateCountLimit>100000</calculateCountLimit>
  </termination>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <termination>
    <scoreCalculationCountLimit>100000</scoreCalculationCountLimit>
  </termination>
----

[.upgrade-recipe-minor]
=== `ProblemStatisticType`: `CALCULATE_COUNT_PER_SECOND` renamed

The benchmark ProblemStatisticType `CALCULATE_COUNT_PER_SECOND` has been renamed to `SCORE_CALCULATION_SPEED`.

Before in `*BenchmarkConfig.xml`:
[source, xml]
----
      <problemStatisticType>CALCULATE_COUNT_PER_SECOND</problemStatisticType>
----

After in `*BenchmarkConfig.xml`:
[source, xml]
----
      <problemStatisticType>SCORE_CALCULATION_SPEED</problemStatisticType>
----

[.upgrade-recipe-readme]
=== `Score`: uninitialized variable count

A solution's `Score` now also contains the number of uninitialized variables (usually `0`) as a negative `getInitScore()`.
This is useful in exotic cases with multiple phases to fully initialize a solution.
It also prevents bugs in multithreaded use cases.

With `Score.isSolutionInitialized()`, it's now possible to quickly and reliably determine if a solution is fully initialized.
The method `FeasibilityScore.isFeasible()` now also checks if the solution was fully initialized during score calculation.

[.upgrade-recipe-major.upgrade-recipe-reverted]
=== `EasyScoreCalculator`: `calculateScore()` changed

*This change has been reverted in version 7.0.0.Beta6. Ignore this item if you're upgrading directly to that version or higher.*

The `EasyScoreCalculator` interface method `calculateScore(solution)` has been changed to `calculateScore(solution, initScore)`.
Change the method signature to add the `initScore` and then pass it to the `Score.valueOf()` method.

Before in `*.java`:
[source, java]
----
public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance) {
        ...
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}
----

After in `*.java`:
[source, java]
----
public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance, int initScore) {
        ...
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}
----

OptaPlanner keeps track of the `initScore` internally, but it needs to be passed into the `Score` creation because a `Score` is immutable by design.

[.upgrade-recipe-minor.upgrade-recipe-reverted]
=== `IncrementalScoreCalculator`: `calculateScore()` changed

*This change has been reverted in version 7.0.0.Beta6. Ignore this item if you're upgrading directly to that version or higher.*

The `IncrementalScoreCalculator` interface method `calculateScore()` has been changed to `calculateScore(initScore)`.
Change the method signature to add the `initScore` and then pass it to the `Score.valueOf()` method.

Before in `*.java`:
[source, java]
----
public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore() {
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}
----

After in `*.java`:
[source, java]
----
public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(int initScore) {
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}
----

[.upgrade-recipe-major.upgrade-recipe-public-api.upgrade-recipe-reverted]
=== `Score`: `valueOf(...)` changed and `valueOfInitialized(...)` added

*This change has been reverted in version 7.0.0.Beta6. Ignore this item if you're upgrading directly to that version or higher.
Instead, the method `valueOfUninitialized(...)` has been added, but that doesn't affect your code.*

Each `Score` implementation now requires an `initScore` parameter.
Inside a `ScoreCalculator`, the `initScore` must be passed from the `calculateScore()` method (see the 2 previous notes above).

Outside of a `ScoreCalculator`, if you're constructing a score for an initialized solution,
just replace `valueOf()` with `valueOfInitialized()`:

Before in `*.java`:
[source, java]
----
        SimpleScore score = SimpleScore.valueOf(1234);
----

After in `*.java`:
[source, java]
----
        SimpleScore score = SimpleScore.valueOfInitialized(1234);
----

Or with a `HardSoftScore`:

Before in `*.java`:
[source, java]
----
        HardSoftScore score = HardSoftScore.valueOf(1200, 34);
----

After in `*.java`:
[source, java]
----
        HardSoftScore score = HardSoftScore.valueOfInitialized(1200, 34);
----

It is intentional that `valueOfInitialized()` doesn't just overload `valueOf()`,
to avoid that an `EasyScoreCalculator` implementation forgets to pass the `initScore` parameter.

[.upgrade-recipe-major]
=== `BestSolutionChangedEvent`: `isNewBestSolutionInitialized()` replaced

The method `BestSolutionChangedEvent.isNewBestSolutionInitialized()`
has been deprecated in favor of `BestSolutionChangedEvent.getNewBestSolution().getScore().isSolutionInitialized()`.

Before in `*.java`:
[source, java]
----
    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        if (event.isEveryProblemFactChangeProcessed()
                && event.isNewBestSolutionInitialized()) {
            ...
        }
    }
----

After in `*.java`:
[source, java]
----
    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        if (event.isEveryProblemFactChangeProcessed()
                && event.getNewBestSolution().getScore().isSolutionInitialized()) {
            ...
        }
    }
----

However, if you also check `isFeasible()`, that's enough because it now also checks if the solution is initialized.

After in `*.java` for a `FeasibleScore`:
[source, java]
----
    public void bestSolutionChanged(BestSolutionChangedEvent<CloudBalance> event) {
        if (event.isEveryProblemFactChangeProcessed()
                // isFeasible() checks isSolutionInitialized() too
                && event.getNewBestSolution().getScore().isFeasible()) {
            ...
        }
    }
----

[.upgrade-recipe-minor.upgrade-recipe-reverted]
=== Custom initializer: `Score.compareTo()` behaviour changed

*This change has been reverted in version 7.0.0.Beta6. Ignore this item if you're upgrading directly to that version or higher.*

The `Score.compareTo()` now also takes the uninitialized variable count into account.
If you have a `CustomPhaseCommand` that implements a custom solution initializer (instead of using a Construction Heuristic),
it will need to transform all scores with `Score.toInitializedScore()` before comparison to avoid making the wrong decision:

Before in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOf(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.compareTo(bestScore) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}
----

After in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOfInitialized(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.toInitializedScore().compareTo(bestScore.toInitializedScore()) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}
----

[.upgrade-recipe-minor]
=== `Score` and `ScoreDefinition`: methods changed

The `ScoreDefinition.fromLevelNumbers(...)` method now requires an `initScore` parameter.

Before in `*.java`:
[source, java]
----
Score score = scoreDefinition.fromLevelNumbers(new int[]{0, -200});
----

After in `*.java` (quick and dirty fix):
[source, java]
----
Score score = scoreDefinition.fromLevelNumbers(0, new int[]{0, -200});
----

[.upgrade-recipe-minor]
=== Custom `Score`: methods added

If you have a custom `Score`:
The `Score` interface has several new methods: `getInitScore()`, `isSolutionInitialized()`, `toInitializedScore()` and `withInitScore()`.
The first two methods are implemented by `AbstractScore`, but the last two methods need to be specifically implemented.

Before in `*.java`:
[source, java]
----
public final class HardSoftScore extends AbstractScore<HardSoftScore> ... {
    ...
}
----

After in `*.java`:
[source, java]
----
public final class HardSoftScore extends AbstractScore<HardSoftScore> ... {
    ...

    public HardSoftScore toInitializedScore() {
        return initScore == 0 ? this : new HardSoftScore(0, hardScore, softScore);
    }

    public HardSoftScore withInitScore(int newInitScore) {
        assertNoInitScore();
        return new HardSoftScore(newInitScore, hardScore, softScore);
    }

}
----

Furthermore, a score that implements `FeasibleScore` needs to take the `initScore` into account in the `isFeasible()` method implementation.

[.upgrade-recipe-minor]
=== Hibernate integration: extra `@Column` needed

Because a `Score` now also contains an `initScore` of type `int` (regardless of the type of the other fields),
add an extra `@Column` annotation to the beginning of the `@Columns` list to map that field to a database column.

Set it to `0` for all existing records (unless you have reason to believe that some scores weren't calculated on a fully initialized solution).

Before in `*.java`:
[source, java]
----
        @Columns(columns = {
                @Column(name = "hardScore"),
                @Column(name = "softScore")})
        public HardSoftScore getScore() {
            return score;
        }
----

After in `*.java`:
[source, java]
----
        @Columns(columns = {
                @Column(name = "initScore"),
                @Column(name = "hardScore"),
                @Column(name = "softScore")})
        public HardSoftScore getScore() {
            return score;
        }
----

[.upgrade-recipe-impl-detail]
=== `XStreamSolutionFileIO`: no-arg constructor removed

The no-arg constructor of `XStreamSolutionFileIO` has been removed because it's useless.

[.upgrade-recipe-minor]
=== JAXB support added

If you're using JAXB, take advantage the new JAXB Score bindings etc.
See the reference manual, chapter _Integration_.

These new `ScoreJaxbXmlAdapter` implementations have been promoted to the public API,
so they are guaranteed to be backwards compatible in future versions.

[.upgrade-recipe-minor]
=== Jackson support added

If you're using Jackson, take advantage the new Jackson Score bindings etc.
See the reference manual, chapter _Integration_.

These new `ScoreJacksonJsonSerializer` and `ScoreJacksonJsonDeserializer` implementations have been promoted to the public API,
so they are guaranteed to be backwards compatible in future versions.

[.upgrade-recipe-major]
=== `XStreamScoreConverter` replaced

The general purpose `XStreamScoreConverter` to bind `Score` implementations
has been replaced by specific implementations, such as `HardSoftScoreXStreamConverter` and `SimpleScoreXStreamConverter`
that are easier to use.

Furthermore, these  new `ScoreXStreamConverter` implementations have been promoted to the public API,
so they are now guaranteed to be backwards compatible in future versions.

Before in `*.java`:
[source, java]
----
public class CloudBalance {

    @XStreamConverter(value = XStreamScoreConverter.class, types = {HardSoftScoreDefinition.class})
    private HardSoftScore score;

    ...
}
----

After in `*.java`:
[source, java]
----
public class CloudBalance {

    @XStreamConverter(HardSoftScoreXStreamConverter.class)
    private HardSoftScore score;

    ...
}
----

For a bendable score, it's no longer needed to configure the `hardLevelSize` and `softLevelSize`.

Before in `*.java`:
[source, java]
----
public class Schedule {

    @XStreamConverter(value = XStreamScoreConverter.class, types = {BendableScoreDefinition.class}, ints = {1, 2})
    private BendableScore score;

    ...
}
----

After in `*.java`:
[source, java]
----
public class Schedule {

    @XStreamConverter(BendableScoreXStreamConverter.class)
    private BendableScore score;

    ...
}
----

[.upgrade-recipe-major.upgrade-recipe-public-api]
===  `@CustomShadowVariable`: `sources` type changed

A shadow variable annotated with `@CustomShadowVariable`
now expects that the `sources` parameter is of type `@PlanningVariableReference`
instead of `@CustomShadowVariable.Source`.

This way it's consistent with the `variableListenerRef` parameter.

Before in `*.java`:
[source, java]
----
        @CustomShadowVariable(variableListenerClass = ArrivalTimeUpdatingVariableListener.class,
                sources = {@CustomShadowVariable.Source(variableName = "previousStandstill")})
        public Long getArrivalTime() {
            return arrivalTime;
        }
----

After in `*.java`:
[source, java]
----
        @CustomShadowVariable(variableListenerClass = ArrivalTimeUpdatingVariableListener.class,
                sources = {@PlanningVariableReference(variableName = "previousStandstill")})
        public Long getArrivalTime() {
            return arrivalTime;
        }
----

== From 7.0.0.Beta1 to 7.0.0.Beta2

[.upgrade-recipe-minor]
===  `ProblemFactChange`: `before/afterProblemFactChanged` renamed

The `ScoreDirector` methods `beforeProblemFactChanged()` and `afterProblemFactChanged()`
have been renamed to `beforeProblemPropertyChanged()` and `afterProblemPropertyChanged()`.
This can affect your `ProblemFactChange` implementations.

A problem fact is a class that doesn't change during planning.
A problem property is a property on a problem fact or a planning entity that doesn't change during planning
(so it's not a planning variable).

Before in `*.java`:
[source, java]
----
        scoreDirector.beforeProblemFactChanged(computer);
        computer.setMemory(newMemoryCapacity);
        scoreDirector.afterProblemFactChanged(computer);
----

After in `*.java`:
[source, java]
----
        scoreDirector.beforeProblemPropertyChanged(computer);
        computer.setMemory(newMemoryCapacity);
        scoreDirector.afterProblemPropertyChanged(computer);
----

[.upgrade-recipe-major]
=== Solver configuration: `<scoreDefinitionType>` removed

Don't specify the `scoreDefinitionType` in the solver configuration any more
because OptaPlanner will now figure it out automatically from the domain.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <scoreDrl>org/optaplanner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <scoreDirectorFactory>
    <scoreDrl>org/optaplanner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>
----

For a bendable score, also move the `bendableHardLevelsSize` and `bendableSoftLevelsSize` lines from the solver configuration XML
into the `@PlanningScore` annotation on your domain class.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <scoreDirectorFactory>
    <scoreDefinitionType>BENDABLE</scoreDefinitionType>
    <bendableHardLevelsSize>1</bendableHardLevelsSize>
    <bendableSoftLevelsSize>2</bendableSoftLevelsSize>
    <scoreDrl>org/optaplanner/examples/projectjobscheduling/solver/projectJobSchedulingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <scoreDirectorFactory>
    <scoreDrl>org/optaplanner/examples/projectjobscheduling/solver/projectJobSchedulingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>
----

Before in `*.java`:
[source, java]
----
    @PlanningScore
    private BendableScore score;
----

After in `*.java`:
[source, java]
----
    @PlanningScore(bendableHardLevelsSize = 1, bendableSoftLevelsSize = 2)
    private BendableScore score;
----

In the rare case that you're using a custom score, also move its declaration into the domain:

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
    <scoreDefinitionClass>...MyCustomScoreDefinition</scoreDefinitionClass>
----

After in `*.java`:
[source, java]
----
    @PlanningScore(scoreDefinitionClass = MyCustomScoreDefinition.class)
----

[.upgrade-recipe-minor.upgrade-recipe-public-api]
===  `@PlanningVariable` on primitive types: no longer supported

A `@PlanningVariable` annotation on a primitive type such as `int` or `long` (instead of `Integer` or `Long`)
now fails fast instead of causing an inferior result.
The use of a primitive type caused the Construction Heuristics to presume the variable is already initialized
(because it's not null and it might be form of Repeated Planning),
which lead to inferior results.
It was hard to diagnose the cause of that issue for many users, so now this inferior approach fails fast with a clear message.

Before in `*.java`:
[source, java]
----
    private int delay;

    @PlanningVariable(valueRangeProviderRefs = {"delayRange"})
    public int getDelay() {
        return delay;
    }

    public void setDelay(int delay) {
        this.delay = delay;
    }
----

After in `*.java`:
[source, java]
----
    private Integer delay;

    @PlanningVariable(valueRangeProviderRefs = {"delayRange"})
    public Integer getDelay() {
        return delay;
    }

    public void setDelay(Integer delay) {
        this.delay = delay;
    }
----

[.upgrade-recipe-minor]
===  `VariableListener` events are no longer unique

OptaPlanner might call the `before...` and `after...` methods on your `VariableListener` implementation
twice with the exact same parameters.
Most `VariableListener` implementations can deal with this,
getting a small performance boost because OptaPlanner doesn't have to guarantee uniqueness.
If your implementation can't deal with it, then overwrite the `requiresUniqueEntityEvents()` method.

Before in `*.java`:
[source, java]
----
public class StartTimeUpdatingVariableListener implements VariableListener<Task> {

    ...
}
----

After in `*.java` (optional):
[source, java]
----
public class StartTimeUpdatingVariableListener implements VariableListener<Task> {

    @Override
    public boolean requiresUniqueEntityEvents() {
        // If you don't need to overwrite this method, you get a small performance gain
        return true;
    }

    ...
}
----

[.upgrade-recipe-recommended]
===  Faster and nicer `accumulate()` in `drl`

Drools now uses typed `sum()`, `min()`, `max()` and `avg()` functions in `accumulate()` patterns.
This means that a sum of ints is now an int (instead of a double) and a sum of BigDecimals is now a BigDecimal (without rounding errors).
This is faster and it also gets rid of the `intValue()` conversions.

Meanwhile, also take advantage of migrating to the clearer `accumulate` form, if you haven't already.

Before in `*.drl`:
[source, drl]
----
rule "requiredCpuPowerTotal"
    when
        $c : CloudComputer($capacity : cpuPower)
        $total : Number(intValue > $capacity) from accumulate(
            CloudProcess(
                computer == $c,
                $required : requiredCpuPower),
            sum($required)
        )
    then
        scoreHolder.addHardConstraintMatch(kcontext, $capacity - $total.intValue());
end
----

After in `*.drl`:
[source, drl]
----
rule "requiredCpuPowerTotal"
    when
        $c : CloudComputer($capacity : cpuPower)
        accumulate(
            CloudProcess(
                computer == $c,
                $required : requiredCpuPower);
            $total : sum($required);
            $total > $capacity
        )
    then
        scoreHolder.addHardConstraintMatch(kcontext, $capacity - $total);
end
----

Notice that the pattern, the function list and the DRL constraint list in the `accumulate()`
are recommended to be separated by a `;` character instead of a `,` character.

[.upgrade-recipe-minor]
===  Custom `Score`: implement `isCompatibleArithmeticArgument()`

An `AbstractScore` no longer implements the `Score` interface method `isCompatibleArithmeticArgument()` (which is still there).
Now, your custom `Score` implementation needs to implement it itself.

This way, `Score` instances can be reused by GWT and other JavaScript generating code.

Before in `*.java`:
[source, java]
----
public final class HardSoftScore extends AbstractScore<HardSoftScore> {
    ...

}
----

After in `*.java`:
[source, java]
----
public final class HardSoftScore extends AbstractScore<HardSoftScore> {
    ...

    @Override
    public boolean isCompatibleArithmeticArgument(Score otherScore) {
        return otherScore instanceof HardSoftScore;
    }

}
----

== From 7.0.0.Beta2 to 7.0.0.Beta3

[.upgrade-recipe-minor]
===  `Solver.getScoreDirectorFactory`: call `ScoreDirector.dispose()`

Every `ScoreDirector` needs to be disposed to avoid a potential memory leak.
The old docs didn't clearly mention that, so your code might not do that.

Before in `*.java`:
[source, java]
----
ScoreDirectorFactory<CloudBalance> scoreDirectorFactory = solver.getScoreDirectorFactory();
ScoreDirector<CloudBalance> guiScoreDirector = scoreDirectorFactory.buildScoreDirector();
...
----

After in `*.java`:
[source, java]
----
ScoreDirectorFactory<CloudBalance> scoreDirectorFactory = solver.getScoreDirectorFactory();
ScoreDirector<CloudBalance> guiScoreDirector = scoreDirectorFactory.buildScoreDirector();
...
guiScoreDirector.dispose();
----

[.upgrade-recipe-minor]
=== Custom cloning: `PlanningCloneable` replaced

The interface `PlanningCloneable` has been removed,
use a `SolutionCloner` instead.

Before in `*.java`:
[source, java]
----
public class NQueens implements PlanningCloneable<NQueens> {
    ...

    public NQueens planningClone() {
        ...
    }

}
----

After in `*.java`:
[source, java]
----
public class NQueensSolutionCloner implements SolutionCloner<NQueens> {

    @Override
    public NQueens cloneSolution(CloneLedger ledger, NQueens original) {
        ...
    }

}
----

[source, java]
----
@PlanningSolution(solutionCloner = NQueensSolutionCloner.class)
public class NQueens {
    ...
}
----

== From 7.0.0.Beta3 to 7.0.0.Beta4

[.upgrade-recipe-recommended]
=== Add `@PlanningId` annotation

It is recommended to add a `@PlanningId` annotation
on the unique ID of every planning entity and on most problem fact classes
(especially on each class that is a planning value class).
The ID must never be null and must be unique per class (no need to be globally unique).

This enables the use of multithreaded solvers (such as Partitioned Search)
and makes it easier to implement a real-time planning `ProblemFactChange` by using `ScoreDirector.lookUpWorkingObject()`.

Before in `*.java`:
[source, java]
----
public abstract class AbstractPersistable ... {

    protected Long id; // Can also be a String, Integer, ...

    public Long getId() {
        return id;
    }

    ...
}
----

After in `*.java`:
[source, java]
----
public abstract class AbstractPersistable ... {

    protected Long id; // Can also be a String, Integer, ...

    @PlanningId
    public Long getId() {
        return id;
    }

    ...
}
----

You can also put the `@PlanningId` annotation on the field instead.

[.upgrade-recipe-minor]
=== `ProblemFactChange`: Use `lookUpWorkingObject()`

Use the new method `ScoreDirector.lookUpWorkingObject(Object)` to translate a planning entity or problem fact
to its working instance planning clone more efficiently.

This requires that the class has a `@PlanningId` annotation on one of its getters or fields.

Before in `*.java`:
[source, java]
----
public class EditComputerProblemFactChange implements ProblemFactChange<CloudBalance> {

    private final CloudComputer changedComputer;
    ...

    public void doChange(ScoreDirector<CloudBalance> scoreDirector) {
        CloudComputer workingComputer = null;
        for (CloudComputer computer : cloudBalance.getComputerList()) {
            if (changedComputer.getId().equals(computer.getId())) {
                workingComputer = computer;
                break;
            }
        }

        scoreDirector.beforeProblemPropertyChanged(workingComputer);
        workingComputer.setCpuPower(changedComputer.getCpuPower());
        scoreDirector.afterProblemPropertyChanged(workingComputer);
        ...
        scoreDirector.triggerVariableListeners();
    }

}
----

After in `*.java`:
[source, java]
----
public class EditComputerProblemFactChange implements ProblemFactChange<CloudBalance> {

    private final CloudComputer changedComputer;
    ...

    public void doChange(ScoreDirector<CloudBalance> scoreDirector) {
        CloudComputer workingComputer = scoreDirector.lookUpWorkingObject(changedComputer);

        scoreDirector.beforeProblemPropertyChanged(workingComputer);
        workingComputer.setCpuPower(changedComputer.getCpuPower());
        scoreDirector.afterProblemPropertyChanged(workingComputer);
        ...
        scoreDirector.triggerVariableListeners();
    }

}
----

== From 7.0.0.Beta5 to 7.0.0.Beta6

[.upgrade-recipe-minor]
=== Benchmarker warms up by default

It is no longer needed to explicitly configure a warm up time for the benchmarks.
It now warms up for 30 seconds by default.

Before in `*BenchmarkConfig.xml`:
[source, xml]
----
<plannerBenchmark>
  ...
  <warmUpSecondsSpentLimit>30</warmUpSecondsSpentLimit>
  ...
</plannerBenchmark>
----

After in `*BenchmarkConfig.xml`:
[source, xml]
----
<plannerBenchmark>
  ...
</plannerBenchmark>
----

To disable the warm up, explicitly set it to 0:
[source, xml]
----
<plannerBenchmark>
  ...
  <warmUpSecondsSpentLimit>0</warmUpSecondsSpentLimit>
  ...
</plannerBenchmark>
----

[.upgrade-recipe-minor]
=== `CustomPhaseCommand`: method `applyCustomProperties()` replaced

The `CustomPhaseCommand` no longer has the method `applyCustomProperties()`.
If you have custom properties in your solver configuration,
simply implement a public setter for each custom property.
The supported types for a setter currently include booleans, numbers and string.

Similar custom properties support is available on some other custom classes (such as `SolutionPartitioner`).

Before in `*.java`:
[source, java]
----
public class MyCustomPhaseCommand extends AbstractCustomPhaseCommand {

    private int mySelectionSize;

    @Override
    public void applyCustomProperties(Map<String, String> customPropertyMap) {
        String mySelectionSizeString = customPropertyMap.get("mySelectionSize");
        try {
            mySelectionSize = mySelectionSizeString == null ? 10 : Integer.parseInt(mySelectionSizeString);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("The mySelectionSize (" + mySelectionSizeString + ") cannot be parsed.", e);
        }
        ...
    }

    ...
}
----

After in `*.java`:
[source, java]
----
public class MyCustomPhaseCommand extends AbstractCustomPhaseCommand {

    private int mySelectionSize = 10;

    @SuppressWarnings("unused")
    public void setMySelectionSize(int mySelectionSize) {
        this.mySelectionSize = mySelectionSize;
    }

    ...
}
----

[.upgrade-recipe-minor]
=== `Bendable*Score`: method `isFeasible()` fixed

A bendable score with at least 2 hard score levels is now infeasible
if any of those hard levels is negative, even if one of them is positive (1 or higher).

[.upgrade-recipe-minor]
=== `EasyScoreCalculator`: `calculateScore()` reverted to 6.x style

*This change reverts a change of 7.0.0.Beta1. Ignore this item if you're upgrading directly from version 6.*

Before in `*.java`:
[source, java]
----
public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance, int initScore) {
        ...
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}
----

After in `*.java`:
[source, java]
----
public class CloudBalancingEasyScoreCalculator implements EasyScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(CloudBalance cloudBalance) {
        ...
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}
----

OptaPlanner still keeps track of the `initScore` internally.

[.upgrade-recipe-minor]
=== `IncrementalScoreCalculator`: `calculateScore()` reverted to 6.x style

*This change reverts a change of 7.0.0.Beta1. Ignore this item if you're upgrading directly from version 6.*

Before in `*.java`:
[source, java]
----
public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore(int initScore) {
        return HardSoftScore.valueOf(initScore, hardScore, softScore);
    }

}
----

After in `*.java`:
[source, java]
----
public class CloudBalancingIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<CloudBalance> {

    public HardSoftScore calculateScore() {
        return HardSoftScore.valueOf(hardScore, softScore);
    }

}
----

[.upgrade-recipe-minor]
=== `Score`: `valueOf(...)` and `valueOfInitialized(...)` reverted to 6.x style

*This change reverts a change of 7.0.0.Beta1. Ignore this item if you're upgrading directly from version 6.*

Before in `*.java`:
[source, java]
----
        SimpleScore score = SimpleScore.valueOfInitialized(1234);
----

After in `*.java`:
[source, java]
----
        SimpleScore score = SimpleScore.valueOf(1234);
----

Or with a `HardSoftScore`:

Before in `*.java`:
[source, java]
----
        HardSoftScore score = HardSoftScore.valueOfInitialized(1200, 34);
----

After in `*.java`:
[source, java]
----
        HardSoftScore score = HardSoftScore.valueOf(1200, 34);
----

[.upgrade-recipe-minor]
=== Custom initializer: `Score.compareTo()` behaviour reverted to 6.x style

*This change reverts a change of 7.0.0.Beta1. Ignore this item if you're upgrading directly from version 6.*

Before in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOfInitialized(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.toInitializedScore().compareTo(bestScore.toInitializedScore()) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}
----

After in `*.java`:
[source, java]
----
public class DinnerPartySolutionInitializer extends AbstractCustomPhaseCommand<DinnerParty> {
    ...

    private void initializeSeatDesignationList(ScoreDirector<DinnerParty> scoreDirector, DinnerParty dinnerParty) {
        ...
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            Score bestScore = SimpleScore.valueOf(Integer.MIN_VALUE);
            ...
            for (Seat seat : undesignatedSeatList) {
                ...
                if (score.compareTo(bestScore) > 0) {
                    bestScore = score;
                    ...
                }
            }
            ...
        }
    }

}
----

[.upgrade-recipe-minor]
=== Custom `Move`: added optional generic type parameter

To avoid the awkward cast to your `Solution` implementation,
`Move` and `AbstractMove` now optionally support a generic type parameter (which is the solution class).

Before in `*.java`:
[source, java]
----
public class CloudComputerChangeMove extends AbstractMove {

    @Override
    public boolean isMoveDoable(ScoreDirector scoreDirector) {
        return !Objects.equals(cloudProcess.getComputer(), toCloudComputer);
    }

    @Override
    public Move createUndoMove(ScoreDirector scoreDirector) {
        return new CloudComputerChangeMove(cloudProcess, cloudProcess.getComputer());
    }

    @Override
    protected void doMoveOnGenuineVariables(ScoreDirector scoreDirector) {
        scoreDirector.beforeVariableChanged(cloudProcess, "computer");
        cloudProcess.setComputer(toCloudComputer);
        scoreDirector.afterVariableChanged(cloudProcess, "computer");
    }

    ...
}
----

After in `*.java`:
[source, java]
----
public class CloudComputerChangeMove extends AbstractMove<CloudBalance> {

    @Override
    public boolean isMoveDoable(ScoreDirector<CloudBalance> scoreDirector) {
        return !Objects.equals(cloudProcess.getComputer(), toCloudComputer);
    }

    @Override
    public CloudComputerChangeMove createUndoMove(ScoreDirector<CloudBalance> scoreDirector) {
        return new CloudComputerChangeMove(cloudProcess, cloudProcess.getComputer());
    }

    @Override
    protected void doMoveOnGenuineVariables(ScoreDirector<CloudBalance> scoreDirector) {
        scoreDirector.beforeVariableChanged(cloudProcess, "computer");
        cloudProcess.setComputer(toCloudComputer);
        scoreDirector.afterVariableChanged(cloudProcess, "computer");
    }

    ...
}
----

== From 7.0.0.Beta6 to 7.0.0.Beta7

[.upgrade-recipe-major.upgrade-recipe-public-api]
=== `ConstraintMatch(Total)`: `getWeightTotal()` and `getWeight()` replaced

When explaining a score through `ScoreDirector.getConstraintMatchTotals()`,
the `ConstraintMatchTotal` and `ConstraintMatch` instances now have a `Score`
instead of an `int scoreLevel` and a `Number weight`.

This simplifies the API and allows the reuse of `ConstraintMatch` in the indictment API.

Before in `*.java`:
[source, java]
----
    for (ConstraintMatchTotal constraintMatchTotal : guiScoreDirector.getConstraintMatchTotals()) {
        int scoreLevel = constraintMatchTotal.getScoreLevel();
        Integer weightTotal = (Integer) constraintMatchTotal.getWeightTotalAsNumber();
        String text = weightTotal.toString() + (scoreLevel == 0 ? "hard" : "soft");
        ...
    }
----

After in `*.java`:
[source, java]
----
    for (ConstraintMatchTotal constraintMatchTotal : guiScoreDirector.getConstraintMatchTotals()) {
        HardSoftScore scoreTotal = (HardSoftScore) constraintMatchTotal.getScoreTotal();
        String text = scoreTotal.toShortString();
        ...
    }
----

[.upgrade-recipe-minor.upgrade-recipe-public-api]
=== `ConstraintMatchAwareIncrementalScoreCalculator`: `getConstraintMatchTotals()` impact

When implementing the interface `ConstraintMatchAwareIncrementalScoreCalculator`'s method `getConstraintMatchTotals()`,
the constructor of `ConstraintMatchTotal` and the method `addConstraintMatch(...)`
now use `Score` instances instead of numbers.

Before in `*.java`:
[source, java]
----
    public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
        LongConstraintMatchTotal maximumCapacityMatchTotal = new LongConstraintMatchTotal(
                CONSTRAINT_PACKAGE, "maximumCapacity", 0);
        ...
        serviceLocationSpreadMatchTotal.addConstraintMatch(
                ..., - weight);
        ...
    }
----

After in `*.java`:
[source, java]
----
    public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
        ConstraintMatchTotal maximumCapacityMatchTotal = new ConstraintMatchTotal(
                CONSTRAINT_PACKAGE, "maximumCapacity", HardSoftLongScore.ZERO);
        ...
        serviceLocationSpreadMatchTotal.addConstraintMatch(
                ..., HardSoftLongScore.valueOf(- weight, 0));
        ...
    }
----

[.upgrade-recipe-minor.upgrade-recipe-public-api]
=== Score rule that changes 2 score levels: call `addMultiConstraintMatch()`

A score rule that changes 2 score levels in its RHS,
must now call `addMultiConstraintMatch()` instead of 2 separate `add*ConstraintMatch()` calls.

Before in `*.drl`:
[source, drl]
----
rule "Costly and unfair"
when
    // Complex pattern
then
    scoreHolder.addMediumConstraintMatch(kcontext, -3); // Financial cost
    scoreHolder.addSoftConstraintMatch(kcontext, -4); // Employee happiness cost
end
----

After in `*.drl`:
[source, drl]
----
rule "Costly and unfair"
when
    // Complex pattern
then
    scoreHolder.addMultiConstraintMatch(kcontext, 0, -3, -4);
end
----

When calling `guiScoreDirector.getConstraintMatchTotals()`,
there is now also only one `ConstraintMatchTotal` instance for this score rule
and only one `ConstraintMatch` instance per fired rule.

[.upgrade-recipe-minor]
=== Custom `Score`: `toShortString()` added

If you have a custom `ScoreDefinition`: the `Score` interface has another new method `toShortString()`.

After in `*.java`:
[source, java]
----
public final class HardSoftScore extends AbstractScore<HardSoftScore> ... {
    ...
    @Override
    public String toShortString() {
        return buildShortString((n) -> ((Integer) n).intValue() != 0, HARD_LABEL, SOFT_LABEL);
    }
}
----

[.upgrade-recipe-minor]
=== `Move`: `createUndoMove()` and `doMove()` changed

The `Move` interface has changed: `doMove()` now returns the undo move, so `createUndoMove()` has been removed.
This was needed to fix a bug in `CompositeMove`.
However, `AbstractMove` completely deals with this change, so *your custom move implementation stays the same*.

In the very rare case that you're actually calling `createUndoMove()` yourself,
use the return type of `doMove()` instead.

Before in `*.java`:
[source, java]
----
        Move<Solution_> move = ...;
        Move<Solution_> undoMove = move.createUndoMove(scoreDirector);
        move.doMove(scoreDirector);
----

After in `*.java`:
[source, java]
----
        Move<Solution_> move = ...;
        Move<Solution_> undoMove = move.doMove(scoreDirector);
----

[.upgrade-recipe-minor]
=== Custom `ScoreDefinition`: `getZeroScore()` added

If you have a custom `ScoreDefinition`: the `ScoreDefinition` interface has another new method `getZeroScore()`.

After in `*.java`:
[source, java]
----
public class HardSoftScoreDefinition extends AbstractFeasibilityScoreDefinition<HardSoftScore> {
    ...

    @Override
    public HardSoftScore getZeroScore() {
        return HardSoftScore.ZERO;
    }
}
----

== From 7.0.0.Beta7 to 7.0.0.CR1

[.upgrade-recipe-minor]
=== `ConstraintMatchAwareIncrementalScoreCalculator`: `getIndictmentMap()` added

If you're using a Java incremental score calculator that is also `ConstraintMatch` aware,
it now needs to also implement the method `getIndictmentMap()`.
Simply `return null` to have it calculated automatically from the return value of `getConstraintMatchTotals()`.

Before in `*.java`:
[source, java]
----
public class MachineReassignmentIncrementalScoreCalculator
        implements ConstraintMatchAwareIncrementalScoreCalculator<MachineReassignment> {

    ...

    @Override
    public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
        ...
    }
}
----

After in `*.java`:
[source, java]
----
public class MachineReassignmentIncrementalScoreCalculator
        implements ConstraintMatchAwareIncrementalScoreCalculator<MachineReassignment> {

    ...

    @Override
    public Collection<ConstraintMatchTotal> getConstraintMatchTotals() {
        ...
    }

    @Override
    public Map<Object, Indictment> getIndictmentMap() {
        return null; // Calculate it non-incrementally from getConstraintMatchTotals()
    }
}
----

[.upgrade-recipe-minor]
=== Custom `MoveListFactory` and `MoveIteratorFactory`: method return `Move<Solution_>`

The `MoveListFactory` and `MoveIteratorFactory`'s methods now use `Move<Solution_>` instead of a raw-typed `Move`.
This way your `MoveListFactory` can return `List<ChangeMove<MySolution>>` instead of `List<ChangeMove>`.

Before in `*.java`:
[source, java]
----
public class CloudComputerChangeMoveFactory implements MoveListFactory<CloudBalance> {

    @Override
    public List<Move> createMoveList(CloudBalance solution) {
        ...
    }

}
----

After in `*.java` (if it creates generic `ChangeMove` instances):
[source, java]
----
public class CloudComputerChangeMoveFactory implements MoveListFactory<CloudBalance> {

    @Override
    public List<ChangeMove<CloudBalance>> createMoveList(CloudBalance nQueens) {
        ...
    }

}
----

After in `*.java` (if it creates `CloudComputerChangeMove` instances and that implements `Move<CloudBalance>`):

[source, java]
----
public class RowChangeMoveFactory implements MoveListFactory<CloudBalance> {

    @Override
    public List<CloudComputerChangeMove> createMoveList(CloudBalance nQueens) {
        ...
    }

}
----

Before in `*.java`:
[source, java]
----
public class CheapTimePillarSlideMoveIteratorFactory implements MoveIteratorFactory<CheapTimeSolution> {

    public Iterator<Move> createOriginalMoveIterator(...) {...}
    public Iterator<Move> createRandomMoveIterator(...) {...}

}
----

After in `*.java`:
[source, java]
----
public class CheapTimePillarSlideMoveIteratorFactory implements MoveIteratorFactory<CheapTimeSolution> {

    public Iterator<CheapTimePillarSlideMove> createOriginalMoveIterator(...) {...}
    public Iterator<CheapTimePillarSlideMove> createRandomMoveIterator(...) {...}

}
----

== From 7.0.0.CR3 to 7.0.0.Final

[.upgrade-recipe-impl-detail]
=== Workbench's `AbstractSolution` deprecated

The implementation class `AbstractSolution`, used only by workbench 6,
has been deprecated and replaced by the `autoDiscoverMemberType` feature.

Before in `*.java`:
[source, java]
----
@PlanningSolution
public class Mysolution extends AbstractSolution<HardSoftScore> {

    private List<FooFact> fooFactList;
    private List<BarFact> barFactList;

    ...
}
----

After in `*.java`:
[source, java]
----
@PlanningSolution(autoDiscoverMemberType = AutoDiscoverMemberType.FIELD)
public class Mysolution {

    private List<FooFact> fooFactList;
    private List<BarFact> barFactList;

    private HardSoftScore score;

    ...
}
----

== From 7.1.0.Beta2 to 7.1.0.Beta3

[.upgrade-recipe-minor]
=== `<valueSelector>`: `variableName` is now an attribute

When power tweaking move selectors, such as `<changeMoveSelector>`,
in a use case with multiple planning variables,
the `<variableName>` XML element has been replaced by a `variableName="..."` XML attribute.
This reduces the solver configuration verbosity.
For backwards compatibility, the old way is still supported in the 7.x series.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <valueSelector>
    <variableName>room</variableName>
  </valueSelector>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <valueSelector variableName="room"/>
----

[.upgrade-recipe-minor]
=== Construction Heuristic: multiple variable power tweaking simplified

It's now easier to configure construction heuristics that scale better for multiple variables,
but assigning one variable at a time.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <constructionHeuristic>
    <queuedEntityPlacer>
      <entitySelector id="placerEntitySelector"/>
      <changeMoveSelector>
        <entitySelector mimicSelectorRef="placerEntitySelector"/>
        <valueSelector variableName="period"/>
      </changeMoveSelector>
      <changeMoveSelector>
        <entitySelector mimicSelectorRef="placerEntitySelector"/>
        <valueSelector variableName="room"/>
      </changeMoveSelector>
    </queuedEntityPlacer>
  </constructionHeuristic>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <constructionHeuristic>
    <constructionHeuristicType>FIRST_FIT</constructionHeuristicType>
    <changeMoveSelector>
      <valueSelector variableName="period"/>
    </changeMoveSelector>
    <changeMoveSelector>
      <valueSelector variableName="room"/>
    </changeMoveSelector>
  </constructionHeuristic>
----

== From 7.1.0.Final to 7.2.0.Final

There is no impact on your code.

== From 7.2.0.Final to 7.3.0.Final

[.upgrade-recipe-minor]
=== `SolutionFileIO`: `getOutputFileExtension()` is now defaulted

It's no longer needed to implement `getOutputFileExtension()` of the `SolutionFileIO` interface
if it returns the same as `getInputFileExtension()`.

Before in `*.java`:
[source, java]
----
public class VehicleRoutingFileIO implements SolutionFileIO<VehicleRoutingSolution> {

    @Override
    public String getInputFileExtension() {
        return "vrp";
    }

    @Override
    public String getOutputFileExtension() {
        return "vrp";
    }

    ...
}
----

After in `*.java`:
[source, java]
----
public class VehicleRoutingFileIO implements SolutionFileIO<VehicleRoutingSolution> {

    @Override
    public String getInputFileExtension() {
        return "vrp";
    }

    ...
}
----

[.upgrade-recipe-minor]
=== Benchmarker: direct POJO input

The benchmarker now also accepts problem instances directly, without reading them from disk.
If you're generating your problems or fetching them from a database,
it might be interesting to switch to this approach (otherwise stick with the old approach because it works offline).

Before in `*.java`:
[source, java]
----
    CloudBalance problem1 = readFromDatabase(...);
    CloudBalance problem2 = readFromDatabase(...);
    ...
    CloudBalanceFileIO solutionFileIO = new CloudBalanceFileIO();
    solutionFileIO.write(problem1, new File("tmp/problem1.xml"));
    solutionFileIO.write(problem2, new File("tmp/problem1.xml"));
    ...
    PlannerBenchmark plannerBenchmark = benchmarkFactory.buildPlannerBenchmark();
    plannerBenchmark.benchmark();
----

Before in `*BenchmarkConfig.xml`:
[source, xml]
----
    <problemBenchmarks>
      <solutionFileIOClass>...CloudBalanceFileIO</solutionFileIOClass>
      <inputSolutionFile>tmp/problem1.xml</inputSolutionFile>
      <inputSolutionFile>tmp/problem2.xml</inputSolutionFile>
      ...
    </problemBenchmarks>
----

After in `*.java`:
[source, java]
----
    CloudBalance problem1 = readFromDatabase(...);
    CloudBalance problem2 = readFromDatabase(...);
    ...
    PlannerBenchmark plannerBenchmark = benchmarkFactory.buildPlannerBenchmark(problem1, problem2, ...);
    plannerBenchmark.benchmark();
----

After in `*BenchmarkConfig.xml`:
[source, xml]
----
    <problemBenchmarks>
    </problemBenchmarks>
----

[.upgrade-recipe-minor]
=== Benchmarker: `BEST_SCORE` statistic by default

The benchmarker now includes the `BEST_SCORE` statistic by default.
It no longer needs to be explicitly configured.

Before in `*BenchmarkConfig.xml`:
[source, xml]
----
    <problemBenchmarks>
      ...
      <problemStatisticType>BEST_SCORE</problemStatisticType>
    </problemBenchmarks>
----

After in `*BenchmarkConfig.xml`:
[source, xml]
----
    <problemBenchmarks>
      ...
    </problemBenchmarks>
----

To disable the `BEST_SCORE` statistic, use `<problemStatisticEnabled>` in `*BenchmarkConfig.xml`:
[source, xml]
----
    <problemBenchmarks>
      ...
      <problemStatisticEnabled>false</problemStatisticEnabled>
    </problemBenchmarks>
----

[.upgrade-recipe-major]
=== `ScoreDirector`: `dispose()` replaced by `close()`

`ScoreDirector` now implements `AutoCloseable`,
so the `dispose()` method has been deprecated and replaced by `close()`.

Before in `*.java`:
[source, java]
----
    ScoreDirector<VehicleRoutingSolution> scoreDirector = scoreDirectorFactory.buildScoreDirector();
    ...
    scoreDirector.dispose();
----

After in `*.java`:
[source, java]
----
    ScoreDirector<VehicleRoutingSolution> scoreDirector = scoreDirectorFactory.buildScoreDirector();
    ...
    scoreDirector.close();
----

After in `*.java` (with ARM usage):
[source, java]
----
    try (ScoreDirector<VehicleRoutingSolution> scoreDirector = scoreDirectorFactory.buildScoreDirector()) {
        ...
    }
----

== From 7.3.0.Final to 7.4.0.Final

[.upgrade-recipe-minor]
=== `movableEntitySelectionFilter` is now inherited

An entity's `movableEntitySelectionFilter` is now inherited by child entities.
The workaround of configuring the filter twice, is now obsolete.

Before in `*.java`:
[source, java]
----
@PlanningEntity(movableEntitySelectionFilter = ParentFilter.class)
public class Animal {
   ...
}

@PlanningEntity(movableEntitySelectionFilter = ParentFilter.class)
public class Dog extends Animal {
   ...
}
----

After in `*.java`:
[source, java]
----
@PlanningEntity(movableEntitySelectionFilter = ParentFilter.class)
public class Animal {
   ...
}

@PlanningEntity()
public class Dog extends Animal {
   ...
}
----

== From 7.4.0.Final to 7.5.0.Final

[.upgrade-recipe-minor]
=== `Indictment`: natural comparison changed

An `Indictment` is now naturally sorted by its justification.
To sort it based on its score, use `IndictmentScoreTotalComparator`.

Before in `*.java`:
[source, java]
----
Collections.sort(indictmentList);
----

After in `*.java`:
[source, java]
----
Collections.sort(indictmentList, new IndictmentScoreTotalComparator());
----

== From 7.5.0.Final to 7.6.0.Final

[.upgrade-recipe-minor]
=== `PlannerBenchmark`: new method `benchmarkAndShowReportInBrowser()`

If you're running local benchmarks, this new method will save time by opening the report automatically.

Before in `*.java`:
[source, java]
----
plannerBenchmark.benchmark();
// Afterwards manually find the benchmark dir to open the report
----

After in `*.java`:
[source, java]
----
plannerBenchmark.benchmarkAndShowReportInBrowser();
----

[.upgrade-recipe-minor]
=== `ConstraintMatchAwareIncrementalScoreCalculator`: `Indictment.addConstraintMatch()` changed

This only applies if you're extending `ConstraintMatchAwareIncrementalScoreCalculator`
and you do not simply return `null` in `getIndictmentMap()`.

The method `Indictment.addConstraintMatch(ConstraintMatch)` now returns void instead of a boolean.
If the same `ConstraintMatch` is added twice, it now fails fast instead of returning false.
If the same `ConstraintMatch` has the same justification twice,
it must now be added to that justification's `Indictment` only once.

== From 7.6.0.Final to 7.7.0.Final

[.upgrade-recipe-major]
=== Replace `movableEntitySelectionFilter` with `@PlanningPin` when possible

In many cases, the complex use of a `movableEntitySelectionFilter` to pin down planning entities
can by simplified by a `@PlanningPin` annotation on a field or method
that returns true if the entity is immovable.

Before in `*.java`:
[source, java]
----
@PlanningEntity(movableEntitySelectionFilter = MovableLectureSelectionFilter.class)
public class Lecture {
    private boolean pinned;

    public boolean isPinned() {
        return pinned;
    }
}
----

[source, java]
----
public class MovableLectureSelectionFilter implements SelectionFilter<CourseSchedule, Lecture> {

    @Override
    public boolean accept(ScoreDirector<CourseSchedule> scoreDirector, Lecture lecture) {
        return !lecture.isPinned();
    }

}
----

After in `*.java`:
[source, java]
----
@PlanningEntity
public class Lecture {
    private boolean pinned;

    @PlanningPin
    public boolean isPinned() {
        return pinned;
    }
}
----

[.upgrade-recipe-minor]
=== Jackson integration: use `OptaPlannerJacksonModule`

Instead of using `@JsonSerialize` and `@JsonDeserialize` Jackson annotations on every `Score` field,
just register `OptaPlannerJacksonModule` once instead.

Before in `*.java`:

[source, java]
----
    ObjectMapper objectMapper = new ObjectMapper();
----

[source, java]
----
@PlanningSolution
public class MySolution {

    @JsonSerialize(using = ScoreJacksonJsonSerializer.class)
    @JsonDeserialize(using = HardSoftScoreJacksonJsonDeserializer.class)
    private HardSoftScore score;

    ...
}
----

After in `*.java`:

[source, java]
----
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(OptaPlannerJacksonModule.createModule());
----

[source, java]
----
@PlanningSolution
public class MySolution {

    private HardSoftScore score;

    ...
}
----

[.upgrade-recipe-minor]
=== Jackson integration: replace `ScoreJacksonJsonSerializer`

If you do prefer to use `@JsonSerialize` and `@JsonDeserialize` Jackson annotations,
instead of registering `OptaPlannerJacksonModule`,
replace `ScoreJacksonJsonSerializer` with a specific serializer,
such as `HardSoftScoreJacksonJsonSerializer`.
This won't affect the json output.

`ScoreJacksonJsonSerializer` is deprecated.

Before in `*.java`:
[source, java]
----
@JsonSerialize(using = ScoreJacksonJsonSerializer.class)
@JsonDeserialize(using = HardSoftScoreJacksonJsonDeserializer.class)
private HardSoftScore score;
----

After in `*.java`:
[source, java]
----
@JsonSerialize(using = HardSoftScoreJacksonJsonSerializer.class)
@JsonDeserialize(using = HardSoftScoreJacksonJsonDeserializer.class)
private HardSoftScore score;
----

== From 7.7.0.Final to 7.8.0.Final

[.upgrade-recipe-minor]
=== Partitioned Search: `threadFactoryClass` moved

Now that `<solver>` directly supports a `<threadFactoryClass>` element,
the `<threadFactoryClass>` element under `<partitionedSearch>` has been deprecated.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <solver>
    ...
    <partitionedSearch>
      <threadFactoryClass>...MyAppServerThreadFactory</threadFactoryClass>
      ...
    </partitionedSearch>
  </solver>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
  <solver>
    <threadFactoryClass>...MyAppServerThreadFactory</threadFactoryClass>
    ...
    <partitionedSearch>
      ...
    </partitionedSearch>
  </solver>
----

[.upgrade-recipe-minor]
=== `ConstraintMatchTotal` and `Indictment`: `getScoreTotal()` renamed to `getScore()`

The `getScoreTotal()` methods on `ConstraintMatchTotal` and `Indictment`
have been deprecated and replaced by `getScore()`.
Those deprecated methods will be removed in 8.0.

Before in `*.java`:
[source, java]
----
Score score = constraintMatchTotal.getScoreTotal();
----

After in `*.java`:
[source, java]
----
Score score = constraintMatchTotal.getScore();
----

Before in `*.java`:
[source, java]
----
Score score = indictment.getScoreTotal();
----

After in `*.java`:
[source, java]
----
Score score = indictment.getScore();
----

[.upgrade-recipe-minor]
=== `IndictmentScoreTotalComparator` renamed to `IndictmentScoreComparator`

The comparator `IndictmentScoreTotalComparator` has been deprecated
and replaced by `IndictmentScoreComparator`.
The deprecated class will be removed in 8.0.

Before in `*.java`:
[source, java]
----
indictmentList.sort(new IndictmentScoreTotalComparator());
----

After in `*.java`:
[source, java]
----
indictmentList.sort(new IndictmentScoreComparator());
----

== From 7.10.0.Final to 7.11.0.Final

[.upgrade-recipe-major]
=== Chained ChangeMove: cache type `PHASE` no longer supported

To work correctly with multithreaded solving,
`ChainedChangeMove` and `ChainedSwapMove` aren't `PHASE` cacheable any longer.

Before in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
      <changeMoveSelector><!-- On at least 1 chained variable -->
        <cacheType>PHASE</cacheType>
        ...
      </changeMoveSelector>
----

After in `*SolverConfig.xml` and `*BenchmarkConfig.xml`:
[source, xml]
----
      <changeMoveSelector>
        <cacheType>STEP</cacheType>
        ...
      </changeMoveSelector>
----