Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion build/build-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@
<version.org.jspecify>1.0.0</version.org.jspecify>
<version.org.junit.jupiter>6.0.2</version.org.junit.jupiter>
<version.org.openrewrite.recipe>3.23.0</version.org.openrewrite.recipe>
<version.org.springframework.boot>3.5.10</version.org.springframework.boot>
<version.org.springframework.boot>4.0.2</version.org.springframework.boot>
<version.ow2.asm>9.9.1</version.ow2.asm>
<version.com.fasterxml.jackson>2.20.1</version.com.fasterxml.jackson>
<version.tools.jackson>3.0.4</version.tools.jackson>

<!-- ************************************************************************ -->
<!-- Plugins -->
Expand Down Expand Up @@ -102,6 +104,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>tools.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${version.tools.jackson}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Normal dependencies versions-->
<dependency>
<groupId>ch.qos.logback</groupId>
Expand Down
8 changes: 4 additions & 4 deletions persistence/jackson/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@

<artifactId>timefold-solver-jackson</artifactId>

<name>Timefold Solver Jackson</name>
<name>Timefold Solver Jackson 3</name>
<description>
Timefold solves planning problems.
This lightweight, embeddable planning engine implements powerful and scalable algorithms
to optimize business resource scheduling and planning.

This module contains the Jackson integration.
This module contains the Jackson 3 integration.
</description>
<url>https://solver.timefold.ai</url>

Expand Down Expand Up @@ -50,15 +50,15 @@
</dependency>
<!-- XML and JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Testing -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@
import ai.timefold.solver.jackson.preview.api.domain.solution.diff.PlanningSolutionDiffJacksonSerializer;
import ai.timefold.solver.jackson.preview.api.domain.solution.diff.PlanningVariableDiffJacksonSerializer;

import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;

