Skip to content

Commit

Permalink
feat: add improvements for @CascadingUpdateShadowVariable (#992)
Browse files Browse the repository at this point in the history
This PR enhances the `@CascadingUpdateShadowVariable` by allowing the
definition of multiple sources per field and adding support to
`@PiggybackShadowVariable`.

---------

Co-authored-by: Christopher Chianelli <[email protected]>
  • Loading branch information
zepfred and Christopher-Chianelli authored Jul 25, 2024
1 parent ec3b796 commit 2bb6b26
Show file tree
Hide file tree
Showing 25 changed files with 1,659 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Automatically cascades change events to {@link NextElementShadowVariable} of a {@link PlanningListVariable}.
* <p>
* Important: it must only change the shadow variable(s) for which it's configured.
* It is only possible to define either {@code sourceVariableName} or {@code sourceVariableNames}.
* It can be applied to multiple fields to modify different shadow variables.
* It should never change a genuine variable or a problem fact.
* It can change its shadow variable(s) on multiple entity instances
Expand All @@ -28,7 +29,14 @@
*
* @return never null, a genuine or shadow variable name
*/
String sourceVariableName();
String sourceVariableName() default "";

/**
* The source variable name.
*
* @return never null, a genuine or shadow variable name
*/
String[] sourceVariableNames() default {};

/**
* The target method element.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ public void processAnnotations(DescriptorPolicy descriptorPolicy) {
+ ") should have at least 1 getter method or 1 field with a "
+ PlanningVariable.class.getSimpleName() + " annotation or a shadow variable annotation.");
}
processPiggyBackForCascadingUpdateShadowVariables();
processVariableAnnotations(descriptorPolicy);
}

Expand Down Expand Up @@ -304,6 +305,38 @@ private void processPlanningVariableAnnotation(MutableInt variableDescriptorCoun
}
}

private void processPiggyBackForCascadingUpdateShadowVariables() {
if (!declaredCascadingUpdateShadowVariableDecriptorMap.isEmpty()) {
var piggybackShadowVariableDescriptorList = declaredShadowVariableDescriptorMap
.values()
.stream()
.filter(v -> PiggybackShadowVariableDescriptor.class.isAssignableFrom(v.getClass()))
.map(v -> (PiggybackShadowVariableDescriptor<Solution_>) v)
.toList();
for (var descriptor : piggybackShadowVariableDescriptorList) {
var cascadingUpdateShadowVariableDescriptor =
findNotifiableCascadingUpdateDescriptor(descriptor.getShadowVariableName());
if (cascadingUpdateShadowVariableDescriptor != null) {
cascadingUpdateShadowVariableDescriptor.addTargetVariable(descriptor.getEntityDescriptor(),
descriptor.getMemberAccessor());
}
}
}
}

private CascadingUpdateShadowVariableDescriptor<Solution_>
findNotifiableCascadingUpdateDescriptor(String variableName) {
var descriptor = declaredShadowVariableDescriptorMap.get(variableName);
var isCascadingUpdateDescriptor =
descriptor != null && CascadingUpdateShadowVariableDescriptor.class.isAssignableFrom(descriptor.getClass());
if (isCascadingUpdateDescriptor && !descriptor.hasVariableListener()) {
descriptor =
declaredCascadingUpdateShadowVariableDecriptorMap
.get(((CascadingUpdateShadowVariableDescriptor<Solution_>) descriptor).getTargetMethodName());
}
return isCascadingUpdateDescriptor ? (CascadingUpdateShadowVariableDescriptor<Solution_>) descriptor : null;
}

private void registerVariableAccessor(int nextVariableDescriptorOrdinal,
Class<? extends Annotation> variableAnnotationClass, MemberAccessor memberAccessor) {
var memberName = memberAccessor.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

public final class CascadingUpdateShadowVariableDescriptor<Solution_> extends ShadowVariableDescriptor<Solution_> {

private final List<TargetVariable<Solution_>> targetVariables;
private final List<ShadowVariableTarget<Solution_>> shadowVariableTargetList;
private ListVariableDescriptor<Solution_> sourceListVariable;
private final List<VariableDescriptor<Solution_>> targetVariableDescriptorList = new ArrayList<>();
private final Set<ShadowVariableDescriptor<Solution_>> sourceShadowVariableDescriptorSet = new HashSet<>();
Expand All @@ -41,23 +41,23 @@ public final class CascadingUpdateShadowVariableDescriptor<Solution_> extends Sh
public CascadingUpdateShadowVariableDescriptor(int ordinal, EntityDescriptor<Solution_> entityDescriptor,
MemberAccessor variableMemberAccessor) {
super(ordinal, entityDescriptor, variableMemberAccessor);
targetVariables = new ArrayList<>();
shadowVariableTargetList = new ArrayList<>();
addTargetVariable(entityDescriptor, variableMemberAccessor);
}

public void addTargetVariable(EntityDescriptor<Solution_> entityDescriptor,
MemberAccessor variableMemberAccessor) {
targetVariables.add(new TargetVariable<>(entityDescriptor, variableMemberAccessor));
shadowVariableTargetList.add(new ShadowVariableTarget<>(entityDescriptor, variableMemberAccessor));
}

public void setNotifiable(boolean notifiable) {
this.notifiable = notifiable;
}

private List<CascadingUpdateShadowVariable> getDeclaredListeners(MemberAccessor variableMemberAccessor) {
var declaredListenerList = Arrays.asList(variableMemberAccessor
private List<CascadingUpdateShadowVariable> getDeclaredShadowVariables(MemberAccessor variableMemberAccessor) {
var declaredShadowVariableList = Arrays.asList(variableMemberAccessor
.getDeclaredAnnotationsByType(CascadingUpdateShadowVariable.class));
var targetMethodList = declaredListenerList.stream()
var targetMethodList = declaredShadowVariableList.stream()
.map(CascadingUpdateShadowVariable::targetMethodName)
.distinct()
.toList();
Expand All @@ -72,11 +72,11 @@ The entityClass (%s) has multiple @%s in the annotated property (%s), and there
String.join(", ", targetMethodList),
variableMemberAccessor.getName()));
}
return declaredListenerList;
return declaredShadowVariableList;
}

public String getTargetMethodName() {
return getDeclaredListeners(variableMemberAccessor).get(0).targetMethodName();
return getDeclaredShadowVariables(variableMemberAccessor).get(0).targetMethodName();
}

@Override
Expand All @@ -86,14 +86,14 @@ public void processAnnotations(DescriptorPolicy descriptorPolicy) {

@Override
public void linkVariableDescriptors(DescriptorPolicy descriptorPolicy) {
for (TargetVariable<Solution_> targetVariable : targetVariables) {
var declaredListenerList =
getDeclaredListeners(targetVariable.variableMemberAccessor());
for (var listener : declaredListenerList) {
linkVariableDescriptorToSource(listener);
for (ShadowVariableTarget<Solution_> shadowVariableTarget : shadowVariableTargetList) {
var declaredShadowVariableList =
getDeclaredShadowVariables(shadowVariableTarget.variableMemberAccessor());
for (var shadowVariable : declaredShadowVariableList) {
linkVariableDescriptorToSource(shadowVariable);
}
targetVariableDescriptorList.add(targetVariable.entityDescriptor()
.getShadowVariableDescriptor(targetVariable.variableMemberAccessor().getName()));
targetVariableDescriptorList.add(shadowVariableTarget.entityDescriptor()
.getShadowVariableDescriptor(shadowVariableTarget.variableMemberAccessor().getName()));
}

// Currently, only one list variable is supported per design.
Expand All @@ -104,7 +104,7 @@ public void linkVariableDescriptors(DescriptorPolicy descriptorPolicy) {
.toList();
if (listVariableDescriptorList.size() > 1) {
throw new IllegalArgumentException(
"The listener @%s does not support models with multiple planning list variables [%s].".formatted(
"The shadow variable @%s does not support models with multiple planning list variables [%s].".formatted(
CascadingUpdateShadowVariable.class.getSimpleName(),
listVariableDescriptorList.stream().map(
v -> v.getEntityDescriptor().getEntityClass().getSimpleName() + "::" + v.getVariableName())
Expand Down Expand Up @@ -137,8 +137,39 @@ public void linkVariableDescriptors(DescriptorPolicy descriptorPolicy) {
MemberAccessorFactory.MemberAccessorType.REGULAR_METHOD, null, descriptorPolicy.getDomainAccessType());
}

public void linkVariableDescriptorToSource(CascadingUpdateShadowVariable listener) {
var sourceDescriptor = entityDescriptor.getShadowVariableDescriptor(listener.sourceVariableName());
public void linkVariableDescriptorToSource(CascadingUpdateShadowVariable shadowVariable) {
var nonEmptySources = Arrays.stream(shadowVariable.sourceVariableNames())
.filter(s -> !s.isBlank())
.toList();
if (shadowVariable.sourceVariableName().isBlank() && nonEmptySources.isEmpty()) {
throw new IllegalArgumentException(
"""
The entity class (%s) has an @%s annotated property (%s), but neither the sourceVariableName nor the sourceVariableNames properties are set.
Maybe update the field "%s" and set one of the properties ([sourceVariableName, sourceVariableNames])."""
.formatted(entityDescriptor.getEntityClass(),
CascadingUpdateShadowVariable.class.getSimpleName(),
variableMemberAccessor.getName(),
variableMemberAccessor.getName()));
}
if (!shadowVariable.sourceVariableName().isBlank() && !nonEmptySources.isEmpty()) {
throw new IllegalArgumentException(
"""
The entity class (%s) has an @%s annotated property (%s), but it is only possible to define either sourceVariableName or sourceVariableNames.
Maybe update the field "%s" to set only one of the properties ([sourceVariableName, sourceVariableNames])."""
.formatted(entityDescriptor.getEntityClass(),
CascadingUpdateShadowVariable.class.getSimpleName(),
variableMemberAccessor.getName(),
variableMemberAccessor.getName()));
}
if (nonEmptySources.isEmpty()) {
registerSource(shadowVariable.sourceVariableName());
} else {
nonEmptySources.forEach(this::registerSource);
}
}

private void registerSource(String sourceVariableName) {
var sourceDescriptor = entityDescriptor.getShadowVariableDescriptor(sourceVariableName);
if (sourceDescriptor == null) {
throw new IllegalArgumentException(
"""
Expand All @@ -147,7 +178,7 @@ The entity class (%s) has an @%s annotated property (%s), but the shadow variabl
.formatted(entityDescriptor.getEntityClass(),
CascadingUpdateShadowVariable.class.getSimpleName(),
variableMemberAccessor.getName(),
listener.sourceVariableName(),
sourceVariableName,
entityDescriptor.getEntityClass()));
}
if (sourceShadowVariableDescriptorSet.add(sourceDescriptor)) {
Expand All @@ -171,37 +202,38 @@ public Demand<?> getProvidedDemand() {
}

@Override
public Iterable<VariableListenerWithSources<Solution_>> buildVariableListeners(SupplyManager supplyManager) {
public boolean hasVariableListener() {
// There are use cases where the shadow variable is applied to different fields
// and relies on the same method to update their values.
// Therefore, only one listener will be generated when multiple descriptors use the same method,
//and the notifiable flag won't be enabled in such cases.
if (notifiable) {
AbstractCascadingUpdateShadowVariableListener<Solution_> listener;
if (nextElementShadowVariableDescriptor != null) {
if (targetVariableDescriptorList.size() == 1) {
listener = new SingleCascadingUpdateShadowVariableListener<>(targetVariableDescriptorList,
nextElementShadowVariableDescriptor, targetMethod);
} else {
listener = new CollectionCascadingUpdateShadowVariableListener<>(targetVariableDescriptorList,
nextElementShadowVariableDescriptor, targetMethod);
}
return notifiable;
}

@Override
public Iterable<VariableListenerWithSources<Solution_>> buildVariableListeners(SupplyManager supplyManager) {
AbstractCascadingUpdateShadowVariableListener<Solution_> listener;
if (nextElementShadowVariableDescriptor != null) {
if (targetVariableDescriptorList.size() == 1) {
listener = new SingleCascadingUpdateShadowVariableListener<>(targetVariableDescriptorList,
nextElementShadowVariableDescriptor, targetMethod);
} else {
if (targetVariableDescriptorList.size() == 1) {
listener = new SingleCascadingUpdateShadowVariableWithSupplyListener<>(targetVariableDescriptorList,
supplyManager.demand(new NextElementVariableDemand<>(sourceListVariable)), targetMethod);
} else {
listener = new CollectionCascadingUpdateShadowVariableWithSupplyListener<>(targetVariableDescriptorList,
supplyManager.demand(new NextElementVariableDemand<>(sourceListVariable)), targetMethod);
}
listener = new CollectionCascadingUpdateShadowVariableListener<>(targetVariableDescriptorList,
nextElementShadowVariableDescriptor, targetMethod);
}
return Collections.singleton(new VariableListenerWithSources<>(listener, getSourceVariableDescriptorList()));
} else {
return Collections.emptyList();
if (targetVariableDescriptorList.size() == 1) {
listener = new SingleCascadingUpdateShadowVariableWithSupplyListener<>(targetVariableDescriptorList,
supplyManager.demand(new NextElementVariableDemand<>(sourceListVariable)), targetMethod);
} else {
listener = new CollectionCascadingUpdateShadowVariableWithSupplyListener<>(targetVariableDescriptorList,
supplyManager.demand(new NextElementVariableDemand<>(sourceListVariable)), targetMethod);
}
}
return Collections.singleton(new VariableListenerWithSources<>(listener, getSourceVariableDescriptorList()));
}

private record TargetVariable<Solution_>(EntityDescriptor<Solution_> entityDescriptor,
private record ShadowVariableTarget<Solution_>(EntityDescriptor<Solution_> entityDescriptor,
MemberAccessor variableMemberAccessor) {

}
Expand Down
Loading

0 comments on commit 2bb6b26

Please sign in to comment.