From 6283516d6af80485e34909a1445c6ce3852b63df Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Thu, 29 May 2025 11:53:25 -0400 Subject: [PATCH] chore: fail fast if variable path ends on a fact --- .../variable/declarative/RootVariableSource.java | 16 +++++++++++----- .../declarative/RootVariableSourceTest.java | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSource.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSource.java index 5423081c497..52064992abe 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSource.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSource.java @@ -65,7 +65,7 @@ public static RootVariableSource from( List> chainStartingFromSourceVariableList = new ArrayList<>(); boolean isAfterVariable = false; Class currentEntity = rootEntityClass; - var factCount = 0; + var factCountSinceLastVariable = 0; for (var iterator = pathIterator(rootEntityClass, variablePath); iterator.hasNext();) { var pathPart = iterator.next(); @@ -87,7 +87,7 @@ public static RootVariableSource from( descriptorPolicy); listMemberAccessors.add(memberAccessor); chainToVariable = new ArrayList<>(); - factCount = 0; + factCountSinceLastVariable = 0; currentEntity = ConfigUtils.extractGenericTypeParameterOrFail(ShadowSources.class.getSimpleName(), currentEntity, @@ -115,10 +115,10 @@ public static RootVariableSource from( chainStartingFromSourceVariableList.add(chainStartingFromSourceVariable); isAfterVariable = true; - factCount = 0; + factCountSinceLastVariable = 0; } else { - factCount++; - if (factCount == 2) { + factCountSinceLastVariable++; + if (factCountSinceLastVariable == 2) { throw new IllegalArgumentException( "The source path (%s) starting from root entity (%s) referencing multiple facts (%s, %s) in a row." .formatted(variablePath, rootEntityClass.getSimpleName(), @@ -156,6 +156,12 @@ public static RootVariableSource from( .formatted(variablePath, rootEntityClass.getSimpleName())); } + if (factCountSinceLastVariable != 0) { + throw new IllegalArgumentException( + "The source path (%s) starting from root entity class (%s) does not end on a variable." + .formatted(variablePath, rootEntityClass.getSimpleName())); + } + for (var variableSourceReference : variableSourceReferences) { assertIsValidVariableReference(rootEntityClass, variablePath, variableSourceReference); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSourceTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSourceTest.java index 443e3a5f66a..79612e6524f 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSourceTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/variable/declarative/RootVariableSourceTest.java @@ -428,6 +428,21 @@ void invalidPathNoVariables() { " does not reference any variables."); } + @Test + void invalidPathEndOnFact() { + assertThatCode(() -> RootVariableSource.from( + planningSolutionMetaModel, + TestdataInvalidDeclarativeValue.class, + "shadow", + "previous.fact", + DEFAULT_MEMBER_ACCESSOR_FACTORY, + DEFAULT_DESCRIPTOR_POLICY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("The source path (previous.fact)" + + " starting from root entity class (TestdataInvalidDeclarativeValue)" + + " does not end on a variable."); + } + @Test void invalidPathMultipleFactsInARow() { assertThatCode(() -> RootVariableSource.from(