Skip to content

Commit

Permalink
PLANNER-352 Field annotations for planning variable annotations and e…
Browse files Browse the repository at this point in the history
…ntity property annotations
  • Loading branch information
ge0ffrey committed Jun 15, 2015
1 parent d1aa82f commit a385008
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 64 deletions.
Expand Up @@ -18,6 +18,7 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -38,6 +39,7 @@
import org.optaplanner.core.config.heuristic.selector.common.decorator.SelectionSorterOrder;
import org.optaplanner.core.config.util.ConfigUtils;
import org.optaplanner.core.impl.domain.common.AlphabeticMemberComparator;
import org.optaplanner.core.impl.domain.common.member.FieldMemberAccessor;
import org.optaplanner.core.impl.domain.common.member.MemberAccessor;
import org.optaplanner.core.impl.domain.common.member.BeanPropertyMemberAccessor;
import org.optaplanner.core.impl.domain.common.ReflectionHelper;
Expand Down Expand Up @@ -157,6 +159,16 @@ private void processPlanningVariableAnnotations(DescriptorPolicy descriptorPolic
declaredGenuineVariableDescriptorMap = new LinkedHashMap<String, GenuineVariableDescriptor>();
declaredShadowVariableDescriptorMap = new LinkedHashMap<String, ShadowVariableDescriptor>();
boolean noVariableAnnotation = true;
List<Field> fieldList = Arrays.asList(entityClass.getDeclaredFields());
Collections.sort(fieldList, new AlphabeticMemberComparator());
for (Field field : fieldList) {
Class<? extends Annotation> variableAnnotationClass = extractVariableAnnotationClass(field);
if (variableAnnotationClass != null) {
noVariableAnnotation = false;
MemberAccessor memberAccessor = new FieldMemberAccessor(field);
registerVariableAccessor(descriptorPolicy, variableAnnotationClass, memberAccessor);
}
}
List<Method> methodList = Arrays.asList(entityClass.getDeclaredMethods());
Collections.sort(methodList, new AlphabeticMemberComparator());
for (Method method : methodList) {
Expand All @@ -176,30 +188,7 @@ private void processPlanningVariableAnnotations(DescriptorPolicy descriptorPolic
+ " annotated getter method (" + method
+ "), but lacks a setter for that property (" + memberAccessor.getName() + ").");
}
if (variableAnnotationClass.equals(PlanningVariable.class)) {
GenuineVariableDescriptor variableDescriptor = new GenuineVariableDescriptor(
this, memberAccessor);
declaredGenuineVariableDescriptorMap.put(memberAccessor.getName(), variableDescriptor);
variableDescriptor.processAnnotations(descriptorPolicy);
} else if (variableAnnotationClass.equals(InverseRelationShadowVariable.class)) {
ShadowVariableDescriptor variableDescriptor = new InverseRelationShadowVariableDescriptor(
this, memberAccessor);
declaredShadowVariableDescriptorMap.put(memberAccessor.getName(), variableDescriptor);
variableDescriptor.processAnnotations(descriptorPolicy);
} else if (variableAnnotationClass.equals(AnchorShadowVariable.class)) {
ShadowVariableDescriptor variableDescriptor = new AnchorShadowVariableDescriptor(
this, memberAccessor);
declaredShadowVariableDescriptorMap.put(memberAccessor.getName(), variableDescriptor);
variableDescriptor.processAnnotations(descriptorPolicy);
} else if (variableAnnotationClass.equals(CustomShadowVariable.class)) {
ShadowVariableDescriptor variableDescriptor = new CustomShadowVariableDescriptor(
this, memberAccessor);
declaredShadowVariableDescriptorMap.put(memberAccessor.getName(), variableDescriptor);
variableDescriptor.processAnnotations(descriptorPolicy);
} else {
throw new IllegalStateException("The variableAnnotationClass ("
+ variableAnnotationClass + ") is not implemented.");
}
registerVariableAccessor(descriptorPolicy, variableAnnotationClass, memberAccessor);
}
}
if (noVariableAnnotation) {
Expand All @@ -209,6 +198,34 @@ private void processPlanningVariableAnnotations(DescriptorPolicy descriptorPolic
}
}