/**
* This class adds all Jackson serializers and deserializers.
Expand All @@ -85,11 +85,11 @@ public class TimefoldJacksonModule extends SimpleModule {
/**
* Jackson modules can be loaded automatically via {@link java.util.ServiceLoader}.
* This will happen if you use {@link JacksonSolutionFileIO}.
* Otherwise, register the module with {@link ObjectMapper#registerModule(Module)}.
* Otherwise, register the module with {@link JsonMapper.Builder#addModule(JacksonModule)}.
*
* @return never null
*/
public static Module createModule() {
public static JacksonModule createModule() {
return new TimefoldJacksonModule();

}
Expand Down Expand Up @@ -135,10 +135,10 @@ public TimefoldJacksonModule() {
addSerializer(ConstraintRef.class, new ConstraintRefJacksonSerializer());
addDeserializer(ConstraintRef.class, new ConstraintRefJacksonDeserializer());
addSerializer(ScoreAnalysis.class, new ScoreAnalysisJacksonSerializer());
var serializer = (JsonSerializer) new RecommendedAssignmentJacksonSerializer<>();
var serializer = (ValueSerializer) new RecommendedAssignmentJacksonSerializer<>();
addSerializer(RecommendedAssignment.class, serializer);
addSerializer(DefaultRecommendedAssignment.class, serializer);
addSerializer(RecommendedFit.class, (JsonSerializer) new RecommendedFitJacksonSerializer<>());
addSerializer(RecommendedFit.class, (ValueSerializer) new RecommendedFitJacksonSerializer<>());

// Constraint weights
addSerializer(ConstraintWeightOverrides.class, new ConstraintWeightOverridesSerializer());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
package ai.timefold.solver.jackson.api.domain.solution;

import java.io.IOException;
import java.util.LinkedHashMap;

import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides;
import ai.timefold.solver.core.api.score.Score;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;

/**
* Extend this to implement {@link ConstraintWeightOverrides} deserialization specific for your domain.
*
* @param <Score_>
*/
public abstract class AbstractConstraintWeightOverridesDeserializer<Score_ extends Score<Score_>>
extends JsonDeserializer<ConstraintWeightOverrides<Score_>> {
extends ValueDeserializer<ConstraintWeightOverrides<Score_>> {

@Override
public final ConstraintWeightOverrides<Score_> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
public final ConstraintWeightOverrides<Score_> deserialize(JsonParser p, DeserializationContext ctxt)
throws JacksonException {
var resultMap = new LinkedHashMap<String, Score_>();
JsonNode node = p.readValueAsTree();
node.fields().forEachRemaining(entry -> {
node.properties().iterator().forEachRemaining(entry -> {
var constraintName = entry.getKey();
var weight = parseScore(entry.getValue().asText());
var weight = parseScore(entry.getValue().asString());
resultMap.put(constraintName, weight);
});
return ConstraintWeightOverrides.of(resultMap);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
package ai.timefold.solver.jackson.api.domain.solution;

import java.io.IOException;
import java.util.Objects;

import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides;
import ai.timefold.solver.core.api.score.Score;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;

public final class ConstraintWeightOverridesSerializer<Score_ extends Score<Score_>>
extends JsonSerializer<ConstraintWeightOverrides<Score_>> {
extends ValueSerializer<ConstraintWeightOverrides<Score_>> {

@Override
public void serialize(ConstraintWeightOverrides<Score_> constraintWeightOverrides, JsonGenerator generator,
SerializerProvider serializerProvider) throws IOException {
SerializationContext serializerProvider) throws JacksonException {
generator.writeStartObject();
for (var constraintName : constraintWeightOverrides.getKnownConstraintNames()) {
var weight = Objects.requireNonNull(constraintWeightOverrides.getConstraintWeight(constraintName));
generator.writeStringField(constraintName, weight.toString());
generator.writeStringProperty(constraintName, weight.toString());
}
generator.writeEndObject();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

import ai.timefold.solver.core.api.score.Score;

import com.fasterxml.jackson.databind.JsonDeserializer;
import tools.jackson.databind.ValueDeserializer;

/**
* Jackson binding support for a {@link Score} type.
* <p>
* For example: use
* {@code @JsonSerialize(using = HardSoftScoreScoreJacksonSerializer.class) @JsonDeserialize(using = HardSoftScoreJacksonDeserializer.class)}
* {@code @JsonSerialize(using = HardSoftScoreJacksonSerializer.class) @JsonDeserialize(using = HardSoftScoreJacksonDeserializer.class)}
* on a {@code HardSoftScore score} field and it will marshalled to JSON as {@code "score":"-999hard/-999soft"}.
*
* @see Score
* @param <Score_> the actual score type
*/
public abstract class AbstractScoreJacksonDeserializer<Score_ extends Score<Score_>>
extends JsonDeserializer<Score_> {
extends ValueDeserializer<Score_> {

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package ai.timefold.solver.jackson.api.score;

import java.io.IOException;

import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.jackson.api.TimefoldJacksonModule;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.BeanProperty;
import tools.jackson.databind.DatabindException;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;

/**
* Jackson binding support for a {@link Score} subtype.
Expand All @@ -26,12 +24,11 @@
* @see Score
* @param <Score_> the actual score type
*/
public abstract class AbstractScoreJacksonSerializer<Score_ extends Score<Score_>> extends JsonSerializer<Score_>
implements ContextualSerializer {
public abstract class AbstractScoreJacksonSerializer<Score_ extends Score<Score_>> extends ValueSerializer<Score_> {

@Override
public JsonSerializer<?> createContextual(SerializerProvider provider, BeanProperty property)
throws JsonMappingException {
public ValueSerializer<?> createContextual(SerializationContext provider, BeanProperty property)
throws DatabindException {
JavaType propertyType = property.getType();
if (Score.class.equals(propertyType.getRawClass())) {
// If the property type is Score (not HardSoftScore for example),
Expand All @@ -43,7 +40,7 @@ public JsonSerializer<?> createContextual(SerializerProvider provider, BeanPrope
}

@Override
public void serialize(Score_ score, JsonGenerator generator, SerializerProvider serializers) throws IOException {
public void serialize(Score_ score, JsonGenerator generator, SerializationContext serializers) throws JacksonException {
generator.writeString(score.toString());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package ai.timefold.solver.jackson.api.score;

import java.io.IOException;

import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.api.score.buildin.bendable.BendableScore;
import ai.timefold.solver.core.api.score.buildin.bendablebigdecimal.BendableBigDecimalScore;
Expand All @@ -17,9 +15,10 @@
import ai.timefold.solver.core.api.score.buildin.simplelong.SimpleLongScore;
import ai.timefold.solver.jackson.api.score.buildin.hardsoft.HardSoftScoreJacksonDeserializer;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.ValueDeserializer;

/**
* Jackson binding support for a {@link Score} type (but not a subtype).
Expand All @@ -33,12 +32,12 @@
* @see Score
* @see PolymorphicScoreJacksonDeserializer
*/
public class PolymorphicScoreJacksonDeserializer extends JsonDeserializer<Score> {
public class PolymorphicScoreJacksonDeserializer extends ValueDeserializer<Score> {

@Override
public Score deserialize(JsonParser parser, DeserializationContext context) throws IOException {
public Score deserialize(JsonParser parser, DeserializationContext context) throws JacksonException {
parser.nextToken();
String scoreClassSimpleName = parser.getCurrentName();
String scoreClassSimpleName = parser.currentName();
parser.nextToken();
String scoreString = parser.getValueAsString();
if (scoreClassSimpleName.equals(SimpleScore.class.getSimpleName())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package ai.timefold.solver.jackson.api.score;

import java.io.IOException;

import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore;
import ai.timefold.solver.jackson.api.score.buildin.hardsoft.HardSoftScoreJacksonSerializer;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;

/**
* Jackson binding support for a {@link Score} type (but not a subtype).
Expand All @@ -22,12 +21,12 @@
* @see Score
* @see PolymorphicScoreJacksonDeserializer
*/
public class PolymorphicScoreJacksonSerializer extends JsonSerializer<Score> {
public class PolymorphicScoreJacksonSerializer extends ValueSerializer<Score> {

@Override
public void serialize(Score score, JsonGenerator generator, SerializerProvider serializers) throws IOException {
public void serialize(Score score, JsonGenerator generator, SerializationContext serializers) throws JacksonException {
generator.writeStartObject();
generator.writeStringField(score.getClass().getSimpleName(), score.toString());
generator.writeStringProperty(score.getClass().getSimpleName(), score.toString());
generator.writeEndObject();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package ai.timefold.solver.jackson.api.score.analysis;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

Expand All @@ -13,41 +12,42 @@
import ai.timefold.solver.core.api.score.stream.ConstraintJustification;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;

/**
* Extend this to implement {@link ScoreAnalysis} deserialization specific for your domain.
*
* @param <Score_>
*/
public abstract class AbstractScoreAnalysisJacksonDeserializer<Score_ extends Score<Score_>>
extends JsonDeserializer<ScoreAnalysis<Score_>> {
extends ValueDeserializer<ScoreAnalysis<Score_>> {

@Override
public final ScoreAnalysis<Score_> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
public final ScoreAnalysis<Score_> deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
JsonNode node = p.readValueAsTree();
var score = parseScore(node.get("score").asText());
var score = parseScore(node.get("score").asString());
var initialized = node.get("initialized").asBoolean();
var constraintAnalysisList = new HashMap<ConstraintRef, ConstraintAnalysis<Score_>>();
for (var constraintNode : node.get("constraints")) {
var constraintPackage = constraintNode.get("package").asText();
var constraintName = constraintNode.get("name").asText();
var constraintPackage = constraintNode.get("package").asString();
var constraintName = constraintNode.get("name").asString();
var constraintRef = ConstraintRef.of(constraintPackage, constraintName);
var constraintWeight = parseScore(constraintNode.get("weight").asText());
var constraintScore = parseScore(constraintNode.get("score").asText());
var constraintWeight = parseScore(constraintNode.get("weight").asString());
var constraintScore = parseScore(constraintNode.get("score").asString());
var matchScoreList = new ArrayList<MatchAnalysis<Score_>>();
var matchesNode = constraintNode.get("matches");
var matchCountNode = constraintNode.get("matchCount");
if (matchesNode == null) {
constraintAnalysisList.put(constraintRef,
new ConstraintAnalysis<>(constraintRef, constraintWeight, constraintScore, null,
matchCountNode == null ? -1 : Integer.parseInt(matchCountNode.asText())));
matchCountNode == null ? -1 : matchCountNode.asInt(-1)));
} else {
for (var matchNode : constraintNode.get("matches")) {
var matchScore = parseScore(matchNode.get("score").asText());
var matchScore = parseScore(matchNode.get("score").asString());
var justificationNode = matchNode.get("justification");
if (justificationNode == null) {
// Not allowed; if matches are present, they must have justifications.
Expand Down
Loading
Loading