/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.optaplanner.core.impl.domain.solution.descriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
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.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.google.common.collect.Iterators;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.optaplanner.core.api.domain.autodiscover.AutoDiscoverMemberType;
import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningEntityProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.Solution;
import org.optaplanner.core.api.domain.solution.cloner.SolutionCloner;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.score.AbstractBendableScore;
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.score.buildin.bendable.BendableScore;
import org.optaplanner.core.api.score.buildin.bendablebigdecimal.BendableBigDecimalScore;
import org.optaplanner.core.api.score.buildin.bendablelong.BendableLongScore;
import org.optaplanner.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore;
import org.optaplanner.core.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScore;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScore;
import org.optaplanner.core.api.score.buildin.hardsoftdouble.HardSoftDoubleScore;
import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;
import org.optaplanner.core.api.score.buildin.simple.SimpleScore;
import org.optaplanner.core.api.score.buildin.simplebigdecimal.SimpleBigDecimalScore;
import org.optaplanner.core.api.score.buildin.simpledouble.SimpleDoubleScore;
import org.optaplanner.core.api.score.buildin.simplelong.SimpleLongScore;
import org.optaplanner.core.config.util.ConfigUtils;
import org.optaplanner.core.impl.domain.common.ReflectionHelper;
import org.optaplanner.core.impl.domain.common.accessor.BeanPropertyMemberAccessor;
import org.optaplanner.core.impl.domain.common.accessor.FieldMemberAccessor;
import org.optaplanner.core.impl.domain.common.accessor.MemberAccessor;
import org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor;
import org.optaplanner.core.impl.domain.lookup.LookUpStrategyResolver;
import org.optaplanner.core.impl.domain.policy.DescriptorPolicy;
import org.optaplanner.core.impl.domain.solution.AbstractSolution;
import org.optaplanner.core.impl.domain.solution.cloner.FieldAccessingSolutionCloner;
import org.optaplanner.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.ShadowVariableDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.VariableDescriptor;
import org.optaplanner.core.impl.score.buildin.bendable.BendableScoreDefinition;
import org.optaplanner.core.impl.score.buildin.bendablebigdecimal.BendableBigDecimalScoreDefinition;
import org.optaplanner.core.impl.score.buildin.bendablelong.BendableLongScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardmediumsoft.HardMediumSoftScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardmediumsoftlong.HardMediumSoftLongScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardsoft.HardSoftScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardsoftdouble.HardSoftDoubleScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardsoftlong.HardSoftLongScoreDefinition;
import org.optaplanner.core.impl.score.buildin.simple.SimpleScoreDefinition;
import org.optaplanner.core.impl.score.buildin.simplebigdecimal.SimpleBigDecimalScoreDefinition;
import org.optaplanner.core.impl.score.buildin.simpledouble.SimpleDoubleScoreDefinition;
import org.optaplanner.core.impl.score.buildin.simplelong.SimpleLongScoreDefinition;
import org.optaplanner.core.impl.score.definition.ScoreDefinition;
import org.optaplanner.core.impl.score.director.ScoreDirector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.optaplanner.core.config.util.ConfigUtils.MemberAccessorType.*;
/**
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
*/
public class SolutionDescriptor<Solution_> {
public static <Solution_> SolutionDescriptor<Solution_> buildSolutionDescriptor(Class<Solution_> solutionClass,
Class<?>... entityClasses) {
return buildSolutionDescriptor(solutionClass, Arrays.asList(entityClasses), null);
}
public static <Solution_> SolutionDescriptor<Solution_> buildSolutionDescriptor(Class<Solution_> solutionClass,
List<Class<?>> entityClassList, ScoreDefinition deprecatedScoreDefinition) {
DescriptorPolicy descriptorPolicy = new DescriptorPolicy();
SolutionDescriptor<Solution_> solutionDescriptor = new SolutionDescriptor<>(solutionClass);
solutionDescriptor.processAnnotations(descriptorPolicy, deprecatedScoreDefinition, entityClassList);
for (Class<?> entityClass : sortEntityClassList(entityClassList)) {
EntityDescriptor<Solution_> entityDescriptor = new EntityDescriptor<>(solutionDescriptor, entityClass);
solutionDescriptor.addEntityDescriptor(entityDescriptor);
entityDescriptor.processAnnotations(descriptorPolicy);
}
solutionDescriptor.afterAnnotationsProcessed(descriptorPolicy);
return solutionDescriptor;
}
private static List<Class<?>> sortEntityClassList(List<Class<?>> entityClassList) {
List<Class<?>> sortedEntityClassList = new ArrayList<>(entityClassList.size());
for (Class<?> entityClass : entityClassList) {
boolean added = false;
for (int i = 0; i < sortedEntityClassList.size(); i++) {
Class<?> sortedEntityClass = sortedEntityClassList.get(i);
if (entityClass.isAssignableFrom(sortedEntityClass)) {
sortedEntityClassList.add(i, entityClass);
added = true;
break;
}
}
if (!added) {
sortedEntityClassList.add(entityClass);
}
}
return sortedEntityClassList;
}
// ************************************************************************
// Non-static members
// ************************************************************************
protected final transient Logger logger = LoggerFactory.getLogger(getClass());
private final Class<Solution_> solutionClass;
private SolutionCloner<Solution_> solutionCloner;
private AutoDiscoverMemberType autoDiscoverMemberType;
private final Map<String, MemberAccessor> problemFactMemberAccessorMap;
private final Map<String, MemberAccessor> problemFactCollectionMemberAccessorMap;
private final Map<String, MemberAccessor> entityMemberAccessorMap;
private final Map<String, MemberAccessor> entityCollectionMemberAccessorMap;
private MemberAccessor scoreMemberAccessor;
private ScoreDefinition scoreDefinition;
private final Map<Class<?>, EntityDescriptor<Solution_>> entityDescriptorMap;
private final List<Class<?>> reversedEntityClassList;
private final ConcurrentMap<Class<?>, EntityDescriptor<Solution_>> lowestEntityDescriptorCache = new ConcurrentHashMap<>();
private LookUpStrategyResolver lookUpStrategyResolver = null;
public SolutionDescriptor(Class<Solution_> solutionClass) {
this.solutionClass = solutionClass;
problemFactMemberAccessorMap = new LinkedHashMap<>();
problemFactCollectionMemberAccessorMap = new LinkedHashMap<>();
entityMemberAccessorMap = new LinkedHashMap<>();
entityCollectionMemberAccessorMap = new LinkedHashMap<>();
entityDescriptorMap = new LinkedHashMap<>();
reversedEntityClassList = new ArrayList<>();
}
public void addEntityDescriptor(EntityDescriptor<Solution_> entityDescriptor) {
Class<?> entityClass = entityDescriptor.getEntityClass();
for (Class<?> otherEntityClass : entityDescriptorMap.keySet()) {
if (entityClass.isAssignableFrom(otherEntityClass)) {
throw new IllegalArgumentException("An earlier entityClass (" + otherEntityClass
+ ") should not be a subclass of a later entityClass (" + entityClass
+ "). Switch their declaration so superclasses are defined earlier.");
}
}
entityDescriptorMap.put(entityClass, entityDescriptor);
reversedEntityClassList.add(0, entityClass);
lowestEntityDescriptorCache.put(entityClass, entityDescriptor);
}
public void processAnnotations(DescriptorPolicy descriptorPolicy, ScoreDefinition deprecatedScoreDefinition, List<Class<?>> entityClassList) {
processSolutionAnnotations(descriptorPolicy);
// Iterate inherited members too (unlike for EntityDescriptor where each one is declared)
// to make sure each one is registered
for (Class<?> lineageClass : ConfigUtils.getAllAnnotatedLineageClasses(solutionClass, PlanningSolution.class)) {
List<Member> memberList = ConfigUtils.getDeclaredMembers(lineageClass);
for (Member member : memberList) {
processValueRangeProviderAnnotation(descriptorPolicy, member);
processFactEntityOrScoreAnnotation(descriptorPolicy, member, deprecatedScoreDefinition, entityClassList);
}
}
if (entityCollectionMemberAccessorMap.isEmpty() && entityMemberAccessorMap.isEmpty()) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") must have at least 1 member with a "
+ PlanningEntityCollectionProperty.class.getSimpleName() + " annotation or a "
+ PlanningEntityProperty.class.getSimpleName() + " annotation.");
}
if (Solution.class.isAssignableFrom(solutionClass)) {
processLegacySolution(descriptorPolicy, deprecatedScoreDefinition);
return;
}
// Do not check if problemFactCollectionMemberAccessorMap and problemFactMemberAccessorMap are empty
// because they are only required for Drools score calculation.
if (scoreMemberAccessor == null) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") must have 1 member with a " + PlanningScore.class.getSimpleName() + " annotation.\n"
+ "Maybe add a getScore() method with a " + PlanningScore.class.getSimpleName() + " annotation.");
}
}
private void processLegacySolution(DescriptorPolicy descriptorPolicy, ScoreDefinition deprecatedScoreDefinition) {
if (!problemFactMemberAccessorMap.isEmpty()) {
MemberAccessor memberAccessor = problemFactMemberAccessorMap.values().iterator().next();
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") which implements the legacy interface " + Solution.class.getSimpleName()
+ ") must not have a member (" + memberAccessor.getName()
+ ") with a " + ProblemFactProperty.class.getSimpleName() + " annotation.\n"
+ "Maybe remove the use of the legacy interface.");
}
if (!problemFactCollectionMemberAccessorMap.isEmpty()) {
MemberAccessor memberAccessor = problemFactCollectionMemberAccessorMap.values().iterator().next();
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") which implements the legacy interface " + Solution.class.getSimpleName()
+ ") must not have a member (" + memberAccessor.getName()
+ ") with a " + ProblemFactCollectionProperty.class.getSimpleName() + " annotation.\n"
+ "Maybe remove the use of the legacy interface.");
}
try {
Method getProblemFactsMethod = solutionClass.getMethod("getProblemFacts");
MemberAccessor problemFactsMemberAccessor = new BeanPropertyMemberAccessor(getProblemFactsMethod);
problemFactCollectionMemberAccessorMap.put(
problemFactsMemberAccessor.getName(), problemFactsMemberAccessor);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Impossible situation: the solutionClass (" + solutionClass
+ ") which implements the legacy interface " + Solution.class.getSimpleName()
+ ", lacks its getProblemFacts() method.", e);
}
if (scoreMemberAccessor != null) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") which implements the legacy interface " + Solution.class.getSimpleName()
+ ") must not have a member (" + scoreMemberAccessor.getName()
+ ") with a " + PlanningScore.class.getSimpleName() + " annotation.\n"
+ "Maybe remove the use of the legacy interface.");
}
try {
Method getScoreMethod = solutionClass.getMethod("getScore");
scoreMemberAccessor = new BeanPropertyMemberAccessor(getScoreMethod);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Impossible situation: the solutionClass (" + solutionClass
+ ") which implements the legacy interface " + Solution.class.getSimpleName()
+ ", lacks its getScore() method.", e);
}
if (deprecatedScoreDefinition == null) {
deprecatedScoreDefinition = new SimpleScoreDefinition();
}
scoreDefinition = deprecatedScoreDefinition;
Class<? extends Score> scoreClass = extractScoreClass();
if (!scoreClass.isAssignableFrom(scoreDefinition.getScoreClass())) {
throw new IllegalArgumentException("The scoreClass (" + scoreClass
+ ") of solutionClass (" + solutionClass
+ ") is not the same or a superclass as the scoreDefinition's scoreClass ("
+ scoreDefinition.getScoreClass() + ").");
}
}
/**
* Only called if Drools score calculation is used.
*/
public void checkIfProblemFactsExist() {
if (problemFactCollectionMemberAccessorMap.isEmpty() && problemFactMemberAccessorMap.isEmpty()) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") must have at least 1 member with a "
+ ProblemFactCollectionProperty.class.getSimpleName() + " annotation or a "
+ ProblemFactProperty.class.getSimpleName() + " annotation"
+ " when used with Drools score calculation.");
}
}
private void processSolutionAnnotations(DescriptorPolicy descriptorPolicy) {
PlanningSolution solutionAnnotation = solutionClass.getAnnotation(PlanningSolution.class);
if (solutionAnnotation == null) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has been specified as a solution in the configuration," +
" but does not have a " + PlanningSolution.class.getSimpleName() + " annotation.");
}
autoDiscoverMemberType = solutionAnnotation.autoDiscoverMemberType();
processSolutionCloner(descriptorPolicy, solutionAnnotation);
lookUpStrategyResolver = new LookUpStrategyResolver(solutionAnnotation.lookUpStrategyType());
}
private void processSolutionCloner(DescriptorPolicy descriptorPolicy, PlanningSolution solutionAnnotation) {
Class<? extends SolutionCloner> solutionClonerClass = solutionAnnotation.solutionCloner();
if (solutionClonerClass == PlanningSolution.NullSolutionCloner.class) {
solutionClonerClass = null;
}
if (solutionClonerClass != null) {
solutionCloner = ConfigUtils.newInstance(this, "solutionClonerClass", solutionClonerClass);
} else {
solutionCloner = new FieldAccessingSolutionCloner<>(this);
}
}
private void processValueRangeProviderAnnotation(DescriptorPolicy descriptorPolicy, Member member) {
if (((AnnotatedElement) member).isAnnotationPresent(ValueRangeProvider.class)) {
MemberAccessor memberAccessor = ConfigUtils.buildMemberAccessor(
member, FIELD_OR_READ_METHOD, ValueRangeProvider.class);
descriptorPolicy.addFromSolutionValueRangeProvider(memberAccessor);
}
}
private void processFactEntityOrScoreAnnotation(DescriptorPolicy descriptorPolicy, Member member,
ScoreDefinition deprecatedScoreDefinition, List<Class<?>> entityClassList) {
Class<? extends Annotation> annotationClass = extractFactEntityOrScoreAnnotationClassOrAutoDiscover(
member, entityClassList);
if (annotationClass == null) {
return;
} else if (annotationClass.equals(ProblemFactProperty.class)
|| annotationClass.equals(ProblemFactCollectionProperty.class)) {
processProblemFactPropertyAnnotation(descriptorPolicy, member, annotationClass);
} else if (annotationClass.equals(PlanningEntityProperty.class)
|| annotationClass.equals(PlanningEntityCollectionProperty.class)) {
processPlanningEntityPropertyAnnotation(descriptorPolicy, member, annotationClass);
} else if (annotationClass.equals(PlanningScore.class)) {
processScoreAnnotation(descriptorPolicy, member, annotationClass, deprecatedScoreDefinition);
}
}
private Class<? extends Annotation> extractFactEntityOrScoreAnnotationClassOrAutoDiscover(
Member member, List<Class<?>> entityClassList) {
Class<? extends Annotation> annotationClass = ConfigUtils.extractAnnotationClass(member,
ProblemFactProperty.class, ProblemFactCollectionProperty.class,
PlanningEntityProperty.class, PlanningEntityCollectionProperty.class,
PlanningScore.class);
if (annotationClass == null) {
Class<?> type;
if (autoDiscoverMemberType == AutoDiscoverMemberType.FIELD
&& member instanceof Field) {
Field field = (Field) member;
type = field.getType();
} else if (autoDiscoverMemberType == AutoDiscoverMemberType.GETTER
&& (member instanceof Method) && ReflectionHelper.isGetterMethod((Method) member)) {
Method method = (Method) member;
type = method.getReturnType();
} else {
type = null;
}
if (type != null) {
if (Score.class.isAssignableFrom(type)) {
annotationClass = PlanningScore.class;
} else if (Collection.class.isAssignableFrom(type)) {
Type genericType = (member instanceof Field) ? ((Field) member).getGenericType()
: ((Method) member).getGenericReturnType();
Class<?> elementType = ConfigUtils.extractCollectionGenericTypeParameter(
"solutionClass", solutionClass,
type, genericType,
null, member.getName());
if (entityClassList.stream().anyMatch(entityClass -> entityClass.isAssignableFrom(elementType))) {
annotationClass = PlanningEntityCollectionProperty.class;
} else {
annotationClass = ProblemFactCollectionProperty.class;
}
} else if (Map.class.isAssignableFrom(type)) {
throw new IllegalStateException("The autoDiscoverMemberType (" + autoDiscoverMemberType
+ ") does not yet support the member (" + member
+ ") of type (" + type
+ ") which is an implementation of " + Map.class.getSimpleName() + ".");
} else if (entityClassList.stream().anyMatch(entityClass -> entityClass.isAssignableFrom(type))) {
annotationClass = PlanningEntityProperty.class;
} else {
annotationClass = ProblemFactProperty.class;
}
}
}
return annotationClass;
}
private void processProblemFactPropertyAnnotation(DescriptorPolicy descriptorPolicy, Member member,
Class<? extends Annotation> annotationClass) {
MemberAccessor memberAccessor = ConfigUtils.buildMemberAccessor(
member, FIELD_OR_READ_METHOD, annotationClass);
assertUnexistingProblemFactOrPlanningEntityProperty(memberAccessor, annotationClass);
if (annotationClass == ProblemFactProperty.class) {
problemFactMemberAccessorMap.put(memberAccessor.getName(), memberAccessor);
} else if (annotationClass == ProblemFactCollectionProperty.class) {
if (!Collection.class.isAssignableFrom(memberAccessor.getType())) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has a " + ProblemFactCollectionProperty.class.getSimpleName()
+ " annotated member (" + member + ") that does not return a "
+ Collection.class.getSimpleName() + ".");
}
problemFactCollectionMemberAccessorMap.put(memberAccessor.getName(), memberAccessor);
} else {
throw new IllegalStateException("Impossible situation with annotationClass (" + annotationClass + ").");
}
}
private void processPlanningEntityPropertyAnnotation(DescriptorPolicy descriptorPolicy, Member member,
Class<? extends Annotation> annotationClass) {
MemberAccessor memberAccessor = ConfigUtils.buildMemberAccessor(
member, FIELD_OR_GETTER_METHOD, annotationClass);
assertUnexistingProblemFactOrPlanningEntityProperty(memberAccessor, annotationClass);
if (annotationClass == PlanningEntityProperty.class) {
entityMemberAccessorMap.put(memberAccessor.getName(), memberAccessor);
} else if (annotationClass == PlanningEntityCollectionProperty.class) {
if (!Collection.class.isAssignableFrom(memberAccessor.getType())) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has a " + PlanningEntityCollectionProperty.class.getSimpleName()
+ " annotated member (" + member + ") that does not return a "
+ Collection.class.getSimpleName() + ".");
}
entityCollectionMemberAccessorMap.put(memberAccessor.getName(), memberAccessor);
} else {
throw new IllegalStateException("Impossible situation with annotationClass (" + annotationClass + ").");
}
}
private void assertUnexistingProblemFactOrPlanningEntityProperty(
MemberAccessor memberAccessor, Class<? extends Annotation> annotationClass) {
MemberAccessor duplicate;
Class<? extends Annotation> otherAnnotationClass;
String memberName = memberAccessor.getName();
if (problemFactMemberAccessorMap.containsKey(memberName)) {
duplicate = problemFactMemberAccessorMap.get(memberName);
otherAnnotationClass = ProblemFactProperty.class;
} else if (problemFactCollectionMemberAccessorMap.containsKey(memberName)) {
duplicate = problemFactCollectionMemberAccessorMap.get(memberName);
otherAnnotationClass = ProblemFactCollectionProperty.class;
} else if (entityMemberAccessorMap.containsKey(memberName)) {
duplicate = entityMemberAccessorMap.get(memberName);
otherAnnotationClass = PlanningEntityProperty.class;
} else if (entityCollectionMemberAccessorMap.containsKey(memberName)) {
duplicate = entityCollectionMemberAccessorMap.get(memberName);
otherAnnotationClass = PlanningEntityCollectionProperty.class;
} else {
return;
}
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has a " + annotationClass.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") that is duplicated by a " + otherAnnotationClass.getSimpleName()
+ " annotated member (" + duplicate + ").\n"
+ (annotationClass.equals(otherAnnotationClass)
? "Maybe the annotation is defined on both the field and its getter."
: "Maybe 2 mutually exclusive annotations are configured."));
}
private void processScoreAnnotation(DescriptorPolicy descriptorPolicy, Member member,
Class<? extends Annotation> annotationClass, ScoreDefinition deprecatedScoreDefinition) {
MemberAccessor memberAccessor = ConfigUtils.buildMemberAccessor(
member, FIELD_OR_GETTER_METHOD_WITH_SETTER, PlanningScore.class);
if (deprecatedScoreDefinition != null) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has a " + PlanningScore.class.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") but the solver configuration still has a deprecated scoreDefinitionType"
+ " or scoreDefinitionClass element.\n"
+ "Maybe remove the <scoreDefinitionType>, <bendableHardLevelsSize>, <bendableSoftLevelsSize> and <scoreDefinitionClass> elements from the solver configuration.");
}
if (!Score.class.isAssignableFrom(memberAccessor.getType())) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has a " + PlanningScore.class.getSimpleName()
+ " annotated member (" + memberAccessor + ") that does not return a subtype of Score.");
}
if (scoreMemberAccessor != null) {
if (!scoreMemberAccessor.getName().equals(memberAccessor.getName())
|| !scoreMemberAccessor.getClass().equals(memberAccessor.getClass())) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has a " + PlanningScore.class.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") that is duplicated by another member (" + scoreMemberAccessor + ").\n"
+ " Verify that the annotation is not defined on both the field and its getter.");
}
// Bottom class wins. Bottom classes are parsed first due to ConfigUtil.getAllAnnotatedLineageClasses()
return;
}
scoreMemberAccessor = memberAccessor;
Class<? extends Score> scoreType = (Class<? extends Score>) scoreMemberAccessor.getType();
PlanningScore annotation = scoreMemberAccessor.getAnnotation(PlanningScore.class);
if (annotation == null) {
// The member was autodiscovered
try {
annotation = AutoDiscoverAnnotationDefaults.class.getDeclaredField("PLANNING_SCORE").getAnnotation(PlanningScore.class);
} catch (NoSuchFieldException e) {
throw new IllegalStateException("Impossible situation: the field (PLANNING_SCORE) must exist.", e);
}
}
scoreDefinition = buildScoreDefinition(scoreType, annotation);
}
private static class AutoDiscoverAnnotationDefaults {
@PlanningScore private static final Object PLANNING_SCORE = new Object();
}
public ScoreDefinition buildScoreDefinition(Class<? extends Score> scoreType, PlanningScore annotation) {
Class<? extends ScoreDefinition> scoreDefinitionClass = annotation.scoreDefinitionClass();
if (scoreDefinitionClass != PlanningScore.NullScoreDefinition.class) {
if (annotation.bendableHardLevelsSize() != PlanningScore.NO_LEVEL_SIZE
|| annotation.bendableSoftLevelsSize() != PlanningScore.NO_LEVEL_SIZE) {
throw new IllegalArgumentException("The solutionClass (" + solutionClass
+ ") has a " + PlanningScore.class.getSimpleName()
+ " annotated member (" + scoreMemberAccessor
+ ") that has a scoreDefinition (" + scoreDefinitionClass
+ ") that must not have a bendableHardLevelsSize (" + annotation.bendableHardLevelsSize()
+ ") or a bendableSoftLevelsSize (" + annotation.bendableSoftLevelsSize() + ").");
}
return ConfigUtils.newInstance(this, "scoreDefinitionClass", scoreDefinitionClass);
}
if (scoreType == Score.class) {
if (!AbstractSolution.class.isAssignableFrom(solutionClass)) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has a " + PlanningScore.class.getSimpleName()
+ " annotated member (" + scoreMemberAccessor
+ ") that doesn't return a non-abstract " + Score.class.getSimpleName() + " class.\n"
+ "Maybe make it return " + HardSoftScore.class.getSimpleName()
+ " or another specific " + Score.class.getSimpleName() + " implementation.");
} else {
// Magic to support AbstractSolution
if (solutionClass == AbstractSolution.class) {
throw new IllegalArgumentException(
"The solutionClass (" + solutionClass + ") cannot be directly a "
+ AbstractSolution.class.getSimpleName() + ", but a subclass would be ok.");
}
Class<?> baseClass = solutionClass;
while (baseClass.getSuperclass() != AbstractSolution.class) {
baseClass = baseClass.getSuperclass();
if (baseClass == null) {
throw new IllegalStateException(
"Impossible situation because the solutionClass (" + solutionClass
+ ") is assignable from " + AbstractSolution.class.getSimpleName() + ".");
}
}
Type genericAbstractSolution = solutionClass.getGenericSuperclass();
if (!(genericAbstractSolution instanceof ParameterizedType)) {
throw new IllegalStateException(
"Impossible situation because the genericAbstractSolution (" + genericAbstractSolution
+ ") is a " + AbstractSolution.class.getSimpleName() + ".");
}
ParameterizedType parameterizedAbstractSolution = (ParameterizedType) genericAbstractSolution;
Type[] typeArguments = parameterizedAbstractSolution.getActualTypeArguments();
if (typeArguments.length != 1) {
throw new IllegalStateException(
"Impossible situation because the parameterizedAbstractSolution ("
+ parameterizedAbstractSolution
+ ") is a " + AbstractSolution.class.getSimpleName() + ".");
}
Type typeArgument = typeArguments[0];
if (!(typeArgument instanceof Class)) {
throw new IllegalStateException(
"Impossible situation because a (" + AbstractSolution.class.getSimpleName()
+ "'s typeArgument (" + typeArgument + ") must be a " + Score.class.getSimpleName() + ".");
}
scoreType = (Class<? extends Score>) typeArgument;
}
}
if (!AbstractBendableScore.class.isAssignableFrom(scoreType)) {
if (annotation.bendableHardLevelsSize() != PlanningScore.NO_LEVEL_SIZE
|| annotation.bendableSoftLevelsSize() != PlanningScore.NO_LEVEL_SIZE) {
throw new IllegalArgumentException("The solutionClass (" + solutionClass
+ ") has a " + PlanningScore.class.getSimpleName()
+ " annotated member (" + scoreMemberAccessor
+ ") that returns a scoreType (" + scoreType
+ ") that must not have a bendableHardLevelsSize (" + annotation.bendableHardLevelsSize()
+ ") or a bendableSoftLevelsSize (" + annotation.bendableSoftLevelsSize() + ").");
}
if (scoreType.equals(SimpleScore.class)) {
return new SimpleScoreDefinition();
} else if (scoreType.equals(SimpleLongScore.class)) {
return new SimpleLongScoreDefinition();
} else if (scoreType.equals(SimpleDoubleScore.class)) {
return new SimpleDoubleScoreDefinition();
} else if (scoreType.equals(SimpleBigDecimalScore.class)) {
return new SimpleBigDecimalScoreDefinition();
} else if (scoreType.equals(HardSoftScore.class)) {
return new HardSoftScoreDefinition();
} else if (scoreType.equals(HardSoftLongScore.class)) {
return new HardSoftLongScoreDefinition();
} else if (scoreType.equals(HardSoftDoubleScore.class)) {
return new HardSoftDoubleScoreDefinition();
} else if (scoreType.equals(HardSoftBigDecimalScore.class)) {
return new HardSoftBigDecimalScoreDefinition();
} else if (scoreType.equals(HardMediumSoftScore.class)) {
return new HardMediumSoftScoreDefinition();
} else if (scoreType.equals(HardMediumSoftLongScore.class)) {
return new HardMediumSoftLongScoreDefinition();
} else {
throw new IllegalArgumentException("The solutionClass (" + solutionClass
+ ") has a " + PlanningScore.class.getSimpleName()
+ " annotated member (" + scoreMemberAccessor
+ ") that returns a scoreType (" + scoreType
+ ") that is not recognized as a default " + Score.class.getSimpleName() + " implementation.\n"
+ " If you intend to use a custom implementation,"
+ " maybe set a scoreDefinition in the " + PlanningScore.class.getSimpleName()
+ " annotation.");
}
} else {
int bendableHardLevelsSize = annotation.bendableHardLevelsSize();
int bendableSoftLevelsSize = annotation.bendableSoftLevelsSize();
if (bendableHardLevelsSize == PlanningScore.NO_LEVEL_SIZE
|| bendableSoftLevelsSize == PlanningScore.NO_LEVEL_SIZE) {
throw new IllegalArgumentException("The solutionClass (" + solutionClass
+ ") has a " + PlanningScore.class.getSimpleName()
+ " annotated member (" + scoreMemberAccessor
+ ") that returns a scoreType (" + scoreType
+ ") that must have a bendableHardLevelsSize (" + annotation.bendableHardLevelsSize()
+ ") and a bendableSoftLevelsSize (" + annotation.bendableSoftLevelsSize() + ").");
}
if (scoreType.equals(BendableScore.class)) {
return new BendableScoreDefinition(bendableHardLevelsSize, bendableSoftLevelsSize);
} else if (scoreType.equals(BendableLongScore.class)) {
return new BendableLongScoreDefinition(bendableHardLevelsSize, bendableSoftLevelsSize);
} else if (scoreType.equals(BendableBigDecimalScore.class)) {
return new BendableBigDecimalScoreDefinition(bendableHardLevelsSize, bendableSoftLevelsSize);
} else {
throw new IllegalArgumentException("The solutionClass (" + solutionClass
+ ") has a " + PlanningScore.class.getSimpleName()
+ " annotated member (" + scoreMemberAccessor
+ ") that returns a bendable scoreType (" + scoreType
+ ") that is not recognized as a default " + Score.class.getSimpleName() + " implementation.\n"
+ " If you intend to use a custom implementation,"
+ " maybe set a scoreDefinition in the annotation.");
}
}
}
public void afterAnnotationsProcessed(DescriptorPolicy descriptorPolicy) {
for (EntityDescriptor<Solution_> entityDescriptor : entityDescriptorMap.values()) {
entityDescriptor.linkInheritedEntityDescriptors(descriptorPolicy);
}
for (EntityDescriptor<Solution_> entityDescriptor : entityDescriptorMap.values()) {
entityDescriptor.linkShadowSources(descriptorPolicy);
}
determineGlobalShadowOrder();
if (logger.isTraceEnabled()) {
logger.trace(" Model annotations parsed for Solution {}:", solutionClass.getSimpleName());
for (Map.Entry<Class<?>, EntityDescriptor<Solution_>> entry : entityDescriptorMap.entrySet()) {
EntityDescriptor<Solution_> entityDescriptor = entry.getValue();
logger.trace(" Entity {}:", entityDescriptor.getEntityClass().getSimpleName());
for (VariableDescriptor<Solution_> variableDescriptor : entityDescriptor.getDeclaredVariableDescriptors()) {
logger.trace(" Variable {} ({})", variableDescriptor.getVariableName(),
variableDescriptor instanceof GenuineVariableDescriptor ? "genuine" : "shadow");
}
}
}
}
private void determineGlobalShadowOrder() {
// Topological sorting with Kahn's algorithm
List<Pair<ShadowVariableDescriptor<Solution_>, Integer>> pairList = new ArrayList<>();
Map<ShadowVariableDescriptor<Solution_>, Pair<ShadowVariableDescriptor<Solution_>, Integer>> shadowToPairMap
= new HashMap<>();
for (EntityDescriptor<Solution_> entityDescriptor : entityDescriptorMap.values()) {
for (ShadowVariableDescriptor<Solution_> shadow : entityDescriptor.getDeclaredShadowVariableDescriptors()) {
int sourceSize = shadow.getSourceVariableDescriptorList().size();
Pair<ShadowVariableDescriptor<Solution_>, Integer> pair = MutablePair.of(shadow, sourceSize);
pairList.add(pair);
shadowToPairMap.put(shadow, pair);
}
}
for (EntityDescriptor<Solution_> entityDescriptor : entityDescriptorMap.values()) {
for (GenuineVariableDescriptor<Solution_> genuine : entityDescriptor.getDeclaredGenuineVariableDescriptors()) {
for (ShadowVariableDescriptor<Solution_> sink : genuine.getSinkVariableDescriptorList()) {
Pair<ShadowVariableDescriptor<Solution_>, Integer> sinkPair = shadowToPairMap.get(sink);
sinkPair.setValue(sinkPair.getValue() - 1);
}
}
}
int globalShadowOrder = 0;
while (!pairList.isEmpty()) {
pairList.sort(Comparator.comparingInt(Pair::getValue));
Pair<ShadowVariableDescriptor<Solution_>, Integer> pair = pairList.remove(0);
ShadowVariableDescriptor<Solution_> shadow = pair.getKey();
if (pair.getValue() != 0) {
if (pair.getValue() < 0) {
throw new IllegalStateException("Impossible state because the shadowVariable ("
+ shadow.getSimpleEntityAndVariableName()
+ ") can not be used more as a sink than it has sources.");
}
throw new IllegalStateException("There is a cyclic shadow variable path"
+ " that involves the shadowVariable (" + shadow.getSimpleEntityAndVariableName()
+ ") because it must be later than its sources (" + shadow.getSourceVariableDescriptorList()
+ ") and also earlier than its sinks (" + shadow.getSinkVariableDescriptorList() + ").");
}
for (ShadowVariableDescriptor<Solution_> sink : shadow.getSinkVariableDescriptorList()) {
Pair<ShadowVariableDescriptor<Solution_>, Integer> sinkPair = shadowToPairMap.get(sink);
sinkPair.setValue(sinkPair.getValue() - 1);
}
shadow.setGlobalShadowOrder(globalShadowOrder);
globalShadowOrder++;
}
}
public Class<Solution_> getSolutionClass() {
return solutionClass;
}
/**
* @return the {@link Class} of {@link PlanningScore}
*/
public Class<? extends Score> extractScoreClass() {
return (Class<? extends Score>) scoreMemberAccessor.getType();
}
public ScoreDefinition getScoreDefinition() {
return scoreDefinition;
}
public SolutionCloner<Solution_> getSolutionCloner() {
return solutionCloner;
}
public Map<String, MemberAccessor> getProblemFactMemberAccessorMap() {
return problemFactMemberAccessorMap;
}
public Map<String, MemberAccessor> getProblemFactCollectionMemberAccessorMap() {
return problemFactCollectionMemberAccessorMap;
}
public List<String> getProblemFactMemberAndProblemFactCollectionMemberNames() {
List<String> memberNames = new ArrayList<>(problemFactMemberAccessorMap.size()
+ problemFactCollectionMemberAccessorMap.size());
memberNames.addAll(problemFactMemberAccessorMap.keySet());
memberNames.addAll(problemFactCollectionMemberAccessorMap.keySet());
return memberNames;
}
public Map<String, MemberAccessor> getEntityMemberAccessorMap() {
return entityMemberAccessorMap;
}
public Map<String, MemberAccessor> getEntityCollectionMemberAccessorMap() {
return entityCollectionMemberAccessorMap;
}
public List<String> getEntityMemberAndEntityCollectionMemberNames() {
List<String> memberNames = new ArrayList<>(entityMemberAccessorMap.size()
+ entityCollectionMemberAccessorMap.size());
memberNames.addAll(entityMemberAccessorMap.keySet());
memberNames.addAll(entityCollectionMemberAccessorMap.keySet());
return memberNames;
}
// ************************************************************************
// Model methods
// ************************************************************************
public Set<Class<?>> getEntityClassSet() {
return entityDescriptorMap.keySet();
}
public Collection<EntityDescriptor<Solution_>> getEntityDescriptors() {
return entityDescriptorMap.values();
}
public Collection<EntityDescriptor<Solution_>> getGenuineEntityDescriptors() {
List<EntityDescriptor<Solution_>> genuineEntityDescriptorList = new ArrayList<>(entityDescriptorMap.size());
for (EntityDescriptor<Solution_> entityDescriptor : entityDescriptorMap.values()) {
if (entityDescriptor.hasAnyDeclaredGenuineVariableDescriptor()) {
genuineEntityDescriptorList.add(entityDescriptor);
}
}
return genuineEntityDescriptorList;
}
public boolean hasEntityDescriptorStrict(Class<?> entityClass) {
return entityDescriptorMap.containsKey(entityClass);
}
public EntityDescriptor<Solution_> getEntityDescriptorStrict(Class<?> entityClass) {
return entityDescriptorMap.get(entityClass);
}
public boolean hasEntityDescriptor(Class<?> entitySubclass) {
EntityDescriptor<Solution_> entityDescriptor = findEntityDescriptor(entitySubclass);
return entityDescriptor != null;
}
public EntityDescriptor<Solution_> findEntityDescriptorOrFail(Class<?> entitySubclass) {
EntityDescriptor<Solution_> entityDescriptor = findEntityDescriptor(entitySubclass);
if (entityDescriptor == null) {
throw new IllegalArgumentException("A planning entity is an instance of an entitySubclass ("
+ entitySubclass + ") that is not configured as a planning entity.\n" +
"If that class (" + entitySubclass.getSimpleName()
+ ") (or superclass thereof) is not a entityClass (" + getEntityClassSet()
+ "), check your Solution implementation's annotated methods.\n" +
"If it is, check your solver configuration.");
}
return entityDescriptor;
}
public EntityDescriptor<Solution_> findEntityDescriptor(Class<?> entitySubclass) {
return lowestEntityDescriptorCache.computeIfAbsent(entitySubclass, key -> {
// Reverse order to find the nearest ancestor
for (Class<?> entityClass : reversedEntityClassList) {
if (entityClass.isAssignableFrom(entitySubclass)) {
return entityDescriptorMap.get(entityClass);
}
}
return null;
});
}
public GenuineVariableDescriptor<Solution_> findGenuineVariableDescriptor(Object entity, String variableName) {
EntityDescriptor<Solution_> entityDescriptor = findEntityDescriptorOrFail(entity.getClass());
return entityDescriptor.getGenuineVariableDescriptor(variableName);
}
public GenuineVariableDescriptor<Solution_> findGenuineVariableDescriptorOrFail(Object entity, String variableName) {
EntityDescriptor<Solution_> entityDescriptor = findEntityDescriptorOrFail(entity.getClass());
GenuineVariableDescriptor<Solution_> variableDescriptor = entityDescriptor.getGenuineVariableDescriptor(variableName);
if (variableDescriptor == null) {
throw new IllegalArgumentException(entityDescriptor.buildInvalidVariableNameExceptionMessage(variableName));
}
return variableDescriptor;
}
public VariableDescriptor<Solution_> findVariableDescriptor(Object entity, String variableName) {
EntityDescriptor<Solution_> entityDescriptor = findEntityDescriptorOrFail(entity.getClass());
return entityDescriptor.getVariableDescriptor(variableName);
}
public VariableDescriptor<Solution_> findVariableDescriptorOrFail(Object entity, String variableName) {
EntityDescriptor<Solution_> entityDescriptor = findEntityDescriptorOrFail(entity.getClass());
VariableDescriptor<Solution_> variableDescriptor = entityDescriptor.getVariableDescriptor(variableName);
if (variableDescriptor == null) {
throw new IllegalArgumentException(entityDescriptor.buildInvalidVariableNameExceptionMessage(variableName));
}
return variableDescriptor;
}
// ************************************************************************
// Look up methods
// ************************************************************************
public LookUpStrategyResolver getLookUpStrategyResolver() {
return lookUpStrategyResolver;
}
// ************************************************************************
// Extraction methods
// ************************************************************************
public Collection<Object> getAllFacts(Solution_ solution) {
Collection<Object> facts = new ArrayList<>();
// Adds both entities and facts
Arrays.asList(entityMemberAccessorMap, problemFactMemberAccessorMap)
.forEach(map -> map.forEach((key, memberAccessor) -> {
Object object = extractMemberObject(memberAccessor, solution);
if (object != null) {
facts.add(object);
}
}));
entityCollectionMemberAccessorMap.forEach(
(key, memberAccessor) -> facts.addAll(extractMemberCollection(memberAccessor, solution, false)));
problemFactCollectionMemberAccessorMap.forEach(
(key, memberAccessor) -> facts.addAll(extractMemberCollection(memberAccessor, solution, true)));
return facts;
}
/**
* @param solution never null
* @return {@code >= 0}
*/
public int getEntityCount(Solution_ solution) {
int entityCount = 0;
for (MemberAccessor entityMemberAccessor : entityMemberAccessorMap.values()) {
Object entity = extractMemberObject(entityMemberAccessor, solution);
if (entity != null) {
entityCount++;
}
}
for (MemberAccessor entityCollectionMemberAccessor : entityCollectionMemberAccessorMap.values()) {
Collection<Object> entityCollection = extractMemberCollection(entityCollectionMemberAccessor, solution, false);
entityCount += entityCollection.size();
}
return entityCount;
}
public List<Object> getEntityList(Solution_ solution) {
List<Object> entityList = new ArrayList<>();
for (MemberAccessor entityMemberAccessor : entityMemberAccessorMap.values()) {
Object entity = extractMemberObject(entityMemberAccessor, solution);
if (entity != null) {
entityList.add(entity);
}
}
for (MemberAccessor entityCollectionMemberAccessor : entityCollectionMemberAccessorMap.values()) {
Collection<Object> entityCollection = extractMemberCollection(entityCollectionMemberAccessor, solution, false);
entityList.addAll(entityCollection);
}
return entityList;
}
public List<Object> getEntityListByEntityClass(Solution_ solution, Class<?> entityClass) {
List<Object> entityList = new ArrayList<>();
for (MemberAccessor entityMemberAccessor : entityMemberAccessorMap.values()) {
if (entityMemberAccessor.getType().isAssignableFrom(entityClass)) {
Object entity = extractMemberObject(entityMemberAccessor, solution);
if (entity != null && entityClass.isInstance(entity)) {
entityList.add(entity);
}
}
}
for (MemberAccessor entityCollectionMemberAccessor : entityCollectionMemberAccessorMap.values()) {
// TODO if (entityCollectionPropertyAccessor.getPropertyType().getElementType().isAssignableFrom(entityClass)) {
Collection<Object> entityCollection = extractMemberCollection(entityCollectionMemberAccessor, solution, false);
for (Object entity : entityCollection) {
if (entityClass.isInstance(entity)) {
entityList.add(entity);
}
}
}
return entityList;
}
/**
* @param solution never null
* @return {@code >= 0}
*/
public long getGenuineVariableCount(Solution_ solution) {
long variableCount = 0L;
for (Iterator<Object> it = extractAllEntitiesIterator(solution); it.hasNext(); ) {
Object entity = it.next();
EntityDescriptor<Solution_> entityDescriptor = findEntityDescriptorOrFail(entity.getClass());
variableCount += entityDescriptor.getGenuineVariableCount();
}
return variableCount;
}
public long getMaximumValueCount(Solution_ solution) {
long maximumValueCount = 0L;
for (Iterator<Object> it = extractAllEntitiesIterator(solution); it.hasNext(); ) {
Object entity = it.next();
EntityDescriptor<Solution_> entityDescriptor = findEntityDescriptorOrFail(entity.getClass());
maximumValueCount = Math.max(maximumValueCount, entityDescriptor.getMaximumValueCount(solution, entity));
}
return maximumValueCount;
}
/**
* @param solution never null
* @return {@code >= 0}
*/
public int getValueCount(Solution_ solution) {
int valueCount = 0;
// TODO FIXME for ValueRatioTabuSizeStrategy (or reuse maximumValueCount() for that variable descriptor?)
throw new UnsupportedOperationException(
"getValueCount is not yet supported - this blocks ValueRatioTabuSizeStrategy");
// return valueCount;
}
/**
* Calculates an indication on how big this problem instance is.
* This is intentionally very loosely defined for now.
* @param solution never null
* @return {@code >= 0}
*/
public long getProblemScale(Solution_ solution) {
long problemScale = 0L;
for (Iterator<Object> it = extractAllEntitiesIterator(solution); it.hasNext(); ) {
Object entity = it.next();
EntityDescriptor<Solution_> entityDescriptor = findEntityDescriptorOrFail(entity.getClass());
problemScale += entityDescriptor.getProblemScale(solution, entity);
}
return problemScale;
}
public int countUninitializedVariables(Solution_ solution) {
int count = 0;
for (Iterator<Object> it = extractAllEntitiesIterator(solution); it.hasNext(); ) {
Object entity = it.next();
EntityDescriptor<Solution_> entityDescriptor = findEntityDescriptorOrFail(entity.getClass());
count += entityDescriptor.countUninitializedVariables(entity);
}
return count;
}
public int countReinitializableVariables(ScoreDirector<Solution_> scoreDirector, Solution_ solution) {
int count = 0;
for (Iterator<Object> it = extractAllEntitiesIterator(solution); it.hasNext(); ) {
Object entity = it.next();
EntityDescriptor<Solution_> entityDescriptor = findEntityDescriptorOrFail(entity.getClass());
count += entityDescriptor.countReinitializableVariables(scoreDirector, entity);
}
return count;
}
public Iterator<Object> extractAllEntitiesIterator(Solution_ solution) {
List<Iterator<Object>> iteratorList = new ArrayList<>(
entityMemberAccessorMap.size() + entityCollectionMemberAccessorMap.size());
for (MemberAccessor entityMemberAccessor : entityMemberAccessorMap.values()) {
Object entity = extractMemberObject(entityMemberAccessor, solution);
if (entity != null) {
iteratorList.add(Collections.singletonList(entity).iterator());
}
}
for (MemberAccessor entityCollectionMemberAccessor : entityCollectionMemberAccessorMap.values()) {
Collection<Object> entityCollection = extractMemberCollection(entityCollectionMemberAccessor, solution, false);
iteratorList.add(entityCollection.iterator());
}
return Iterators.concat(iteratorList.iterator());
}
private Object extractMemberObject(MemberAccessor memberAccessor, Solution_ solution) {
return memberAccessor.executeGetter(solution);
}
private Collection<Object> extractMemberCollection(MemberAccessor collectionMemberAccessor, Solution_ solution,
boolean isFact) {
Collection<Object> collection = (Collection<Object>) collectionMemberAccessor.executeGetter(solution);
if (collection == null) {
throw new IllegalArgumentException("The solutionClass (" + solutionClass
+ ")'s " + (isFact ? "factCollectionProperty" : "entityCollectionProperty") + " ("
+ collectionMemberAccessor + ") should never return null.\n"
+ (collectionMemberAccessor instanceof FieldMemberAccessor ? ""
: "Maybe the getter/method always returns null instead of the actual data.\n")
+ "Maybe the value is null instead of an empty collection.");
}
return collection;
}
/**
* @param solution never null
* @return sometimes null, if the {@link Score} hasn't been calculated yet
*/
public Score getScore(Solution_ solution) {
return (Score) scoreMemberAccessor.executeGetter(solution);
}
/**
* Called when the {@link Score} has been calculated or predicted.
* @param solution never null
* @param score sometimes null, in rare occasions to indicate that the old {@link Score} is stale,
* but no new ones has been calculated
*/
public void setScore(Solution_ solution, Score score) {
scoreMemberAccessor.executeSetter(solution, score);
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + solutionClass.getName() + ")";
}
}