/* * Copyright 2011 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.entity.descriptor; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Member; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.optaplanner.core.api.domain.entity.PlanningEntity; import org.optaplanner.core.api.domain.solution.PlanningSolution; import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider; import org.optaplanner.core.api.domain.variable.AnchorShadowVariable; import org.optaplanner.core.api.domain.variable.CustomShadowVariable; import org.optaplanner.core.api.domain.variable.InverseRelationShadowVariable; import org.optaplanner.core.api.domain.variable.PlanningVariable; import org.optaplanner.core.config.heuristic.selector.common.decorator.SelectionSorterOrder; import org.optaplanner.core.config.util.ConfigUtils; import org.optaplanner.core.impl.domain.common.ReflectionHelper; import org.optaplanner.core.impl.domain.common.accessor.MemberAccessor; import org.optaplanner.core.impl.domain.policy.DescriptorPolicy; import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor; import org.optaplanner.core.impl.domain.variable.anchor.AnchorShadowVariableDescriptor; import org.optaplanner.core.impl.domain.variable.custom.CustomShadowVariableDescriptor; 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.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor; import org.optaplanner.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionFilter; import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionSorter; import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import org.optaplanner.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter; import org.optaplanner.core.impl.score.director.ScoreDirector; import static org.optaplanner.core.config.util.ConfigUtils.MemberAccessorType.*; /** * @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation */ public class EntityDescriptor<Solution_> { public static final Class[] VARIABLE_ANNOTATION_CLASSES = { PlanningVariable.class, InverseRelationShadowVariable.class, AnchorShadowVariable.class, CustomShadowVariable.class}; private final SolutionDescriptor<Solution_> solutionDescriptor; private final Class<?> entityClass; private SelectionFilter movableEntitySelectionFilter; private SelectionSorter decreasingDifficultySorter; private List<EntityDescriptor<Solution_>> inheritedEntityDescriptorList; // Only declared variable descriptors, excludes inherited variable descriptors private Map<String, GenuineVariableDescriptor<Solution_>> declaredGenuineVariableDescriptorMap; private Map<String, ShadowVariableDescriptor<Solution_>> declaredShadowVariableDescriptorMap; // Caches the inherited and declared variable descriptors private Map<String, GenuineVariableDescriptor<Solution_>> effectiveGenuineVariableDescriptorMap; private Map<String, ShadowVariableDescriptor<Solution_>> effectiveShadowVariableDescriptorMap; private Map<String, VariableDescriptor<Solution_>> effectiveVariableDescriptorMap; public EntityDescriptor(SolutionDescriptor<Solution_> solutionDescriptor, Class<?> entityClass) { this.solutionDescriptor = solutionDescriptor; this.entityClass = entityClass; } public void processAnnotations(DescriptorPolicy descriptorPolicy) { processEntityAnnotations(descriptorPolicy); processValueRangeProviderAnnotations(descriptorPolicy); processPlanningVariableAnnotations(descriptorPolicy); } private void processEntityAnnotations(DescriptorPolicy descriptorPolicy) { PlanningEntity entityAnnotation = entityClass.getAnnotation(PlanningEntity.class); if (entityAnnotation == null) { throw new IllegalStateException("The entityClass (" + entityClass + ") has been specified as a planning entity in the configuration," + " but does not have a " + PlanningEntity.class.getSimpleName() + " annotation."); } processMovable(descriptorPolicy, entityAnnotation); processDifficulty(descriptorPolicy, entityAnnotation); } private void processMovable(DescriptorPolicy descriptorPolicy, PlanningEntity entityAnnotation) { Class<? extends SelectionFilter> movableEntitySelectionFilterClass = entityAnnotation.movableEntitySelectionFilter(); if (movableEntitySelectionFilterClass == PlanningEntity.NullMovableEntitySelectionFilter.class) { movableEntitySelectionFilterClass = null; } if (movableEntitySelectionFilterClass != null) { movableEntitySelectionFilter = ConfigUtils.newInstance(this, "movableEntitySelectionFilterClass", movableEntitySelectionFilterClass); } } private void processDifficulty(DescriptorPolicy descriptorPolicy, PlanningEntity entityAnnotation) { Class<? extends Comparator> difficultyComparatorClass = entityAnnotation.difficultyComparatorClass(); if (difficultyComparatorClass == PlanningEntity.NullDifficultyComparator.class) { difficultyComparatorClass = null; } Class<? extends SelectionSorterWeightFactory> difficultyWeightFactoryClass = entityAnnotation.difficultyWeightFactoryClass(); if (difficultyWeightFactoryClass == PlanningEntity.NullDifficultyWeightFactory.class) { difficultyWeightFactoryClass = null; } if (difficultyComparatorClass != null && difficultyWeightFactoryClass != null) { throw new IllegalStateException("The entityClass (" + entityClass + ") cannot have a difficultyComparatorClass (" + difficultyComparatorClass.getName() + ") and a difficultyWeightFactoryClass (" + difficultyWeightFactoryClass.getName() + ") at the same time."); } if (difficultyComparatorClass != null) { Comparator<Object> difficultyComparator = ConfigUtils.newInstance(this, "difficultyComparatorClass", difficultyComparatorClass); decreasingDifficultySorter = new ComparatorSelectionSorter<Solution_, Object>( difficultyComparator, SelectionSorterOrder.DESCENDING); } if (difficultyWeightFactoryClass != null) { SelectionSorterWeightFactory<Solution_, Object> difficultyWeightFactory = ConfigUtils.newInstance(this, "difficultyWeightFactoryClass", difficultyWeightFactoryClass); decreasingDifficultySorter = new WeightFactorySelectionSorter<>( difficultyWeightFactory, SelectionSorterOrder.DESCENDING); } } private void processValueRangeProviderAnnotations(DescriptorPolicy descriptorPolicy) { // Only iterate declared fields and methods, not inherited members, to avoid registering the same one twice List<Member> memberList = ConfigUtils.getDeclaredMembers(entityClass); for (Member member : memberList) { if (((AnnotatedElement) member).isAnnotationPresent(ValueRangeProvider.class)) { MemberAccessor memberAccessor = ConfigUtils.buildMemberAccessor( member, FIELD_OR_READ_METHOD, ValueRangeProvider.class); descriptorPolicy.addFromEntityValueRangeProvider( memberAccessor); } } } private void processPlanningVariableAnnotations(DescriptorPolicy descriptorPolicy) { declaredGenuineVariableDescriptorMap = new LinkedHashMap<>(); declaredShadowVariableDescriptorMap = new LinkedHashMap<>(); boolean noVariableAnnotation = true; // Only iterate declared fields and methods, not inherited members, to avoid registering the same one twice List<Member> memberList = ConfigUtils.getDeclaredMembers(entityClass); for (Member member : memberList) { Class<? extends Annotation> variableAnnotationClass = ConfigUtils.extractAnnotationClass( member, VARIABLE_ANNOTATION_CLASSES); if (variableAnnotationClass != null) { noVariableAnnotation = false; MemberAccessor memberAccessor = ConfigUtils.buildMemberAccessor( member, FIELD_OR_GETTER_METHOD_WITH_SETTER, variableAnnotationClass); registerVariableAccessor(descriptorPolicy, variableAnnotationClass, memberAccessor); } } if (noVariableAnnotation) { throw new IllegalStateException("The entityClass (" + entityClass + ") should have at least 1 getter method or 1 field with a " + PlanningVariable.class.getSimpleName() + " annotation or a shadow variable annotation."); } } private void registerVariableAccessor(DescriptorPolicy descriptorPolicy, Class<? extends Annotation> variableAnnotationClass, MemberAccessor memberAccessor) { String memberName = memberAccessor.getName(); if (declaredGenuineVariableDescriptorMap.containsKey(memberName) || declaredShadowVariableDescriptorMap.containsKey(memberName)) { VariableDescriptor<Solution_> duplicate = declaredGenuineVariableDescriptorMap.get(memberName); if (duplicate == null) { duplicate = declaredShadowVariableDescriptorMap.get(memberName); } throw new IllegalStateException("The entityClass (" + entityClass + ") has a " + variableAnnotationClass.getSimpleName() + " annotated member (" + memberAccessor + ") that is duplicated by another member for variableDescriptor (" + duplicate + ").\n" + " Verify that the annotation is not defined on both the field and its getter."); } if (variableAnnotationClass.equals(PlanningVariable.class)) { GenuineVariableDescriptor<Solution_> variableDescriptor = new GenuineVariableDescriptor<>(this, memberAccessor); declaredGenuineVariableDescriptorMap.put(memberName, variableDescriptor); variableDescriptor.processAnnotations(descriptorPolicy); } else if (variableAnnotationClass.equals(InverseRelationShadowVariable.class)) { ShadowVariableDescriptor<Solution_> variableDescriptor = new InverseRelationShadowVariableDescriptor<>( this, memberAccessor); declaredShadowVariableDescriptorMap.put(memberName, variableDescriptor); variableDescriptor.processAnnotations(descriptorPolicy); } else if (variableAnnotationClass.equals(AnchorShadowVariable.class)) { ShadowVariableDescriptor<Solution_> variableDescriptor = new AnchorShadowVariableDescriptor<>( this, memberAccessor); declaredShadowVariableDescriptorMap.put(memberName, variableDescriptor); variableDescriptor.processAnnotations(descriptorPolicy); } else if (variableAnnotationClass.equals(CustomShadowVariable.class)) { ShadowVariableDescriptor<Solution_> variableDescriptor = new CustomShadowVariableDescriptor<>( this, memberAccessor); declaredShadowVariableDescriptorMap.put(memberName, variableDescriptor); variableDescriptor.processAnnotations(descriptorPolicy); } else { throw new IllegalStateException("The variableAnnotationClass (" + variableAnnotationClass + ") is not implemented."); } } public void linkInheritedEntityDescriptors(DescriptorPolicy descriptorPolicy) { inheritedEntityDescriptorList = new ArrayList<>(4); investigateParentsToLinkInherited(entityClass); createEffectiveVariableDescriptorMaps(); } private void investigateParentsToLinkInherited(Class<?> investigateClass) { if (investigateClass == null || investigateClass.isArray()) { return; } linkInherited(investigateClass.getSuperclass()); for (Class<?> superInterface : investigateClass.getInterfaces()) { linkInherited(superInterface); } } private void linkInherited(Class<?> investigateClass) { EntityDescriptor<Solution_> superEntityDescriptor = solutionDescriptor.getEntityDescriptorStrict( investigateClass); if (superEntityDescriptor != null) { inheritedEntityDescriptorList.add(superEntityDescriptor); } else { investigateParentsToLinkInherited(investigateClass); } } public void linkShadowSources(DescriptorPolicy descriptorPolicy) { for (ShadowVariableDescriptor<Solution_> shadowVariableDescriptor : declaredShadowVariableDescriptorMap.values()) { shadowVariableDescriptor.linkShadowSources(descriptorPolicy); } } private void createEffectiveVariableDescriptorMaps() { effectiveGenuineVariableDescriptorMap = new LinkedHashMap<>(declaredGenuineVariableDescriptorMap.size()); effectiveShadowVariableDescriptorMap = new LinkedHashMap<>(declaredShadowVariableDescriptorMap.size()); for (EntityDescriptor<Solution_> inheritedEntityDescriptor : inheritedEntityDescriptorList) { effectiveGenuineVariableDescriptorMap.putAll(inheritedEntityDescriptor.getGenuineVariableDescriptorMap()); effectiveShadowVariableDescriptorMap.putAll(inheritedEntityDescriptor.getShadowVariableDescriptorMap()); } effectiveGenuineVariableDescriptorMap.putAll(declaredGenuineVariableDescriptorMap); effectiveShadowVariableDescriptorMap.putAll(declaredShadowVariableDescriptorMap); effectiveVariableDescriptorMap = new LinkedHashMap<>( effectiveGenuineVariableDescriptorMap.size() + effectiveShadowVariableDescriptorMap.size()); effectiveVariableDescriptorMap.putAll(effectiveGenuineVariableDescriptorMap); effectiveVariableDescriptorMap.putAll(effectiveShadowVariableDescriptorMap); } // ************************************************************************ // Worker methods // ************************************************************************ public SolutionDescriptor<Solution_> getSolutionDescriptor() { return solutionDescriptor; } public Class<?> getEntityClass() { return entityClass; } public boolean matchesEntity(Object entity) { return entityClass.isAssignableFrom(entity.getClass()); } public boolean hasMovableEntitySelectionFilter() { return movableEntitySelectionFilter != null; } public SelectionFilter getMovableEntitySelectionFilter() { return movableEntitySelectionFilter; } public SelectionSorter getDecreasingDifficultySorter() { return decreasingDifficultySorter; } public boolean hasAnyDeclaredGenuineVariableDescriptor() { return !declaredGenuineVariableDescriptorMap.isEmpty(); } public Collection<String> getGenuineVariableNameSet() { return effectiveGenuineVariableDescriptorMap.keySet(); } public Map<String, GenuineVariableDescriptor<Solution_>> getGenuineVariableDescriptorMap() { return effectiveGenuineVariableDescriptorMap; } public Collection<GenuineVariableDescriptor<Solution_>> getGenuineVariableDescriptors() { return effectiveGenuineVariableDescriptorMap.values(); } public List<GenuineVariableDescriptor<Solution_>> getGenuineVariableDescriptorList() { // TODO We might want to cache that list return new ArrayList<>(effectiveGenuineVariableDescriptorMap.values()); } public boolean hasGenuineVariableDescriptor(String variableName) { return effectiveGenuineVariableDescriptorMap.containsKey(variableName); } public GenuineVariableDescriptor<Solution_> getGenuineVariableDescriptor(String variableName) { return effectiveGenuineVariableDescriptorMap.get(variableName); } public Map<String, ShadowVariableDescriptor<Solution_>> getShadowVariableDescriptorMap() { return effectiveShadowVariableDescriptorMap; } public Collection<ShadowVariableDescriptor<Solution_>> getShadowVariableDescriptors() { return effectiveShadowVariableDescriptorMap.values(); } public boolean hasShadowVariableDescriptor(String variableName) { return effectiveShadowVariableDescriptorMap.containsKey(variableName); } public ShadowVariableDescriptor<Solution_> getShadowVariableDescriptor(String variableName) { return effectiveShadowVariableDescriptorMap.get(variableName); } public Map<String, VariableDescriptor<Solution_>> getVariableDescriptorMap() { return effectiveVariableDescriptorMap; } public Collection<VariableDescriptor<Solution_>> getVariableDescriptors() { return effectiveVariableDescriptorMap.values(); } public boolean hasVariableDescriptor(String variableName) { return effectiveVariableDescriptorMap.containsKey(variableName); } public VariableDescriptor<Solution_> getVariableDescriptor(String variableName) { return effectiveVariableDescriptorMap.get(variableName); } public Collection<GenuineVariableDescriptor<Solution_>> getDeclaredGenuineVariableDescriptors() { return declaredGenuineVariableDescriptorMap.values(); } public Collection<ShadowVariableDescriptor<Solution_>> getDeclaredShadowVariableDescriptors() { return declaredShadowVariableDescriptorMap.values(); } public Collection<VariableDescriptor<Solution_>> getDeclaredVariableDescriptors() { Collection<VariableDescriptor<Solution_>> variableDescriptors = new ArrayList<>( declaredGenuineVariableDescriptorMap.size() + declaredShadowVariableDescriptorMap.size()); variableDescriptors.addAll(declaredGenuineVariableDescriptorMap.values()); variableDescriptors.addAll(declaredShadowVariableDescriptorMap.values()); return variableDescriptors; } public String buildInvalidVariableNameExceptionMessage(String variableName) { if (!ReflectionHelper.hasGetterMethod(entityClass, variableName) && !ReflectionHelper.hasField(entityClass, variableName)) { String exceptionMessage = "The variableName (" + variableName + ") for entityClass (" + entityClass + ") does not exists as a getter or field on that class.\n" + "Check the spelling of the variableName (" + variableName + ")."; if (variableName.length() >= 2 && !Character.isUpperCase(variableName.charAt(0)) && Character.isUpperCase(variableName.charAt(1))) { String correctedVariableName = variableName.substring(0, 1).toUpperCase() + variableName.substring(1); exceptionMessage += "Maybe it needs to be correctedVariableName (" + correctedVariableName + ") instead, if it's a getter, because the JavaBeans spec states that " + "the first letter should be a upper case if the second is upper case."; } return exceptionMessage; } return "The variableName (" + variableName + ") for entityClass (" + entityClass + ") exists as a getter or field on that class," + " but isn't in the planning variables (" + effectiveVariableDescriptorMap.keySet() + ").\n" + (Character.isUpperCase(variableName.charAt(0)) ? "Maybe the variableName (" + variableName + ") should start with a lowercase.\n" : "") + "Maybe your planning entity's getter or field lacks a " + PlanningVariable.class.getSimpleName() + " annotation or a shadow variable annotation."; } public boolean hasAnyChainedGenuineVariables() { for (GenuineVariableDescriptor<Solution_> variableDescriptor : effectiveGenuineVariableDescriptorMap.values()) { if (!variableDescriptor.isChained()) { return true; } } return false; } // ************************************************************************ // Extraction methods // ************************************************************************ public List<Object> extractEntities(Solution_ solution) { return solutionDescriptor.getEntityListByEntityClass(solution, entityClass); } public long getGenuineVariableCount() { return effectiveGenuineVariableDescriptorMap.size(); } public long getMaximumValueCount(Solution_ solution, Object entity) { long maximumValueCount = 0L; for (GenuineVariableDescriptor<Solution_> variableDescriptor : effectiveGenuineVariableDescriptorMap.values()) { maximumValueCount = Math.max(maximumValueCount, variableDescriptor.getValueCount(solution, entity)); } return maximumValueCount; } public long getProblemScale(Solution_ solution, Object entity) { long problemScale = 1L; for (GenuineVariableDescriptor<Solution_> variableDescriptor : effectiveGenuineVariableDescriptorMap.values()) { problemScale *= variableDescriptor.getValueCount(solution, entity); } return problemScale; } public int countUninitializedVariables(Object entity) { int count = 0; for (GenuineVariableDescriptor<Solution_> variableDescriptor : effectiveGenuineVariableDescriptorMap.values()) { if (!variableDescriptor.isInitialized(entity)) { count++; } } return count; } public boolean isInitialized(Object entity) { for (GenuineVariableDescriptor<Solution_> variableDescriptor : effectiveGenuineVariableDescriptorMap.values()) { if (!variableDescriptor.isInitialized(entity)) { return false; } } return true; } public int countReinitializableVariables(ScoreDirector<Solution_> scoreDirector, Object entity) { int count = 0; for (GenuineVariableDescriptor<Solution_> variableDescriptor : effectiveGenuineVariableDescriptorMap.values()) { if (variableDescriptor.isReinitializable(scoreDirector, entity)) { count++; } } return count; } public boolean isMovable(ScoreDirector<Solution_> scoreDirector, Object entity) { return movableEntitySelectionFilter == null || movableEntitySelectionFilter.accept(scoreDirector, entity); } /** * @param scoreDirector never null * @param entity never null * @return true if the entity is initialized or immovable */ public boolean isEntityInitializedOrImmovable(ScoreDirector<Solution_> scoreDirector, Object entity) { return isInitialized(entity) || !isMovable(scoreDirector, entity); } @Override public String toString() { return getClass().getSimpleName() + "(" + entityClass.getName() + ")"; } }