1010import java .util .Objects ;
1111import java .util .concurrent .ConcurrentHashMap ;
1212
13- import ai .timefold .solver .core .api .domain .common .DomainAccessType ;
1413import ai .timefold .solver .core .api .solver .SolverFactory ;
14+ import ai .timefold .solver .core .impl .domain .common .DomainAccessType ;
1515import ai .timefold .solver .core .impl .domain .common .ReflectionHelper ;
1616import ai .timefold .solver .core .impl .domain .common .accessor .gizmo .AccessorInfo ;
1717import ai .timefold .solver .core .impl .domain .common .accessor .gizmo .GizmoClassLoader ;
1818import ai .timefold .solver .core .impl .domain .common .accessor .gizmo .GizmoMemberAccessorFactory ;
1919
20+ import org .jspecify .annotations .NullMarked ;
21+ import org .jspecify .annotations .Nullable ;
22+ import org .slf4j .Logger ;
23+ import org .slf4j .LoggerFactory ;
24+
25+ @ NullMarked
2026public final class MemberAccessorFactory {
2127
28+ static final Logger LOGGER = LoggerFactory .getLogger (MemberAccessorFactory .class );
2229 // exists only so that the various member accessors can share the same text in their exception messages
2330 static final String CLASSLOADER_NUDGE_MESSAGE =
2431 "Maybe add getClass().getClassLoader() as a parameter to the %s.create...() method call."
@@ -30,10 +37,10 @@ public final class MemberAccessorFactory {
3037 * @param member never null, method or field to access
3138 * @param memberAccessorType never null
3239 * @param domainAccessType never null
33- * @param classLoader null or {@link GizmoClassLoader} if domainAccessType is {@link DomainAccessType#GIZMO }.
40+ * @param classLoader null or {@link GizmoClassLoader} if domainAccessType is {@link DomainAccessType#FORCE_GIZMO }.
3441 * @return never null, new instance of the member accessor
3542 */
36- public static MemberAccessor buildMemberAccessor (Member member , MemberAccessorType memberAccessorType ,
43+ private static MemberAccessor buildMemberAccessor (Member member , MemberAccessorType memberAccessorType ,
3744 DomainAccessType domainAccessType , ClassLoader classLoader ) {
3845 return buildMemberAccessor (member , memberAccessorType , null , domainAccessType , classLoader );
3946 }
@@ -45,28 +52,31 @@ public static MemberAccessor buildMemberAccessor(Member member, MemberAccessorTy
4552 * @param memberAccessorType never null
4653 * @param annotationClass the annotation the member was annotated with (used for error reporting)
4754 * @param domainAccessType never null
48- * @param classLoader null or {@link GizmoClassLoader} if domainAccessType is {@link DomainAccessType#GIZMO }.
55+ * @param classLoader null or {@link GizmoClassLoader} if domainAccessType is {@link DomainAccessType#FORCE_GIZMO }.
4956 * @return never null, new instance of the member accessor
5057 */
51- public static MemberAccessor buildMemberAccessor (Member member , MemberAccessorType memberAccessorType ,
52- Class <? extends Annotation > annotationClass , DomainAccessType domainAccessType , ClassLoader classLoader ) {
58+ static MemberAccessor buildMemberAccessor (Member member , MemberAccessorType memberAccessorType ,
59+ @ Nullable Class <? extends Annotation > annotationClass , DomainAccessType domainAccessType , ClassLoader classLoader ) {
60+ MemberAccessorValidator .verifyIsValidMember (annotationClass , member , memberAccessorType );
5361 return switch (domainAccessType ) {
54- case GIZMO -> GizmoMemberAccessorFactory .buildGizmoMemberAccessor (member , annotationClass ,
55- AccessorInfo .of (memberAccessorType != MemberAccessorType .VOID_METHOD ,
56- memberAccessorType == MemberAccessorType .FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER ),
62+ case AUTO -> throw new IllegalStateException (
63+ "Impossible state: called with %s (AUTO) instead of a resolved domain access type"
64+ .formatted (DomainAccessType .class .getSimpleName ()));
65+ case FORCE_GIZMO -> GizmoMemberAccessorFactory .buildGizmoMemberAccessor (member , annotationClass ,
66+ AccessorInfo .of (memberAccessorType ),
5767 (GizmoClassLoader ) Objects .requireNonNull (classLoader ));
58- case REFLECTION -> buildReflectiveMemberAccessor (member , memberAccessorType , annotationClass );
68+ case FORCE_REFLECTION -> buildReflectiveMemberAccessor (member , memberAccessorType , annotationClass );
5969 };
6070 }
6171
6272 private static MemberAccessor buildReflectiveMemberAccessor (Member member , MemberAccessorType memberAccessorType ,
63- Class <? extends Annotation > annotationClass ) {
73+ @ Nullable Class <? extends Annotation > annotationClass ) {
6474 return buildReflectiveMemberAccessor (member , memberAccessorType , annotationClass ,
6575 (AnnotatedElement ) member );
6676 }
6777
6878 private static MemberAccessor buildReflectiveMemberAccessor (Member member , MemberAccessorType memberAccessorType ,
69- Class <? extends Annotation > annotationClass , AnnotatedElement annotatedElement ) {
79+ @ Nullable Class <? extends Annotation > annotationClass , AnnotatedElement annotatedElement ) {
7080 var messagePrefix = (annotationClass == null ) ? "The" : "The @%s annotated" .formatted (annotationClass .getSimpleName ());
7181 if (member instanceof Field field ) {
7282 var getter = ReflectionHelper .getGetterMethod (field .getDeclaringClass (), field .getName ());
@@ -126,7 +136,7 @@ private static MemberAccessor buildReflectiveMemberAccessor(Member member, Membe
126136 memberAccessor = new ReflectionBeanPropertyMemberAccessor (method , annotatedElement , getterOnly );
127137 break ;
128138 case VOID_METHOD :
129- memberAccessor = new ReflectionMethodMemberAccessor (method , false , false );
139+ memberAccessor = new ReflectionMethodMemberAccessor (method );
130140 break ;
131141 default :
132142 throw new IllegalStateException ("The memberAccessorType (%s) is not implemented."
@@ -154,6 +164,7 @@ private static MemberAccessor buildReflectiveMemberAccessor(Member member, Membe
154164
155165 private final Map <String , MemberAccessor > memberAccessorCache ;
156166 private final GizmoClassLoader gizmoClassLoader = new GizmoClassLoader ();
167+ private final boolean isGizmoSupported ;
157168
158169 public MemberAccessorFactory () {
159170 this (null );
@@ -162,12 +173,19 @@ public MemberAccessorFactory() {
162173 /**
163174 * Prefills the member accessor cache.
164175 *
165- * @param memberAccessorMap key is the fully qualified member name
176+ * @param memberAccessorMap key is the fully qualified member name, value is a pregenerated {@link MemberAccessor}.
177+ * Used by Quarkus since the {@link MemberAccessor} are generated at build time.
178+ * If null, it is treated as an empty map.
166179 */
167- public MemberAccessorFactory (Map <String , MemberAccessor > memberAccessorMap ) {
180+ public MemberAccessorFactory (@ Nullable Map <String , MemberAccessor > memberAccessorMap ) {
168181 // The MemberAccessorFactory may be accessed, and this cache both read and updated, by multiple threads.
169182 this .memberAccessorCache =
170183 memberAccessorMap == null ? new ConcurrentHashMap <>() : new ConcurrentHashMap <>(memberAccessorMap );
184+ // If the memberAccessorMap is not empty, we are in Quarkus using pregenerated member accessors
185+ this .isGizmoSupported =
186+ (memberAccessorMap != null && !memberAccessorMap .isEmpty ()) || gizmoClassLoader .isGizmoSupported ();
187+ LOGGER .trace ("Using domain access type {} for member accessors." ,
188+ isGizmoSupported ? DomainAccessType .FORCE_GIZMO : DomainAccessType .FORCE_REFLECTION );
171189 }
172190
173191 /**
@@ -180,10 +198,16 @@ public MemberAccessorFactory(Map<String, MemberAccessor> memberAccessorMap) {
180198 * @return never null, new {@link MemberAccessor} instance unless already found in memberAccessorMap
181199 */
182200 public MemberAccessor buildAndCacheMemberAccessor (Member member , MemberAccessorType memberAccessorType ,
183- Class <? extends Annotation > annotationClass , DomainAccessType domainAccessType ) {
201+ @ Nullable Class <? extends Annotation > annotationClass , DomainAccessType domainAccessType ) {
184202 String generatedClassName = GizmoMemberAccessorFactory .getGeneratedClassName (member );
203+ if (domainAccessType == DomainAccessType .AUTO ) {
204+ domainAccessType = isGizmoSupported ? DomainAccessType .FORCE_GIZMO : DomainAccessType .FORCE_REFLECTION ;
205+ }
206+
207+ var finalDomainAccessType = domainAccessType ;
185208 return memberAccessorCache .computeIfAbsent (generatedClassName ,
186- k -> MemberAccessorFactory .buildMemberAccessor (member , memberAccessorType , annotationClass , domainAccessType ,
209+ k -> MemberAccessorFactory .buildMemberAccessor (member , memberAccessorType , annotationClass ,
210+ finalDomainAccessType ,
187211 gizmoClassLoader ));
188212 }
189213
@@ -198,33 +222,18 @@ public MemberAccessor buildAndCacheMemberAccessor(Member member, MemberAccessorT
198222 public MemberAccessor buildAndCacheMemberAccessor (Member member , MemberAccessorType memberAccessorType ,
199223 DomainAccessType domainAccessType ) {
200224 String generatedClassName = GizmoMemberAccessorFactory .getGeneratedClassName (member );
225+ if (domainAccessType == DomainAccessType .AUTO ) {
226+ domainAccessType = isGizmoSupported ? DomainAccessType .FORCE_GIZMO : DomainAccessType .FORCE_REFLECTION ;
227+ }
228+
229+ var finalDomainAccessType = domainAccessType ;
201230 return memberAccessorCache .computeIfAbsent (generatedClassName ,
202- k -> MemberAccessorFactory .buildMemberAccessor (member , memberAccessorType , domainAccessType , gizmoClassLoader ));
231+ k -> MemberAccessorFactory .buildMemberAccessor (member , memberAccessorType , finalDomainAccessType ,
232+ gizmoClassLoader ));
203233 }
204234
205235 public GizmoClassLoader getGizmoClassLoader () {
206236 return gizmoClassLoader ;
207237 }
208238
209- public enum MemberAccessorType {
210- FIELD_OR_READ_METHOD ,
211- FIELD_OR_READ_METHOD_WITH_OPTIONAL_PARAMETER ,
212- FIELD_OR_GETTER_METHOD ,
213- FIELD_OR_GETTER_METHOD_WITH_SETTER (true ),
214- VOID_METHOD ;
215-
216- private final boolean setterRequired ;
217-
218- MemberAccessorType () {
219- setterRequired = false ;
220- }
221-
222- MemberAccessorType (boolean setterRequired ) {
223- this .setterRequired = setterRequired ;
224- }
225-
226- public boolean isSetterRequired () {
227- return setterRequired ;
228- }
229- }
230239}
0 commit comments