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
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,15 @@ private GeneratedGizmoClasses generateDomainAccessors(Map<String, SolverConfig>
Set<Class<?>> reflectiveClassSet) {
// Use mvn quarkus:dev -Dquarkus.debug.generated-classes-dir=dump-classes
// to dump generated classes
var classOutput = new GeneratedClassGizmo2Adaptor(generatedClasses, generatedResources, true);
var createdClassSet = new LinkedHashSet<String>();
var classOutput = new GeneratedClassGizmo2Adaptor(createdClass -> {
// It would be more error-prone to find all locations where
// duplicate classes can be made, so instead, allow the duplicate
// classes but do not send them to the downstream producer
if (createdClassSet.add(createdClass.binaryName())) {
generatedClasses.produce(createdClass);
}
}, generatedResources, true);
var beanClassOutput = new GeneratedBeanGizmo2Adaptor(generatedBeans);

var generatedMemberAccessorsClassNameSet = new HashSet<String>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package ai.timefold.solver.quarkus;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.util.concurrent.ExecutionException;
import java.util.stream.IntStream;

import jakarta.inject.Inject;

import ai.timefold.solver.core.api.solver.SolverManager;
import ai.timefold.solver.quarkus.testdomain.cascade.TestdataQuarkusDuplicateCascadingConstraintProvider;
import ai.timefold.solver.quarkus.testdomain.cascade.TestdataQuarkusDuplicateCascadingEntity;
import ai.timefold.solver.quarkus.testdomain.cascade.TestdataQuarkusDuplicateCascadingSolution;
import ai.timefold.solver.quarkus.testdomain.cascade.TestdataQuarkusDuplicateCascadingValue;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
import io.quarkus.test.QuarkusUnitTest;

class TimefoldProcessorDuplicateCascadingShadowVariableSolveTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.overrideConfigKey("quarkus.timefold.solver.termination.best-score-limit", "-3")
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(TestdataQuarkusDuplicateCascadingEntity.class,
TestdataQuarkusDuplicateCascadingValue.class,
TestdataQuarkusDuplicateCascadingSolution.class,
TestdataQuarkusDuplicateCascadingConstraintProvider.class))
.addBuildChainCustomizer(buildChainBuilder -> {
// Needed for unit test to check for duplicate GeneratedClassBuildItem
buildChainBuilder.addFinal(ArtifactResultBuildItem.class);
});

@Inject
SolverManager<TestdataQuarkusDuplicateCascadingSolution> solverManager;

