/*
* Copyright 2013 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.valuerange.descriptor;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.valuerange.CountableValueRange;
import org.optaplanner.core.api.domain.valuerange.ValueRange;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.optaplanner.core.impl.domain.common.accessor.MemberAccessor;
import org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor;
import org.optaplanner.core.impl.domain.valuerange.buildin.collection.ListValueRange;
import org.optaplanner.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
/**
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
*/
public abstract class AbstractFromPropertyValueRangeDescriptor<Solution_>
extends AbstractValueRangeDescriptor<Solution_> {
protected final MemberAccessor memberAccessor;
protected boolean collectionWrapping;
protected boolean arrayWrapping;
protected boolean countable;
public AbstractFromPropertyValueRangeDescriptor(GenuineVariableDescriptor<Solution_> variableDescriptor,
boolean addNullInValueRange,
MemberAccessor memberAccessor) {
super(variableDescriptor, addNullInValueRange);
this.memberAccessor = memberAccessor;
ValueRangeProvider valueRangeProviderAnnotation = memberAccessor.getAnnotation(ValueRangeProvider.class);
if (valueRangeProviderAnnotation == null) {
throw new IllegalStateException("The member (" + memberAccessor
+ ") must have a valueRangeProviderAnnotation (" + valueRangeProviderAnnotation + ").");
}
processValueRangeProviderAnnotation(valueRangeProviderAnnotation);
if (addNullInValueRange && !countable) {
throw new IllegalStateException("The valueRangeDescriptor (" + this
+ ") is nullable, but not countable (" + countable + ").\n"
+ "Maybe the member (" + memberAccessor + ") should return "
+ CountableValueRange.class.getSimpleName() + ".");
}
}
private void processValueRangeProviderAnnotation(ValueRangeProvider valueRangeProviderAnnotation) {
EntityDescriptor<Solution_> entityDescriptor = variableDescriptor.getEntityDescriptor();
Class<?> type = memberAccessor.getType();
collectionWrapping = Collection.class.isAssignableFrom(type);
arrayWrapping = type.isArray();
if (!collectionWrapping && !arrayWrapping && !ValueRange.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass()
+ ") has a " + PlanningVariable.class.getSimpleName()
+ " annotated property (" + variableDescriptor.getVariableName()
+ ") that refers to a " + ValueRangeProvider.class.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") that does not return a " + Collection.class.getSimpleName()
+ ", an array or a " + ValueRange.class.getSimpleName() + ".");
}
if (collectionWrapping) {
Type genericType = memberAccessor.getGenericType();
// TODO reuse ConfgUtils.extractCollectionGenericTypeParameter() if possible
if (!(genericType instanceof ParameterizedType)) {
throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass()
+ ") has a " + PlanningVariable.class.getSimpleName()
+ " annotated property (" + variableDescriptor.getVariableName()
+ ") that refers to a " + ValueRangeProvider.class.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") that returns a " + Collection.class.getSimpleName()
+ " which has no generic parameters.\n"
+ "Maybe the member (" + memberAccessor + ") should return a typed "
+ Collection.class.getSimpleName() + ".");
}
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Type[] typeArguments = parameterizedType.getActualTypeArguments();
if (typeArguments.length != 1) {
throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass()
+ ") has a " + PlanningVariable.class.getSimpleName()
+ " annotated property (" + variableDescriptor.getVariableName()
+ ") that refers to a " + ValueRangeProvider.class.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") that returns a parameterized " + Collection.class.getSimpleName()
+ ") with an unsupported number of generic parameters (" + typeArguments.length + ").");
}
Type typeArgument = typeArguments[0];
if (typeArgument instanceof ParameterizedType) {
// TODO fail fast if the type arguments don't match
// with the variableDescriptor's generic type's type arguments
typeArgument = ((ParameterizedType) typeArgument).getRawType();
}
if (!(typeArgument instanceof Class)) {
throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass()
+ ") has a " + PlanningVariable.class.getSimpleName()
+ " annotated property (" + variableDescriptor.getVariableName()
+ ") that refers to a " + ValueRangeProvider.class.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") that returns a parameterized " + Collection.class.getSimpleName()
+ " with an unsupported type arguments (" + typeArgument + ").");
}
Class<?> collectionElementClass = ((Class) typeArgument);
Class<?> variablePropertyType = variableDescriptor.getVariablePropertyType();
if (!variablePropertyType.isAssignableFrom(collectionElementClass)) {
throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass()
+ ") has a " + PlanningVariable.class.getSimpleName()
+ " annotated property (" + variableDescriptor.getVariableName()
+ ") that refers to a " + ValueRangeProvider.class.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") that returns a " + Collection.class.getSimpleName()
+ " with elements of type (" + collectionElementClass
+ ") which cannot be assigned to the " + PlanningVariable.class.getSimpleName()
+ "'s type (" + variablePropertyType + ").");
}
} else if (arrayWrapping) {
Class<?> arrayElementClass = type.getComponentType();
Class<?> variablePropertyType = variableDescriptor.getVariablePropertyType();
if (!variablePropertyType.isAssignableFrom(arrayElementClass)) {
throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass()
+ ") has a " + PlanningVariable.class.getSimpleName()
+ " annotated property (" + variableDescriptor.getVariableName()
+ ") that refers to a " + ValueRangeProvider.class.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") that returns a array with elements of type (" + arrayElementClass
+ ") which cannot be assigned to the " + PlanningVariable.class.getSimpleName()
+ "'s type (" + variablePropertyType + ").");
}
}
countable = collectionWrapping || arrayWrapping || CountableValueRange.class.isAssignableFrom(type);
}
// ************************************************************************
// Worker methods
// ************************************************************************
@Override
public boolean isCountable() {
return countable;
}
protected ValueRange<?> readValueRange(Object bean) {
Object valueRangeObject = memberAccessor.executeGetter(bean);
if (valueRangeObject == null) {
throw new IllegalStateException("The @" + ValueRangeProvider.class.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") called on bean (" + bean
+ ") must not return a null valueRangeObject (" + valueRangeObject + ").");
}
ValueRange<Object> valueRange;
if (collectionWrapping) {
List<Object> list = transformCollectionToList((Collection<Object>) valueRangeObject);
valueRange = new ListValueRange<>(list);
} else if (arrayWrapping) {
List<Object> list = transformArrayToList(valueRangeObject);
valueRange = new ListValueRange<>(list);
} else {
valueRange = (ValueRange<Object>) valueRangeObject;
}
valueRange = doNullInValueRangeWrapping(valueRange);
if (valueRange.isEmpty()) {
throw new IllegalStateException("The @" + ValueRangeProvider.class.getSimpleName()
+ " annotated member (" + memberAccessor
+ ") called on bean (" + bean
+ ") must not return an empty valueRange (" + valueRangeObject + ").\n"
+ " If this a valid dataset, apply overconstrained planning as described in the documentation.");
}
return valueRange;
}
private <T> List<T> transformCollectionToList(Collection<T> collection) {
// TODO The user might not be aware of these performance pitfalls with Set and LinkedList:
// - If only ValueRange.createOriginalIterator() is used, cloning a Set to a List is a waste of time.
// - If the List is a LinkedList, ValueRange.createRandomIterator(Random)
// and ValueRange.get(int) are not efficient.
return (collection instanceof List ? (List<T>) collection : new ArrayList<>(collection));
}
private List<Object> transformArrayToList(Object valueRangeObject) {
int arrayLength = Array.getLength(valueRangeObject);
List<Object> list = new ArrayList<>(arrayLength);
for (int i = 0; i < arrayLength; i++) {
list.add(Array.get(valueRangeObject, i));
}
return list;
}
}