Skip to content

Commit

Permalink
PLANNER-572 ValueRangeProvider should support arrays too (useful for …
Browse files Browse the repository at this point in the history
…enums)
  • Loading branch information
ge0ffrey committed May 3, 2016
1 parent 915d5c2 commit 3af3f58
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 5 deletions.
Expand Up @@ -16,6 +16,7 @@


package org.optaplanner.core.impl.domain.valuerange.descriptor; package org.optaplanner.core.impl.domain.valuerange.descriptor;


import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
Expand All @@ -40,6 +41,7 @@ public abstract class AbstractFromPropertyValueRangeDescriptor<Solution_>


protected final MemberAccessor memberAccessor; protected final MemberAccessor memberAccessor;
protected boolean collectionWrapping; protected boolean collectionWrapping;
protected boolean arrayWrapping;
protected boolean countable; protected boolean countable;


public AbstractFromPropertyValueRangeDescriptor(GenuineVariableDescriptor<Solution_> variableDescriptor, public AbstractFromPropertyValueRangeDescriptor(GenuineVariableDescriptor<Solution_> variableDescriptor,
Expand All @@ -65,14 +67,15 @@ private void processValueRangeProviderAnnotation(ValueRangeProvider valueRangePr
EntityDescriptor<Solution_> entityDescriptor = variableDescriptor.getEntityDescriptor(); EntityDescriptor<Solution_> entityDescriptor = variableDescriptor.getEntityDescriptor();
Class<?> type = memberAccessor.getType(); Class<?> type = memberAccessor.getType();
collectionWrapping = Collection.class.isAssignableFrom(type); collectionWrapping = Collection.class.isAssignableFrom(type);
if (!collectionWrapping && !ValueRange.class.isAssignableFrom(type)) { arrayWrapping = type.isArray();
if (!collectionWrapping && !arrayWrapping && !ValueRange.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass() throw new IllegalArgumentException("The entityClass (" + entityDescriptor.getEntityClass()
+ ") has a " + PlanningVariable.class.getSimpleName() + ") has a " + PlanningVariable.class.getSimpleName()
+ " annotated property (" + variableDescriptor.getVariableName() + " annotated property (" + variableDescriptor.getVariableName()
+ ") that refers to a " + ValueRangeProvider.class.getSimpleName() + ") that refers to a " + ValueRangeProvider.class.getSimpleName()
+ " annotated member (" + memberAccessor + " annotated member (" + memberAccessor
+ ") that does not return a " + Collection.class.getSimpleName() + ") that does not return a " + Collection.class.getSimpleName()
+ " or a " + ValueRange.class.getSimpleName() + "."); + ", an array or a " + ValueRange.class.getSimpleName() + ".");
} }
if (collectionWrapping) { if (collectionWrapping) {
Type genericType = memberAccessor.getGenericType(); Type genericType = memberAccessor.getGenericType();
Expand Down Expand Up @@ -122,8 +125,21 @@ private void processValueRangeProviderAnnotation(ValueRangeProvider valueRangePr
+ "'s type (" + variablePropertyType + ")."); + "'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 || CountableValueRange.class.isAssignableFrom(type); countable = collectionWrapping || arrayWrapping || CountableValueRange.class.isAssignableFrom(type);
} }


// ************************************************************************ // ************************************************************************
Expand All @@ -145,7 +161,10 @@ protected ValueRange<?> readValueRange(Object bean) {
} }
ValueRange<Object> valueRange; ValueRange<Object> valueRange;
if (collectionWrapping) { if (collectionWrapping) {
List<Object> list = transformToList((Collection<Object>) valueRangeObject); List<Object> list = transformCollectionToList((Collection<Object>) valueRangeObject);
valueRange = new ListValueRange<>(list);
} else if (arrayWrapping) {
List<Object> list = transformArrayToList(valueRangeObject);
valueRange = new ListValueRange<>(list); valueRange = new ListValueRange<>(list);
} else { } else {
valueRange = (ValueRange<Object>) valueRangeObject; valueRange = (ValueRange<Object>) valueRangeObject;
Expand All @@ -161,12 +180,21 @@ protected ValueRange<?> readValueRange(Object bean) {
return valueRange; return valueRange;
} }


private <T> List<T> transformToList(Collection<T> collection) { private <T> List<T> transformCollectionToList(Collection<T> collection) {
// TODO The user might not be aware of these performance pitfalls with Set and LinkedList: // 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 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) // - If the List is a LinkedList, ValueRange.createRandomIterator(Random)
// and ValueRange.get(int) are not efficient. // and ValueRange.get(int) are not efficient.
return (collection instanceof List ? (List<T>) collection : new ArrayList<>(collection)); 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(arrayLength, i));
}
return list;
}

} }
Expand Up @@ -15,9 +15,12 @@
*/ */
package org.optaplanner.core.impl.domain.solution.descriptor; package org.optaplanner.core.impl.domain.solution.descriptor;