private void registerVariableAccessor(DescriptorPolicy descriptorPolicy,
Class<? extends Annotation> variableAnnotationClass, MemberAccessor memberAccessor) {
if (variableAnnotationClass.equals(PlanningVariable.class)) {
GenuineVariableDescriptor variableDescriptor = new GenuineVariableDescriptor(
this, memberAccessor);
declaredGenuineVariableDescriptorMap.put(memberAccessor.getName(), variableDescriptor);
variableDescriptor.processAnnotations(descriptorPolicy);
} else if (variableAnnotationClass.equals(InverseRelationShadowVariable.class)) {
ShadowVariableDescriptor variableDescriptor = new InverseRelationShadowVariableDescriptor(
this, memberAccessor);
declaredShadowVariableDescriptorMap.put(memberAccessor.getName(), variableDescriptor);
variableDescriptor.processAnnotations(descriptorPolicy);
} else if (variableAnnotationClass.equals(AnchorShadowVariable.class)) {
ShadowVariableDescriptor variableDescriptor = new AnchorShadowVariableDescriptor(
this, memberAccessor);
declaredShadowVariableDescriptorMap.put(memberAccessor.getName(), variableDescriptor);
variableDescriptor.processAnnotations(descriptorPolicy);
} else if (variableAnnotationClass.equals(CustomShadowVariable.class)) {
ShadowVariableDescriptor variableDescriptor = new CustomShadowVariableDescriptor(
this, memberAccessor);
declaredShadowVariableDescriptorMap.put(memberAccessor.getName(), variableDescriptor);
variableDescriptor.processAnnotations(descriptorPolicy);
} else {
throw new IllegalStateException("The variableAnnotationClass ("
+ variableAnnotationClass + ") is not implemented.");
}
}

private Class<? extends Annotation> extractVariableAnnotationClass(AnnotatedElement member) {
Class<? extends Annotation> variableAnnotationClass = null;
for (Class<? extends Annotation> detectedAnnotationClass : VARIABLE_ANNOTATION_CLASSES) {
Expand Down
Expand Up @@ -16,6 +16,7 @@

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

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -39,6 +40,7 @@
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.config.util.ConfigUtils;
import org.optaplanner.core.impl.domain.common.AlphabeticMemberComparator;
import org.optaplanner.core.impl.domain.common.member.FieldMemberAccessor;
import org.optaplanner.core.impl.domain.common.member.MemberAccessor;
import org.optaplanner.core.impl.domain.common.member.BeanPropertyMemberAccessor;
import org.optaplanner.core.impl.domain.common.ReflectionHelper;
Expand Down Expand Up @@ -134,18 +136,24 @@ private void processValueRangeProviderAnnotations(DescriptorPolicy descriptorPol

private void processEntityPropertyAnnotations(DescriptorPolicy descriptorPolicy) {
boolean noEntityPropertyAnnotation = true;
// TODO This does not support annotations on private/protected methods like EntityDescriptor
List<Method> methodList = Arrays.asList(solutionClass.getMethods());
// TODO This does not support annotations on inherited fields
List<Field> fieldList = Arrays.asList(solutionClass.getDeclaredFields());
Collections.sort(fieldList, new AlphabeticMemberComparator());
for (Field field : fieldList) {
boolean entityPropertyAnnotated = field.isAnnotationPresent(PlanningEntityProperty.class);
boolean entityCollectionPropertyAnnotated = field.isAnnotationPresent(PlanningEntityCollectionProperty.class);
if (entityPropertyAnnotated || entityCollectionPropertyAnnotated) {
noEntityPropertyAnnotation = false;
MemberAccessor memberAccessor = new FieldMemberAccessor(field);
registerEntityPropertyAccessor(entityPropertyAnnotated, entityCollectionPropertyAnnotated, memberAccessor);
}
}
// TODO This does not support annotations on inherited methods
List<Method> methodList = Arrays.asList(solutionClass.getDeclaredMethods());
Collections.sort(methodList, new AlphabeticMemberComparator());
for (Method method : methodList) {
boolean entityPropertyAnnotated = method.isAnnotationPresent(PlanningEntityProperty.class);
boolean entityCollectionPropertyAnnotated = method.isAnnotationPresent(PlanningEntityCollectionProperty.class);
if (entityPropertyAnnotated && entityCollectionPropertyAnnotated) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has a method (" + method + ") that has both a "
+ PlanningEntityProperty.class.getSimpleName() + " annotation and a "
+ PlanningEntityCollectionProperty.class.getSimpleName() + " annotation.");
}
if (entityPropertyAnnotated || entityCollectionPropertyAnnotated) {
noEntityPropertyAnnotation = false;
if (!ReflectionHelper.isGetterMethod(method)) {
Expand All @@ -156,18 +164,7 @@ private void processEntityPropertyAnnotations(DescriptorPolicy descriptorPolicy)
+ " That annotation can only be used on a JavaBeans getter method or on a field.");
}
MemberAccessor memberAccessor = new BeanPropertyMemberAccessor(method);
if (entityPropertyAnnotated) {
entityPropertyAccessorMap.put(memberAccessor.getName(), memberAccessor);
} else if (entityCollectionPropertyAnnotated) {
noEntityPropertyAnnotation = false;
if (!Collection.class.isAssignableFrom(memberAccessor.getType())) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has a " + PlanningEntityCollectionProperty.class.getSimpleName()
+ " annotated property (" + memberAccessor.getName() + ") that does not return a "
+ Collection.class.getSimpleName() + ".");
}
entityCollectionPropertyAccessorMap.put(memberAccessor.getName(), memberAccessor);
}
registerEntityPropertyAccessor(entityPropertyAnnotated, entityCollectionPropertyAnnotated, memberAccessor);
}
}
if (noEntityPropertyAnnotation) {
Expand All @@ -177,6 +174,27 @@ private void processEntityPropertyAnnotations(DescriptorPolicy descriptorPolicy)
}
}

