/* * 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.variable.descriptor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.List; import org.apache.commons.lang3.ArrayUtils; import org.optaplanner.core.api.domain.solution.PlanningSolution; import org.optaplanner.core.api.domain.valuerange.CountableValueRange; import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider; import org.optaplanner.core.api.domain.variable.PlanningVariable; import org.optaplanner.core.api.domain.variable.PlanningVariableGraphType; import org.optaplanner.core.config.heuristic.selector.common.decorator.SelectionSorterOrder; import org.optaplanner.core.config.util.ConfigUtils; import org.optaplanner.core.impl.domain.common.accessor.MemberAccessor; import org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor; import org.optaplanner.core.impl.domain.policy.DescriptorPolicy; import org.optaplanner.core.impl.domain.valuerange.descriptor.CompositeValueRangeDescriptor; import org.optaplanner.core.impl.domain.valuerange.descriptor.FromEntityPropertyValueRangeDescriptor; import org.optaplanner.core.impl.domain.valuerange.descriptor.FromSolutionPropertyValueRangeDescriptor; import org.optaplanner.core.impl.domain.valuerange.descriptor.ValueRangeDescriptor; 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.heuristic.selector.entity.decorator.NullValueReinitializeVariableEntityFilter; import org.optaplanner.core.impl.heuristic.selector.value.decorator.MovableChainedTrailingValueFilter; import org.optaplanner.core.impl.score.director.ScoreDirector; /** * @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation */ public class GenuineVariableDescriptor<Solution_> extends VariableDescriptor<Solution_> { private boolean chained; private ValueRangeDescriptor<Solution_> valueRangeDescriptor; private boolean nullable; private SelectionFilter movableChainedTrailingValueFilter; private SelectionFilter reinitializeVariableEntityFilter; private SelectionSorter increasingStrengthSorter; private SelectionSorter decreasingStrengthSorter; public GenuineVariableDescriptor(EntityDescriptor<Solution_> entityDescriptor, MemberAccessor variableMemberAccessor) { super(entityDescriptor, variableMemberAccessor); } public void processAnnotations(DescriptorPolicy descriptorPolicy) { processPropertyAnnotations(descriptorPolicy); } private void processPropertyAnnotations(DescriptorPolicy descriptorPolicy) { PlanningVariable planningVariableAnnotation = variableMemberAccessor.getAnnotation(PlanningVariable.class); processNullable(descriptorPolicy, planningVariableAnnotation); processChained(descriptorPolicy, planningVariableAnnotation); processValueRangeRefs(descriptorPolicy, planningVariableAnnotation); processStrength(descriptorPolicy, planningVariableAnnotation); } private void processNullable(DescriptorPolicy descriptorPolicy, PlanningVariable planningVariableAnnotation) { nullable = planningVariableAnnotation.nullable(); if (nullable && variableMemberAccessor.getType().isPrimitive()) { throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass() + ") has a PlanningVariable annotated property (" + variableMemberAccessor.getName() + ") with nullable (" + nullable + "), which is not compatible with the primitive propertyType (" + variableMemberAccessor.getType() + ")."); } Class<? extends SelectionFilter> reinitializeVariableEntityFilterClass = planningVariableAnnotation.reinitializeVariableEntityFilter(); if (reinitializeVariableEntityFilterClass == PlanningVariable.NullReinitializeVariableEntityFilter.class) { reinitializeVariableEntityFilterClass = null; } if (reinitializeVariableEntityFilterClass != null) { reinitializeVariableEntityFilter = ConfigUtils.newInstance(this, "reinitializeVariableEntityFilterClass", reinitializeVariableEntityFilterClass); } else { reinitializeVariableEntityFilter = new NullValueReinitializeVariableEntityFilter(this); } } private void processChained(DescriptorPolicy descriptorPolicy, PlanningVariable planningVariableAnnotation) { chained = planningVariableAnnotation.graphType() == PlanningVariableGraphType.CHAINED; if (chained && !variableMemberAccessor.getType().isAssignableFrom( entityDescriptor.getEntityClass())) { throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass() + ") has a PlanningVariable annotated property (" + variableMemberAccessor.getName() + ") with chained (" + chained + ") and propertyType (" + variableMemberAccessor.getType() + ") which is not a superclass/interface of or the same as the entityClass (" + entityDescriptor.getEntityClass() + ").\n" + "If an entity's chained planning variable cannot point to another entity of the same class," + " then it is impossible to make chain longer than 1 entity and therefore chaining is useless."); } if (chained && nullable) { throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass() + ") has a PlanningVariable annotated property (" + variableMemberAccessor.getName() + ") with chained (" + chained + "), which is not compatible with nullable (" + nullable + ")."); } if (chained && entityDescriptor.hasMovableEntitySelectionFilter()) { movableChainedTrailingValueFilter = new MovableChainedTrailingValueFilter(this); } else { movableChainedTrailingValueFilter = null; } } private void processValueRangeRefs(DescriptorPolicy descriptorPolicy, PlanningVariable planningVariableAnnotation) { String[] valueRangeProviderRefs = planningVariableAnnotation.valueRangeProviderRefs(); if (ArrayUtils.isEmpty(valueRangeProviderRefs)) { throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass() + ") has a " + PlanningVariable.class.getSimpleName() + " annotated property (" + variableMemberAccessor.getName() + ") that has no valueRangeProviderRefs (" + Arrays.toString(valueRangeProviderRefs) + ")."); } List<ValueRangeDescriptor<Solution_>> valueRangeDescriptorList = new ArrayList<>(valueRangeProviderRefs.length); boolean addNullInValueRange = nullable && valueRangeProviderRefs.length == 1; for (String valueRangeProviderRef : valueRangeProviderRefs) { valueRangeDescriptorList.add(buildValueRangeDescriptor(descriptorPolicy, valueRangeProviderRef, addNullInValueRange)); } if (valueRangeDescriptorList.size() == 1) { valueRangeDescriptor = valueRangeDescriptorList.get(0); } else { valueRangeDescriptor = new CompositeValueRangeDescriptor<>(this, nullable, valueRangeDescriptorList); } } private ValueRangeDescriptor<Solution_> buildValueRangeDescriptor(DescriptorPolicy descriptorPolicy, String valueRangeProviderRef, boolean addNullInValueRange) { if (descriptorPolicy.hasFromSolutionValueRangeProvider(valueRangeProviderRef)) { MemberAccessor memberAccessor = descriptorPolicy.getFromSolutionValueRangeProvider(valueRangeProviderRef); return new FromSolutionPropertyValueRangeDescriptor<>(this, addNullInValueRange, memberAccessor); } else if (descriptorPolicy.hasFromEntityValueRangeProvider(valueRangeProviderRef)) { MemberAccessor memberAccessor = descriptorPolicy.getFromEntityValueRangeProvider(valueRangeProviderRef); return new FromEntityPropertyValueRangeDescriptor<>(this, addNullInValueRange, memberAccessor); } else { Collection<String> providerIds = descriptorPolicy.getValueRangeProviderIds(); throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass() + ") has a @" + PlanningVariable.class.getSimpleName() + " property (" + variableMemberAccessor.getName() + ") with a valueRangeProviderRef (" + valueRangeProviderRef + ") that does not exist in a @" + ValueRangeProvider.class.getSimpleName() + " on the solution class (" + entityDescriptor.getSolutionDescriptor().getSolutionClass().getSimpleName() + ") or on that entityClass.\n" + "The valueRangeProviderRef (" + valueRangeProviderRef + ") does not appear in the valueRangeProvideIds (" + providerIds + ")." + (!providerIds.isEmpty() ? "" : "\nMaybe a @" + ValueRangeProvider.class.getSimpleName() + " annotation is missing on a method in the solution class (" + entityDescriptor.getSolutionDescriptor().getSolutionClass().getSimpleName() + ").")); } } private void processStrength(DescriptorPolicy descriptorPolicy, PlanningVariable planningVariableAnnotation) { Class<? extends Comparator> strengthComparatorClass = planningVariableAnnotation.strengthComparatorClass(); if (strengthComparatorClass == PlanningVariable.NullStrengthComparator.class) { strengthComparatorClass = null; } Class<? extends SelectionSorterWeightFactory> strengthWeightFactoryClass = planningVariableAnnotation.strengthWeightFactoryClass(); if (strengthWeightFactoryClass == PlanningVariable.NullStrengthWeightFactory.class) { strengthWeightFactoryClass = null; } if (strengthComparatorClass != null && strengthWeightFactoryClass != null) { throw new IllegalStateException("The entityClass (" + entityDescriptor.getEntityClass() + ") property (" + variableMemberAccessor.getName() + ") cannot have a strengthComparatorClass (" + strengthComparatorClass.getName() + ") and a strengthWeightFactoryClass (" + strengthWeightFactoryClass.getName() + ") at the same time."); } if (strengthComparatorClass != null) { Comparator<Object> strengthComparator = ConfigUtils.newInstance(this, "strengthComparatorClass", strengthComparatorClass); increasingStrengthSorter = new ComparatorSelectionSorter( strengthComparator, SelectionSorterOrder.ASCENDING); decreasingStrengthSorter = new ComparatorSelectionSorter( strengthComparator, SelectionSorterOrder.DESCENDING); } if (strengthWeightFactoryClass != null) { SelectionSorterWeightFactory strengthWeightFactory = ConfigUtils.newInstance(this, "strengthWeightFactoryClass", strengthWeightFactoryClass); increasingStrengthSorter = new WeightFactorySelectionSorter( strengthWeightFactory, SelectionSorterOrder.ASCENDING); decreasingStrengthSorter = new WeightFactorySelectionSorter( strengthWeightFactory, SelectionSorterOrder.DESCENDING); } } // ************************************************************************ // Worker methods // ************************************************************************ public boolean isChained() { return chained; } public boolean isNullable() { return nullable; } public boolean hasMovableChainedTrailingValueFilter() { return movableChainedTrailingValueFilter != null; } public SelectionFilter getMovableChainedTrailingValueFilter() { return movableChainedTrailingValueFilter; } public SelectionFilter getReinitializeVariableEntityFilter() { return reinitializeVariableEntityFilter; } public ValueRangeDescriptor<Solution_> getValueRangeDescriptor() { return valueRangeDescriptor; } public boolean isValueRangeEntityIndependent() { return valueRangeDescriptor.isEntityIndependent(); } // ************************************************************************ // Extraction methods // ************************************************************************ /** * A {@link PlanningVariable#nullable()} value is always considered initialized, but it can still be reinitialized * with {@link PlanningVariable#reinitializeVariableEntityFilter()}. * @param entity never null * @return true if the variable on that entity is initialized */ public boolean isInitialized(Object entity) { if (nullable) { return true; } Object variable = getValue(entity); return variable != null; } @Override public boolean isGenuineAndUninitialized(Object entity) { return !isInitialized(entity); } public boolean isReinitializable(ScoreDirector<Solution_> scoreDirector, Object entity) { return reinitializeVariableEntityFilter.accept(scoreDirector, entity); } public SelectionSorter getIncreasingStrengthSorter() { return increasingStrengthSorter; } public SelectionSorter getDecreasingStrengthSorter() { return decreasingStrengthSorter; } public long getValueCount(Solution_ solution, Object entity) { if (!valueRangeDescriptor.isCountable()) { // TODO report this better than just ignoring it return 0L; } return ((CountableValueRange<?>) valueRangeDescriptor.extractValueRange(solution, entity)).getSize(); } @Override public String toString() { return getSimpleEntityAndVariableName() + " variable"; } }