> entityClassList) {
this.entityClassList = entityClassList;
}
- public @Nullable DomainAccessType getDomainAccessType() {
- return domainAccessType;
- }
-
- public void setDomainAccessType(@Nullable DomainAccessType domainAccessType) {
- this.domainAccessType = domainAccessType;
- }
-
public @Nullable Map<@NonNull String, @NonNull MemberAccessor> getGizmoMemberAccessorMap() {
return gizmoMemberAccessorMap;
}
@@ -527,11 +516,6 @@ public void setMonitoringConfig(@Nullable MonitoringConfig monitoringConfig) {
return this;
}
- public @NonNull SolverConfig withDomainAccessType(@NonNull DomainAccessType domainAccessType) {
- this.domainAccessType = domainAccessType;
- return this;
- }
-
public @NonNull SolverConfig
withGizmoMemberAccessorMap(@NonNull Map<@NonNull String, @NonNull MemberAccessor> memberAccessorMap) {
this.gizmoMemberAccessorMap = memberAccessorMap;
@@ -651,10 +635,6 @@ public boolean canTerminate() {
return Objects.requireNonNullElse(environmentMode, EnvironmentMode.PHASE_ASSERT);
}
- public @NonNull DomainAccessType determineDomainAccessType() {
- return Objects.requireNonNullElse(domainAccessType, DomainAccessType.REFLECTION);
- }
-
public @NonNull MonitoringConfig determineMetricConfig() {
return Objects.requireNonNullElse(monitoringConfig,
new MonitoringConfig().withSolverMetricList(Arrays.asList(SolverMetric.SOLVE_DURATION, SolverMetric.ERROR_COUNT,
@@ -698,7 +678,6 @@ public void offerRandomSeedFromSubSingleIndex(long subSingleIndex) {
solutionClass = ConfigUtils.inheritOverwritableProperty(solutionClass, inheritedConfig.getSolutionClass());
entityClassList = ConfigUtils.inheritMergeableListProperty(entityClassList,
inheritedConfig.getEntityClassList());
- domainAccessType = ConfigUtils.inheritOverwritableProperty(domainAccessType, inheritedConfig.getDomainAccessType());
gizmoMemberAccessorMap = ConfigUtils.inheritMergeableMapProperty(
gizmoMemberAccessorMap, inheritedConfig.getGizmoMemberAccessorMap());
gizmoSolutionClonerMap = ConfigUtils.inheritMergeableMapProperty(
diff --git a/core/src/main/java/ai/timefold/solver/core/config/util/ConfigUtils.java b/core/src/main/java/ai/timefold/solver/core/config/util/ConfigUtils.java
index 3575d050d4d..14b32c94bc5 100644
--- a/core/src/main/java/ai/timefold/solver/core/config/util/ConfigUtils.java
+++ b/core/src/main/java/ai/timefold/solver/core/config/util/ConfigUtils.java
@@ -1,6 +1,6 @@
package ai.timefold.solver.core.config.util;
-import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory.MemberAccessorType.FIELD_OR_READ_METHOD;
+import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType.FIELD_OR_READ_METHOD;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
@@ -30,10 +30,10 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.domain.common.PlanningId;
import ai.timefold.solver.core.config.AbstractConfig;
import ai.timefold.solver.core.impl.domain.common.AlphabeticMemberComparator;
+import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/DomainAccessType.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/DomainAccessType.java
similarity index 56%
rename from core/src/main/java/ai/timefold/solver/core/api/domain/common/DomainAccessType.java
rename to core/src/main/java/ai/timefold/solver/core/impl/domain/common/DomainAccessType.java
index 6de72354f6a..c2827850bbb 100644
--- a/core/src/main/java/ai/timefold/solver/core/api/domain/common/DomainAccessType.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/DomainAccessType.java
@@ -1,4 +1,4 @@
-package ai.timefold.solver.core.api.domain.common;
+package ai.timefold.solver.core.impl.domain.common;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
@@ -7,25 +7,26 @@
* are accessed.
*/
public enum DomainAccessType {
+ /**
+ * Determine what domain access type to use automatically.
+ *
+ * This is the default.
+ */
+ AUTO,
+
/**
* Use reflection to read/write members (fields and methods) of the domain.
*
* When used in a modulepath, the module must be open.
* When used in GraalVM, the domain must be open for reflection.
- *
- * This is the default, except with timefold-solver-quarkus.
*/
- REFLECTION,
+ FORCE_REFLECTION,
+
/**
* Use Gizmo generated bytecode to access members (fields and methods) to avoid reflection
* for additional performance.
*
- * With timefold-solver-quarkus, this bytecode is generated at build time
- * and it supports planning annotations on non-public members too.
- *
- * Without timefold-solver-quarkus, this bytecode is generated at bootstrap runtime
- * and you must add Gizmo in your classpath or modulepath
- * and use planning annotations on public members only.
+ * This is the default when the application is run inside a JVM and not a native image.
*/
- GIZMO
+ FORCE_GIZMO
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorFactory.java
index f5869249eb1..f916cdf6fba 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorFactory.java
@@ -10,15 +10,22 @@
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.solver.SolverFactory;
+import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.AccessorInfo;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.GizmoClassLoader;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.GizmoMemberAccessorFactory;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@NullMarked
public final class MemberAccessorFactory {
+ static final Logger LOGGER = LoggerFactory.getLogger(MemberAccessorFactory.class);
// exists only so that the various member accessors can share the same text in their exception messages
static final String CLASSLOADER_NUDGE_MESSAGE =
"Maybe add getClass().getClassLoader() as a parameter to the %s.create...() method call."
@@ -30,10 +37,10 @@ public final class MemberAccessorFactory {
* @param member never null, method or field to access
* @param memberAccessorType never null
* @param domainAccessType never null
- * @param classLoader null or {@link GizmoClassLoader} if domainAccessType is {@link DomainAccessType#GIZMO}.
+ * @param classLoader null or {@link GizmoClassLoader} if domainAccessType is {@link DomainAccessType#FORCE_GIZMO}.
* @return never null, new instance of the member accessor
*/
- public static MemberAccessor buildMemberAccessor(Member member, MemberAccessorType memberAccessorType,
+ private static MemberAccessor buildMemberAccessor(Member member, MemberAccessorType memberAccessorType,
DomainAccessType domainAccessType, ClassLoader classLoader) {
return buildMemberAccessor(member, memberAccessorType, null, domainAccessType, classLoader);
}
@@ -45,28 +52,31 @@ public static MemberAccessor buildMemberAccessor(Member member, MemberAccessorTy
* @param memberAccessorType never null
* @param annotationClass the annotation the member was annotated with (used for error reporting)
* @param domainAccessType never null
- * @param classLoader null or {@link GizmoClassLoader} if domainAccessType is {@link DomainAccessType#GIZMO}.
+ * @param classLoader null or {@link GizmoClassLoader} if domainAccessType is {@link DomainAccessType#FORCE_GIZMO}.
* @return never null, new instance of the member accessor
*/
- public static MemberAccessor buildMemberAccessor(Member member, MemberAccessorType memberAccessorType,
- Class extends Annotation> annotationClass, DomainAccessType domainAccessType, ClassLoader classLoader) {
+ static MemberAccessor buildMemberAccessor(Member member, MemberAccessorType memberAccessorType,
+ @Nullable Class extends Annotation> annotationClass, DomainAccessType domainAccessType, ClassLoader classLoader) {
+ MemberAccessorValidator.verifyIsValidMember(annotationClass, member, memberAccessorType);
return switch (domainAccessType) {
- case GIZMO -> GizmoMemberAccessorFactory.buildGizmoMemberAccessor(member, annotationClass,
- AccessorInfo.of(memberAccessorType != MemberAccessorType.VOID_METHOD,
- memberAccessorType == MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER),
+ case AUTO -> throw new IllegalStateException(
+ "Impossible state: called with %s (AUTO) instead of a resolved domain access type"
+ .formatted(DomainAccessType.class.getSimpleName()));
+ case FORCE_GIZMO -> GizmoMemberAccessorFactory.buildGizmoMemberAccessor(member, annotationClass,
+ AccessorInfo.of(memberAccessorType),
(GizmoClassLoader) Objects.requireNonNull(classLoader));
- case REFLECTION -> buildReflectiveMemberAccessor(member, memberAccessorType, annotationClass);
+ case FORCE_REFLECTION -> buildReflectiveMemberAccessor(member, memberAccessorType, annotationClass);
};
}
private static MemberAccessor buildReflectiveMemberAccessor(Member member, MemberAccessorType memberAccessorType,
- Class extends Annotation> annotationClass) {
+ @Nullable Class extends Annotation> annotationClass) {
return buildReflectiveMemberAccessor(member, memberAccessorType, annotationClass,
(AnnotatedElement) member);
}
private static MemberAccessor buildReflectiveMemberAccessor(Member member, MemberAccessorType memberAccessorType,
- Class extends Annotation> annotationClass, AnnotatedElement annotatedElement) {
+ @Nullable Class extends Annotation> annotationClass, AnnotatedElement annotatedElement) {
var messagePrefix = (annotationClass == null) ? "The" : "The @%s annotated".formatted(annotationClass.getSimpleName());
if (member instanceof Field field) {
var getter = ReflectionHelper.getGetterMethod(field.getDeclaringClass(), field.getName());
@@ -126,7 +136,7 @@ private static MemberAccessor buildReflectiveMemberAccessor(Member member, Membe
memberAccessor = new ReflectionBeanPropertyMemberAccessor(method, annotatedElement, getterOnly);
break;
case VOID_METHOD:
- memberAccessor = new ReflectionMethodMemberAccessor(method, false, false);
+ memberAccessor = new ReflectionMethodMemberAccessor(method);
break;
default:
throw new IllegalStateException("The memberAccessorType (%s) is not implemented."
@@ -154,6 +164,7 @@ private static MemberAccessor buildReflectiveMemberAccessor(Member member, Membe
private final Map memberAccessorCache;
private final GizmoClassLoader gizmoClassLoader = new GizmoClassLoader();
+ private final boolean isGizmoSupported;
public MemberAccessorFactory() {
this(null);
@@ -162,12 +173,19 @@ public MemberAccessorFactory() {
/**
* Prefills the member accessor cache.
*
- * @param memberAccessorMap key is the fully qualified member name
+ * @param memberAccessorMap key is the fully qualified member name, value is a pregenerated {@link MemberAccessor}.
+ * Used by Quarkus since the {@link MemberAccessor} are generated at build time.
+ * If null, it is treated as an empty map.
*/
- public MemberAccessorFactory(Map memberAccessorMap) {
+ public MemberAccessorFactory(@Nullable Map memberAccessorMap) {
// The MemberAccessorFactory may be accessed, and this cache both read and updated, by multiple threads.
this.memberAccessorCache =
memberAccessorMap == null ? new ConcurrentHashMap<>() : new ConcurrentHashMap<>(memberAccessorMap);
+ // If the memberAccessorMap is not empty, we are in Quarkus using pregenerated member accessors
+ this.isGizmoSupported =
+ (memberAccessorMap != null && !memberAccessorMap.isEmpty()) || gizmoClassLoader.isGizmoSupported();
+ LOGGER.trace("Using domain access type {} for member accessors.",
+ isGizmoSupported ? DomainAccessType.FORCE_GIZMO : DomainAccessType.FORCE_REFLECTION);
}
/**
@@ -180,10 +198,16 @@ public MemberAccessorFactory(Map memberAccessorMap) {
* @return never null, new {@link MemberAccessor} instance unless already found in memberAccessorMap
*/
public MemberAccessor buildAndCacheMemberAccessor(Member member, MemberAccessorType memberAccessorType,
- Class extends Annotation> annotationClass, DomainAccessType domainAccessType) {
+ @Nullable Class extends Annotation> annotationClass, DomainAccessType domainAccessType) {
String generatedClassName = GizmoMemberAccessorFactory.getGeneratedClassName(member);
+ if (domainAccessType == DomainAccessType.AUTO) {
+ domainAccessType = isGizmoSupported ? DomainAccessType.FORCE_GIZMO : DomainAccessType.FORCE_REFLECTION;
+ }
+
+ var finalDomainAccessType = domainAccessType;
return memberAccessorCache.computeIfAbsent(generatedClassName,
- k -> MemberAccessorFactory.buildMemberAccessor(member, memberAccessorType, annotationClass, domainAccessType,
+ k -> MemberAccessorFactory.buildMemberAccessor(member, memberAccessorType, annotationClass,
+ finalDomainAccessType,
gizmoClassLoader));
}
@@ -198,33 +222,18 @@ public MemberAccessor buildAndCacheMemberAccessor(Member member, MemberAccessorT
public MemberAccessor buildAndCacheMemberAccessor(Member member, MemberAccessorType memberAccessorType,
DomainAccessType domainAccessType) {
String generatedClassName = GizmoMemberAccessorFactory.getGeneratedClassName(member);
+ if (domainAccessType == DomainAccessType.AUTO) {
+ domainAccessType = isGizmoSupported ? DomainAccessType.FORCE_GIZMO : DomainAccessType.FORCE_REFLECTION;
+ }
+
+ var finalDomainAccessType = domainAccessType;
return memberAccessorCache.computeIfAbsent(generatedClassName,
- k -> MemberAccessorFactory.buildMemberAccessor(member, memberAccessorType, domainAccessType, gizmoClassLoader));
+ k -> MemberAccessorFactory.buildMemberAccessor(member, memberAccessorType, finalDomainAccessType,
+ gizmoClassLoader));
}
public GizmoClassLoader getGizmoClassLoader() {
return gizmoClassLoader;
}
- public enum MemberAccessorType {
- FIELD_OR_READ_METHOD,
- FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
- FIELD_OR_GETTER_METHOD,
- FIELD_OR_GETTER_METHOD_WITH_SETTER(true),
- VOID_METHOD;
-
- private final boolean setterRequired;
-
- MemberAccessorType() {
- setterRequired = false;
- }
-
- MemberAccessorType(boolean setterRequired) {
- this.setterRequired = setterRequired;
- }
-
- public boolean isSetterRequired() {
- return setterRequired;
- }
- }
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorType.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorType.java
new file mode 100644
index 00000000000..f5d5324001a
--- /dev/null
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorType.java
@@ -0,0 +1,23 @@
+package ai.timefold.solver.core.impl.domain.common.accessor;
+
+public enum MemberAccessorType {
+ FIELD_OR_READ_METHOD,
+ FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ FIELD_OR_GETTER_METHOD,
+ FIELD_OR_GETTER_METHOD_WITH_SETTER(true),
+ VOID_METHOD;
+
+ private final boolean setterRequired;
+
+ MemberAccessorType() {
+ setterRequired = false;
+ }
+
+ MemberAccessorType(boolean setterRequired) {
+ this.setterRequired = setterRequired;
+ }
+
+ public boolean isSetterRequired() {
+ return setterRequired;
+ }
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorValidator.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorValidator.java
new file mode 100644
index 00000000000..796c2d85640
--- /dev/null
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorValidator.java
@@ -0,0 +1,227 @@
+package ai.timefold.solver.core.impl.domain.common.accessor;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
+
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+@NullMarked
+final class MemberAccessorValidator {
+ private MemberAccessorValidator() {
+ }
+
+ public static void verifyIsValidMember(@Nullable Class> annotationClass, Member member,
+ MemberAccessorType memberAccessorType) {
+ var memberType = (member instanceof Field) ? "field" : "method";
+ var messagePrefix = (annotationClass == null)
+ ? "The %s (%s) in class (%s)".formatted(memberType, member.getName(),
+ member.getDeclaringClass().getCanonicalName())
+ : "The @%s annotated %s (%s) in class (%s)".formatted(annotationClass.getSimpleName(), memberType,
+ member.getName(),
+ member.getDeclaringClass().getCanonicalName());
+
+ verifyDeclaringClassIsAccessible(member, messagePrefix);
+ switch (memberAccessorType) {
+ case FIELD_OR_READ_METHOD -> verifyIsPublicFieldOrHasReadMethod(member, messagePrefix, false);
+ case FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER ->
+ verifyIsPublicFieldOrHasReadMethod(member, messagePrefix, true);
+ case FIELD_OR_GETTER_METHOD -> verifyFieldOrGetter(member, messagePrefix);
+ case VOID_METHOD -> verifyIsVoidMethod(member, messagePrefix);
+ case FIELD_OR_GETTER_METHOD_WITH_SETTER -> {
+ verifyFieldOrGetter(member, messagePrefix);
+ verifyIsPublicFieldOrHasPublicSetter(member, messagePrefix);
+ }
+ }
+ }
+
+ private static void verifyDeclaringClassIsAccessible(Member member, String messagePrefix) {
+ var declaringClass = member.getDeclaringClass();
+ if (!Modifier.isPublic(declaringClass.getModifiers())) {
+ throw new IllegalArgumentException(
+ "%s is not accessible because its declaring class (%s) is not public. Maybe make the class (%s) public?"
+ .formatted(messagePrefix, declaringClass.getCanonicalName(), declaringClass.getSimpleName()));
+ }
+ }
+
+ private static void verifyIsVoidMethod(Member member, String messagePrefix) {
+ if (!(member instanceof Method method)) {
+ throw new IllegalArgumentException(
+ "%s must be a void method, but is a field instead.".formatted(messagePrefix));
+ }
+ if (!method.getReturnType().equals(void.class)) {
+ throw new IllegalArgumentException("%s must be a void method, but it returns (%s).".formatted(messagePrefix,
+ method.getReturnType().getCanonicalName()));
+ }
+ if (!Modifier.isPublic(method.getModifiers())) {
+ throw new IllegalArgumentException(
+ "%s is a void method, but it is not public. Maybe make the method (%s) public?"
+ .formatted(messagePrefix, method.getName()));
+ }
+ }
+
+ private static void verifyGetterName(Method method, String messagePrefix) {
+ if (!method.getName().startsWith("get") && !(method.getReturnType().equals(boolean.class) && method.getName()
+ .startsWith("is"))) {
+ throw new IllegalArgumentException("""
+ %s is suppose to be a public getter method, but its name (%s) does not start with "get"%s.
+ Maybe add a "get" prefix to the method?"""
+ .formatted(messagePrefix, method.getName(),
+ method.getReturnType().equals(boolean.class) ? " or \"is\"" : ""));
+ }
+ }
+
+ private static void verifyFieldOrGetter(Member member, String messagePrefix) {
+ switch (member) {
+ case Field field -> verifyIsPublicFieldOrHasPublicGetter(field, messagePrefix);
+ case Method method -> {
+ verifyGetterName(method, messagePrefix);
+ if (!Modifier.isPublic(method.getModifiers())) {
+ throw new IllegalArgumentException(
+ "%s is a getter method, but it is not public. Maybe make the method (%s) public?"
+ .formatted(messagePrefix, method.getName()));
+ }
+ if (method.getReturnType().equals(void.class)) {
+ throw new IllegalArgumentException(
+ "%s has a public getter method that returns void. Maybe make the method (%s) return a value instead?"
+ .formatted(messagePrefix, method.getName()));
+ }
+ }
+ default -> throw new IllegalArgumentException("Unhandled member type (%s)."
+ .formatted(member.getClass().getCanonicalName()));
+ }
+ }
+
+ private static void verifyIsPublicFieldOrHasPublicGetter(Field field, String messagePrefix) {
+ var getterMethod = ReflectionHelper.getGetterMethod(field.getDeclaringClass(), field.getName());
+ if (getterMethod == null) {
+ if (!Modifier.isPublic(field.getModifiers())) {
+ throw new IllegalArgumentException(
+ "%s is not public and does not have a public getter method. Maybe add a public getter method?"
+ .formatted(messagePrefix));
+ }
+ } else {
+ if (!Modifier.isPublic(getterMethod.getModifiers())) {
+ throw new IllegalArgumentException(
+ "%s has a non-public getter method. Maybe make the method (%s) public?"
+ .formatted(messagePrefix, getterMethod.getName()));
+ }
+ if (getterMethod.getReturnType().equals(void.class)) {
+ throw new IllegalArgumentException(
+ "%s has a public getter method that returns void. Maybe make the method (%s) return (%s) instead?"
+ .formatted(messagePrefix, getterMethod.getName(), field.getType().getCanonicalName()));
+ }
+ }
+ }
+
+ private static void verifyIsPublicFieldOrHasReadMethod(Member member, String messagePrefix,
+ boolean acceptOptionalParameter) {
+ switch (member) {
+ case Field field -> verifyIsPublicFieldOrHasPublicGetter(field, messagePrefix);
+ case Method method -> {
+ if (!Modifier.isPublic(method.getModifiers())) {
+ throw new IllegalArgumentException(
+ "%s is a read method, but it is not public. Maybe make the method (%s) public?"
+ .formatted(messagePrefix, method.getName()));
+ }
+ if (method.getReturnType().equals(void.class)) {
+ throw new IllegalArgumentException(
+ "%s is a public read method, but it returns void. Maybe make the method (%s) return a value instead?"
+ .formatted(messagePrefix, method.getName()));
+ }
+
+ if (acceptOptionalParameter) {
+ if (method.getParameterCount() > 1) {
+ throw new IllegalArgumentException("""
+ %s is a public read method, but takes (%d) parameters instead of zero or one.
+ Maybe make the method (%s) take zero or one parameter(s)?"""
+ .formatted(messagePrefix, method.getParameterCount(), method.getName()));
+ }
+ } else if (method.getParameterCount() != 0) {
+ throw new IllegalArgumentException("""
+ %s is a public read method, but takes (%d) parameters instead of none.
+ Maybe make the method (%s) take no parameters?"""
+ .formatted(messagePrefix, method.getParameterCount(), method.getName()));
+ }
+ }
+ default -> throw new IllegalArgumentException("Unhandled member type (%s)."
+ .formatted(member.getClass().getCanonicalName()));
+ }
+ }
+
+ private static void verifyIsPublicFieldOrHasPublicSetter(Member member, String messagePrefix) {
+ switch (member) {
+ case Field field -> {
+ var getterMethod = ReflectionHelper.getGetterMethod(field.getDeclaringClass(), field.getName());
+ var setterMethod = ReflectionHelper.getSetterMethod(field.getDeclaringClass(), field.getName());
+ if (setterMethod == null) {
+ if (!Modifier.isPublic(field.getModifiers())) {
+ throw new IllegalArgumentException(
+ "%s does not have a setter method and is not public. Maybe add a public setter method?"
+ .formatted(messagePrefix));
+ }
+ } else {
+ if (getterMethod == null) {
+ throw new IllegalArgumentException(
+ "%s has a setter method (%s) without a getter method. Maybe add a public getter method?"
+ .formatted(member, setterMethod.getName()));
+ }
+ verifyGetterSetterProperties(getterMethod, setterMethod, messagePrefix);
+ }
+ }
+ case Method getterMethod -> {
+ verifyGetterName(getterMethod, messagePrefix);
+ var memberName = ReflectionHelper.getGetterPropertyName(getterMethod);
+ var setterMethod = ReflectionHelper.getSetterMethod(getterMethod.getDeclaringClass(),
+ memberName);
+ if (setterMethod == null) {
+ throw new IllegalArgumentException("""
+ %s requires both a public getter and a public setter but only have a public getter.
+ Maybe add a public setter for the member (%s)?"""
+ .formatted(messagePrefix, memberName));
+ }
+ verifyGetterSetterProperties(getterMethod, setterMethod, messagePrefix);
+ }
+ default -> throw new IllegalArgumentException("Unhandled member type (%s)."
+ .formatted(member.getClass().getCanonicalName()));
+ }
+ }
+
+ private static void verifyGetterSetterProperties(Method getterMethod, Method setterMethod, String messagePrefix) {
+ if (!Modifier.isPublic(getterMethod.getModifiers())) {
+ throw new IllegalArgumentException(
+ "%s has a non-public getter method. Maybe make the method (%s) public?"
+ .formatted(messagePrefix, getterMethod.getName()));
+ }
+ if (!Modifier.isPublic(setterMethod.getModifiers())) {
+ throw new IllegalArgumentException(
+ "%s has a non-public setter method. Maybe make the method (%s) public?"
+ .formatted(messagePrefix, setterMethod.getName()));
+ }
+ if (setterMethod.getParameterCount() != 1) {
+ throw new IllegalArgumentException("""
+ %s has a public setter method that takes (%d) parameters instead of one.
+ Maybe make the method (%s) take exactly one parameter?"""
+ .formatted(messagePrefix, setterMethod.getParameterCount(),
+ setterMethod.getName()));
+ }
+ if (!setterMethod.getParameterTypes()[0].isAssignableFrom(getterMethod.getReturnType())) {
+ throw new IllegalArgumentException(
+ """
+ %s has a public setter method but its parameter type (%s) is not assignable from the getter return type (%s).
+ Maybe make the public setter (%s) accept (%s)?"""
+ .formatted(messagePrefix, setterMethod.getParameterTypes()[0].getCanonicalName(),
+ getterMethod.getReturnType().getCanonicalName(), setterMethod.getName(),
+ getterMethod.getReturnType().getCanonicalName()));
+ }
+ if (!setterMethod.getReturnType().equals(void.class)) {
+ throw new IllegalArgumentException(
+ "%s has a public getter method that returns void. Maybe make the method (%s) return (%s) instead?"
+ .formatted(messagePrefix, setterMethod.getName(), getterMethod.getReturnType().getCanonicalName()));
+ }
+ }
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionBeanPropertyMemberAccessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionBeanPropertyMemberAccessor.java
index 99a9ffbd7fc..8ef1451d988 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionBeanPropertyMemberAccessor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionBeanPropertyMemberAccessor.java
@@ -5,10 +5,8 @@
import java.lang.invoke.MethodHandles;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Objects;
-import java.util.function.IntPredicate;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
@@ -34,7 +32,7 @@ public ReflectionBeanPropertyMemberAccessor(Method getterMethod, AnnotatedElemen
this.annotatedElement = annotatedElement;
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
- getterMethod.setAccessible(true);
+ this.getterMethod.setAccessible(true);
this.getherMethodHandle = lookup.unreflect(getterMethod)
.asFixedArity();
} catch (IllegalAccessException e) {
@@ -44,10 +42,6 @@ public ReflectionBeanPropertyMemberAccessor(Method getterMethod, AnnotatedElemen
.formatted(getterMethod, MemberAccessorFactory.CLASSLOADER_NUDGE_MESSAGE), e);
}
Class> declaringClass = getterMethod.getDeclaringClass();
- if (!ReflectionHelper.isGetterMethod(getterMethod)) {
- throw new IllegalArgumentException("The getterMethod (%s) is not a valid getter."
- .formatted(getterMethod));
- }
propertyType = getterMethod.getReturnType();
propertyName = ReflectionHelper.getGetterPropertyName(getterMethod);
if (getterOnly) {
@@ -55,25 +49,8 @@ public ReflectionBeanPropertyMemberAccessor(Method getterMethod, AnnotatedElemen
setterMethodHandle = null;
} else {
setterMethod = ReflectionHelper.getDeclaredSetterMethod(declaringClass, getterMethod.getReturnType(), propertyName);
- if (setterMethod == null) {
- throw new IllegalArgumentException("The getterMethod (%s) does not have a matching setterMethod on class (%s)."
- .formatted(getterMethod.getName(), declaringClass.getCanonicalName()));
- }
- var getterAccess = AccessModifier.forMethod(getterMethod);
- var setterAccess = AccessModifier.forMethod(setterMethod);
- if (getterAccess != AccessModifier.PUBLIC) {
- throw new IllegalArgumentException(
- "The getterMethod (%s) on class (%s) is not public, having access modifier (%s) instead."
- .formatted(getterMethod.getName(), declaringClass.getCanonicalName(), getterAccess));
- }
- if (getterAccess != setterAccess) {
- throw new IllegalArgumentException(
- "The getterMethod (%s) has access modifier (%s) which does not match the setterMethod (%s) access modifier (%s) on class (%s)."
- .formatted(getterMethod.getName(), getterAccess, setterMethod.getName(), setterAccess,
- declaringClass.getCanonicalName()));
- }
try {
- setterMethod.setAccessible(true);
+ this.setterMethod.setAccessible(true);
this.setterMethodHandle = lookup.unreflect(setterMethod)
.asFixedArity();
} catch (IllegalAccessException e) {
@@ -85,36 +62,6 @@ public ReflectionBeanPropertyMemberAccessor(Method getterMethod, AnnotatedElemen
}
}
- private enum AccessModifier {
- PUBLIC("public", Modifier::isPublic),
- PROTECTED("protected", Modifier::isProtected),
- PACKAGE_PRIVATE("package-private", modifier -> false),
- PRIVATE("private", Modifier::isPrivate);
-
- final String name;
- final IntPredicate predicate;
-
- AccessModifier(String name, IntPredicate predicate) {
- this.name = name;
- this.predicate = predicate;
- }
-
- public static AccessModifier forMethod(Method method) {
- var modifiers = method.getModifiers();
- for (var accessModifier : AccessModifier.values()) {
- if (accessModifier.predicate.test(modifiers)) {
- return accessModifier;
- }
- }
- return PACKAGE_PRIVATE;
- }
-
- @Override
- public String toString() {
- return name;
- }
- }
-
@Override
public Class> getDeclaringClass() {
return getterMethod.getDeclaringClass();
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodExtendedMemberAccessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodExtendedMemberAccessor.java
index 76d7b46af3a..9f4e8ac0862 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodExtendedMemberAccessor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodExtendedMemberAccessor.java
@@ -9,11 +9,7 @@ public final class ReflectionMethodExtendedMemberAccessor extends ReflectionMeth
private final Type getterMethodParameterType;
public ReflectionMethodExtendedMemberAccessor(Method readMethod) {
- this(readMethod, true);
- }
-
- public ReflectionMethodExtendedMemberAccessor(Method readMethod, boolean returnTypeRequired) {
- super(readMethod, returnTypeRequired, true);
+ super(readMethod);
this.getterMethodParameterType = readMethod.getGenericParameterTypes()[0];
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodMemberAccessor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodMemberAccessor.java
index f35abe5d38c..5117f0b6870 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodMemberAccessor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodMemberAccessor.java
@@ -5,7 +5,6 @@
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
-import java.util.Arrays;
/**
* A {@link MemberAccessor} based on a single read {@link Method}.
@@ -20,10 +19,6 @@ public sealed class ReflectionMethodMemberAccessor extends AbstractMemberAccesso
private final MethodHandle methodHandle;
public ReflectionMethodMemberAccessor(Method readMethod) {
- this(readMethod, true, false);
- }
-
- public ReflectionMethodMemberAccessor(Method readMethod, boolean returnTypeRequired, boolean readMethodWithParameter) {
this.readMethod = readMethod;
this.returnType = readMethod.getReturnType();
this.methodName = readMethod.getName();
@@ -37,18 +32,6 @@ public ReflectionMethodMemberAccessor(Method readMethod, boolean returnTypeRequi
%s
""".formatted(readMethod, MemberAccessorFactory.CLASSLOADER_NUDGE_MESSAGE), e);
}
- if (!readMethodWithParameter && readMethod.getParameterCount() != 0) {
- throw new IllegalArgumentException("The readMethod (%s) must not have any parameters (%s).".formatted(readMethod,
- Arrays.toString(readMethod.getParameterTypes())));
- }
- if (readMethodWithParameter && readMethod.getParameterCount() > 1) {
- throw new IllegalArgumentException("The readMethod (%s) must have only one parameter (%s).".formatted(readMethod,
- Arrays.toString(readMethod.getParameterTypes())));
- }
- if (returnTypeRequired && readMethod.getReturnType() == void.class) {
- throw new IllegalArgumentException(
- "The readMethod (%s) must have a return type (%s).".formatted(readMethod, readMethod.getReturnType()));
- }
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/AccessorInfo.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/AccessorInfo.java
index ac86cdadf07..347a7d46003 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/AccessorInfo.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/AccessorInfo.java
@@ -1,22 +1,27 @@
package ai.timefold.solver.core.impl.domain.common.accessor.gizmo;
+import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType;
+
/**
* Additional information for the GIZMO accessor generation.
*
* @param returnTypeRequired a flag that indicates if the return type is required or optional
* @param readMethodWithParameter a flag that allows the read method to accept an argument
*/
-public record AccessorInfo(boolean returnTypeRequired, boolean readMethodWithParameter) {
+public record AccessorInfo(MemberAccessorType memberAccessorType, boolean returnTypeRequired,
+ boolean readMethodWithParameter) {
public static AccessorInfo withReturnValueAndNoArguments() {
- return new AccessorInfo(true, false);
+ return new AccessorInfo(MemberAccessorType.FIELD_OR_READ_METHOD, true, false);
}
public static AccessorInfo withReturnValueAndArguments() {
- return new AccessorInfo(true, true);
+ return new AccessorInfo(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER, true,
+ true);
}
- public static AccessorInfo of(boolean returnTypeRequired, boolean readMethodWithParameter) {
- return new AccessorInfo(returnTypeRequired, readMethodWithParameter);
+ public static AccessorInfo of(MemberAccessorType memberAccessorType) {
+ return new AccessorInfo(memberAccessorType, memberAccessorType != MemberAccessorType.VOID_METHOD,
+ memberAccessorType == MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER);
}
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoClassLoader.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoClassLoader.java
index 6ab553277ea..06ddab357a8 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoClassLoader.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoClassLoader.java
@@ -1,16 +1,28 @@
package ai.timefold.solver.core.impl.domain.common.accessor.gizmo;
+import java.lang.constant.ClassDesc;
+import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+import io.quarkus.gizmo2.Gizmo;
+import io.quarkus.gizmo2.desc.ConstructorDesc;
/**
* Loads a class if we have the Gizmo-generated bytecode for it,
* otherwise uses the current {@link Thread}'s context {@link ClassLoader}.
* This implementation is thread-safe.
*/
+@NullMarked
public final class GizmoClassLoader extends ClassLoader {
private final Map classNameToBytecodeMap;
+ @Nullable
+ private GizmoSupportStatus gizmoSupportStatus;
public GizmoClassLoader() {
this(new HashMap<>());
@@ -24,6 +36,7 @@ public GizmoClassLoader(Map classNameToBytecodeMap) {
*/
super(GizmoClassLoader.class.getClassLoader());
this.classNameToBytecodeMap = classNameToBytecodeMap;
+ this.gizmoSupportStatus = null;
}
@Override
@@ -33,7 +46,7 @@ public String getName() {
@Override
public Class> findClass(String name) throws ClassNotFoundException {
- byte[] byteCode = getBytecodeFor(name);
+ var byteCode = getBytecodeFor(name);
if (byteCode == null) { // Not a Gizmo generated class; load from context class loader.
return Thread.currentThread().getContextClassLoader().loadClass(name);
} else { // Gizmo generated class.
@@ -41,7 +54,7 @@ public Class> findClass(String name) throws ClassNotFoundException {
}
}
- public synchronized byte[] getBytecodeFor(String className) {
+ public synchronized byte @Nullable [] getBytecodeFor(String className) {
return classNameToBytecodeMap.get(className);
}
@@ -53,4 +66,43 @@ public synchronized void storeBytecode(String className, byte[] bytecode) {
classNameToBytecodeMap.put(className, bytecode);
}
+ public boolean isGizmoSupported() {
+ if (gizmoSupportStatus != null) {
+ return gizmoSupportStatus == GizmoSupportStatus.SUPPORTED;
+ }
+ var classPackage = GizmoClassLoader.class.getPackage().getName();
+ var bytecodeHolder = new AtomicReference();
+ var gizmo = Gizmo.create((className, bytecode) -> bytecodeHolder.set(bytecode));
+
+ var classDesc = ClassDesc.of("%s.GizmoSupportCanary".formatted(classPackage));
+ gizmo.class_(classDesc,
+ classCreator -> classCreator.constructor(ConstructorDesc.of(classDesc), constructorCreator -> {
+ constructorCreator.public_();
+ var this_ = constructorCreator.this_();
+ constructorCreator.body(constructor -> {
+ constructor.invokeSpecial(ConstructorDesc.of(Object.class), this_);
+ constructor.return_();
+ });
+ }));
+ try {
+ var generatedClass = MethodHandles.lookup().defineHiddenClass(bytecodeHolder.get(), true).lookupClass();
+ var instance = generatedClass.getConstructor().newInstance();
+ if (instance == null) {
+ // Should be impossible, but a native image might decide to optimize out
+ // instance if it is unused
+ gizmoSupportStatus = GizmoSupportStatus.UNSUPPORTED;
+ return false;
+ } else {
+ gizmoSupportStatus = GizmoSupportStatus.SUPPORTED;
+ return true;
+ }
+ } catch (Throwable e) {
+ // Note: GraalVM will throw a com.oracle.svm.core.jdk.UnsupportedFeatureError
+ // on defineHiddenClass, so we catch "Throwable" here so we don't need
+ // to add GraalVM as a library
+ gizmoSupportStatus = GizmoSupportStatus.UNSUPPORTED;
+ return false;
+ }
+ }
+
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoFieldHandler.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoFieldHandler.java
index 979a40805b2..631af2b9c43 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoFieldHandler.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoFieldHandler.java
@@ -23,32 +23,17 @@ final class GizmoFieldHandler implements GizmoMemberHandler {
private final @Nullable MethodDesc setterDescriptor;
private final boolean canBeWritten;
- GizmoFieldHandler(Class> declaringClass, FieldDesc fieldDescriptor, boolean ignoreChecks, boolean canBeWritten,
- boolean isFieldPublic) {
+ GizmoFieldHandler(Class> declaringClass, FieldDesc fieldDescriptor, boolean canBeWritten) {
this.declaringClass = declaringClass;
this.fieldDescriptor = fieldDescriptor;
var getterMethod = ReflectionHelper.getGetterMethod(declaringClass, fieldDescriptor.name());
var setterMethod = ReflectionHelper.getSetterMethod(declaringClass, fieldDescriptor.name());
if (getterMethod == null) {
- if (setterMethod != null) {
- throw new IllegalArgumentException("Field (%s) in class (%s) is a write-only field."
- .formatted(fieldDescriptor.name(), declaringClass.getName()));
- }
- if (!ignoreChecks && !isFieldPublic) {
- throw new IllegalStateException("""
- Member (%s) of class (%s) is not public."""
- .formatted(fieldDescriptor.name(), declaringClass.getName()));
- }
getterDescriptor = null;
setterDescriptor = null;
this.canBeWritten = canBeWritten;
} else {
- if (!ignoreChecks && !Modifier.isPublic(getterMethod.getModifiers())) {
- throw new IllegalStateException("""
- Member (%s) of class (%s) is not public."""
- .formatted(getterMethod.getName(), getterMethod.getDeclaringClass().getName()));
- }
ReflectionHelper.assertGetterMethod(getterMethod);
getterDescriptor = MethodDesc.of(getterMethod);
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorFactory.java
index 0a6c0c7ecec..49c7e69d6bc 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorFactory.java
@@ -5,7 +5,6 @@
import java.lang.reflect.Member;
import java.util.Objects;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
@@ -35,15 +34,6 @@ public static String getGeneratedClassName(Member member) {
*/
public static MemberAccessor buildGizmoMemberAccessor(Member member, Class extends Annotation> annotationClass,
AccessorInfo accessorInfo, GizmoClassLoader gizmoClassLoader) {
- try {
- // Check if Gizmo on the classpath by verifying we can access one of its classes
- Class.forName("io.quarkus.gizmo2.Gizmo", false,
- Thread.currentThread().getContextClassLoader());
- } catch (ClassNotFoundException e) {
- throw new IllegalStateException("""
- When using the domainAccessType (%s) the classpath or modulepath must contain io.quarkus.gizmo:gizmo2.
- Maybe add a dependency to io.quarkus.gizmo:gizmo2.""".formatted(DomainAccessType.GIZMO));
- }
return GizmoMemberAccessorImplementor.createAccessorFor(member, annotationClass, accessorInfo, gizmoClassLoader);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorImplementor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorImplementor.java
index d1526ce384c..42f65821d41 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorImplementor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorImplementor.java
@@ -2,21 +2,17 @@
import java.lang.annotation.Annotation;
import java.lang.constant.ClassDesc;
-import java.lang.constant.ConstantDescs;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
-import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
import ai.timefold.solver.core.impl.util.MutableReference;
-import org.jspecify.annotations.NonNull;
-
import io.quarkus.gizmo2.ClassOutput;
import io.quarkus.gizmo2.Const;
import io.quarkus.gizmo2.Gizmo;
@@ -94,8 +90,8 @@ public static void defineAccessorFor(String className, ClassOutput classOutput,
}
private static Class extends AbstractGizmoMemberAccessor> getCorrectSuperclass(GizmoMemberInfo memberInfo) {
- AtomicBoolean supportsSetter = new AtomicBoolean();
- AtomicBoolean methodWithParameter = new AtomicBoolean();
+ var supportsSetter = new AtomicBoolean();
+ var methodWithParameter = new AtomicBoolean();
memberInfo.descriptor().whenIsMethod(method -> {
supportsSetter.set(memberInfo.descriptor().getSetter().isPresent());
methodWithParameter.set(memberInfo.readMethodWithParameter());
@@ -129,17 +125,17 @@ private static Class extends AbstractGizmoMemberAccessor> getCorrectSuperclass
*/
static MemberAccessor createAccessorFor(Member member, Class extends Annotation> annotationClass,
AccessorInfo accessorInfo, GizmoClassLoader gizmoClassLoader) {
- String className = GizmoMemberAccessorFactory.getGeneratedClassName(member);
+ var className = GizmoMemberAccessorFactory.getGeneratedClassName(member);
if (gizmoClassLoader.hasBytecodeFor(className)) {
return createInstance(className, gizmoClassLoader);
}
- final MutableReference classBytecodeHolder = new MutableReference<>(null);
+ var classBytecodeHolder = new MutableReference(null);
ClassOutput classOutput = (path, byteCode) -> classBytecodeHolder.setValue(byteCode);
- var descriptor = new GizmoMemberDescriptor(member, accessorInfo.readMethodWithParameter());
- GizmoMemberInfo memberInfo = new GizmoMemberInfo(descriptor, accessorInfo.returnTypeRequired(),
+ var descriptor = new GizmoMemberDescriptor(member, accessorInfo);
+ var memberInfo = new GizmoMemberInfo(descriptor, accessorInfo.returnTypeRequired(),
descriptor.getMethodParameterType() != null, annotationClass);
defineAccessorFor(className, classOutput, memberInfo);
- byte[] classBytecode = classBytecodeHolder.getValue();
+ var classBytecode = classBytecodeHolder.getValue();
gizmoClassLoader.storeBytecode(className, classBytecode);
return createInstance(className, gizmoClassLoader);
@@ -231,85 +227,6 @@ private static void createGetDeclaringClass(GeneratedClassInfo generatedClassInf
});
}
- /**
- * Asserts method is a getter or read method
- *
- * @param method Method to assert is getter or read
- * @param returnTypeRequired Flag used to check method return type
- * @param readMethodWithParameter Flag used to enable the method to accept an argument
- */
- private static void assertIsGoodMethod(MethodDesc method, boolean returnTypeRequired, boolean readMethodWithParameter) {
- // V = void return type
- // Z = primitive boolean return type
- String methodName = method.name();
- if (!readMethodWithParameter && method.parameterCount() != 0) {
- // not read or getter method
- throw new IllegalStateException("The getterMethod (%s) must not have any parameters, but has parameters (%s)."
- .formatted(methodName, Arrays.toString(method.parameterTypes().toArray())));
- }
- if (methodName.startsWith("get")) {
- if (method.returnType().equals(ConstantDescs.CD_void)) {
- throw new IllegalStateException("The getterMethod (%s) must have a non-void return type."
- .formatted(methodName));
- }
- } else if (methodName.startsWith("is")) {
- if (!method.returnType().equals(ConstantDescs.CD_boolean)) {
- throw new IllegalStateException("""
- The getterMethod (%s) must have a primitive boolean return type but returns (%s).
- Maybe rename the method (get%s)?"""
- .formatted(methodName, method.returnType(), methodName.substring(2)));
- }
- } else {
- // must be a read method
- if (returnTypeRequired && method.returnType().equals(ConstantDescs.CD_void)) {
- throw new IllegalStateException("The readMethod (%s) must have a non-void return type."
- .formatted(methodName));
- }
- }
- }
-
- /**
- * Asserts method is a getter or read method
- *
- * @param method Method to assert is getter or read
- * @param returnTypeRequired Flag used to check method return type
- * @param readMethodWithParameter Flag used to enable the method to accept an argument
- * @param annotationClass Used in exception message
- */
- private static void assertIsGoodMethod(MethodDesc method, boolean returnTypeRequired, boolean readMethodWithParameter,
- Class extends Annotation> annotationClass) {
- // V = void return type
- // Z = primitive boolean return type
- String methodName = method.name();
- if (!readMethodWithParameter && method.parameterCount() != 0) {
- // not read or getter method
- throw new IllegalStateException(
- "The getterMethod (%s) with a %s annotation must not have any parameters, but has parameters (%s)."
- .formatted(methodName, annotationClass.getSimpleName(),
- method.parameterTypes().stream().map(ClassDesc::descriptorString).toList()));
- }
- if (methodName.startsWith("get")) {
- if (method.returnType().equals(ConstantDescs.CD_void)) {
- throw new IllegalStateException("The getterMethod (%s) with a %s annotation must have a non-void return type."
- .formatted(methodName, annotationClass.getSimpleName()));
- }
- } else if (methodName.startsWith("is")) {
- if (!method.returnType().equals(ConstantDescs.CD_boolean)) {
- throw new IllegalStateException("""
- The getterMethod (%s) with a %s annotation must have a primitive boolean return type but returns (%s).
- Maybe rename the method (get%s)?"""
- .formatted(methodName, annotationClass.getSimpleName(), method.returnType().descriptorString(),
- methodName.substring(2)));
- }
- } else {
- // must be a read method and return a result only if returnTypeRequired is true
- if (returnTypeRequired && method.returnType().equals(ConstantDescs.CD_void)) {
- throw new IllegalStateException("The readMethod (%s) with a %s annotation must have a non-void return type."
- .formatted(methodName, annotationClass.getSimpleName()));
- }
- }
- }
-
/**
* Generates the following code:
*
@@ -330,18 +247,6 @@ private static void createGetName(GeneratedClassInfo generatedClassInfo) {
builder.public_();
builder.returning(String.class);
builder.body(blockCreator -> {
- // If it is a method, assert that it has the required
- // properties
- memberInfo.descriptor().whenIsMethod(method -> {
- var annotationClass = memberInfo.annotationClass();
- if (annotationClass == null) {
- assertIsGoodMethod(method, memberInfo.returnTypeRequired(), memberInfo.readMethodWithParameter());
- } else {
- assertIsGoodMethod(method, memberInfo.returnTypeRequired(), memberInfo.readMethodWithParameter(),
- annotationClass);
- }
- });
-
String fieldName = memberInfo.descriptor().getName();
blockCreator.return_(Const.of(fieldName));
});
@@ -506,8 +411,6 @@ private static void createExecuteGetterWithParameter(GeneratedClassInfo generate
builder.returning(Object.class);
var bean = builder.parameter("bean", Object.class);
var value = builder.parameter("value", Object.class);
- memberInfo.descriptor().whenIsMethod(
- md -> assertIsGoodMethod(md, memberInfo.returnTypeRequired(), memberInfo.readMethodWithParameter()));
builder.body(blockCreator -> {
var castedBean =
blockCreator.localVar("castedBean", ClassDesc.of(memberInfo.descriptor().getDeclaringClassName()),
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberDescriptor.java
index f113abfb40e..2d4fc449611 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberDescriptor.java
@@ -10,6 +10,7 @@
import java.util.function.Consumer;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
+import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@@ -40,6 +41,8 @@ public final class GizmoMemberDescriptor {
@Nullable
private final Type methodParameterType;
+ private final AccessorInfo accessorInfo;
+
private final GizmoMemberHandler memberHandler;
/**
@@ -54,11 +57,12 @@ public final class GizmoMemberDescriptor {
private final MethodDesc setter;
public GizmoMemberDescriptor(Member member) {
- this(member, false);
+ this(member, AccessorInfo.withReturnValueAndArguments());
}
- public GizmoMemberDescriptor(Member member, boolean methodWithParameter) {
+ public GizmoMemberDescriptor(Member member, AccessorInfo accessorInfo) {
Class> declaringClass = member.getDeclaringClass();
+ this.accessorInfo = accessorInfo;
if (member instanceof Field field) {
var fieldDescriptor = FieldDesc.of(field);
this.name = member.getName();
@@ -74,7 +78,7 @@ public GizmoMemberDescriptor(Member member, boolean methodWithParameter) {
var methodDescriptor = MethodDesc.of(method);
this.name = ReflectionHelper.isGetterMethod(method) ? ReflectionHelper.getGetterPropertyName(member)
: member.getName();
- this.methodParameterType = getMethodParameterType(method, methodWithParameter);
+ this.methodParameterType = getMethodParameterType(method, accessorInfo.readMethodWithParameter());
this.memberHandler = GizmoMemberHandler.of(declaringClass, (Class>) methodParameterType, methodDescriptor);
this.setter = lookupSetter(methodDescriptor, declaringClass, name).orElse(null);
} else {
@@ -83,31 +87,36 @@ public GizmoMemberDescriptor(Member member, boolean methodWithParameter) {
this.metadataHandler = this.memberHandler;
}
- public GizmoMemberDescriptor(String name, FieldDesc fieldDescriptor, Class> declaringClass) {
+ public GizmoMemberDescriptor(String name, FieldDesc fieldDescriptor, Class> declaringClass,
+ AccessorInfo accessorInfo) {
this.name = name;
this.memberHandler = GizmoMemberHandler.of(declaringClass, name, fieldDescriptor, true);
this.metadataHandler = this.memberHandler;
this.setter = null;
this.methodParameterType = null;
+ this.accessorInfo = accessorInfo;
}
public GizmoMemberDescriptor(String name, MethodDesc memberDescriptor, MethodDesc metadataDescriptor,
- Type methodParameterType, Class> declaringClass, @Nullable MethodDesc setterDescriptor) {
+ Type methodParameterType, Class> declaringClass, @Nullable MethodDesc setterDescriptor,
+ AccessorInfo accessorInfo) {
this.name = name;
this.memberHandler = GizmoMemberHandler.of(declaringClass, (Class>) methodParameterType, memberDescriptor);
this.metadataHandler = memberDescriptor == metadataDescriptor ? this.memberHandler
: GizmoMemberHandler.of(declaringClass, metadataDescriptor);
this.methodParameterType = methodParameterType;
this.setter = setterDescriptor;
+ this.accessorInfo = accessorInfo;
}
public GizmoMemberDescriptor(String name, MethodDesc memberDescriptor, Type methodParameterType, Class> declaringClass,
- @Nullable MethodDesc setterDescriptor) {
+ @Nullable MethodDesc setterDescriptor, AccessorInfo accessorInfo) {
this.name = name;
this.memberHandler = GizmoMemberHandler.of(declaringClass, (Class>) methodParameterType, memberDescriptor);
this.metadataHandler = this.memberHandler;
this.methodParameterType = methodParameterType;
this.setter = setterDescriptor;
+ this.accessorInfo = accessorInfo;
}
public GizmoMemberDescriptor(String name, MethodDesc memberDescriptor, FieldDesc metadataDescriptor,
@@ -117,6 +126,7 @@ public GizmoMemberDescriptor(String name, MethodDesc memberDescriptor, FieldDesc
this.metadataHandler = GizmoMemberHandler.of(declaringClass, name, metadataDescriptor, true);
this.methodParameterType = methodParameterType;
this.setter = setterDescriptor;
+ this.accessorInfo = AccessorInfo.of(MemberAccessorType.FIELD_OR_READ_METHOD);
}
@Nullable
@@ -251,6 +261,10 @@ public Type getMethodParameterType() {
return methodParameterType;
}
+ public AccessorInfo getAccessorInfo() {
+ return accessorInfo;
+ }
+
@Override
public String toString() {
return memberHandler.toString();
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberHandler.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberHandler.java
index d83ff9ca958..58541e3ecb1 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberHandler.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberHandler.java
@@ -23,15 +23,13 @@ interface GizmoMemberHandler {
* @param ignoreFinalChecks true if Quarkus will make the field non-final for us
* @return never null
*/
- static GizmoMemberHandler of(Class> declaringClass, String name, FieldDesc fieldDescriptor,
- boolean ignoreFinalChecks) {
+ static GizmoMemberHandler of(Class> declaringClass, String name, FieldDesc fieldDescriptor, boolean ignoreFinalChecks) {
try {
Field field = declaringClass.getField(name);
return new GizmoFieldHandler(declaringClass, fieldDescriptor,
- ignoreFinalChecks, ignoreFinalChecks || !Modifier.isFinal(field.getModifiers()),
- Modifier.isPublic(field.getModifiers()));
+ ignoreFinalChecks || !Modifier.isFinal(field.getModifiers()));
} catch (NoSuchFieldException e) { // The field is only used for its metadata and never actually called.
- return new GizmoFieldHandler(declaringClass, fieldDescriptor, ignoreFinalChecks, false, false);
+ return new GizmoFieldHandler(declaringClass, fieldDescriptor, false);
}
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoSupportStatus.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoSupportStatus.java
new file mode 100644
index 00000000000..33fd980fde2
--- /dev/null
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoSupportStatus.java
@@ -0,0 +1,6 @@
+package ai.timefold.solver.core.impl.domain.common.accessor.gizmo;
+
+public enum GizmoSupportStatus {
+ SUPPORTED,
+ UNSUPPORTED
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java
index 46495afc4b5..8ced593a76e 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java
@@ -1,8 +1,8 @@
package ai.timefold.solver.core.impl.domain.entity.descriptor;
-import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory.MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER;
-import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory.MemberAccessorType.FIELD_OR_READ_METHOD;
-import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory.MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER;
+import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER;
+import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType.FIELD_OR_READ_METHOD;
+import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER;
import static ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptorValidator.assertNotMixedInheritance;
import static ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptorValidator.assertSingleInheritance;
import static ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptorValidator.assertValidPlanningVariables;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyResolver.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyResolver.java
index 8f70cfb4486..0060cadb565 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyResolver.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyResolver.java
@@ -3,9 +3,9 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.domain.common.PlanningId;
import ai.timefold.solver.core.config.util.ConfigUtils;
+import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy;
import ai.timefold.solver.core.impl.domain.solution.cloner.DeepCloningUtils;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/policy/DescriptorPolicy.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/policy/DescriptorPolicy.java
index 5568a77558e..57775043947 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/policy/DescriptorPolicy.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/policy/DescriptorPolicy.java
@@ -1,6 +1,6 @@
package ai.timefold.solver.core.impl.domain.policy;
-import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory.MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER;
+import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER;
import java.lang.reflect.Member;
import java.util.ArrayList;
@@ -12,7 +12,6 @@
import java.util.Map;
import java.util.Set;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.domain.solution.PlanningScore;
import ai.timefold.solver.core.api.domain.solution.cloner.SolutionCloner;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
@@ -27,6 +26,7 @@
import ai.timefold.solver.core.api.score.SimpleBigDecimalScore;
import ai.timefold.solver.core.api.score.SimpleScore;
import ai.timefold.solver.core.config.solver.PreviewFeature;
+import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
@@ -58,7 +58,7 @@ public class DescriptorPolicy {
private final Set anonymousFromSolutionValueRangeProviderSet = new LinkedHashSet<>();
private final Map fromEntityValueRangeProviderMap = new LinkedHashMap<>();
private final Set anonymousFromEntityValueRangeProviderSet = new LinkedHashSet<>();
- private DomainAccessType domainAccessType = DomainAccessType.REFLECTION;
+ private DomainAccessType domainAccessType = DomainAccessType.FORCE_REFLECTION;
private Set enabledPreviewFeatureSet = EnumSet.noneOf(PreviewFeature.class);
@Nullable
private MemberAccessorFactory memberAccessorFactory;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/ConstraintWeightSupplier.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/ConstraintWeightSupplier.java
index 1de76faa1c3..175f977ca0d 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/ConstraintWeightSupplier.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/ConstraintWeightSupplier.java
@@ -2,11 +2,11 @@
import java.util.Set;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides;
import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.api.score.constraint.ConstraintRef;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
+import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/OverridesBasedConstraintWeightSupplier.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/OverridesBasedConstraintWeightSupplier.java
index 0c6c8f66dc3..f42907e6b06 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/OverridesBasedConstraintWeightSupplier.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/OverridesBasedConstraintWeightSupplier.java
@@ -6,14 +6,15 @@
import java.util.Set;
import java.util.stream.Collectors;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides;
import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.api.score.constraint.ConstraintRef;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
+import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
+import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType;
import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraint;
@@ -37,7 +38,7 @@ public static > ConstraintWeightSupplier
overridesClass = (Class extends ConstraintWeightOverrides>) method.getReturnType();
}
var memberAccessor = descriptorPolicy.getMemberAccessorFactory().buildAndCacheMemberAccessor(member,
- MemberAccessorFactory.MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
descriptorPolicy.getDomainAccessType());
return new OverridesBasedConstraintWeightSupplier<>(solutionDescriptor, memberAccessor, overridesClass);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerFactory.java
index f52bb8c9382..ec6a1ca8718 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerFactory.java
@@ -1,6 +1,5 @@
package ai.timefold.solver.core.impl.domain.solution.cloner.gizmo;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.domain.solution.cloner.SolutionCloner;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.GizmoClassLoader;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
@@ -19,14 +18,6 @@ public static String getGeneratedClassName(SolutionDescriptor> solutionDescrip
}
public static SolutionCloner build(SolutionDescriptor solutionDescriptor, GizmoClassLoader gizmoClassLoader) {
- try {
- // Check if Gizmo on the classpath by verifying we can access one of its classes
- Class.forName("io.quarkus.gizmo2.Gizmo", false, Thread.currentThread().getContextClassLoader());
- } catch (ClassNotFoundException e) {
- throw new IllegalStateException("""
- When using the domainAccessType (%s) the classpath or modulepath must contain io.quarkus.gizmo:gizmo2.
- Maybe add a dependency to io.quarkus.gizmo:gizmo2.""".formatted(DomainAccessType.GIZMO));
- }
return new GizmoSolutionClonerImplementor().createClonerFor(solutionDescriptor,
gizmoClassLoader);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java
index 6e7e38af6dc..9ff642c5adf 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java
@@ -1,7 +1,7 @@
package ai.timefold.solver.core.impl.domain.solution.descriptor;
-import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory.MemberAccessorType.FIELD_OR_GETTER_METHOD;
-import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory.MemberAccessorType.FIELD_OR_READ_METHOD;
+import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType.FIELD_OR_GETTER_METHOD;
+import static ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType.FIELD_OR_READ_METHOD;
import static ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor.extractInheritedClasses;
import static java.util.stream.Stream.concat;
@@ -35,7 +35,6 @@
import java.util.stream.Stream;
import ai.timefold.solver.core.api.domain.autodiscover.AutoDiscoverMemberType;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides;
import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty;
@@ -50,6 +49,7 @@
import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.config.util.ConfigUtils;
+import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
@@ -116,7 +116,7 @@ public static SolutionDescriptor buildSolutionDescriptor(
Set enabledPreviewFeaturesSet,
Class solutionClass,
List> entityClassList) {
- return buildSolutionDescriptor(enabledPreviewFeaturesSet, DomainAccessType.REFLECTION, solutionClass, null,
+ return buildSolutionDescriptor(enabledPreviewFeaturesSet, DomainAccessType.FORCE_REFLECTION, solutionClass, null,
null, entityClassList);
}
@@ -723,17 +723,11 @@ private void initSolutionCloner(DescriptorPolicy descriptorPolicy) {
gizmoSolutionCloner.setSolutionDescriptor(this);
}
if (solutionCloner == null) {
- switch (descriptorPolicy.getDomainAccessType()) {
- case GIZMO:
- solutionCloner = GizmoSolutionClonerFactory.build(this, memberAccessorFactory.getGizmoClassLoader());
- break;
- case REFLECTION:
- solutionCloner = new FieldAccessingSolutionCloner<>(this);
- break;
- default:
- throw new IllegalStateException("The domainAccessType (" + domainAccessType
- + ") is not implemented.");
- }
+ solutionCloner = switch (descriptorPolicy.getDomainAccessType()) {
+ case FORCE_GIZMO -> GizmoSolutionClonerFactory.build(this, memberAccessorFactory.getGizmoClassLoader());
+ // AUTO means we are probably in plain Java, so we need to use reflection so we can clone final fields
+ case AUTO, FORCE_REFLECTION -> new FieldAccessingSolutionCloner<>(this);
+ };
}
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/cascade/CascadingUpdateShadowVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/cascade/CascadingUpdateShadowVariableDescriptor.java
index 77bfa0f552b..1c2f9be3924 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/cascade/CascadingUpdateShadowVariableDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/cascade/CascadingUpdateShadowVariableDescriptor.java
@@ -14,7 +14,7 @@
import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.util.ConfigUtils;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
-import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
+import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType;
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ShadowVariableDescriptor;
@@ -130,7 +130,7 @@ public void linkVariableDescriptors(DescriptorPolicy descriptorPolicy) {
targetMethodName));
}
targetMethod = descriptorPolicy.getMemberAccessorFactory().buildAndCacheMemberAccessor(allSourceMethodMembers.get(0),
- MemberAccessorFactory.MemberAccessorType.VOID_METHOD, null, descriptorPolicy.getDomainAccessType());
+ MemberAccessorType.VOID_METHOD, null, descriptorPolicy.getDomainAccessType());
firstTargetVariableDescriptor = targetVariableDescriptorList.get(0);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/DeclarativeShadowVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/DeclarativeShadowVariableDescriptor.java
index c7e35bff5a3..fca3814d874 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/DeclarativeShadowVariableDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/DeclarativeShadowVariableDescriptor.java
@@ -11,7 +11,7 @@
import ai.timefold.solver.core.api.domain.variable.ShadowVariable;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
-import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
+import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType;
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ShadowVariableDescriptor;
@@ -71,7 +71,7 @@ Maybe you included a parameter which is not a planning solution (%s)?
}
this.calculator =
entityDescriptor.getSolutionDescriptor().getMemberAccessorFactory().buildAndCacheMemberAccessor(method,
- MemberAccessorFactory.MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
ShadowSources.class,
descriptorPolicy.getDomainAccessType());
@@ -134,7 +134,7 @@ public void linkVariableDescriptors(DescriptorPolicy descriptorPolicy) {
alignmentKey);
if (alignmentKeyMember != null) {
alignmentKeyMap = memberAccessorFactory.buildAndCacheMemberAccessor(alignmentKeyMember,
- MemberAccessorFactory.MemberAccessorType.FIELD_OR_GETTER_METHOD, ShadowSources.class,
+ MemberAccessorType.FIELD_OR_GETTER_METHOD, ShadowSources.class,
descriptorPolicy.getDomainAccessType())::executeGetter;
} else {
alignmentKeyMap = null;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSource.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSource.java
index 624c20fb9ee..de226d1cdb7 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSource.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSource.java
@@ -19,6 +19,7 @@
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
+import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType;
import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy;
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel;
import ai.timefold.solver.core.preview.api.domain.metamodel.VariableMetaModel;
@@ -363,7 +364,7 @@ public static Member getMember(Class> rootClass, String sourcePath, Class> d
private static MemberAccessor getMemberAccessor(Member member, MemberAccessorFactory memberAccessorFactory,
DescriptorPolicy descriptorPolicy) {
return memberAccessorFactory.buildAndCacheMemberAccessor(member,
- MemberAccessorFactory.MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ MemberAccessorType.FIELD_OR_GETTER_METHOD,
descriptorPolicy.getDomainAccessType());
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java
index 12b29585f57..c2952c8f30e 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java
@@ -26,6 +26,7 @@
import ai.timefold.solver.core.config.util.ConfigUtils;
import ai.timefold.solver.core.impl.AbstractFromConfigFactory;
import ai.timefold.solver.core.impl.constructionheuristic.DefaultConstructionHeuristicPhaseFactory;
+import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
@@ -189,7 +190,7 @@ private SolutionDescriptor buildSolutionDescriptor() {
.formatted(solverConfig.getEntityClassList()));
}
return SolutionDescriptor.buildSolutionDescriptor(solverConfig.getEnablePreviewFeatureSet(),
- solverConfig.determineDomainAccessType(),
+ DomainAccessType.AUTO,
(Class) solverConfig.getSolutionClass(),
solverConfig.getGizmoMemberAccessorMap(),
solverConfig.getGizmoSolutionClonerMap(),
diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd
index 1e68b872d4b..3aff40417d8 100644
--- a/core/src/main/resources/solver.xsd
+++ b/core/src/main/resources/solver.xsd
@@ -35,8 +35,6 @@
-
-
@@ -1541,18 +1539,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java b/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java
index 549469c6f9c..f7912816be7 100644
--- a/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java
@@ -299,7 +299,7 @@ private record DummyRecordSolution(
}
@PlanningSolution
- private static class DummySolutionWithRecordEntity {
+ public static class DummySolutionWithRecordEntity {
@PlanningEntityCollectionProperty
List entities;
@@ -358,7 +358,7 @@ private static class DummyEntityWithMixedSimpleAndListVariable {
}
@PlanningSolution
- private static class DummySolutionWithTwoListVariablesEntity {
+ public static class DummySolutionWithTwoListVariablesEntity {
@PlanningEntityCollectionProperty
List entities;
@@ -409,7 +409,7 @@ public void setScore(SimpleScore score) {
}
@PlanningEntity
- private static class DummyEntityWithTwoListVariables {
+ public static class DummyEntityWithTwoListVariables {
@PlanningListVariable(valueRangeProviderRefs = "firstListValueRange")
private List firstListVariable;
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorFactoryTest.java
index 505175edac8..bba41f10ae5 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorFactoryTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorFactoryTest.java
@@ -9,9 +9,9 @@
import java.util.HashMap;
import java.util.Map;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.domain.solution.ProblemFactProperty;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
+import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.GizmoMemberAccessorFactory;
import ai.timefold.solver.core.testdomain.TestdataEntity;
import ai.timefold.solver.core.testdomain.TestdataValue;
@@ -26,8 +26,8 @@ class MemberAccessorFactoryTest {
void fieldAnnotatedEntityWithPublicGetter() throws NoSuchFieldException {
MemberAccessor memberAccessor =
MemberAccessorFactory.buildMemberAccessor(TestdataFieldAnnotatedEntity.class.getDeclaredField("value"),
- MemberAccessorFactory.MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER, PlanningVariable.class,
- DomainAccessType.REFLECTION, null);
+ MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER, PlanningVariable.class,
+ DomainAccessType.FORCE_REFLECTION, null);
assertThat(memberAccessor)
.isInstanceOf(ReflectionBeanPropertyMemberAccessor.class);
assertThat(memberAccessor.getName()).isEqualTo("value");
@@ -45,8 +45,8 @@ void fieldAnnotatedEntityWithPublicGetter() throws NoSuchFieldException {
void privateField() {
assertThatCode(() -> MemberAccessorFactory.buildMemberAccessor(
TestdataVisibilityModifierSolution.class.getDeclaredField("privateField"),
- MemberAccessorFactory.MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER, ProblemFactProperty.class,
- DomainAccessType.REFLECTION, null))
+ MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER, ProblemFactProperty.class,
+ DomainAccessType.FORCE_REFLECTION, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContainingAll("The @ProblemFactProperty annotated field",
"privateField",
@@ -58,8 +58,8 @@ void privateField() {
void publicField() throws NoSuchFieldException {
MemberAccessor memberAccessor = MemberAccessorFactory.buildMemberAccessor(
TestdataVisibilityModifierSolution.class.getDeclaredField("publicField"),
- MemberAccessorFactory.MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER, ProblemFactProperty.class,
- DomainAccessType.REFLECTION, null);
+ MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER, ProblemFactProperty.class,
+ DomainAccessType.FORCE_REFLECTION, null);
assertThat(memberAccessor)
.isInstanceOf(ReflectionFieldMemberAccessor.class);
assertThat(memberAccessor.getName()).isEqualTo("publicField");
@@ -77,8 +77,8 @@ void publicField() throws NoSuchFieldException {
void publicProperty() throws NoSuchMethodException {
MemberAccessor memberAccessor = MemberAccessorFactory.buildMemberAccessor(
TestdataVisibilityModifierSolution.class.getDeclaredMethod("getPublicProperty"),
- MemberAccessorFactory.MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER, ProblemFactProperty.class,
- DomainAccessType.REFLECTION, null);
+ MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER, ProblemFactProperty.class,
+ DomainAccessType.FORCE_REFLECTION, null);
assertThat(memberAccessor)
.isInstanceOf(ReflectionBeanPropertyMemberAccessor.class);
assertThat(memberAccessor.getName()).isEqualTo("publicProperty");
@@ -95,7 +95,7 @@ void publicProperty() throws NoSuchMethodException {
@Test
void methodReturnVoid() throws NoSuchMethodException {
MemberAccessor memberAccessor = MemberAccessorFactory.buildMemberAccessor(TestdataEntity.class.getMethod("updateValue"),
- MemberAccessorFactory.MemberAccessorType.VOID_METHOD, null, DomainAccessType.REFLECTION, null);
+ MemberAccessorType.VOID_METHOD, null, DomainAccessType.FORCE_REFLECTION, null);
assertThat(memberAccessor).isInstanceOf(ReflectionMethodMemberAccessor.class);
assertThat(memberAccessor.getName()).isEqualTo("updateValue");
assertThat(memberAccessor.getType()).isEqualTo(void.class);
@@ -119,8 +119,8 @@ void shouldUseGeneratedMemberAccessorIfExists() throws NoSuchMethodException {
preexistingMemberAccessors.put(GizmoMemberAccessorFactory.getGeneratedClassName(member), mockMemberAccessor);
MemberAccessorFactory memberAccessorFactory = new MemberAccessorFactory(preexistingMemberAccessors);
MemberAccessor memberAccessor = memberAccessorFactory.buildAndCacheMemberAccessor(member,
- MemberAccessorFactory.MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER, ProblemFactProperty.class,
- DomainAccessType.REFLECTION);
+ MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER, ProblemFactProperty.class,
+ DomainAccessType.FORCE_REFLECTION);
assertThat(memberAccessor)
.isSameAs(mockMemberAccessor);
}
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorValidatorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorValidatorTest.java
new file mode 100644
index 00000000000..669e6e3bd3f
--- /dev/null
+++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/MemberAccessorValidatorTest.java
@@ -0,0 +1,313 @@
+package ai.timefold.solver.core.impl.domain.common.accessor;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import ai.timefold.solver.core.api.domain.common.PlanningId;
+
+import org.junit.jupiter.api.Test;
+
+class MemberAccessorValidatorTest {
+ @Test
+ void includeAnnotationInfoIfPresent() {
+ assertThatCode(() -> {
+ MemberAccessorValidator.verifyIsValidMember(PlanningId.class,
+ PublicClass.class.getDeclaredField("privateFieldWithoutGetter"),
+ MemberAccessorType.FIELD_OR_GETTER_METHOD);
+ }).isInstanceOf(IllegalArgumentException.class).hasMessageContainingAll(
+ "@%s annotated field".formatted(PlanningId.class.getSimpleName()),
+ "privateFieldWithoutGetter");
+
+ assertThatCode(() -> {
+ MemberAccessorValidator.verifyIsValidMember(PlanningId.class,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithPrivateGetter"),
+ MemberAccessorType.FIELD_OR_GETTER_METHOD);
+ }).isInstanceOf(IllegalArgumentException.class).hasMessageContainingAll(
+ "@%s annotated method".formatted(PlanningId.class.getSimpleName()),
+ "getPrivateFieldWithPrivateGetter");
+ }
+
+ @Test
+ void membersInPrivateClassFail() throws NoSuchFieldException, NoSuchMethodException {
+ assertFieldFails(MemberAccessorType.FIELD_OR_GETTER_METHOD, PrivateClass.class.getDeclaredField("publicField"),
+ "its declaring class (%s) is not public".formatted(PrivateClass.class.getCanonicalName()));
+ assertFieldFails(MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ PrivateClass.class.getDeclaredField("privateFieldWithGetter"),
+ "its declaring class (%s) is not public".formatted(PrivateClass.class.getCanonicalName()));
+ assertMethodFails(MemberAccessorType.VOID_METHOD, PrivateClass.class.getDeclaredMethod("publicVoidMethod"),
+ "its declaring class (%s) is not public".formatted(PrivateClass.class.getCanonicalName()));
+ }
+
+ @Test
+ void fieldOrGetterMethod() throws NoSuchFieldException, NoSuchMethodException {
+ assertFieldPasses(MemberAccessorType.FIELD_OR_GETTER_METHOD, PublicClass.class.getDeclaredField("publicField"));
+ assertFieldPasses(MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ PublicClass.class.getDeclaredField("privateFieldWithGetter"));
+ assertFieldPasses(MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ PublicClass.class.getDeclaredField("privateFieldWithGetterAndSetter"));
+ assertFieldPasses(MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ PublicClass.class.getDeclaredField("privateFieldWithPrivateSetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithGetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithPrivateSetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithGetterAndSetter"));
+
+ assertFieldFails(MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ PublicClass.class.getDeclaredField("privateFieldWithoutGetter"),
+ "is not public and does not have a public getter method");
+ assertFieldFails(MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ PublicClass.class.getDeclaredField("privateFieldWithPrivateGetter"),
+ "does not have a public getter method");
+ assertMethodFails(MemberAccessorType.FIELD_OR_GETTER_METHOD, PublicClass.class.getDeclaredMethod("getVoidMethod"),
+ "has a public getter method that returns void");
+ assertMethodFails(MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithPrivateGetter"),
+ "is a getter method, but it is not public");
+ assertMethodFails(MemberAccessorType.FIELD_OR_GETTER_METHOD,
+ PublicClass.class.getDeclaredMethod("publicReadMethod"),
+ "is suppose to be a public getter method, but its name (publicReadMethod) does not start with \"get\"");
+ }
+
+ @Test
+ void fieldOrGetterMethodWithSetter() throws NoSuchFieldException, NoSuchMethodException {
+ assertFieldPasses(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredField("publicField"));
+ assertFieldPasses(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredField("privateFieldWithGetterAndSetter"));
+ assertFieldPasses(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredField("privateFieldWithGetterAndSetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithGetterAndSetter"));
+
+ assertFieldFails(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredField("privateFieldWithGetter"),
+ "does not have a setter method and is not public");
+ assertFieldFails(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredField("privateFieldWithoutGetter"),
+ "is not public and does not have a public getter method");
+ assertFieldFails(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredField("privateFieldWithPrivateGetter"),
+ "does not have a public getter method");
+ assertFieldFails(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredField("privateFieldWithPrivateSetter"),
+ "does not have a setter method and is not public");
+
+ assertMethodFails(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithPrivateSetter"),
+ "requires both a public getter and a public setter but only have a public getter");
+ assertMethodFails(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithGetter"),
+ "requires both a public getter and a public setter but only have a public getter");
+ assertMethodFails(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredMethod("getVoidMethod"),
+ "has a public getter method that returns void");
+ assertMethodFails(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredMethod("getVoidMethod"),
+ "has a public getter method that returns void");
+ assertMethodFails(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithPrivateGetter"),
+ "is a getter method, but it is not public");
+ assertMethodFails(MemberAccessorType.FIELD_OR_GETTER_METHOD_WITH_SETTER,
+ PublicClass.class.getDeclaredMethod("publicReadMethod"),
+ "is suppose to be a public getter method, but its name (publicReadMethod) does not start with \"get\"");
+ }
+
+ @Test
+ void fieldOrReadMethod() throws NoSuchFieldException, NoSuchMethodException {
+ assertFieldPasses(MemberAccessorType.FIELD_OR_READ_METHOD, PublicClass.class.getDeclaredField("publicField"));
+ assertFieldPasses(MemberAccessorType.FIELD_OR_READ_METHOD,
+ PublicClass.class.getDeclaredField("privateFieldWithGetter"));
+ assertFieldPasses(MemberAccessorType.FIELD_OR_READ_METHOD,
+ PublicClass.class.getDeclaredField("privateFieldWithGetterAndSetter"));
+ assertFieldPasses(MemberAccessorType.FIELD_OR_READ_METHOD,
+ PublicClass.class.getDeclaredField("privateFieldWithPrivateSetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_READ_METHOD,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithGetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_READ_METHOD,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithPrivateSetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_READ_METHOD,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithGetterAndSetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_READ_METHOD,
+ PublicClass.class.getDeclaredMethod("publicReadMethod"));
+
+ assertFieldFails(MemberAccessorType.FIELD_OR_READ_METHOD,
+ PublicClass.class.getDeclaredField("privateFieldWithoutGetter"),
+ "is not public and does not have a public getter method");
+ assertFieldFails(MemberAccessorType.FIELD_OR_READ_METHOD,
+ PublicClass.class.getDeclaredField("privateFieldWithPrivateGetter"),
+ "does not have a public getter method");
+ assertMethodFails(MemberAccessorType.FIELD_OR_READ_METHOD, PublicClass.class.getDeclaredMethod("getVoidMethod"),
+ "is a public read method, but it returns void");
+ assertMethodFails(MemberAccessorType.FIELD_OR_READ_METHOD,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithPrivateGetter"),
+ "is a read method, but it is not public.");
+ assertMethodFails(MemberAccessorType.FIELD_OR_READ_METHOD,
+ PublicClass.class.getDeclaredMethod("publicReadMethodWithParameter", String.class),
+ "is a public read method, but takes (1) parameters instead of none");
+ }
+
+ @Test
+ void fieldOrReadMethodWithOptionalParameter() throws NoSuchFieldException, NoSuchMethodException {
+ assertFieldPasses(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredField("publicField"));
+ assertFieldPasses(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredField("privateFieldWithGetter"));
+ assertFieldPasses(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredField("privateFieldWithGetterAndSetter"));
+ assertFieldPasses(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredField("privateFieldWithPrivateSetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithGetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithPrivateSetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithGetterAndSetter"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredMethod("publicReadMethod"));
+ assertMethodPasses(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredMethod("publicReadMethodWithParameter", String.class));
+
+ assertFieldFails(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredField("privateFieldWithoutGetter"),
+ "is not public and does not have a public getter method");
+ assertFieldFails(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredField("privateFieldWithPrivateGetter"),
+ "does not have a public getter method");
+ assertMethodFails(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredMethod("getVoidMethod"),
+ "is a public read method, but it returns void");
+ assertMethodFails(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredMethod("getPrivateFieldWithPrivateGetter"),
+ "is a read method, but it is not public.");
+ assertMethodFails(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER,
+ PublicClass.class.getDeclaredMethod("publicReadMethodWithManyParameter", String.class, String.class),
+ "is a public read method, but takes (2) parameters instead of zero or one");
+ }
+
+ @Test
+ void voidMethod() throws NoSuchFieldException, NoSuchMethodException {
+ assertMethodPasses(MemberAccessorType.VOID_METHOD, PublicClass.class.getDeclaredMethod("getVoidMethod"));
+
+ assertFieldFails(MemberAccessorType.VOID_METHOD, PublicClass.class.getDeclaredField("publicField"),
+ "must be a void method, but is a field instead");
+ assertFieldFails(MemberAccessorType.VOID_METHOD,
+ PublicClass.class.getDeclaredField("privateFieldWithGetter"),
+ "must be a void method, but is a field instead");
+ assertMethodFails(MemberAccessorType.VOID_METHOD, PublicClass.class.getDeclaredMethod("publicReadMethod"),
+ "must be a void method, but it returns (java.lang.String)");
+ assertMethodFails(MemberAccessorType.VOID_METHOD, PublicClass.class.getDeclaredMethod("privateVoidMethod"),
+ "is a void method, but it is not public");
+ }
+
+ void assertFieldPasses(MemberAccessorType memberAccessorType, Field field) {
+ assertThatCode(() -> {
+ MemberAccessorValidator.verifyIsValidMember(null, field, memberAccessorType);
+ }).doesNotThrowAnyException();
+ }
+
+ void assertMethodPasses(MemberAccessorType memberAccessorType, Method method) {
+ assertThatCode(() -> {
+ MemberAccessorValidator.verifyIsValidMember(null, method, memberAccessorType);
+ }).doesNotThrowAnyException();
+ }
+
+ void assertFieldFails(MemberAccessorType memberAccessorType, Field field, String... expectedMessages) {
+ var prefix = "The field (%s) in class (%s)".formatted(field.getName(), field.getDeclaringClass().getCanonicalName());
+ var allMessages = new ArrayList();
+ allMessages.add(prefix);
+ allMessages.addAll(Arrays.asList(expectedMessages));
+
+ assertThatCode(() -> {
+ MemberAccessorValidator.verifyIsValidMember(null, field, memberAccessorType);
+ }).isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContainingAll(allMessages.toArray(new String[0]));
+ }
+
+ void assertMethodFails(MemberAccessorType memberAccessorType, Method method, String... expectedMessages) {
+ var prefix = "The method (%s) in class (%s)".formatted(method.getName(), method.getDeclaringClass().getCanonicalName());
+ var allMessages = new ArrayList();
+ allMessages.add(prefix);
+ allMessages.addAll(Arrays.asList(expectedMessages));
+
+ assertThatCode(() -> {
+ MemberAccessorValidator.verifyIsValidMember(null, method, memberAccessorType);
+ }).isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContainingAll(allMessages.toArray(new String[0]));
+ }
+
+ public static class PublicClass {
+ public String publicField;
+ private String privateFieldWithoutGetter;
+ private String privateFieldWithGetter;
+ private String privateFieldWithGetterAndSetter;
+ private String privateFieldWithPrivateGetter;
+ private String privateFieldWithPrivateSetter;
+
+ public String getPrivateFieldWithGetter() {
+ return privateFieldWithGetter;
+ }
+
+ public String getPrivateFieldWithGetterAndSetter() {
+ return privateFieldWithGetterAndSetter;
+ }
+
+ public void setPrivateFieldWithGetterAndSetter(String privateFieldWithGetterAndSetter) {
+ this.privateFieldWithGetterAndSetter = privateFieldWithGetterAndSetter;
+ }
+
+ private String getPrivateFieldWithPrivateGetter() {
+ return privateFieldWithPrivateGetter;
+ }
+
+ public String getPrivateFieldWithPrivateSetter() {
+ return privateFieldWithPrivateSetter;
+ }
+
+ private void setPrivateFieldWithPrivateSetter(String privateFieldWithPrivateSetter) {
+ this.privateFieldWithPrivateSetter = privateFieldWithPrivateSetter;
+ }
+
+ public void getVoidMethod() {
+ // intentionally empty
+ }
+
+ private void privateVoidMethod() {
+ // intentionally empty
+ }
+
+ public String publicReadMethod() {
+ return null;
+ }
+
+ private String privateReadMethod() {
+ return null;
+ }
+
+ public String publicReadMethodWithParameter(String parameter) {
+ return parameter;
+ }
+
+ public String publicReadMethodWithManyParameter(String parameter1, String parameter2) {
+ return parameter1;
+ }
+ }
+
+ private static class PrivateClass {
+ public String publicField;
+ private String privateFieldWithGetter;
+
+ public void publicVoidMethod() {
+ // Intentionally empty
+ }
+
+ public String getPrivateFieldWithGetter() {
+ return privateFieldWithGetter;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionBeanPropertyMemberAccessorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionBeanPropertyMemberAccessorTest.java
index 323dddfa2dc..759f2b7ccf6 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionBeanPropertyMemberAccessorTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionBeanPropertyMemberAccessorTest.java
@@ -1,13 +1,10 @@
package ai.timefold.solver.core.impl.domain.common.accessor;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
import ai.timefold.solver.core.testdomain.TestdataEntity;
import ai.timefold.solver.core.testdomain.TestdataValue;
-import ai.timefold.solver.core.testdomain.invalid.gettersetter.TestdataDifferentGetterSetterVisibilityEntity;
-import ai.timefold.solver.core.testdomain.invalid.gettersetter.TestdataInvalidGetterEntity;
import org.junit.jupiter.api.Test;
@@ -28,31 +25,4 @@ void methodAnnotatedEntity() throws NoSuchMethodException {
memberAccessor.executeSetter(e1, v2);
assertThat(e1.getValue()).isSameAs(v2);
}
-
- @Test
- void getterSetterVisibilityDoesNotMatch() {
- assertThatCode(() -> new ReflectionBeanPropertyMemberAccessor(
- TestdataDifferentGetterSetterVisibilityEntity.class.getDeclaredMethod("getValue1")))
- .hasMessageContainingAll("getterMethod (getValue1)",
- "has access modifier (public)",
- "not match the setterMethod (setValue1)",
- "access modifier (private)",
- "on class (ai.timefold.solver.core.testdomain.invalid.gettersetter.TestdataDifferentGetterSetterVisibilityEntity)");
- assertThatCode(() -> new ReflectionBeanPropertyMemberAccessor(
- TestdataDifferentGetterSetterVisibilityEntity.class.getDeclaredMethod("getValue2")))
- .hasMessageContainingAll("getterMethod (getValue2)",
- "on class (%s)".formatted(TestdataDifferentGetterSetterVisibilityEntity.class.getCanonicalName()),
- "is not public",
- "having access modifier (package-private) instead.");
- }
-
- @Test
- void setterMissing() {
- assertThatCode(() -> new ReflectionBeanPropertyMemberAccessor(
- TestdataInvalidGetterEntity.class.getDeclaredMethod("getValueWithoutSetter")))
- .hasMessageContainingAll("getterMethod (getValueWithoutSetter)",
- "does not have a matching setterMethod",
- "on class (ai.timefold.solver.core.testdomain.invalid.gettersetter.TestdataInvalidGetterEntity)");
- }
-
}
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodExtendedMemberAccessorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodExtendedMemberAccessorTest.java
index f2933c33d3a..96f152aaefd 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodExtendedMemberAccessorTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/ReflectionMethodExtendedMemberAccessorTest.java
@@ -6,17 +6,12 @@
import java.util.List;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
-import ai.timefold.solver.core.testdomain.TestdataSolution;
import ai.timefold.solver.core.testdomain.TestdataValue;
import ai.timefold.solver.core.testdomain.valuerange.entityproviding.parameter.TestdataEntityProvidingWithParameterEntity;
import ai.timefold.solver.core.testdomain.valuerange.entityproviding.parameter.TestdataEntityProvidingWithParameterSolution;
import ai.timefold.solver.core.testdomain.valuerange.entityproviding.parameter.inheritance.TestdataEntityProvidingEntityProvidingOnlyBaseAnnotatedExtendedSolution;
import ai.timefold.solver.core.testdomain.valuerange.entityproviding.parameter.inheritance.TestdataEntityProvidingOnlyBaseAnnotatedChildEntity;
import ai.timefold.solver.core.testdomain.valuerange.entityproviding.parameter.inheritance.TestdataEntityProvidingOnlyBaseAnnotatedSolution;
-import ai.timefold.solver.core.testdomain.valuerange.entityproviding.parameter.invalid.TestdataInvalidCountEntityProvidingWithParameterEntity;
-import ai.timefold.solver.core.testdomain.valuerange.entityproviding.parameter.invalid.TestdataInvalidTypeEntityProvidingWithParameterEntity;
-import ai.timefold.solver.core.testdomain.valuerange.entityproviding.parameter.invalid.TestdataInvalidTypeEntityProvidingWithParameterSolution;
-import ai.timefold.solver.core.testdomain.valuerange.parameter.invalid.TestdataInvalidParameterSolution;
import org.junit.jupiter.api.Test;
@@ -47,16 +42,17 @@ void methodAnnotatedEntityAndInheritance() throws NoSuchMethodException {
var member = new ReflectionMethodExtendedMemberAccessor(
TestdataEntityProvidingOnlyBaseAnnotatedChildEntity.class.getMethod("getValueList",
TestdataEntityProvidingEntityProvidingOnlyBaseAnnotatedExtendedSolution.class));
- assertMemberWithInheritance(member, TestdataEntityProvidingEntityProvidingOnlyBaseAnnotatedExtendedSolution.class);
+ assertMemberWithInheritance(member, TestdataEntityProvidingEntityProvidingOnlyBaseAnnotatedExtendedSolution.class,
+ "getValueList");
var otherMember = new ReflectionMethodExtendedMemberAccessor(
TestdataEntityProvidingOnlyBaseAnnotatedChildEntity.class.getMethod("getOtherValueList",
TestdataEntityProvidingOnlyBaseAnnotatedSolution.class));
- assertMemberWithInheritance(otherMember, TestdataEntityProvidingOnlyBaseAnnotatedSolution.class);
+ assertMemberWithInheritance(otherMember, TestdataEntityProvidingOnlyBaseAnnotatedSolution.class, "getOtherValueList");
}
- void assertMemberWithInheritance(ReflectionMethodExtendedMemberAccessor member, Class> solutionClass) {
+ void assertMemberWithInheritance(ReflectionMethodExtendedMemberAccessor member, Class> solutionClass, String memberName) {
- assertThat(member.getName()).isEqualTo(member.getName());
+ assertThat(member.getName()).isEqualTo(memberName);
assertThat(member.getType()).isEqualTo(List.class);
assertThat(member.getAnnotation(ValueRangeProvider.class)).isNotNull();
assertThat(member.getGetterMethodParameterType()).isEqualTo(solutionClass);
@@ -72,44 +68,19 @@ void assertMemberWithInheritance(ReflectionMethodExtendedMemberAccessor member,
assertThat(member.executeGetter(e1, s1)).isEqualTo(List.of(v2));
}
- @Test
- void invalidEntityReadMethodWithParameter() {
- assertThatCode(TestdataInvalidTypeEntityProvidingWithParameterEntity::buildVariableDescriptorForValueRange)
- .hasMessageContaining("The parameter type (%s)".formatted(TestdataSolution.class.getCanonicalName()))
- .hasMessageContaining(
- "of the method (getValueRange) must match the solution (%s)."
- .formatted(TestdataInvalidTypeEntityProvidingWithParameterSolution.class.getCanonicalName()));
- assertThatCode(TestdataInvalidCountEntityProvidingWithParameterEntity::buildVariableDescriptorForValueRange)
- .hasMessageContaining("The readMethod")
- .hasMessageContaining("with a @%s annotation must have only one parameter"
- .formatted(ValueRangeProvider.class.getSimpleName()));
- }
-
- @Test
- void invalidSolutionReadMethodWithParameter() {
- assertThatCode(TestdataInvalidParameterSolution::buildSolutionDescriptor)
- .hasMessageContainingAll(
- "The readMethod (public java.util.List %s.getValueList(%s))"
- .formatted(TestdataInvalidParameterSolution.class.getCanonicalName(),
- TestdataInvalidParameterSolution.class.getCanonicalName()))
- .hasMessageContainingAll(
- " with a @%s annotation must not have any parameters ([class %s])."
- .formatted(ValueRangeProvider.class.getSimpleName(),
- TestdataInvalidParameterSolution.class.getCanonicalName()));
- }
-
@Test
void forbiddenEntityReadWithoutParameter() {
assertThatCode(() -> new ReflectionMethodExtendedMemberAccessor(
TestdataEntityProvidingWithParameterEntity.class.getMethod("getValueRange",
- TestdataEntityProvidingWithParameterSolution.class),
- true).executeGetter(new TestdataEntityProvidingWithParameterEntity()))
+ TestdataEntityProvidingWithParameterSolution.class))
+ .executeGetter(
+ new TestdataEntityProvidingWithParameterEntity()))
.hasMessageContainingAll(
"Impossible state: the method executeGetter(Object) without parameter is not supported.");
assertThatCode(() -> new ReflectionMethodExtendedMemberAccessor(
TestdataEntityProvidingWithParameterEntity.class.getMethod("getValueRange",
- TestdataEntityProvidingWithParameterSolution.class),
- true).getGetterFunction())
+ TestdataEntityProvidingWithParameterSolution.class))
+ .getGetterFunction())
.hasMessageContainingAll(
"Impossible state: the method getGetterFunction() is not supported.");
}
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorFactoryTest.java
index 2d94b9f0a4c..17a63938bc9 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorFactoryTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorFactoryTest.java
@@ -1,9 +1,7 @@
package ai.timefold.solver.core.impl.domain.common.accessor.gizmo;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
-import java.lang.reflect.Member;
import java.lang.reflect.Method;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
@@ -11,24 +9,9 @@
import ai.timefold.solver.core.testdomain.TestdataEntity;
import ai.timefold.solver.core.testdomain.TestdataValue;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class GizmoMemberAccessorFactoryTest {
-
- static ClassLoader contextClassLoader;
-
- @BeforeAll
- static void setContextClassLoader() {
- contextClassLoader = Thread.currentThread().getContextClassLoader();
- }
-
- @BeforeEach
- void setup() {
- Thread.currentThread().setContextClassLoader(contextClassLoader);
- }
-
// Duplicates GizmoMemberAccessorImplementor test,
// but this is making sure the member accessor returned
// is the one from GizmoMemberAccessorImplementor
@@ -55,27 +38,4 @@ void testReturnedMemberAccessor() throws NoSuchMethodException {
assertThat(memberAccessor.getName()).isEqualTo("value");
assertThat(memberAccessor.getSpeedNote()).isEqualTo("Fast access with generated bytecode");
}
-
- @Test
- void testGizmoNotOnClasspathThrowsException() throws NoSuchMethodException {
- Member member = TestdataEntity.class.getMethod("getValue");
- Thread.currentThread().setContextClassLoader(new ClassLoader() {
- @Override
- public String getName() {
- return "ClassLoader without Gizmo";
- }
-
- @Override
- public Class> loadClass(String name) {
- return null;
- }
- });
-
- assertThatCode(() -> {
- GizmoMemberAccessorFactory.buildGizmoMemberAccessor(member, PlanningVariable.class,
- AccessorInfo.withReturnValueAndNoArguments(),
- new GizmoClassLoader());
- }).hasMessage("When using the domainAccessType (GIZMO) the classpath or modulepath must contain " +
- "io.quarkus.gizmo:gizmo2.\nMaybe add a dependency to io.quarkus.gizmo:gizmo2.");
- }
}
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorImplementorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorImplementorTest.java
index c8119065099..0e2c3fc2322 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorImplementorTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/accessor/gizmo/GizmoMemberAccessorImplementorTest.java
@@ -10,6 +10,7 @@
import ai.timefold.solver.core.api.domain.entity.PlanningPin;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
+import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType;
import ai.timefold.solver.core.testdomain.TestdataEntity;
import ai.timefold.solver.core.testdomain.TestdataValue;
import ai.timefold.solver.core.testdomain.gizmo.GizmoTestdataEntity;
@@ -19,7 +20,6 @@
import ai.timefold.solver.core.testdomain.valuerange.entityproviding.parameter.inheritance.TestdataEntityProvidingOnlyBaseAnnotatedChildEntity;
import ai.timefold.solver.core.testdomain.valuerange.entityproviding.parameter.inheritance.TestdataEntityProvidingOnlyBaseAnnotatedSolution;
import ai.timefold.solver.core.testdomain.valuerange.entityproviding.parameter.invalid.TestdataInvalidCountEntityProvidingWithParameterEntity;
-import ai.timefold.solver.core.testdomain.valuerange.parameter.invalid.TestdataInvalidParameterSolution;
import org.junit.jupiter.api.Test;
@@ -132,7 +132,8 @@ void testGeneratedMemberAccessorSameClass() throws NoSuchMethodException {
void testGeneratedMemberAccessorReturnVoid() throws NoSuchMethodException {
var member = TestdataEntity.class.getMethod("updateValue");
var memberAccessor =
- GizmoMemberAccessorFactory.buildGizmoMemberAccessor(member, null, AccessorInfo.of(false, false),
+ GizmoMemberAccessorFactory.buildGizmoMemberAccessor(member, null, AccessorInfo.of(
+ MemberAccessorType.VOID_METHOD),
new GizmoClassLoader());
var entity = new TestdataEntity();
@@ -151,37 +152,6 @@ void testGeneratedMemberAccessorReturnVoid() throws NoSuchMethodException {
assertThat(memberAccessor.getSpeedNote()).isEqualTo("Fast access with generated bytecode");
}
- @Test
- void testThrowsWhenGetterMethodHasParameters() throws NoSuchMethodException {
- var member = GizmoTestdataEntity.class.getMethod("methodWithParameters", String.class);
- assertThatCode(() -> {
- GizmoMemberAccessorImplementor.createAccessorFor(member, PlanningVariable.class,
- AccessorInfo.withReturnValueAndNoArguments(),
- new GizmoClassLoader());
- }).hasMessage("The getterMethod (methodWithParameters) with a PlanningVariable annotation " +
- "must not have any parameters, but has parameters ([Ljava/lang/String;]).");
- }
-
- @Test
- void testThrowsWhenGetterMethodReturnVoid() throws NoSuchMethodException {
- var member = GizmoTestdataEntity.class.getMethod("getVoid");
- assertThatCode(() -> {
- GizmoMemberAccessorImplementor.createAccessorFor(member, PlanningVariable.class,
- AccessorInfo.withReturnValueAndNoArguments(),
- new GizmoClassLoader());
- }).hasMessage("The getterMethod (getVoid) with a PlanningVariable annotation must have a non-void return type.");
- }
-
- @Test
- void testThrowsWhenReadMethodReturnVoid() throws NoSuchMethodException {
- var member = GizmoTestdataEntity.class.getMethod("voidMethod");
- assertThatCode(() -> {
- GizmoMemberAccessorImplementor.createAccessorFor(member, PlanningVariable.class,
- AccessorInfo.withReturnValueAndNoArguments(),
- new GizmoClassLoader());
- }).hasMessage("The readMethod (voidMethod) with a PlanningVariable annotation must have a non-void return type.");
- }
-
@Test
void testGeneratedMemberAccessorForBooleanMethod() throws NoSuchMethodException {
var member = GizmoTestdataEntity.class.getMethod("isPinned");
@@ -203,20 +173,6 @@ void testGeneratedMemberAccessorForBooleanMethod() throws NoSuchMethodException
assertThat(memberAccessor.executeGetter(testdataEntity)).isEqualTo(true);
}
- @Test
- void testThrowsWhenGetBooleanReturnsNonBoolean() throws NoSuchMethodException {
- var member = GizmoTestdataEntity.class.getMethod("isAMethodThatHasABadName");
- assertThatCode(
- () -> GizmoMemberAccessorImplementor.createAccessorFor(member, PlanningVariable.class,
- AccessorInfo.withReturnValueAndNoArguments(),
- new GizmoClassLoader()))
- .hasMessage("""
- The getterMethod (isAMethodThatHasABadName) with a PlanningVariable annotation \
- must have a primitive boolean return type but returns (L%s;).
- Maybe rename the method (getAMethodThatHasABadName)?"""
- .formatted(String.class.getName().replace('.', '/')));
- }
-
@Test
void testMethodAnnotatedEntity() throws NoSuchMethodException {
var member = TestdataEntityProvidingWithParameterEntity.class.getMethod("getValueRange",
@@ -280,19 +236,6 @@ void invalidEntityReadMethodWithParameter() throws NoSuchMethodException {
"The getterMethod (getValueRange) must have only one parameter");
}
- @Test
- void invalidSolutionReadMethodWithParameter() throws NoSuchMethodException {
- var member = TestdataInvalidParameterSolution.class.getMethod("getValueList",
- TestdataInvalidParameterSolution.class);
- assertThatCode(() -> GizmoMemberAccessorImplementor.createAccessorFor(member, ValueRangeProvider.class,
- AccessorInfo.withReturnValueAndNoArguments(),
- new GizmoClassLoader()))
- .hasMessageContaining(
- "The getterMethod (getValueList) with a ValueRangeProvider annotation must not have any parameters")
- .hasMessageContaining(
- "but has parameters ([Lai/timefold/solver/core/testdomain/valuerange/parameter/invalid/TestdataInvalidParameterSolution;])");
- }
-
@Test
void testForbiddenEntityReadWithoutParameter() throws NoSuchMethodException {
var member = TestdataEntityProvidingWithParameterEntity.class.getMethod("getValueRange",
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/AbstractLookupTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/AbstractLookupTest.java
index 3642a800975..f1def15bd9a 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/AbstractLookupTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/AbstractLookupTest.java
@@ -1,6 +1,6 @@
package ai.timefold.solver.core.impl.domain.lookup;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
+import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy;
@@ -23,7 +23,7 @@ void setUpLookUpManager() {
protected LookUpStrategyResolver createLookupStrategyResolver(LookUpStrategyType lookUpStrategyType) {
DescriptorPolicy descriptorPolicy = new DescriptorPolicy();
descriptorPolicy.setMemberAccessorFactory(new MemberAccessorFactory());
- descriptorPolicy.setDomainAccessType(DomainAccessType.REFLECTION);
+ descriptorPolicy.setDomainAccessType(DomainAccessType.FORCE_REFLECTION);
return new LookUpStrategyResolver(descriptorPolicy, lookUpStrategyType);
}
}
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerTest.java
index 3c0346d1185..4ed5a838af8 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerTest.java
@@ -13,6 +13,7 @@
import ai.timefold.solver.core.api.domain.solution.cloner.SolutionCloner;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
+import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.AccessorInfo;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.GizmoClassLoader;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.GizmoMemberDescriptor;
import ai.timefold.solver.core.impl.domain.solution.cloner.AbstractSolutionClonerTest;
@@ -99,7 +100,8 @@ private GizmoSolutionOrEntityDescriptor generateGizmoSolutionOrEntityDescriptor(
var name = field.getName();
if (Modifier.isPublic(field.getModifiers())) {
- member = new GizmoMemberDescriptor(name, memberDescriptor, declaringClass);
+ member = new GizmoMemberDescriptor(name, memberDescriptor, declaringClass,
+ AccessorInfo.withReturnValueAndNoArguments());
} else {
var getter = ReflectionHelper.getGetterMethod(currentClass, field.getName());
var setter = ReflectionHelper.getSetterMethod(currentClass, field.getName());
diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptorTest.java
index a349f2d9196..01db6ec9d1a 100644
--- a/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptorTest.java
+++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptorTest.java
@@ -81,7 +81,7 @@ void readMethodProblemFactCollectionProperty() {
@Test
void problemFactCollectionPropertyWithArgument() {
- assertThatIllegalStateException().isThrownBy(
+ assertThatIllegalArgumentException().isThrownBy(
TestdataProblemFactCollectionPropertyWithArgumentSolution::buildSolutionDescriptor);
}
diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/solutionproperties/invalid/TestdataUnknownFactTypeSolution.java b/core/src/test/java/ai/timefold/solver/core/testdomain/solutionproperties/invalid/TestdataUnknownFactTypeSolution.java
index 18efcbe319b..7c6fc9e1db0 100644
--- a/core/src/test/java/ai/timefold/solver/core/testdomain/solutionproperties/invalid/TestdataUnknownFactTypeSolution.java
+++ b/core/src/test/java/ai/timefold/solver/core/testdomain/solutionproperties/invalid/TestdataUnknownFactTypeSolution.java
@@ -38,6 +38,10 @@ public List getValueList() {
return valueList;
}
+ public MyStringCollection getFacts() {
+ return facts;
+ }
+
public static interface MyStringCollection extends Collection {
}
diff --git a/docs/TODO.md b/docs/TODO.md
index a463be529a7..df692a1d046 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -27,8 +27,9 @@
- [ ] BestSolutionChangedEvent now an interface. (All constructors were deprecated anyway.)
- [ ] ProblemFactChange -> ProblemChange, migration script?
- [ ] PinningFilter is gone, so is strengths and difficulties, and nullable.
-- [ ] PlanningId moves from domain.lookup to domain.entity.
+- [ ] PlanningId moves from domain.lookup to domain.common.
- [ ] domain.lookup package is gone, so is lookup from PlanningSolution.
- [ ] lookups no longer accept null values.
+- [ ] `DomainAccessType` is gone, code uses GIZMO when possible
Remove this file when done.
\ No newline at end of file
diff --git a/docs/src/modules/ROOT/pages/integration/config-properties.adoc b/docs/src/modules/ROOT/pages/integration/config-properties.adoc
index 8bbe04db21b..c878c1e5905 100644
--- a/docs/src/modules/ROOT/pages/integration/config-properties.adoc
+++ b/docs/src/modules/ROOT/pages/integration/config-properties.adoc
@@ -44,18 +44,6 @@ Note that this is a feature of the xref:enterprise-edition/enterprise-edition.ad
which is Timefold's commercial offering.
See xref:enterprise-edition/enterprise-edition.adoc#multithreadedIncrementalSolving[multithreaded incremental solving].
-{property_prefix}timefold.solver.{solver_name_prefix}domain-access-type::
-How Timefold Solver should access the domain model.
-See xref:using-timefold-solver/configuration.adoc#domainAccess[the domain access section] for more details.
-ifeval::["{property_prefix}" == "quarkus."]
-Defaults to `GIZMO`.
-The other possible value is `REFLECTION`.
-endif::[]
-ifeval::["{property_prefix}" == ""]
-Defaults to `REFLECTION`.
-The other possible value is `GIZMO`.
-endif::[]
-
{property_prefix}timefold.solver.{solver_name_prefix}nearby-distance-meter-class::
Enable the xref:enterprise-edition/enterprise-edition.adoc#nearbySelection[Nearby Selection] quick configuration.
If the Nearby Selection distance meter class is specified,
diff --git a/docs/src/modules/ROOT/pages/integration/integration.adoc b/docs/src/modules/ROOT/pages/integration/integration.adoc
index 21cafdf98df..c10e8e5914a 100644
--- a/docs/src/modules/ROOT/pages/integration/integration.adoc
+++ b/docs/src/modules/ROOT/pages/integration/integration.adoc
@@ -289,8 +289,8 @@ include::config-properties.adoc[]
The Quarkus integration allows the injection of several managed resources, including `SolverConfig`, `SolverFactory`,
`SolverManager`, `SolutionManager`, `ConstraintVerifier` and `ConstraintMetaModel`.
-The `SolverConfig` resource is constructed by reading the `application.properties` file and classpath. Therefore, Domain entities
-(solution, entities, and constraint classes) and customized properties (`spent-limit`, `domain-access-type`, etc.) for the
+The `SolverConfig` resource is constructed by reading the `application.properties` file and classpath.
+Therefore, Domain entities (solution, entities, and constraint classes) and customized properties (`spent-limit`, etc.) for the
planning problem are identified and loaded into the solver configuration.
The available resouses can be injected as follows:
@@ -647,8 +647,7 @@ include::config-properties.adoc[]
The Spring Boot integration allows the injection of several managed resources, including `SolverConfig`, `SolverFactory`,
`SolverManager`, `SolutionManager`, `ConstraintMetaModel` and `ConstraintVerifier`.
-The `SolverConfig` resource is constructed by reading the `application.properties` file and classpath. Therefore, Domain entities
-(solution, entities, and constraint classes) and customized properties (`spent-limit`, `domain-access-type`, etc.) for the
+The `SolverConfig` resource is constructed by reading the `application.properties` file and classpath. Therefore, Domain entities (solution, entities, and constraint classes) and customized properties (`spent-limit`, etc.) for the
planning problem are identified and loaded into the solver configuration.
The available resouses can be injected as follows:
diff --git a/docs/src/modules/ROOT/pages/using-timefold-solver/configuration.adoc b/docs/src/modules/ROOT/pages/using-timefold-solver/configuration.adoc
index e56174c25db..81645507ae6 100644
--- a/docs/src/modules/ROOT/pages/using-timefold-solver/configuration.adoc
+++ b/docs/src/modules/ROOT/pages/using-timefold-solver/configuration.adoc
@@ -148,33 +148,6 @@ Such a field does not need to be public.
This manual focuses on the first manner, but every feature supports both, even if it's not explicitly mentioned.
-[#domainAccess]
-== Domain access
-
-Timefold Solver by default accesses your domain using reflection, which
-will always work, but is slow compared to direct access.
-Alternatively, you can configure Timefold Solver to access your domain
-using Gizmo, which will generate bytecode that directly access the
-fields/methods of your domain without reflection. However, it comes with some restrictions:
-
-* All fields in the domain must be public.
-* The planning annotations can only be on public fields and
- public getters.
-* io.quarkus.gizmo:gizmo must be on the classpath.
-
-These restrictions do not apply when using Timefold Solver with Quarkus,
-where Gizmo is the default domain access type.
-
-To use Gizmo outside of Quarkus, set the `domainAccessType` in the
-Solver Configuration:
-
-[source,xml,options="nowrap"]
-----
-
- GIZMO
-
-----
-
[#customPropertiesConfiguration]
== Custom properties configuration
diff --git a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/GizmoMemberAccessorEntityEnhancer.java b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/GizmoMemberAccessorEntityEnhancer.java
index 5be8a963778..32c210d14a2 100644
--- a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/GizmoMemberAccessorEntityEnhancer.java
+++ b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/GizmoMemberAccessorEntityEnhancer.java
@@ -192,7 +192,7 @@ public String generateMethodAccessor(@Nullable AnnotationInstance annotationInst
GizmoMemberDescriptor.getMethodParameterType(methodMember, accessorInfo.readMethodWithParameter());
if (Modifier.isPublic(methodInfo.flags())) {
descriptor = new GizmoMemberDescriptor(name, memberDescriptor, methodParameterType, declaringClass,
- setterDescriptor.orElse(null));
+ setterDescriptor.orElse(null), accessorInfo);
} else {
setterDescriptor = addVirtualMethodGetter(classInfo, methodInfo, name, setterDescriptor.orElse(null), transformers);
var methodName = getVirtualGetterName(false, name);
@@ -201,7 +201,7 @@ public String generateMethodAccessor(@Nullable AnnotationInstance annotationInst
memberDescriptor.returnType());
descriptor =
new GizmoMemberDescriptor(name, newMethodDescriptor, memberDescriptor, methodParameterType, declaringClass,
- setterDescriptor.orElse(null));
+ setterDescriptor.orElse(null), accessorInfo);
}
Class extends Annotation> annotationClass = null;
if (accessorInfo.returnTypeRequired() || annotationInstance != null) {
@@ -387,7 +387,8 @@ private GizmoMemberDescriptor createMemberDescriptorForField(Field field,
// Not being recorded, so can use Type and annotated element directly
if ((ReflectionHelper.hasGetterMethod(declaringClass, name) || Modifier.isPublic(field.getModifiers()))
&& !isForCloning) {
- return new GizmoMemberDescriptor(name, memberDescriptor, declaringClass);
+ return new GizmoMemberDescriptor(name, memberDescriptor, declaringClass,
+ AccessorInfo.withReturnValueAndNoArguments());
} else {
addVirtualFieldGetterAndSetter(declaringClass, field, transformers);
var getterName = getVirtualGetterName(true, name);
diff --git a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java
index 773a32ef995..400cbb72284 100644
--- a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java
+++ b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java
@@ -1,7 +1,6 @@
package ai.timefold.solver.quarkus.deployment;
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
-import static java.lang.String.format;
import java.lang.constant.ClassDesc;
import java.lang.reflect.Field;
@@ -26,7 +25,6 @@
import jakarta.inject.Singleton;
import ai.timefold.solver.core.api.domain.autodiscover.AutoDiscoverMemberType;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty;
import ai.timefold.solver.core.api.domain.solution.PlanningScore;
@@ -44,7 +42,9 @@
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.config.solver.SolverConfig;
import ai.timefold.solver.core.config.solver.SolverManagerConfig;
+import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
+import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType;
import ai.timefold.solver.core.impl.domain.common.accessor.gizmo.AccessorInfo;
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
import ai.timefold.solver.core.impl.domain.variable.declarative.RootVariableSource;
@@ -610,14 +610,9 @@ void buildConstraintMetaModel(SolverConfigBuildItem solverConfigBuildItem,
var constraintMetaModelsBySolverNames = new HashMap();
solverConfigBuildItem.getSolverConfigMap().forEach((solverName, solverConfig) -> {
- // Gizmo-generated member accessors are not yet available at build time.
- var originalDomainAccessType = solverConfig.getDomainAccessType();
- solverConfig.setDomainAccessType(DomainAccessType.REFLECTION);
-
var solverFactory = SolverFactory.create(solverConfig);
var constraintMetaModel = BeanUtil.buildConstraintMetaModel(solverFactory);
// Avoid changing the original solver config.
- solverConfig.setDomainAccessType(originalDomainAccessType);
constraintMetaModelsBySolverNames.put(solverName, constraintMetaModel);
});
@@ -804,13 +799,6 @@ private void applySolverProperties(IndexView indexView, String solverName, Solve
applyScoreDirectorFactoryProperties(indexView, solverConfig);
// Override the current configuration with values from the solver properties
- timefoldBuildTimeConfig.getSolverConfig(solverName).flatMap(SolverBuildTimeConfig::domainAccessType)
- .ifPresent(solverConfig::setDomainAccessType);
-
- if (solverConfig.getDomainAccessType() == null) {
- solverConfig.setDomainAccessType(DomainAccessType.GIZMO);
- }
-
timefoldBuildTimeConfig.getSolverConfig(solverName)
.flatMap(SolverBuildTimeConfig::enabledPreviewFeatures)
.ifPresent(solverConfig::setEnablePreviewFeatureSet);
@@ -981,155 +969,156 @@ private GeneratedGizmoClasses generateDomainAccessors(Map
* "entity" in this context means both "planning solution",
* "planning entity" and other things as well.
*/
- assertSolverDomainAccessType(solverConfigMap);
var entityEnhancer = new GizmoMemberAccessorEntityEnhancer();
- if (solverConfigMap.values().stream().anyMatch(c -> c.getDomainAccessType() == DomainAccessType.GIZMO)) {
- var membersToGeneratedAccessorsForCollection = new ArrayList();
+ var membersToGeneratedAccessorsForCollection = new ArrayList();
+
+ // Every entity and solution gets scanned for annotations.
+ // Annotated members get their accessors generated.
+ for (var dotName : DotNames.GIZMO_MEMBER_ACCESSOR_ANNOTATIONS) {
+ membersToGeneratedAccessorsForCollection.addAll(indexView.getAnnotationsWithRepeatable(dotName, indexView));
+ }
+ generateDomainAccessorsForShadowSources(indexView, membersToGeneratedAccessorsForCollection);
+ membersToGeneratedAccessorsForCollection.removeIf(this::shouldIgnoreMember);
+
+ // Fail fast on auto-discovery.
+ var planningSolutionAnnotationInstanceCollection = getAllConcreteSolutionClasses(indexView);
+ var unconfiguredSolverConfigList = solverConfigMap.entrySet().stream()
+ .filter(e -> e.getValue().getSolutionClass() == null)
+ .map(Map.Entry::getKey)
+ .toList();
+ var unusedSolutionClassList = planningSolutionAnnotationInstanceCollection.stream()
+ .map(planningClass -> planningClass.target().asClass().name().toString())
+ .filter(planningClassName -> reflectiveClassSet.stream()
+ .noneMatch(clazz -> clazz.getName().equals(planningClassName)))
+ .toList();
+ if (planningSolutionAnnotationInstanceCollection.isEmpty()) {
+ throw new IllegalStateException(
+ "No classes found with a @%s annotation.".formatted(PlanningSolution.class.getSimpleName()));
+ } else if (planningSolutionAnnotationInstanceCollection.size() > 1 && !unconfiguredSolverConfigList.isEmpty()
+ && !unusedSolutionClassList.isEmpty()) {
+ throw new IllegalStateException(
+ "Unused classes (%s) found with a @%s annotation.".formatted(String.join(", ", unusedSolutionClassList),
+ PlanningSolution.class.getSimpleName()));
+ }
- // Every entity and solution gets scanned for annotations.
- // Annotated members get their accessors generated.
- for (var dotName : DotNames.GIZMO_MEMBER_ACCESSOR_ANNOTATIONS) {
- membersToGeneratedAccessorsForCollection.addAll(indexView.getAnnotationsWithRepeatable(dotName, indexView));
+ planningSolutionAnnotationInstanceCollection.forEach(planningSolutionAnnotationInstance -> {
+ var autoDiscoverMemberType = planningSolutionAnnotationInstance.values().stream()
+ .filter(v -> v.name().equals("autoDiscoverMemberType"))
+ .findFirst()
+ .map(AnnotationValue::asEnum)
+ .map(AutoDiscoverMemberType::valueOf)
+ .orElse(AutoDiscoverMemberType.NONE);
+
+ if (autoDiscoverMemberType != AutoDiscoverMemberType.NONE) {
+ throw new UnsupportedOperationException("""
+ Auto-discovery of members using %s is not supported under Quarkus.
+ Remove the autoDiscoverMemberType property from the @%s annotation
+ and explicitly annotate the fields or getters with annotations such as @%s, @%s or @%s."""
+ .strip()
+ .formatted(
+ AutoDiscoverMemberType.class.getSimpleName(),
+ PlanningSolution.class.getSimpleName(),
+ PlanningScore.class.getSimpleName(),
+ PlanningEntityCollectionProperty.class.getSimpleName(),
+ ProblemFactCollectionProperty.class.getSimpleName()));
}
- generateDomainAccessorsForShadowSources(indexView, membersToGeneratedAccessorsForCollection);
- membersToGeneratedAccessorsForCollection.removeIf(this::shouldIgnoreMember);
-
- // Fail fast on auto-discovery.
- var planningSolutionAnnotationInstanceCollection = getAllConcreteSolutionClasses(indexView);
- var unconfiguredSolverConfigList = solverConfigMap.entrySet().stream()
- .filter(e -> e.getValue().getSolutionClass() == null)
- .map(Map.Entry::getKey)
- .toList();
- var unusedSolutionClassList = planningSolutionAnnotationInstanceCollection.stream()
- .map(planningClass -> planningClass.target().asClass().name().toString())
- .filter(planningClassName -> reflectiveClassSet.stream()
- .noneMatch(clazz -> clazz.getName().equals(planningClassName)))
- .toList();
- if (planningSolutionAnnotationInstanceCollection.isEmpty()) {
- throw new IllegalStateException(
- "No classes found with a @%s annotation.".formatted(PlanningSolution.class.getSimpleName()));
- } else if (planningSolutionAnnotationInstanceCollection.size() > 1 && !unconfiguredSolverConfigList.isEmpty()
- && !unusedSolutionClassList.isEmpty()) {
- throw new IllegalStateException(
- "Unused classes (%s) found with a @%s annotation.".formatted(String.join(", ", unusedSolutionClassList),
- PlanningSolution.class.getSimpleName()));
+ });
+ var solutionClassInstance = planningSolutionAnnotationInstanceCollection.iterator().next();
+ var solutionClassInfo = solutionClassInstance.target().asClass();
+ var visited = new HashSet();
+ for (var annotatedMember : membersToGeneratedAccessorsForCollection) {
+ ClassInfo classInfo = null;
+ String memberName = null;
+ if (!visited.add(annotatedMember.target())) {
+ continue;
}
-
- planningSolutionAnnotationInstanceCollection.forEach(planningSolutionAnnotationInstance -> {
- var autoDiscoverMemberType = planningSolutionAnnotationInstance.values().stream()
- .filter(v -> v.name().equals("autoDiscoverMemberType"))
- .findFirst()
- .map(AnnotationValue::asEnum)
- .map(AutoDiscoverMemberType::valueOf)
- .orElse(AutoDiscoverMemberType.NONE);
-
- if (autoDiscoverMemberType != AutoDiscoverMemberType.NONE) {
- throw new UnsupportedOperationException("""
- Auto-discovery of members using %s is not supported under Quarkus.
- Remove the autoDiscoverMemberType property from the @%s annotation
- and explicitly annotate the fields or getters with annotations such as @%s, @%s or @%s."""
- .strip()
- .formatted(
- AutoDiscoverMemberType.class.getSimpleName(),
- PlanningSolution.class.getSimpleName(),
- PlanningScore.class.getSimpleName(),
- PlanningEntityCollectionProperty.class.getSimpleName(),
- ProblemFactCollectionProperty.class.getSimpleName()));
+ switch (annotatedMember.target().kind()) {
+ case FIELD -> {
+ var fieldInfo = annotatedMember.target().asField();
+ classInfo = fieldInfo.declaringClass();
+ memberName = fieldInfo.name();
+ buildFieldAccessor(annotatedMember, generatedMemberAccessorsClassNameSet, entityEnhancer, classOutput,
+ classInfo, fieldInfo, transformers);
}
- });
- var solutionClassInstance = planningSolutionAnnotationInstanceCollection.iterator().next();
- var solutionClassInfo = solutionClassInstance.target().asClass();
- var visited = new HashSet();
- for (var annotatedMember : membersToGeneratedAccessorsForCollection) {
- ClassInfo classInfo = null;
- String memberName = null;
- if (!visited.add(annotatedMember.target())) {
- continue;
+ case METHOD -> {
+ var methodInfo = annotatedMember.target().asMethod();
+ classInfo = methodInfo.declaringClass();
+ memberName = methodInfo.name();
+ buildMethodAccessor(annotatedMember, generatedMemberAccessorsClassNameSet, entityEnhancer, classOutput,
+ classInfo, methodInfo,
+ AccessorInfo.of((annotatedMember.name().equals(DotNames.VALUE_RANGE_PROVIDER))
+ ? MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER
+ : MemberAccessorType.FIELD_OR_READ_METHOD),
+ transformers);
}
- switch (annotatedMember.target().kind()) {
- case FIELD -> {
- var fieldInfo = annotatedMember.target().asField();
- classInfo = fieldInfo.declaringClass();
- memberName = fieldInfo.name();
- buildFieldAccessor(annotatedMember, generatedMemberAccessorsClassNameSet, entityEnhancer, classOutput,
- classInfo, fieldInfo, transformers);
- }
- case METHOD -> {
- var methodInfo = annotatedMember.target().asMethod();
- classInfo = methodInfo.declaringClass();
- memberName = methodInfo.name();
- buildMethodAccessor(annotatedMember, generatedMemberAccessorsClassNameSet, entityEnhancer, classOutput,
- classInfo, methodInfo,
- AccessorInfo.of(true, annotatedMember.name().equals(DotNames.VALUE_RANGE_PROVIDER)),
- transformers);
- }
- default -> throw new IllegalStateException(
- "The member (%s) is not on a field or method.".formatted(annotatedMember));
+ default -> throw new IllegalStateException(
+ "The member (%s) is not on a field or method.".formatted(annotatedMember));
+ }
+ if (annotatedMember.name().equals(DotNames.CASCADING_UPDATE_SHADOW_VARIABLE)) {
+ // The source method name also must be included
+ // targetMethodName is a required field and is always present
+ var targetMethodName = annotatedMember.value("targetMethodName").asString();
+ var methodInfo = classInfo.method(targetMethodName);
+ buildMethodAccessor(null, generatedMemberAccessorsClassNameSet, entityEnhancer, classOutput, classInfo,
+ methodInfo, AccessorInfo.of(MemberAccessorType.VOID_METHOD), transformers);
+ } else if (annotatedMember.name().equals(DotNames.SHADOW_VARIABLE)
+ && annotatedMember.value("supplierName") != null) {
+ // The source method name also must be included
+ var targetMethodName = annotatedMember.value("supplierName")
+ .asString();
+ var methodInfo = classInfo.method(targetMethodName);
+ if (methodInfo == null) {
+ // Retry with the solution class
+ var solutionType = Type.create(solutionClassInfo.name(), Type.Kind.CLASS);
+ methodInfo = classInfo.method(targetMethodName, solutionType);
}
- if (annotatedMember.name().equals(DotNames.CASCADING_UPDATE_SHADOW_VARIABLE)) {
- // The source method name also must be included
- // targetMethodName is a required field and is always present
- var targetMethodName = annotatedMember.value("targetMethodName").asString();
- var methodInfo = classInfo.method(targetMethodName);
- buildMethodAccessor(null, generatedMemberAccessorsClassNameSet, entityEnhancer, classOutput, classInfo,
- methodInfo, AccessorInfo.of(false, false), transformers);
- } else if (annotatedMember.name().equals(DotNames.SHADOW_VARIABLE)
- && annotatedMember.value("supplierName") != null) {
- // The source method name also must be included
- var targetMethodName = annotatedMember.value("supplierName")
- .asString();
- var methodInfo = classInfo.method(targetMethodName);
- if (methodInfo == null) {
- // Retry with the solution class
- var solutionType = Type.create(solutionClassInfo.name(), Type.Kind.CLASS);
- methodInfo = classInfo.method(targetMethodName, solutionType);
- }
- if (methodInfo == null) {
- throw new IllegalArgumentException(
- """
- @%s (%s) defines a supplierName (%s) that does not exist inside its declaring class (%s).
- Maybe you included a parameter which is not a planning solution (%s)?
- Maybe you misspelled the supplierName name?"""
- .formatted(ShadowVariable.class.getSimpleName(), memberName, targetMethodName,
- classInfo.name().toString(), solutionClassInfo.name().toString()));
- }
- buildMethodAccessor(annotatedMember, generatedMemberAccessorsClassNameSet, entityEnhancer, classOutput,
- classInfo, methodInfo, AccessorInfo.of(true, !methodInfo.parameterTypes().isEmpty()), transformers);
+ if (methodInfo == null) {
+ throw new IllegalArgumentException("""
+ @%s (%s) defines a supplierName (%s) that does not exist inside its declaring class (%s).
+ Maybe you included a parameter which is not a planning solution (%s)?
+ Maybe you misspelled the supplierName name?"""
+ .formatted(ShadowVariable.class.getSimpleName(), memberName, targetMethodName,
+ classInfo.name().toString(), solutionClassInfo.name().toString()));
}
+ buildMethodAccessor(annotatedMember, generatedMemberAccessorsClassNameSet, entityEnhancer, classOutput,
+ classInfo, methodInfo,
+ AccessorInfo
+ .of(MemberAccessorType.FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER),
+ transformers);
}
- // The ConstraintWeightOverrides field is not annotated, but it needs a member accessor
- var constraintFieldInfo = solutionClassInfo.fields().stream()
- .filter(f -> f.type().name().equals(DotNames.CONSTRAINT_WEIGHT_OVERRIDES))
+ }
+ // The ConstraintWeightOverrides field is not annotated, but it needs a member accessor
+ var constraintFieldInfo = solutionClassInfo.fields().stream()
+ .filter(f -> f.type().name().equals(DotNames.CONSTRAINT_WEIGHT_OVERRIDES))
+ .findFirst()
+ .orElse(null);
+ if (constraintFieldInfo != null) {
+ // Prefer method to field
+ var solutionClass = convertClassInfoToClass(solutionClassInfo);
+ var constraintMethod =
+ ReflectionHelper.getGetterMethod(solutionClass, constraintFieldInfo.name());
+ var constraintMethodInfo = solutionClassInfo.methods().stream()
+ .filter(m -> constraintMethod != null && m.name().equals(constraintMethod.getName())
+ && m.parametersCount() == 0)
.findFirst()
.orElse(null);
- if (constraintFieldInfo != null) {
- // Prefer method to field
- var solutionClass = convertClassInfoToClass(solutionClassInfo);
- var constraintMethod =
- ReflectionHelper.getGetterMethod(solutionClass, constraintFieldInfo.name());
- var constraintMethodInfo = solutionClassInfo.methods().stream()
- .filter(m -> constraintMethod != null && m.name().equals(constraintMethod.getName())
- && m.parametersCount() == 0)
- .findFirst()
- .orElse(null);
- if (constraintMethodInfo != null) {
- buildMethodAccessor(solutionClassInstance, generatedMemberAccessorsClassNameSet, entityEnhancer,
- classOutput, solutionClassInfo, constraintMethodInfo, AccessorInfo.withReturnValueAndNoArguments(),
- transformers);
- } else {
- buildFieldAccessor(solutionClassInstance, generatedMemberAccessorsClassNameSet, entityEnhancer, classOutput,
- solutionClassInfo, constraintFieldInfo, transformers);
- }
+ if (constraintMethodInfo != null) {
+ buildMethodAccessor(solutionClassInstance, generatedMemberAccessorsClassNameSet, entityEnhancer,
+ classOutput, solutionClassInfo, constraintMethodInfo, AccessorInfo.withReturnValueAndNoArguments(),
+ transformers);
+ } else {
+ buildFieldAccessor(solutionClassInstance, generatedMemberAccessorsClassNameSet, entityEnhancer, classOutput,
+ solutionClassInfo, constraintFieldInfo, transformers);
}
- // Using REFLECTION domain access type so Timefold doesn't try to generate GIZMO code
- solverConfigMap.values().forEach(c -> {
- var solutionDescriptor = SolutionDescriptor.buildSolutionDescriptor(
- c.getEnablePreviewFeatureSet(), DomainAccessType.REFLECTION,
- c.getSolutionClass(), null, null, c.getEntityClassList());
- gizmoSolutionClonerClassNameSet
- .add(entityEnhancer.generateSolutionCloner(solutionDescriptor, classOutput, indexView, transformers));
- });
}
+ // Using REFLECTION domain access type so Timefold doesn't try to generate GIZMO code
+ solverConfigMap.values().forEach(c -> {
+ var solutionDescriptor = SolutionDescriptor.buildSolutionDescriptor(
+ c.getEnablePreviewFeatureSet(), DomainAccessType.FORCE_REFLECTION,
+ c.getSolutionClass(), null, null, c.getEntityClassList());
+ gizmoSolutionClonerClassNameSet
+ .add(entityEnhancer.generateSolutionCloner(solutionDescriptor, classOutput, indexView, transformers));
+ });
entityEnhancer.generateGizmoBeanFactory(beanClassOutput, reflectiveClassSet, transformers);
return new GeneratedGizmoClasses(generatedMemberAccessorsClassNameSet, gizmoSolutionClonerClassNameSet);
@@ -1210,19 +1199,6 @@ private static void buildMethodAccessor(AnnotationInstance annotatedMember,
}
}
- private void assertSolverDomainAccessType(Map solverConfigMap) {
- // All solver must use the same domain access type
- if (solverConfigMap.values().stream().map(SolverConfig::getDomainAccessType).distinct().count() > 1) {
- throw new ConfigurationException(
- """
- The domain access type must be unique across all Solver configurations.
- %s""".formatted(solverConfigMap.entrySet().stream()
- .map(e -> format("quarkus.timefold.\"%s\".domain-access-type=%s",
- e.getKey(), e.getValue().getDomainAccessType()))
- .collect(Collectors.joining("\n"))));
- }
- }
-
private boolean shouldIgnoreMember(AnnotationInstance annotationInstance) {
return switch (annotationInstance.target().kind()) {
case FIELD -> (annotationInstance.target().asField().flags() & Modifier.STATIC) != 0;
diff --git a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/config/SolverBuildTimeConfig.java b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/config/SolverBuildTimeConfig.java
index 1d0858db5f6..1984e2fbf2a 100644
--- a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/config/SolverBuildTimeConfig.java
+++ b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/config/SolverBuildTimeConfig.java
@@ -3,7 +3,6 @@
import java.util.Optional;
import java.util.Set;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.config.solver.SolverConfig;
import ai.timefold.solver.quarkus.config.SolverRuntimeConfig;
@@ -27,14 +26,6 @@ public interface SolverBuildTimeConfig {
// which generates the constructor of classes used by Quarkus
Optional solverConfigXml();
- /**
- * Determines how to access the fields and methods of domain classes.
- * Defaults to {@link DomainAccessType#GIZMO}.
- */
- // Build time - GIZMO classes are only generated if at least one solver
- // has domain access type GIZMO
- Optional domainAccessType();
-
/**
* Enable the Nearby Selection quick configuration.
*/
diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorNearbySolverYamlTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorNearbySolverYamlTest.java
index ced3351578e..613c8635adc 100644
--- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorNearbySolverYamlTest.java
+++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorNearbySolverYamlTest.java
@@ -9,7 +9,6 @@
import jakarta.inject.Inject;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.score.SimpleScore;
import ai.timefold.solver.core.api.solver.SolverFactory;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
@@ -46,7 +45,6 @@ void solverProperties() {
assertNotNull(solverConfig.getNearbyDistanceMeterClass());
assertTrue(solverConfig.getDaemon());
assertEquals("2", solverConfig.getMoveThreadCount());
- assertEquals(DomainAccessType.REFLECTION, solverConfig.getDomainAccessType());
assertNotNull(solverFactory);
}
diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverPropertiesTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverPropertiesTest.java
index 75e27272a9c..a281074f1f7 100644
--- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverPropertiesTest.java
+++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverPropertiesTest.java
@@ -8,7 +8,6 @@
import jakarta.inject.Inject;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.score.SimpleScore;
import ai.timefold.solver.core.api.solver.SolverFactory;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
@@ -55,7 +54,6 @@ void solverProperties() {
assertEquals(EnvironmentMode.FULL_ASSERT, solverConfig.getEnvironmentMode());
assertTrue(solverConfig.getDaemon());
assertEquals("2", solverConfig.getMoveThreadCount());
- assertEquals(DomainAccessType.REFLECTION, solverConfig.getDomainAccessType());
assertNotNull(solverConfig.getNearbyDistanceMeterClass());
assertNotNull(solverFactory);
}
diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverResourcesTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverResourcesTest.java
index 8ac36d50a66..8af88f347b5 100644
--- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverResourcesTest.java
+++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverResourcesTest.java
@@ -7,7 +7,6 @@
import jakarta.inject.Inject;
import jakarta.inject.Named;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.score.BendableBigDecimalScore;
import ai.timefold.solver.core.api.score.BendableScore;
import ai.timefold.solver.core.api.score.HardMediumSoftBigDecimalScore;
@@ -89,7 +88,6 @@ void solverProperties() {
assertThat((Object) solverConfig.getEnvironmentMode()).isEqualTo(EnvironmentMode.FULL_ASSERT);
assertThat(solverConfig.getNearbyDistanceMeterClass()).isNull();
assertThat(solverConfig.getDaemon()).isTrue();
- assertThat(solverConfig.getDomainAccessType()).isEqualTo(DomainAccessType.REFLECTION);
assertThat(solver1Factory).isNotNull();
assertThat(solverConfig.getTerminationConfig().getSpentLimit()).isEqualTo(Duration.ofHours(4));
assertThat(solverConfig.getTerminationConfig().getUnimprovedSpentLimit()).isEqualTo(Duration.ofHours(5));
diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverYamlTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverYamlTest.java
index 113dd5b600d..4065c7dadd4 100644
--- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverYamlTest.java
+++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverYamlTest.java
@@ -8,7 +8,6 @@
import jakarta.inject.Inject;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.score.SimpleScore;
import ai.timefold.solver.core.api.solver.SolverFactory;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
@@ -43,7 +42,6 @@ void solverProperties() {
assertEquals(EnvironmentMode.FULL_ASSERT, solverConfig.getEnvironmentMode());
assertTrue(solverConfig.getDaemon());
assertEquals("2", solverConfig.getMoveThreadCount());
- assertEquals(DomainAccessType.REFLECTION, solverConfig.getDomainAccessType());
assertNotNull(solverFactory);
}
diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLDefaultTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLDefaultTest.java
index 85f5d2e00b8..8110b072734 100644
--- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLDefaultTest.java
+++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLDefaultTest.java
@@ -7,7 +7,6 @@
import jakarta.inject.Inject;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.solver.SolverFactory;
import ai.timefold.solver.core.config.solver.SolverConfig;
import ai.timefold.solver.quarkus.testdomain.normal.TestdataQuarkusConstraintProvider;
@@ -39,7 +38,6 @@ class TimefoldProcessorXMLDefaultTest {
void solverConfigXml_default() {
assertNotNull(solverConfig);
assertEquals(TestdataQuarkusSolution.class, solverConfig.getSolutionClass());
- assertEquals(DomainAccessType.GIZMO, solverConfig.getDomainAccessType());
assertEquals(Collections.singletonList(TestdataQuarkusEntity.class), solverConfig.getEntityClassList());
assertEquals(TestdataQuarkusConstraintProvider.class,
solverConfig.getScoreDirectorFactoryConfig().getConstraintProviderClass());
diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLNearbyPropertyTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLNearbyPropertyTest.java
index 68e34b976e2..869b63a3d1a 100644
--- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLNearbyPropertyTest.java
+++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLNearbyPropertyTest.java
@@ -7,7 +7,6 @@
import jakarta.inject.Inject;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.solver.SolverFactory;
import ai.timefold.solver.core.config.solver.SolverConfig;
import ai.timefold.solver.quarkus.testdomain.dummy.DummyDistanceMeter;
@@ -41,7 +40,6 @@ class TimefoldProcessorXMLNearbyPropertyTest {
void solverConfigXml_property() {
assertNotNull(solverConfig);
assertNotNull(solverConfig.getNearbyDistanceMeterClass());
- assertEquals(DomainAccessType.GIZMO, solverConfig.getDomainAccessType());
assertEquals(TestdataQuarkusSolution.class, solverConfig.getSolutionClass());
assertEquals(Collections.singletonList(TestdataQuarkusEntity.class), solverConfig.getEntityClassList());
assertEquals(TestdataQuarkusConstraintProvider.class,
diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLNoneTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLNoneTest.java
index 06159003615..29b057bf494 100644
--- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLNoneTest.java
+++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLNoneTest.java
@@ -8,7 +8,6 @@
import jakarta.inject.Inject;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.solver.SolverFactory;
import ai.timefold.solver.core.config.solver.SolverConfig;
import ai.timefold.solver.quarkus.testdomain.normal.TestdataQuarkusConstraintProvider;
@@ -39,7 +38,6 @@ class TimefoldProcessorXMLNoneTest {
void solverConfigXml_default() {
assertNotNull(solverConfig);
assertEquals(TestdataQuarkusSolution.class, solverConfig.getSolutionClass());
- assertEquals(DomainAccessType.GIZMO, solverConfig.getDomainAccessType());
assertEquals(Collections.singletonList(TestdataQuarkusEntity.class), solverConfig.getEntityClassList());
assertEquals(TestdataQuarkusConstraintProvider.class,
solverConfig.getScoreDirectorFactoryConfig().getConstraintProviderClass());
diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLPropertyTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLPropertyTest.java
index a3f0e61c851..5d962e84bce 100644
--- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLPropertyTest.java
+++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorXMLPropertyTest.java
@@ -7,7 +7,6 @@
import jakarta.inject.Inject;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.solver.SolverFactory;
import ai.timefold.solver.core.config.solver.SolverConfig;
import ai.timefold.solver.quarkus.testdomain.normal.TestdataQuarkusConstraintProvider;
@@ -39,7 +38,6 @@ class TimefoldProcessorXMLPropertyTest {
@Test
void solverConfigXml_property() {
assertNotNull(solverConfig);
- assertEquals(DomainAccessType.GIZMO, solverConfig.getDomainAccessType());
assertEquals(TestdataQuarkusSolution.class, solverConfig.getSolutionClass());
assertEquals(Collections.singletonList(TestdataQuarkusEntity.class), solverConfig.getEntityClassList());
assertEquals(TestdataQuarkusConstraintProvider.class,
diff --git a/quarkus-integration/quarkus/deployment/src/test/resources/ai/timefold/solver/quarkus/single-solver/application-nearby.yaml b/quarkus-integration/quarkus/deployment/src/test/resources/ai/timefold/solver/quarkus/single-solver/application-nearby.yaml
index 0d8112f81c6..536b5f8d3a7 100644
--- a/quarkus-integration/quarkus/deployment/src/test/resources/ai/timefold/solver/quarkus/single-solver/application-nearby.yaml
+++ b/quarkus-integration/quarkus/deployment/src/test/resources/ai/timefold/solver/quarkus/single-solver/application-nearby.yaml
@@ -4,7 +4,6 @@ quarkus:
environment-mode: FULL_ASSERT
daemon: true
move-thread-count: 2
- domain-access-type: REFLECTION
nearby-distance-meter-class: ai.timefold.solver.quarkus.testdomain.dummy.DummyDistanceMeter
termination:
spent-limit: 4h
diff --git a/quarkus-integration/quarkus/deployment/src/test/resources/ai/timefold/solver/quarkus/single-solver/application.yaml b/quarkus-integration/quarkus/deployment/src/test/resources/ai/timefold/solver/quarkus/single-solver/application.yaml
index 5cfe19b3242..5bcae6babc7 100644
--- a/quarkus-integration/quarkus/deployment/src/test/resources/ai/timefold/solver/quarkus/single-solver/application.yaml
+++ b/quarkus-integration/quarkus/deployment/src/test/resources/ai/timefold/solver/quarkus/single-solver/application.yaml
@@ -4,7 +4,6 @@ quarkus:
environment-mode: FULL_ASSERT
daemon: true
move-thread-count: 2
- domain-access-type: REFLECTION
termination:
spent-limit: 4h
unimproved-spent-limit: 5h
diff --git a/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUIMultipleSolversTest.java b/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUIMultipleSolversTest.java
index ad0ed8f785a..2afefac4092 100644
--- a/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUIMultipleSolversTest.java
+++ b/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUIMultipleSolversTest.java
@@ -71,7 +71,6 @@ private void assertSolverConfigPage(String solverConfig) {
+ " " + TestdataStringLengthShadowSolution.class.getCanonicalName()
+ "\n"
+ " " + TestdataStringLengthShadowEntity.class.getCanonicalName() + "\n"
- + " GIZMO\n"
+ " \n"
+ " " + TestdataStringLengthConstraintProvider.class.getCanonicalName()
+ "\n"
diff --git a/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUITest.java b/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUITest.java
index 6efb9a1bb8e..e6c6a2257db 100644
--- a/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUITest.java
+++ b/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUITest.java
@@ -79,7 +79,6 @@ void testSolverConfigPage() throws Exception {
+ " " + TestdataStringLengthShadowSolution.class.getCanonicalName()
+ "\n"
+ " " + TestdataStringLengthShadowEntity.class.getCanonicalName() + "\n"
- + " GIZMO\n"
+ " \n"
+ " " + TestdataStringLengthConstraintProvider.class.getCanonicalName()
+ "\n"
diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java
index e88b533c104..e2691faf380 100644
--- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java
+++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotContribution.java
@@ -23,12 +23,8 @@ public TimefoldSolverAotContribution(Map solverConfigMap)
*/
private static void registerType(ReflectionHints reflectionHints, Class> type) {
reflectionHints.registerType(type,
- MemberCategory.INTROSPECT_PUBLIC_METHODS,
- MemberCategory.INTROSPECT_DECLARED_METHODS,
- MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS,
- MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS,
- MemberCategory.PUBLIC_FIELDS,
- MemberCategory.DECLARED_FIELDS,
+ MemberCategory.ACCESS_PUBLIC_FIELDS,
+ MemberCategory.ACCESS_DECLARED_FIELDS,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS,
diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java
index 2f4b5b411d9..026efca83f9 100644
--- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java
+++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java
@@ -277,9 +277,6 @@ private void applyScoreDirectorFactoryProperties(IncludeAbstractClassesEntitySca
if (solverProperties.getEnvironmentMode() != null) {
solverConfig.setEnvironmentMode(solverProperties.getEnvironmentMode());
}
- if (solverProperties.getDomainAccessType() != null) {
- solverConfig.setDomainAccessType(solverProperties.getDomainAccessType());
- }
if (solverProperties.getEnabledPreviewFeatures() != null) {
solverConfig.setEnablePreviewFeatureSet(new HashSet<>(solverProperties.getEnabledPreviewFeatures()));
}
diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperties.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperties.java
index 22e56de6377..69d2e33f74a 100644
--- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperties.java
+++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperties.java
@@ -4,7 +4,6 @@
import java.util.Map;
import java.util.TreeSet;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter;
@@ -41,15 +40,6 @@ public class SolverProperties {
*/
private String moveThreadCount;
- /**
- * Determines how to access the fields and methods of domain classes.
- * Defaults to REFLECTION.
- *
- * To use GIZMO, io.quarkus.gizmo:gizmo must be in your classpath,
- * and all planning annotations must be on public members.
- */
- private DomainAccessType domainAccessType;
-
private List enabledPreviewFeatures;
/**
@@ -115,14 +105,6 @@ public void setMoveThreadCount(String moveThreadCount) {
this.moveThreadCount = moveThreadCount;
}
- public DomainAccessType getDomainAccessType() {
- return domainAccessType;
- }
-
- public void setDomainAccessType(DomainAccessType domainAccessType) {
- this.domainAccessType = domainAccessType;
- }
-
public List getEnabledPreviewFeatures() {
return enabledPreviewFeatures;
}
diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperty.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperty.java
index a5d5fcaed83..a5d6b4b53e6 100644
--- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperty.java
+++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/config/SolverProperty.java
@@ -10,7 +10,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.impl.heuristic.selector.common.nearby.NearbyDistanceMeter;
@@ -21,8 +20,6 @@ public enum SolverProperty {
value -> EnvironmentMode.valueOf(value.toString())),
DAEMON("daemon", SolverProperties::setDaemon, value -> Boolean.valueOf(value.toString())),
MOVE_THREAD_COUNT("move-thread-count", SolverProperties::setMoveThreadCount, Object::toString),
- DOMAIN_ACCESS_TYPE("domain-access-type", SolverProperties::setDomainAccessType,
- value -> DomainAccessType.valueOf(value.toString())),
ENABLED_PREVIEW_FEATURES("enabled-preview-features", SolverProperties::setEnabledPreviewFeatures,
value -> Arrays.stream(value.toString().split(",")).map(PreviewFeature::valueOf).toList()),
NEARBY_DISTANCE_METER_CLASS("nearby-distance-meter-class", SolverProperties::setNearbyDistanceMeterClass,
diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverGizmoAutoConfigurationTest.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverGizmoAutoConfigurationTest.java
deleted file mode 100644
index a9c9ac99767..00000000000
--- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverGizmoAutoConfigurationTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package ai.timefold.solver.spring.boot.autoconfigure;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
-
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
-import ai.timefold.solver.core.api.solver.SolverFactory;
-import ai.timefold.solver.core.api.solver.SolverManager;
-import ai.timefold.solver.core.config.solver.SolverConfig;
-import ai.timefold.solver.spring.boot.autoconfigure.basic.domain.TestdataSpringSolution;
-import ai.timefold.solver.spring.boot.autoconfigure.config.TimefoldProperties;
-import ai.timefold.solver.spring.boot.autoconfigure.gizmo.GizmoSpringTestConfiguration;
-
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.parallel.Execution;
-import org.junit.jupiter.api.parallel.ExecutionMode;
-import org.springframework.boot.autoconfigure.AutoConfigurations;
-import org.springframework.boot.test.context.FilteredClassLoader;
-import org.springframework.boot.test.context.runner.ApplicationContextRunner;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.test.context.TestExecutionListeners;
-
-@TestExecutionListeners
-@Execution(ExecutionMode.CONCURRENT)
-class TimefoldSolverGizmoAutoConfigurationTest {
-
- private final ApplicationContextRunner gizmoContextRunner;
- private final FilteredClassLoader noGizmoFilteredClassLoader;
-
- public TimefoldSolverGizmoAutoConfigurationTest() {
- gizmoContextRunner = new ApplicationContextRunner()
- .withConfiguration(
- AutoConfigurations.of(TimefoldSolverAutoConfiguration.class, TimefoldSolverBeanFactory.class))
- .withUserConfiguration(GizmoSpringTestConfiguration.class);
- noGizmoFilteredClassLoader = new FilteredClassLoader(FilteredClassLoader.PackageFilter.of("io.quarkus.gizmo2"),
- FilteredClassLoader.ClassPathResourceFilter.of(
- new ClassPathResource(TimefoldProperties.DEFAULT_SOLVER_CONFIG_URL)));
- }
-
- @Test
- void solverProperties() {
- gizmoContextRunner
- .withPropertyValues("timefold.solver.domain-access-type=GIZMO")
- .run(context -> {
- var solverConfig = context.getBean(SolverConfig.class);
- assertThat(solverConfig.getDomainAccessType()).isEqualTo(DomainAccessType.GIZMO);
- assertThat(context.getBean(SolverFactory.class)).isNotNull();
- });
- gizmoContextRunner
- .withPropertyValues("timefold.solver.solver1.domain-access-type=GIZMO")
- .withPropertyValues("timefold.solver.solver2.domain-access-type=REFLECTION")
- .run(context -> {
- var solver1 =
- (SolverManager) context.getBean("solver1");
- var solver2 =
- (SolverManager) context.getBean("solver2");
- assertThat(solver1).isNotNull();
- assertThat(solver2).isNotNull();
- });
- }
-
- @Test
- void gizmoThrowsIfGizmoNotPresent() {
- assertThatCode(() -> gizmoContextRunner
- .withClassLoader(noGizmoFilteredClassLoader)
- .withPropertyValues(
- "timefold.solver-config-xml=ai/timefold/solver/spring/boot/autoconfigure/gizmoSpringBootSolverConfig.xml")
- .run(context -> context.getBean(SolverFactory.class)))
- .hasRootCauseMessage("When using the domainAccessType (" +
- DomainAccessType.GIZMO +
- ") the classpath or modulepath must contain io.quarkus.gizmo:gizmo2.\n" +
- "Maybe add a dependency to io.quarkus.gizmo:gizmo2.");
- }
-
-}
diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverWithSolverConfigXmlAutoConfigurationTest.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverWithSolverConfigXmlAutoConfigurationTest.java
index 427718eb9d5..ff7815cb2bd 100644
--- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverWithSolverConfigXmlAutoConfigurationTest.java
+++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverWithSolverConfigXmlAutoConfigurationTest.java
@@ -9,7 +9,6 @@
import java.time.Duration;
import java.util.Collections;
-import ai.timefold.solver.core.api.domain.common.DomainAccessType;
import ai.timefold.solver.core.api.score.SimpleScore;
import ai.timefold.solver.core.api.solver.SolutionManager;
import ai.timefold.solver.core.api.solver.SolverFactory;
@@ -233,7 +232,6 @@ void solverWithYaml() {
assertEquals(EnvironmentMode.FULL_ASSERT, solverConfig.getEnvironmentMode());
assertTrue(solverConfig.getDaemon());
assertEquals("2", solverConfig.getMoveThreadCount());
- assertEquals(DomainAccessType.REFLECTION, solverConfig.getDomainAccessType());
assertEquals(Duration.ofHours(4), solverConfig.getTerminationConfig().getSpentLimit());
assertEquals(Duration.ofHours(5), solverConfig.getTerminationConfig().getUnimprovedSpentLimit());
assertEquals(SimpleScore.of(0).toString(), solverConfig.getTerminationConfig().getBestScoreLimit());
diff --git a/spring-integration/spring-boot-autoconfigure/src/test/resources/ai/timefold/solver/spring/boot/autoconfigure/single-solver/application.yaml b/spring-integration/spring-boot-autoconfigure/src/test/resources/ai/timefold/solver/spring/boot/autoconfigure/single-solver/application.yaml
index 93b6cce396e..e6e2f349b0b 100644
--- a/spring-integration/spring-boot-autoconfigure/src/test/resources/ai/timefold/solver/spring/boot/autoconfigure/single-solver/application.yaml
+++ b/spring-integration/spring-boot-autoconfigure/src/test/resources/ai/timefold/solver/spring/boot/autoconfigure/single-solver/application.yaml
@@ -4,7 +4,6 @@ timefold:
environment-mode: FULL_ASSERT
daemon: true
move-thread-count: 2
- domain-access-type: REFLECTION
nearby-distance-meter-class: ai.timefold.solver.spring.boot.autoconfigure.dummy.DummyDistanceMeter
termination:
spent-limit: 4h
diff --git a/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml b/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml
index 15b66f8bdcd..136ebe5aeed 100644
--- a/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml
+++ b/spring-integration/spring-boot-integration-test/src/test/resources/solver-full.xml
@@ -14,7 +14,6 @@
java.lang.Object
java.lang.Object
- GIZMO
java.lang.Object
diff --git a/tools/benchmark/src/main/resources/benchmark.xsd b/tools/benchmark/src/main/resources/benchmark.xsd
index 16219c25079..79a4242b0a5 100644
--- a/tools/benchmark/src/main/resources/benchmark.xsd
+++ b/tools/benchmark/src/main/resources/benchmark.xsd
@@ -353,9 +353,6 @@
-
-
-
@@ -2573,24 +2570,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-