private void registerEntityPropertyAccessor(boolean entityPropertyAnnotated, boolean entityCollectionPropertyAnnotated,
MemberAccessor memberAccessor) {
if (entityPropertyAnnotated && entityCollectionPropertyAnnotated) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has a member (" + memberAccessor.getName() + ") that has both a "
+ PlanningEntityProperty.class.getSimpleName() + " annotation and a "
+ PlanningEntityCollectionProperty.class.getSimpleName() + " annotation.");
}
if (entityPropertyAnnotated) {
entityPropertyAccessorMap.put(memberAccessor.getName(), memberAccessor);
} else if (entityCollectionPropertyAnnotated) {
if (!Collection.class.isAssignableFrom(memberAccessor.getType())) {
throw new IllegalStateException("The solutionClass (" + solutionClass
+ ") has a " + PlanningEntityCollectionProperty.class.getSimpleName()
+ " annotated member (" + memberAccessor.getName() + ") that does not return a "
+ Collection.class.getSimpleName() + ".");
}
entityCollectionPropertyAccessorMap.put(memberAccessor.getName(), memberAccessor);
}
}

public void afterAnnotationsProcessed(DescriptorPolicy descriptorPolicy) {
for (EntityDescriptor entityDescriptor : entityDescriptorMap.values()) {
entityDescriptor.linkInheritedEntityDescriptors(descriptorPolicy);
Expand Down
Expand Up @@ -27,7 +27,7 @@
public class BeanPropertyMemberAccessorTest {

@Test
public void reflectMethodEntity() throws NoSuchMethodException {
public void methodAnnotatedEntity() throws NoSuchMethodException {
BeanPropertyMemberAccessor memberAccessor = new BeanPropertyMemberAccessor(
TestdataEntity.class.getMethod("getValue"));
assertEquals("value", memberAccessor.getName());
Expand Down
Expand Up @@ -20,22 +20,22 @@
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.optaplanner.core.impl.domain.common.member.FieldMemberAccessor;
import org.optaplanner.core.impl.testdata.domain.TestdataValue;
import org.optaplanner.core.impl.testdata.domain.reflect.field.TestdataReflectFieldEntity;
import org.optaplanner.core.impl.testdata.domain.reflect.field.TestdataFieldAnnotatedEntity;

import static org.junit.Assert.*;

public class FieldMemberAccessorTest {

@Test
public void reflectFieldEntity() throws NoSuchFieldException {
FieldMemberAccessor memberAccessor = new FieldMemberAccessor(TestdataReflectFieldEntity.class.getDeclaredField("value"));
public void fieldAnnotatedEntity() throws NoSuchFieldException {
FieldMemberAccessor memberAccessor = new FieldMemberAccessor(TestdataFieldAnnotatedEntity.class.getDeclaredField("value"));
assertEquals("value", memberAccessor.getName());
assertEquals(TestdataValue.class, memberAccessor.getType());
assertEquals(true, memberAccessor.isAnnotationPresent(PlanningVariable.class));

TestdataValue v1 = new TestdataValue("v1");
TestdataValue v2 = new TestdataValue("v2");
TestdataReflectFieldEntity e1 = new TestdataReflectFieldEntity("e1", v1);
TestdataFieldAnnotatedEntity e1 = new TestdataFieldAnnotatedEntity("e1", v1);
assertSame(v1, memberAccessor.executeGetter(e1));
memberAccessor.executeSetter(e1, v2);
assertSame(v2, e1.getValue());
Expand Down
Expand Up @@ -28,6 +28,7 @@
import java.util.SortedSet;
import java.util.TreeSet;

import org.junit.Ignore;
import org.junit.Test;
import org.optaplanner.core.api.domain.solution.Solution;
import org.optaplanner.core.api.domain.solution.cloner.SolutionCloner;
Expand All @@ -51,6 +52,8 @@
import org.optaplanner.core.impl.testdata.domain.extended.thirdparty.TestdataExtendedThirdPartyEntity;
import org.optaplanner.core.impl.testdata.domain.extended.thirdparty.TestdataExtendedThirdPartySolution;
import org.optaplanner.core.impl.testdata.domain.extended.thirdparty.TestdataThirdPartyEntityPojo;
import org.optaplanner.core.impl.testdata.domain.reflect.field.TestdataFieldAnnotatedEntity;
import org.optaplanner.core.impl.testdata.domain.reflect.field.TestdataFieldAnnotatedSolution;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
Expand Down Expand Up @@ -101,12 +104,53 @@ public void cloneSolution() {
assertEntityClone(c, cloneC, "c", "3");
assertEntityClone(d, cloneD, "d", "3");

assertNotSame(b, cloneB);
b.setValue(val2);
assertCode("2", b.getValue());
// Clone remains unchanged
assertCode("1", cloneB.getValue());
}


@Test @Ignore("TODO")
public void cloneFieldAnnotatedSolution() {
SolutionDescriptor solutionDescriptor = TestdataFieldAnnotatedSolution.buildSolutionDescriptor();
SolutionCloner<TestdataFieldAnnotatedSolution> cloner = createSolutionCloner(solutionDescriptor);

TestdataValue val1 = new TestdataValue("1");
TestdataValue val2 = new TestdataValue("2");
TestdataValue val3 = new TestdataValue("3");
TestdataFieldAnnotatedEntity a = new TestdataFieldAnnotatedEntity("a", val1);
TestdataFieldAnnotatedEntity b = new TestdataFieldAnnotatedEntity("b", val1);
TestdataFieldAnnotatedEntity c = new TestdataFieldAnnotatedEntity("c", val3);
TestdataFieldAnnotatedEntity d = new TestdataFieldAnnotatedEntity("d", val3);

List<TestdataValue> valueList = Arrays.asList(val1, val2, val3);
List<TestdataFieldAnnotatedEntity> originalEntityList = Arrays.asList(a, b, c, d);
TestdataFieldAnnotatedSolution original = new TestdataFieldAnnotatedSolution("solution",
valueList, originalEntityList);

TestdataFieldAnnotatedSolution clone = cloner.cloneSolution(original);

assertNotSame(original, clone);
assertCode("solution", clone);
assertSame(valueList, clone.getValueList());

List<TestdataFieldAnnotatedEntity> cloneEntityList = clone.getEntityList();
assertNotSame(originalEntityList, cloneEntityList);
assertEquals(4, cloneEntityList.size());
TestdataFieldAnnotatedEntity cloneA = cloneEntityList.get(0);
TestdataFieldAnnotatedEntity cloneB = cloneEntityList.get(1);
TestdataFieldAnnotatedEntity cloneC = cloneEntityList.get(2);
TestdataFieldAnnotatedEntity cloneD = cloneEntityList.get(3);
assertEntityClone(a, cloneA, "a", "1");
assertEntityClone(b, cloneB, "b", "1");
assertEntityClone(c, cloneC, "c", "3");
assertEntityClone(d, cloneD, "d", "3");

assertNotSame(b, cloneB);
}

@Test
public void cloneAccessModifierSolution() {
Object staticObject = new Object();
Expand Down Expand Up @@ -153,6 +197,7 @@ public void cloneAccessModifierSolution() {
assertEntityClone(c, cloneC, "c", "3");
assertEntityClone(d, cloneD, "d", "3");

assertNotSame(b, cloneB);
b.setValue(val2);
assertCode("2", b.getValue());
// Clone remains unchanged
Expand Down Expand Up @@ -202,6 +247,7 @@ public void cloneExtendedSolution() {
assertEntityClone(d, cloneD, "d", "3");
assertEquals(cloneC, cloneD.getExtraObject());

assertNotSame(b, cloneB);
b.setValue(val2);
assertCode("2", b.getValue());
// Clone remains unchanged
Expand Down Expand Up @@ -251,6 +297,7 @@ public void cloneExtendedThirdPartySolution() {
assertEntityClone(d, cloneD, "d", "3");
assertEquals(cloneC, cloneD.getExtraObject());

assertNotSame(b, cloneB);
b.setValue(val2);
assertCode("2", b.getValue());
// Clone remains unchanged
Expand All @@ -265,6 +312,14 @@ private void assertEntityClone(TestdataEntity originalEntity, TestdataEntity clo
assertCode(valueCode, cloneEntity.getValue());
}

private void assertEntityClone(TestdataFieldAnnotatedEntity originalEntity, TestdataFieldAnnotatedEntity cloneEntity,
String entityCode, String valueCode) {
assertNotSame(originalEntity, cloneEntity);
assertCode(entityCode, originalEntity);
assertCode(entityCode, cloneEntity);
assertCode(valueCode, cloneEntity.getValue());
}

private void assertEntityClone(TestdataThirdPartyEntityPojo originalEntity,
TestdataThirdPartyEntityPojo cloneEntity, String entityCode, String valueCode) {
assertNotSame(originalEntity, cloneEntity);
Expand Down

0 comments on commit a385008

Please sign in to comment.