Skip to content

Commit 0a2e4ff

Browse files
Christopher-Chianellitriceo
authored andcommitted
fix: ignore duplicate generated member accessors classes
It appears a recent Quarkus update banned duplicate generated classes. Since this depends on the generated class name and not the source class name, it is easier to let the duplicate class be generated and ignore than find all possible spots a duplicate class can be generated and only generate it if it has not been created yet. This also requires less maintenance, since it will also apply to future generated classes.
1 parent c697e85 commit 0a2e4ff

6 files changed

Lines changed: 255 additions & 1 deletion

File tree

quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,15 @@ private GeneratedGizmoClasses generateDomainAccessors(Map<String, SolverConfig>
952952
Set<Class<?>> reflectiveClassSet) {
953953
// Use mvn quarkus:dev -Dquarkus.debug.generated-classes-dir=dump-classes
954954
// to dump generated classes
955-
var classOutput = new GeneratedClassGizmo2Adaptor(generatedClasses, generatedResources, true);
955+
var createdClassSet = new LinkedHashSet<String>();
956+
var classOutput = new GeneratedClassGizmo2Adaptor(createdClass -> {
957+
// It would be more error-prone to find all locations where
958+
// duplicate classes can be made, so instead, allow the duplicate
959+
// classes but do not send them to the downstream producer
960+
if (createdClassSet.add(createdClass.binaryName())) {
961+
generatedClasses.produce(createdClass);
962+
}
963+
}, generatedResources, true);
956964
var beanClassOutput = new GeneratedBeanGizmo2Adaptor(generatedBeans);
957965

958966
var generatedMemberAccessorsClassNameSet = new HashSet<String>();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package ai.timefold.solver.quarkus;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
6+
import java.util.concurrent.ExecutionException;
7+
import java.util.stream.IntStream;
8+
9+
import jakarta.inject.Inject;
10+
11+
import ai.timefold.solver.core.api.solver.SolverManager;
12+
import ai.timefold.solver.quarkus.testdomain.cascade.TestdataQuarkusDuplicateCascadingConstraintProvider;
13+
import ai.timefold.solver.quarkus.testdomain.cascade.TestdataQuarkusDuplicateCascadingEntity;
14+
import ai.timefold.solver.quarkus.testdomain.cascade.TestdataQuarkusDuplicateCascadingSolution;
15+
import ai.timefold.solver.quarkus.testdomain.cascade.TestdataQuarkusDuplicateCascadingValue;
16+
17+
import org.jboss.shrinkwrap.api.ShrinkWrap;
18+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.RegisterExtension;
21+
22+
import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
23+
import io.quarkus.test.QuarkusUnitTest;
24+
25+
class TimefoldProcessorDuplicateCascadingShadowVariableSolveTest {
26+
27+
@RegisterExtension
28+
static final QuarkusUnitTest config = new QuarkusUnitTest()
29+
.overrideConfigKey("quarkus.timefold.solver.termination.best-score-limit", "-3")
30+
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
31+
.addClasses(TestdataQuarkusDuplicateCascadingEntity.class,
32+
TestdataQuarkusDuplicateCascadingValue.class,
33+
TestdataQuarkusDuplicateCascadingSolution.class,
34+
TestdataQuarkusDuplicateCascadingConstraintProvider.class))
35+
.addBuildChainCustomizer(buildChainBuilder -> {
36+
// Needed for unit test to check for duplicate GeneratedClassBuildItem
37+
buildChainBuilder.addFinal(ArtifactResultBuildItem.class);
38+
});
39+
40+
@Inject
41+
SolverManager<TestdataQuarkusDuplicateCascadingSolution> solverManager;
42+
43+
@Test
44+
void solve() throws ExecutionException, InterruptedException {
45+
var problem = new TestdataQuarkusDuplicateCascadingSolution();
46+
problem.setValueList(IntStream.range(1, 5)
47+
.mapToObj(i -> new TestdataQuarkusDuplicateCascadingValue("v%d".formatted(i)))
48+
.toList());
49+
problem.setEntityList(IntStream.range(1, 3)
50+
.mapToObj(i -> new TestdataQuarkusDuplicateCascadingEntity())
51+
.toList());
52+
var solverJob = solverManager.solve(1L, problem);
53+
var solution = solverJob.getFinalBestSolution();
54+
assertNotNull(solution);
55+
assertEquals(-3, solution.getScore().score());
56+
}
57+
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package ai.timefold.solver.quarkus.testdomain.cascade;
2+
3+
import ai.timefold.solver.core.api.score.SimpleScore;
4+
import ai.timefold.solver.core.api.score.stream.Constraint;
5+
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
6+
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
7+
8+
import org.jspecify.annotations.NonNull;
9+
10+
public class TestdataQuarkusDuplicateCascadingConstraintProvider implements ConstraintProvider {
11+
@Override
12+
public Constraint @NonNull [] defineConstraints(@NonNull ConstraintFactory constraintFactory) {
13+
return new Constraint[] {
14+
constraintFactory.forEach(TestdataQuarkusDuplicateCascadingValue.class)
15+
.filter(value -> value.getChainLength() > 2)
16+
.penalize(SimpleScore.ONE)
17+
.asConstraint("length too long"),
18+
constraintFactory.forEach(TestdataQuarkusDuplicateCascadingValue.class)
19+
.filter(value -> value.getChainProduct() < 4)
20+
.penalize(SimpleScore.ONE)
21+
.asConstraint("product too small"),
22+
};
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package ai.timefold.solver.quarkus.testdomain.cascade;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
7+
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
8+
9+
@PlanningEntity
10+
public class TestdataQuarkusDuplicateCascadingEntity {
11+
String id;
12+
13+
@PlanningListVariable
14+
List<TestdataQuarkusDuplicateCascadingValue> valueList;
15+
16+
public TestdataQuarkusDuplicateCascadingEntity() {
17+
valueList = new ArrayList<>();
18+
}
19+
20+
public TestdataQuarkusDuplicateCascadingEntity(String id) {
21+
this.id = id;
22+
valueList = new ArrayList<>();
23+
}
24+
25+
public String getId() {
26+
return id;
27+
}
28+
29+
public void setId(String id) {
30+
this.id = id;
31+
}
32+
33+
public List<TestdataQuarkusDuplicateCascadingValue> getValueList() {
34+
return valueList;
35+
}
36+
37+
public void setValueList(List<TestdataQuarkusDuplicateCascadingValue> valueList) {
38+
this.valueList = valueList;
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package ai.timefold.solver.quarkus.testdomain.cascade;
2+
3+
import java.util.List;
4+
5+
import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty;
6+
import ai.timefold.solver.core.api.domain.solution.PlanningScore;
7+
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
8+
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
9+
import ai.timefold.solver.core.api.score.SimpleScore;
10+
11+
@PlanningSolution
12+
public class TestdataQuarkusDuplicateCascadingSolution {
13+
@PlanningEntityCollectionProperty
14+
List<TestdataQuarkusDuplicateCascadingEntity> entityList;
15+
16+
@PlanningEntityCollectionProperty
17+
@ValueRangeProvider
18+
List<TestdataQuarkusDuplicateCascadingValue> valueList;
19+
@PlanningScore
20+
SimpleScore score;
21+
22+
public TestdataQuarkusDuplicateCascadingSolution() {
23+
24+
}
25+
26+
public TestdataQuarkusDuplicateCascadingSolution(List<TestdataQuarkusDuplicateCascadingEntity> entityList,
27+
List<TestdataQuarkusDuplicateCascadingValue> valueList) {
28+
this.entityList = entityList;
29+
this.valueList = valueList;
30+
}
31+
32+
public List<TestdataQuarkusDuplicateCascadingEntity> getEntityList() {
33+
return entityList;
34+
}
35+
36+
public void setEntityList(List<TestdataQuarkusDuplicateCascadingEntity> entityList) {
37+
this.entityList = entityList;
38+
}
39+
40+
public List<TestdataQuarkusDuplicateCascadingValue> getValueList() {
41+
return valueList;
42+
}
43+
44+
public void setValueList(List<TestdataQuarkusDuplicateCascadingValue> valueList) {
45+
this.valueList = valueList;
46+
}
47+
48+
public SimpleScore getScore() {
49+
return score;
50+
}
51+
52+
public void setScore(SimpleScore score) {
53+
this.score = score;
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package ai.timefold.solver.quarkus.testdomain.cascade;
2+
3+
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
4+
import ai.timefold.solver.core.api.domain.variable.CascadingUpdateShadowVariable;
5+
import ai.timefold.solver.core.api.domain.variable.PreviousElementShadowVariable;
6+
7+
@PlanningEntity
8+
public class TestdataQuarkusDuplicateCascadingValue {
9+
String id;
10+
11+
public TestdataQuarkusDuplicateCascadingValue() {
12+
13+
}
14+
15+
public TestdataQuarkusDuplicateCascadingValue(String id) {
16+
this.id = id;
17+
}
18+
19+
@PreviousElementShadowVariable(sourceVariableName = "valueList")
20+
private TestdataQuarkusDuplicateCascadingValue previousValue;
21+
22+
@CascadingUpdateShadowVariable(targetMethodName = "updateCalculationValue")
23+
private Integer chainLength;
24+
25+
@CascadingUpdateShadowVariable(targetMethodName = "updateCalculationValue")
26+
private Integer chainProduct;
27+
28+
public String getId() {
29+
return id;
30+
}
31+
32+
public void setId(String id) {
33+
this.id = id;
34+
}
35+
36+
public TestdataQuarkusDuplicateCascadingValue getPreviousValue() {
37+
return previousValue;
38+
}
39+
40+
public void setPreviousValue(TestdataQuarkusDuplicateCascadingValue previousValue) {
41+
this.previousValue = previousValue;
42+
}
43+
44+
public Integer getChainLength() {
45+
return chainLength;
46+
}
47+
48+
public void setChainLength(Integer chainLength) {
49+
this.chainLength = chainLength;
50+
}
51+
52+
public Integer getChainProduct() {
53+
return chainProduct;
54+
}
55+
56+
public void setChainProduct(Integer chainProduct) {
57+
this.chainProduct = chainProduct;
58+
}
59+
60+
public void updateCalculationValue() {
61+
if (previousValue == null) {
62+
chainLength = 0;
63+
chainProduct = 1;
64+
} else {
65+
chainLength = previousValue.chainLength + 1;
66+
chainProduct = previousValue.chainProduct * 2;
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)