From 6c4e2e626f6e018ddc12971cf55fd2cde987cd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Mon, 13 May 2024 13:17:50 +0200 Subject: [PATCH] fix: properly detect value ranges with wildcard types (#838) --- .../solver/core/config/util/ConfigUtils.java | 166 ++++++++---------- .../descriptor/SolutionDescriptor.java | 21 +-- ...tractFromPropertyValueRangeDescriptor.java | 5 +- .../descriptor/GenuineVariableDescriptor.java | 42 ++--- .../descriptor/ListVariableDescriptor.java | 7 +- ...verseRelationShadowVariableDescriptor.java | 8 +- .../AnonymousValueRangeFactoryTest.java | 2 +- .../TestdataAnonymousListSolution.java | 2 +- 8 files changed, 107 insertions(+), 146 deletions(-) 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 03d8a5a7e9..3f35a158a0 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 @@ -49,8 +49,7 @@ public class ConfigUtils { * Intended usage: * *
-     * selectionFilter = ConfigUtils.newInstance(
-     *         config, "filterClass", config.getFilterClass());
+     * selectionFilter = ConfigUtils.newInstance(config, "filterClass", config.getFilterClass());
      * 
* * @param configBean the bean holding the {@code clazz} to be instantiated @@ -60,8 +59,7 @@ public class ConfigUtils { * @return new instance of clazz */ public static T newInstance(Object configBean, String propertyName, Class clazz) { - return ConfigUtils.newInstance(() -> (configBean == null ? "?" : configBean.getClass().getSimpleName()), - propertyName, clazz); + return newInstance(() -> (configBean == null ? "?" : configBean.getClass().getSimpleName()), propertyName, clazz); } /** @@ -94,9 +92,9 @@ public static void applyCustomProperties(Object bean, String beanClassPropertyNa if (customProperties == null) { return; } - Class beanClass = bean.getClass(); + var beanClass = bean.getClass(); customProperties.forEach((propertyName, valueString) -> { - Method setterMethod = ReflectionHelper.getSetterMethod(beanClass, propertyName); + var setterMethod = ReflectionHelper.getSetterMethod(beanClass, propertyName); if (setterMethod == null) { throw new IllegalStateException("The custom property " + propertyName + " (" + valueString + ") in the " + customPropertiesPropertyName @@ -107,7 +105,7 @@ public static void applyCustomProperties(Object bean, String beanClassPropertyNa + "Maybe don't configure that custom property " + propertyName + " (" + valueString + ") in the " + customPropertiesPropertyName + "."); } - Class propertyType = setterMethod.getParameterTypes()[0]; + var propertyType = setterMethod.getParameterTypes()[0]; Object typedValue; try { if (propertyType.equals(String.class)) { @@ -171,8 +169,8 @@ public static > List inheritMer List mergedList = new ArrayList<>(inheritedList.size() + (originalList == null ? 0 : originalList.size())); // The inheritedList should be before the originalList - for (Config_ inherited : inheritedList) { - Config_ copy = inherited.copyConfig(); + for (var inherited : inheritedList) { + var copy = inherited.copyConfig(); mergedList.add(copy); } if (originalList != null) { @@ -266,7 +264,7 @@ public static T meldProperty(T a, T b) { if (b == null) { return a; } - return ConfigUtils.mergeProperty(a, b); + return mergeProperty(a, b); } public static boolean isEmptyCollection(Collection collection) { @@ -317,9 +315,9 @@ public static List> getAllAnnotatedLineageClasses(Class bottomClass, } List> lineageClassList = new ArrayList<>(); lineageClassList.add(bottomClass); - Class superclass = bottomClass.getSuperclass(); + var superclass = bottomClass.getSuperclass(); lineageClassList.addAll(getAllAnnotatedLineageClasses(superclass, annotation)); - for (Class superInterface : bottomClass.getInterfaces()) { + for (var superInterface : bottomClass.getInterfaces()) { lineageClassList.addAll(getAllAnnotatedLineageClasses(superInterface, annotation)); } return lineageClassList; @@ -330,14 +328,14 @@ public static List> getAllAnnotatedLineageClasses(Class bottomClass, * @return never null, sorted by type (fields before methods), then by {@link AlphabeticMemberComparator}. */ public static List getDeclaredMembers(Class baseClass) { - Stream fieldStream = Stream.of(baseClass.getDeclaredFields()) + var fieldStream = Stream.of(baseClass.getDeclaredFields()) // A synthetic field is a field generated by the compiler that // does not exist in the source code. It is used mainly in // nested classes so the inner class can access the fields // of the outer class. .filter(field -> !field.isSynthetic()) .sorted(alphabeticMemberComparator); - Stream methodStream = Stream.of(baseClass.getDeclaredMethods()) + var methodStream = Stream.of(baseClass.getDeclaredMethods()) // A synthetic method is a method generated by the compiler that does // not exist in the source code. These include bridge methods. // A bridge method is a generic variant that duplicates a concrete method @@ -354,13 +352,13 @@ public static List getDeclaredMembers(Class baseClass) { * @return never null, sorted by type (fields before methods), then by {@link AlphabeticMemberComparator}. */ public static List getAllMembers(Class baseClass, Class annotationClass) { - Class clazz = baseClass; + var clazz = baseClass; Stream memberStream = Stream.empty(); while (clazz != null) { - Stream fieldStream = Stream.of(clazz.getDeclaredFields()) + var fieldStream = Stream.of(clazz.getDeclaredFields()) .filter(field -> field.isAnnotationPresent(annotationClass) && !field.isSynthetic()) .sorted(alphabeticMemberComparator); - Stream methodStream = Stream.of(clazz.getDeclaredMethods()) + var methodStream = Stream.of(clazz.getDeclaredMethods()) .filter(method -> method.isAnnotationPresent(annotationClass) && !method.isSynthetic()) .sorted(alphabeticMemberComparator); memberStream = Stream.concat(memberStream, Stream.concat(fieldStream, methodStream)); @@ -373,7 +371,7 @@ public static List getAllMembers(Class baseClass, Class extractAnnotationClass(Member member, Class... annotationClasses) { Class annotationClass = null; - for (Class detectedAnnotationClass : annotationClasses) { + for (var detectedAnnotationClass : annotationClasses) { if (((AnnotatedElement) member).isAnnotationPresent(detectedAnnotationClass)) { if (annotationClass != null) { throw new IllegalStateException("The class (" + member.getDeclaringClass() @@ -388,88 +386,66 @@ public static Class extractAnnotationClass(Member member, return annotationClass; } - public static Class extractCollectionGenericTypeParameterStrictly( - String parentClassConcept, Class parentClass, - Class type, Type genericType, - Class annotationClass, String memberName) { - return extractCollectionGenericTypeParameter( - parentClassConcept, parentClass, - type, genericType, - annotationClass, memberName).orElseThrow( - () -> new IllegalArgumentException("The " + parentClassConcept + " (" + parentClass + ") has a " - + (annotationClass == null ? "auto discovered" - : "@" + annotationClass.getSimpleName() + " annotated") - + " member (" + memberName - + ") with a member type (" + type - + ") which has no generic parameters.\n" - + "Maybe the member (" + memberName + ") should return a parameterized " - + type.getSimpleName() - + ".")); + public static Class extractGenericTypeParameterOrFail(String parentClassConcept, Class parentClass, Class type, + Type genericType, Class annotationClass, String memberName) { + return extractGenericTypeParameter(parentClassConcept, parentClass, type, genericType, annotationClass, memberName) + .orElseThrow(() -> new IllegalArgumentException(""" + The %s (%s) has a %s member (%s) with a member type (%s) which has no generic parameters. + Maybe the member (%s) should return a parameterized %s.""" + .formatted(parentClassConcept, parentClass, + annotationClass == null ? "auto discovered" + : "@" + annotationClass.getSimpleName() + " annotated", + memberName, type, memberName, type.getSimpleName()))); } - public static Optional> extractCollectionGenericTypeParameterLeniently( - String parentClassConcept, Class parentClass, - Class type, Type genericType, - Class annotationClass, String memberName) { - return extractCollectionGenericTypeParameter( - parentClassConcept, parentClass, - type, genericType, - annotationClass, memberName); - } - - private static Optional> extractCollectionGenericTypeParameter( - String parentClassConcept, Class parentClass, - Class type, Type genericType, - Class annotationClass, String memberName) { - if (!(genericType instanceof ParameterizedType)) { + public static Optional> extractGenericTypeParameter(String parentClassConcept, Class parentClass, Class type, + Type genericType, Class annotationClass, String memberName) { + if (!(genericType instanceof ParameterizedType parameterizedType)) { return Optional.empty(); } - ParameterizedType parameterizedType = (ParameterizedType) genericType; - Type[] typeArguments = parameterizedType.getActualTypeArguments(); + var typeArguments = parameterizedType.getActualTypeArguments(); if (typeArguments.length != 1) { - throw new IllegalArgumentException("The " + parentClassConcept + " (" + parentClass + ") has a " - + (annotationClass == null ? "auto discovered" : "@" + annotationClass.getSimpleName() + " annotated") - + " member (" + memberName - + ") with a member type (" + type - + ") which is a parameterized collection with an unsupported number of generic parameters (" - + typeArguments.length + ")."); + throw new IllegalArgumentException(""" + The %s (%s) has a %s member (%s) with a member type (%s) which is a parameterized collection \ + with an unsupported number of generic parameters (%s).""" + .formatted(parentClassConcept, parentClass, + annotationClass == null ? "auto discovered" : "@" + annotationClass.getSimpleName() + " annotated", + memberName, type, typeArguments.length)); } - Type typeArgument = typeArguments[0]; - if (typeArgument instanceof ParameterizedType parameterizedType1) { - // Remove the type parameters so it can be cast to a Class - typeArgument = parameterizedType1.getRawType(); + var typeArgument = typeArguments[0]; + if (typeArgument instanceof ParameterizedType parameterizedTypeArgument) { + // Remove the type parameters, so it can be cast to a Class. + typeArgument = parameterizedTypeArgument.getRawType(); } if (typeArgument instanceof WildcardType wildcardType) { - Type[] upperBounds = wildcardType.getUpperBounds(); - if (upperBounds.length > 1) { - // Multiple upper bounds is impossible in traditional Java - // Other JVM languages or future java versions might enabling triggering this - throw new IllegalArgumentException("The " + parentClassConcept + " (" + parentClass + ") has a " - + (annotationClass == null ? "auto discovered" : "@" + annotationClass.getSimpleName() + " annotated") - + " member (" + memberName - + ") with a member type (" + type - + ") which is a parameterized collection with a wildcard type argument (" - + typeArgument + ") that has multiple upper bounds (" + Arrays.toString(upperBounds) + ").\n" - + "Maybe don't use wildcards with multiple upper bounds for the member (" + memberName + ")."); - } - if (upperBounds.length == 0) { - typeArgument = Object.class; - } else { - typeArgument = upperBounds[0]; - } + var upperBounds = wildcardType.getUpperBounds(); + typeArgument = switch (upperBounds.length) { + case 0 -> Object.class; + case 1 -> upperBounds[0]; + // Multiple upper bounds are impossible in traditional Java. + // Other JVM languages or future java versions might enable triggering this. + default -> throw new IllegalArgumentException(""" + The %s (%s) has a %s member (%s) with a member type (%s) which is a parameterized collection \ + with a wildcard type argument (%s) that has multiple upper bounds (%s). + Maybe don't use wildcards with multiple upper bounds for the member (%s).""" + .formatted(parentClassConcept, parentClass, + annotationClass == null ? "auto discovered" + : "@" + annotationClass.getSimpleName() + " annotated", + memberName, type, typeArgument, Arrays.toString(upperBounds), memberName)); + }; } - if (typeArgument instanceof Class class1) { + if (typeArgument instanceof Class class1) { return Optional.of(class1); - } else if (typeArgument instanceof ParameterizedType parameterizedType1) { + } else if (typeArgument instanceof ParameterizedType parameterizedTypeArgument) { // Turns SomeGenericType into SomeGenericType. - return Optional.of((Class) parameterizedType1.getRawType()); + return Optional.of((Class) parameterizedTypeArgument.getRawType()); } else { - throw new IllegalArgumentException("The " + parentClassConcept + " (" + parentClass + ") has a " - + (annotationClass == null ? "auto discovered" : "@" + annotationClass.getSimpleName() + " annotated") - + " member (" + memberName - + ") with a member type (" + type - + ") which is a parameterized collection with a type argument (" + typeArgument - + ") that is not a class or interface."); + throw new IllegalArgumentException(""" + The %s (%s) has a %s member (%s) with a member type (%s) which is a parameterized collection \ + with a type argument (%s) that is not a class or interface.""" + .formatted(parentClassConcept, parentClass, + annotationClass == null ? "auto discovered" : "@" + annotationClass.getSimpleName() + " annotated", + memberName, type, typeArgument)); } } @@ -485,11 +461,11 @@ private static Optional> extractCollectionGenericTypeParameter( */ public static MemberAccessor findPlanningIdMemberAccessor(Class clazz, MemberAccessorFactory memberAccessorFactory, DomainAccessType domainAccessType) { - Member member = getSingleMember(clazz, PlanningId.class); + var member = getSingleMember(clazz, PlanningId.class); if (member == null) { return null; } - MemberAccessor memberAccessor = + var memberAccessor = memberAccessorFactory.buildAndCacheMemberAccessor(member, FIELD_OR_READ_METHOD, PlanningId.class, domainAccessType); assertPlanningIdMemberIsComparable(clazz, member, memberAccessor); @@ -508,11 +484,11 @@ private static void assertPlanningIdMemberIsComparable(Class clazz, Member me } private static Member getSingleMember(Class clazz, Class annotationClass) { - List memberList = getAllMembers(clazz, annotationClass); + var memberList = getAllMembers(clazz, annotationClass); if (memberList.isEmpty()) { return null; } - int size = memberList.size(); + var size = memberList.size(); if (clazz.isRecord()) { /* * A record has a field and a getter for each record component. @@ -554,7 +530,7 @@ private static List getMembers(List memberList, boolean needMeth } public static String abbreviate(List list, int limit) { - String abbreviation = ""; + var abbreviation = ""; if (list != null) { abbreviation = list.stream().limit(limit).collect(Collectors.joining(", ")); if (list.size() > limit) { @@ -568,10 +544,6 @@ public static String abbreviate(List list) { return abbreviate(list, 3); } - public static boolean isNativeImage() { - return System.getProperty("org.graalvm.nativeimage.imagecode") != null; - } - // ************************************************************************ // Private constructor // ************************************************************************ 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 435e45660b..58f2d0d8d3 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 @@ -331,9 +331,7 @@ private Class extractFactEntityOrScoreAnnotationClassOrAut + "Maybe the member (" + memberName + ") should return a typed " + Collection.class.getSimpleName() + "."); } - elementType = ConfigUtils.extractCollectionGenericTypeParameterLeniently( - "solutionClass", solutionClass, - type, genericType, + elementType = ConfigUtils.extractGenericTypeParameter("solutionClass", solutionClass, type, genericType, null, member.getName()).orElse(Object.class); } else { elementType = type.getComponentType(); @@ -588,10 +586,10 @@ private Set> collectEntityAndProblemFactClasses() { Stream> problemFactOrEntityClassStream = concat(entityClassStream, factClassStream); Stream> factCollectionClassStream = problemFactCollectionMemberAccessorMap.values() .stream() - .map(accessor -> ConfigUtils.extractCollectionGenericTypeParameterLeniently( - "solutionClass", getSolutionClass(), - accessor.getType(), accessor.getGenericType(), ProblemFactCollectionProperty.class, - accessor.getName()).orElse(Object.class)); + .map(accessor -> ConfigUtils + .extractGenericTypeParameter("solutionClass", getSolutionClass(), accessor.getType(), + accessor.getGenericType(), ProblemFactCollectionProperty.class, accessor.getName()) + .orElse(Object.class)); problemFactOrEntityClassStream = concat(problemFactOrEntityClassStream, factCollectionClassStream); // Add constraint configuration, if configured. if (constraintConfigurationDescriptor != null) { @@ -932,12 +930,9 @@ public void visitEntitiesByEntityClass(Solution_ solution, Class entityClass, } } for (MemberAccessor entityCollectionMemberAccessor : entityCollectionMemberAccessorMap.values()) { - Optional> optionalTypeParameter = ConfigUtils.extractCollectionGenericTypeParameterLeniently( - "solutionClass", entityCollectionMemberAccessor.getDeclaringClass(), - entityCollectionMemberAccessor.getType(), - entityCollectionMemberAccessor.getGenericType(), - null, - entityCollectionMemberAccessor.getName()); + Optional> optionalTypeParameter = ConfigUtils.extractGenericTypeParameter("solutionClass", + entityCollectionMemberAccessor.getDeclaringClass(), entityCollectionMemberAccessor.getType(), + entityCollectionMemberAccessor.getGenericType(), null, entityCollectionMemberAccessor.getName()); boolean collectionGuaranteedToContainOnlyGivenEntityType = optionalTypeParameter .map(entityClass::isAssignableFrom) .orElse(false); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/AbstractFromPropertyValueRangeDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/AbstractFromPropertyValueRangeDescriptor.java index 24608ac99a..3b50662472 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/AbstractFromPropertyValueRangeDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/valuerange/descriptor/AbstractFromPropertyValueRangeDescriptor.java @@ -63,9 +63,8 @@ private void processValueRangeProviderAnnotation(ValueRangeProvider valueRangePr + ", an array or a " + ValueRange.class.getSimpleName() + "."); } if (collectionWrapping) { - Class collectionElementClass = ConfigUtils.extractCollectionGenericTypeParameterStrictly( - "solutionClass or entityClass", memberAccessor.getDeclaringClass(), - memberAccessor.getType(), memberAccessor.getGenericType(), + Class collectionElementClass = ConfigUtils.extractGenericTypeParameterOrFail("solutionClass or entityClass", + memberAccessor.getDeclaringClass(), memberAccessor.getType(), memberAccessor.getGenericType(), ValueRangeProvider.class, memberAccessor.getName()); if (!variableDescriptor.acceptsValueType(collectionElementClass)) { throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass() diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java index 9c38039adf..b34a7c0ec2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java @@ -1,15 +1,14 @@ package ai.timefold.solver.core.impl.domain.variable.descriptor; +import static ai.timefold.solver.core.config.util.ConfigUtils.newInstance; + import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Comparator; import java.util.stream.Stream; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; -import ai.timefold.solver.core.api.domain.valuerange.ValueRange; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; @@ -91,8 +90,8 @@ The entityClass (%s) has a @%s annotated property (%s) that has no valueRangePro } private MemberAccessor[] findAnonymousValueRangeMemberAccessors(DescriptorPolicy descriptorPolicy) { - boolean supportsValueRangeProviderFromEntity = !isListVariable(); - Stream applicableValueRangeProviderAccessors = + var supportsValueRangeProviderFromEntity = !isListVariable(); + var applicableValueRangeProviderAccessors = supportsValueRangeProviderFromEntity ? Stream.concat( descriptorPolicy.getAnonymousFromEntityValueRangeProviderSet().stream(), descriptorPolicy.getAnonymousFromSolutionValueRangeProviderSet().stream()) @@ -103,26 +102,23 @@ private MemberAccessor[] findAnonymousValueRangeMemberAccessors(DescriptorPolicy * For basic variable, the type is the type of the variable. * For list variable, the type is List, and we need to know X. */ - Class variableType = + var variableType = isListVariable() ? (Class) ((ParameterizedType) variableMemberAccessor.getGenericType()) .getActualTypeArguments()[0] : variableMemberAccessor.getType(); // We expect either ValueRange, Collection or an array. - Type valueRangeType = valueRangeProviderAccessor.getGenericType(); + var valueRangeType = valueRangeProviderAccessor.getGenericType(); if (valueRangeType instanceof ParameterizedType parameterizedValueRangeType) { - Class rawType = (Class) parameterizedValueRangeType.getRawType(); - if (!ValueRange.class.isAssignableFrom(rawType) && !Collection.class.isAssignableFrom(rawType)) { - return false; - } - Type[] generics = parameterizedValueRangeType.getActualTypeArguments(); - if (generics.length != 1) { - return false; - } - Class valueRangeGenericType = (Class) generics[0]; - return variableType.isAssignableFrom(valueRangeGenericType); + return ConfigUtils + .extractGenericTypeParameter("solutionClass", + entityDescriptor.getSolutionDescriptor().getSolutionClass(), + valueRangeProviderAccessor.getType(), parameterizedValueRangeType, + ValueRangeProvider.class, valueRangeProviderAccessor.getName()) + .map(variableType::isAssignableFrom) + .orElse(false); } else { - Class clz = (Class) valueRangeType; + var clz = (Class) valueRangeType; if (clz.isArray()) { - Class componentType = clz.getComponentType(); + var componentType = clz.getComponentType(); return variableType.isAssignableFrom(componentType); } return false; @@ -137,7 +133,7 @@ private MemberAccessor findValueRangeMemberAccessor(DescriptorPolicy descriptorP } else if (descriptorPolicy.hasFromEntityValueRangeProvider(valueRangeProviderRef)) { return descriptorPolicy.getFromEntityValueRangeProvider(valueRangeProviderRef); } else { - Collection providerIds = descriptorPolicy.getValueRangeProviderIds(); + var providerIds = descriptorPolicy.getValueRangeProviderIds(); throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass() + ") has a @" + PlanningVariable.class.getSimpleName() + " annotated property (" + variableMemberAccessor.getName() @@ -183,7 +179,7 @@ protected void processStrength(Class strengthComparatorCla + ") at the same time."); } if (strengthComparatorClass != null) { - Comparator strengthComparator = ConfigUtils.newInstance(this::toString, + Comparator strengthComparator = newInstance(this::toString, "strengthComparatorClass", strengthComparatorClass); increasingStrengthSorter = new ComparatorSelectionSorter<>(strengthComparator, SelectionSorterOrder.ASCENDING); @@ -191,7 +187,7 @@ protected void processStrength(Class strengthComparatorCla SelectionSorterOrder.DESCENDING); } if (strengthWeightFactoryClass != null) { - SelectionSorterWeightFactory strengthWeightFactory = ConfigUtils.newInstance(this::toString, + SelectionSorterWeightFactory strengthWeightFactory = newInstance(this::toString, "strengthWeightFactoryClass", strengthWeightFactoryClass); increasingStrengthSorter = new WeightFactorySelectionSorter<>(strengthWeightFactory, SelectionSorterOrder.ASCENDING); @@ -241,7 +237,7 @@ public boolean isValueRangeEntityIndependent() { * is reinitializable if its value is {@code null}. */ public boolean isReinitializable(Object entity) { - Object value = getValue(entity); + var value = getValue(entity); return value == null; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java index 068773a61a..59ab31d673 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/ListVariableDescriptor.java @@ -93,10 +93,9 @@ public boolean isInitialized(Object entity) { } public Class getElementType() { - return ConfigUtils.extractCollectionGenericTypeParameterStrictly( - "entityClass", entityDescriptor.getEntityClass(), - variableMemberAccessor.getType(), variableMemberAccessor.getGenericType(), - PlanningListVariable.class, variableMemberAccessor.getName()); + return ConfigUtils.extractGenericTypeParameterOrFail("entityClass", entityDescriptor.getEntityClass(), + variableMemberAccessor.getType(), variableMemberAccessor.getGenericType(), PlanningListVariable.class, + variableMemberAccessor.getName()); } public int countUnassigned(Solution_ solution) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/InverseRelationShadowVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/InverseRelationShadowVariableDescriptor.java index ccf0cb3161..401d91bece 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/InverseRelationShadowVariableDescriptor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/inverserelation/InverseRelationShadowVariableDescriptor.java @@ -60,10 +60,10 @@ private void linkShadowSources(DescriptorPolicy descriptorPolicy) { Class sourceClass; if (Collection.class.isAssignableFrom(variablePropertyType)) { Type genericType = variableMemberAccessor.getGenericType(); - sourceClass = ConfigUtils.extractCollectionGenericTypeParameterLeniently( - "entityClass", entityDescriptor.getEntityClass(), - variablePropertyType, genericType, - InverseRelationShadowVariable.class, variableMemberAccessor.getName()).orElse(Object.class); + sourceClass = ConfigUtils + .extractGenericTypeParameter("entityClass", entityDescriptor.getEntityClass(), variablePropertyType, + genericType, InverseRelationShadowVariable.class, variableMemberAccessor.getName()) + .orElse(Object.class); singleton = false; } else { sourceClass = variablePropertyType; diff --git a/core/src/test/java/ai/timefold/solver/core/api/domain/valuerange/AnonymousValueRangeFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/api/domain/valuerange/AnonymousValueRangeFactoryTest.java index 9164b04ef9..7080b98d30 100644 --- a/core/src/test/java/ai/timefold/solver/core/api/domain/valuerange/AnonymousValueRangeFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/api/domain/valuerange/AnonymousValueRangeFactoryTest.java @@ -77,7 +77,7 @@ void solveList() { assertThat(solution).isNotNull(); } - private void assertEntity(SoftAssertions softly, TestdataAnonymousValueRangeEntity entity) { + private static void assertEntity(SoftAssertions softly, TestdataAnonymousValueRangeEntity entity) { softly.assertThat(entity.getNumberValue()).isNotNull(); softly.assertThat(entity.getIntegerValue()).isNotNull(); softly.assertThat(entity.getLongValue()).isNotNull(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/testdata/domain/valuerange/anonymous/TestdataAnonymousListSolution.java b/core/src/test/java/ai/timefold/solver/core/impl/testdata/domain/valuerange/anonymous/TestdataAnonymousListSolution.java index 82363b46de..b8a66a6ddd 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/testdata/domain/valuerange/anonymous/TestdataAnonymousListSolution.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/testdata/domain/valuerange/anonymous/TestdataAnonymousListSolution.java @@ -64,7 +64,7 @@ public List createLongList() { } @ValueRangeProvider - public List createNumberList() { + public List createNumberList() { // Test the wildcards too. return List.of(0, BigInteger.TEN); }