import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.optaplanner.core.api.solver.SolverFactory; import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.impl.testdata.domain.TestdataEntity; import org.optaplanner.core.impl.testdata.domain.TestdataEntity;
import org.optaplanner.core.impl.testdata.domain.collection.TestdataArrayBasedSolution;
import org.optaplanner.core.impl.testdata.domain.collection.TestdataSetBasedSolution;
import org.optaplanner.core.impl.testdata.domain.extended.TestdataAnnotatedExtendedSolution; import org.optaplanner.core.impl.testdata.domain.extended.TestdataAnnotatedExtendedSolution;
import org.optaplanner.core.impl.testdata.domain.extended.abstractsolution.TestdataExtendedAbstractSolution; import org.optaplanner.core.impl.testdata.domain.extended.abstractsolution.TestdataExtendedAbstractSolution;
import org.optaplanner.core.impl.testdata.domain.extended.legacysolution.TestdataLegacySolution; import org.optaplanner.core.impl.testdata.domain.extended.legacysolution.TestdataLegacySolution;
Expand Down Expand Up @@ -105,6 +108,30 @@ public void extended() {
"entityList", "subEntityList"); "entityList", "subEntityList");
} }


@Test
public void setProperties() {
SolutionDescriptor<TestdataSetBasedSolution> solutionDescriptor
= TestdataSetBasedSolution.buildSolutionDescriptor();
assertMapContainsKeysExactly(solutionDescriptor.getProblemFactMemberAccessorMap());
assertMapContainsKeysExactly(solutionDescriptor.getProblemFactCollectionMemberAccessorMap(),
"valueSet");
assertMapContainsKeysExactly(solutionDescriptor.getEntityMemberAccessorMap());
assertMapContainsKeysExactly(solutionDescriptor.getEntityCollectionMemberAccessorMap(),
"entitySet");
}

@Test @Ignore("Resolve PLANNER-573 to fix this")
public void arrayProperties() {
SolutionDescriptor<TestdataArrayBasedSolution> solutionDescriptor
= TestdataArrayBasedSolution.buildSolutionDescriptor();
assertMapContainsKeysExactly(solutionDescriptor.getProblemFactMemberAccessorMap());
assertMapContainsKeysExactly(solutionDescriptor.getProblemFactCollectionMemberAccessorMap(),
"values");
assertMapContainsKeysExactly(solutionDescriptor.getEntityMemberAccessorMap());
assertMapContainsKeysExactly(solutionDescriptor.getEntityCollectionMemberAccessorMap(),
"entities");
}

// ************************************************************************ // ************************************************************************
// Others // Others
// ************************************************************************ // ************************************************************************
Expand Down
@@ -0,0 +1,61 @@
/*
* 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.testdata.domain.collection;

import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.testdata.domain.TestdataObject;
import org.optaplanner.core.impl.testdata.domain.TestdataValue;

@PlanningEntity
public class TestdataArrayBasedEntity extends TestdataObject {

public static EntityDescriptor buildEntityDescriptor() {
SolutionDescriptor solutionDescriptor = TestdataArrayBasedSolution.buildSolutionDescriptor();
return solutionDescriptor.findEntityDescriptorOrFail(TestdataArrayBasedEntity.class);
}

private TestdataValue value;

public TestdataArrayBasedEntity() {
}

public TestdataArrayBasedEntity(String code) {
super(code);
}

public TestdataArrayBasedEntity(String code, TestdataValue value) {
this(code);
this.value = value;
}

@PlanningVariable(valueRangeProviderRefs = "valueRange")
public TestdataValue getValue() {
return value;
}

public void setValue(TestdataValue value) {
this.value = value;
}

// ************************************************************************
// Complex methods
// ************************************************************************

}
@@ -0,0 +1,83 @@
/*
* 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.testdata.domain.collection;

import java.util.List;
import java.util.Set;

import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.score.buildin.simple.SimpleScore;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.testdata.domain.TestdataObject;
import org.optaplanner.core.impl.testdata.domain.TestdataValue;

@PlanningSolution
public class TestdataArrayBasedSolution extends TestdataObject {

public static SolutionDescriptor buildSolutionDescriptor() {
return SolutionDescriptor.buildSolutionDescriptor(TestdataArrayBasedSolution.class, TestdataArrayBasedEntity.class);
}

private TestdataValue[] values;
private TestdataArrayBasedEntity[] entities;

private SimpleScore score;

public TestdataArrayBasedSolution() {
}

public TestdataArrayBasedSolution(String code) {
super(code);
}

@ValueRangeProvider(id = "valueRange")
@ProblemFactCollectionProperty
public TestdataValue[] getValues() {
return values;
}

public void setValues(TestdataValue[] values) {
this.values = values;
}

@PlanningEntityCollectionProperty
public TestdataArrayBasedEntity[] getEntities() {
return entities;
}

public void setEntities(TestdataArrayBasedEntity[] entities) {
this.entities = entities;
}

@PlanningScore
public SimpleScore getScore() {
return score;
}

public void setScore(SimpleScore score) {
this.score = score;
}

// ************************************************************************
// Complex methods
// ************************************************************************

}

0 comments on commit 3af3f58

Please sign in to comment.