@Test
void solve() throws ExecutionException, InterruptedException {
var problem = new TestdataQuarkusDuplicateCascadingSolution();
problem.setValueList(IntStream.range(1, 5)
.mapToObj(i -> new TestdataQuarkusDuplicateCascadingValue("v%d".formatted(i)))
.toList());
problem.setEntityList(IntStream.range(1, 3)
.mapToObj(i -> new TestdataQuarkusDuplicateCascadingEntity())
.toList());
var solverJob = solverManager.solve(1L, problem);
var solution = solverJob.getFinalBestSolution();
assertNotNull(solution);
assertEquals(-3, solution.getScore().score());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ai.timefold.solver.quarkus.testdomain.cascade;

import ai.timefold.solver.core.api.score.SimpleScore;
import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;

import org.jspecify.annotations.NonNull;

public class TestdataQuarkusDuplicateCascadingConstraintProvider implements ConstraintProvider {
@Override
public Constraint @NonNull [] defineConstraints(@NonNull ConstraintFactory constraintFactory) {
return new Constraint[] {
constraintFactory.forEach(TestdataQuarkusDuplicateCascadingValue.class)
.filter(value -> value.getChainLength() > 2)
.penalize(SimpleScore.ONE)
.asConstraint("length too long"),
constraintFactory.forEach(TestdataQuarkusDuplicateCascadingValue.class)
.filter(value -> value.getChainProduct() < 4)
.penalize(SimpleScore.ONE)
.asConstraint("product too small"),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ai.timefold.solver.quarkus.testdomain.cascade;

import java.util.ArrayList;
import java.util.List;

import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;

@PlanningEntity
public class TestdataQuarkusDuplicateCascadingEntity {
String id;

@PlanningListVariable
List<TestdataQuarkusDuplicateCascadingValue> valueList;

public TestdataQuarkusDuplicateCascadingEntity() {
valueList = new ArrayList<>();
}

public TestdataQuarkusDuplicateCascadingEntity(String id) {
this.id = id;
valueList = new ArrayList<>();
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public List<TestdataQuarkusDuplicateCascadingValue> getValueList() {
return valueList;
}

public void setValueList(List<TestdataQuarkusDuplicateCascadingValue> valueList) {
this.valueList = valueList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package ai.timefold.solver.quarkus.testdomain.cascade;

import java.util.List;

import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty;
import ai.timefold.solver.core.api.domain.solution.PlanningScore;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
import ai.timefold.solver.core.api.score.SimpleScore;

@PlanningSolution
public class TestdataQuarkusDuplicateCascadingSolution {
@PlanningEntityCollectionProperty
List<TestdataQuarkusDuplicateCascadingEntity> entityList;

@PlanningEntityCollectionProperty
@ValueRangeProvider
List<TestdataQuarkusDuplicateCascadingValue> valueList;
@PlanningScore
SimpleScore score;

public TestdataQuarkusDuplicateCascadingSolution() {

}

public TestdataQuarkusDuplicateCascadingSolution(List<TestdataQuarkusDuplicateCascadingEntity> entityList,
List<TestdataQuarkusDuplicateCascadingValue> valueList) {
this.entityList = entityList;
this.valueList = valueList;
}

public List<TestdataQuarkusDuplicateCascadingEntity> getEntityList() {
return entityList;
}

public void setEntityList(List<TestdataQuarkusDuplicateCascadingEntity> entityList) {
this.entityList = entityList;
}

public List<TestdataQuarkusDuplicateCascadingValue> getValueList() {
return valueList;
}

public void setValueList(List<TestdataQuarkusDuplicateCascadingValue> valueList) {
this.valueList = valueList;
}

public SimpleScore getScore() {
return score;
}

public void setScore(SimpleScore score) {
this.score = score;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package ai.timefold.solver.quarkus.testdomain.cascade;

import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.variable.CascadingUpdateShadowVariable;
import ai.timefold.solver.core.api.domain.variable.PreviousElementShadowVariable;

@PlanningEntity
public class TestdataQuarkusDuplicateCascadingValue {
String id;

public TestdataQuarkusDuplicateCascadingValue() {

}

public TestdataQuarkusDuplicateCascadingValue(String id) {
this.id = id;
}

@PreviousElementShadowVariable(sourceVariableName = "valueList")
private TestdataQuarkusDuplicateCascadingValue previousValue;

@CascadingUpdateShadowVariable(targetMethodName = "updateCalculationValue")
private Integer chainLength;

@CascadingUpdateShadowVariable(targetMethodName = "updateCalculationValue")
private Integer chainProduct;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public TestdataQuarkusDuplicateCascadingValue getPreviousValue() {
return previousValue;
}

public void setPreviousValue(TestdataQuarkusDuplicateCascadingValue previousValue) {
this.previousValue = previousValue;
}

public Integer getChainLength() {
return chainLength;
}

public void setChainLength(Integer chainLength) {
this.chainLength = chainLength;
}

public Integer getChainProduct() {
return chainProduct;
}

public void setChainProduct(Integer chainProduct) {
this.chainProduct = chainProduct;
}

public void updateCalculationValue() {
if (previousValue == null) {
chainLength = 0;
chainProduct = 1;
} else {
chainLength = previousValue.chainLength + 1;
chainProduct = previousValue.chainProduct * 2;
}
}
}
Loading