diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/StatementMatcher.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/StatementMatcher.java index 140c0b115d6..54ecee274f5 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/StatementMatcher.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/StatementMatcher.java @@ -11,7 +11,9 @@ package org.eclipse.rdf4j.sail.shacl.ast; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -31,7 +33,7 @@ public class StatementMatcher { private final Variable predicate; private final Variable object; -// private final Set varNames; + // private final Set varNames; private final Targetable origin; private final Set inheritedVarNames; @@ -216,11 +218,17 @@ private static String formatForToString(String field, String name, Value value) private StatementMatcher swap(Variable existingVariable, Variable newVariable) { String subjectName = getSubjectName(); + String subjectBasename = getSubjectBasename(); Resource subjectValue = getSubjectValue(); + String predicateName = getPredicateName(); + String predicateBasename = getPredicateBasename(); IRI predicateValue = getPredicateValue(); + String objectName = getObjectName(); + String objectBasename = getObjectBasename(); Value objectValue = getObjectValue(); + boolean changed = false; if (Objects.equals(existingVariable.name, subjectName) @@ -228,6 +236,7 @@ private StatementMatcher swap(Variable existingVariable, Variable newVaria changed = true; subjectName = newVariable.name; subjectValue = (Resource) newVariable.value; + subjectBasename = newVariable.baseName; } if (Objects.equals(existingVariable.name, predicateName) @@ -235,18 +244,21 @@ private StatementMatcher swap(Variable existingVariable, Variable newVaria changed = true; predicateName = newVariable.name; predicateValue = (IRI) newVariable.value; + predicateBasename = newVariable.baseName; } if (Objects.equals(existingVariable.name, objectName) && Objects.equals(existingVariable.value, objectValue)) { changed = true; objectName = newVariable.name; objectValue = newVariable.value; + objectBasename = newVariable.baseName; } if (changed) { assert subset.isEmpty(); - return new StatementMatcher(new Variable<>(subjectName, subjectValue), - new Variable<>(predicateName, predicateValue), new Variable<>(objectName, objectValue), origin, + return new StatementMatcher(new Variable<>(subjectName, subjectValue, subjectBasename), + new Variable<>(predicateName, predicateValue, predicateBasename), + new Variable<>(objectName, objectValue, objectBasename), origin, inheritedVarNames); } return this; @@ -268,6 +280,10 @@ public String getSubjectName() { return subject.name; } + public String getSubjectBasename() { + return subject.baseName; + } + public Resource getSubjectValue() { return subject.value; } @@ -280,6 +296,10 @@ public String getPredicateName() { return predicate.name; } + public String getPredicateBasename() { + return predicate.baseName; + } + public IRI getPredicateValue() { return predicate.value; } @@ -292,6 +312,10 @@ public String getObjectName() { return object.name; } + public String getObjectBasename() { + return object.baseName; + } + public Value getObjectValue() { return object.value; } @@ -324,6 +348,7 @@ public int hashCode() { public String getSparqlValuesDecl(Set varNamesRestriction, boolean addInheritedVarNames, Set varNamesInQueryFragment) { + StringBuilder sb = new StringBuilder("VALUES ( "); if (subject.name != null && varNamesRestriction.contains(subject.name) || subject.baseName != null && varNamesRestriction.contains(subject.baseName)) { @@ -362,13 +387,13 @@ public String getSparqlValuesDecl(Set varNamesRestriction, boolean addIn return sb.toString(); } - public Set getVarNames(Set varNamesRestriction, boolean addInheritedVarNames, + public LinkedHashSet getVarNames(Set varNamesRestriction, boolean addInheritedVarNames, Set varNamesInQueryFragment) { if (varNamesRestriction.isEmpty()) { - return Set.of(); + return new LinkedHashSet<>(); } - HashSet ret = new HashSet<>(); + LinkedHashSet ret = new LinkedHashSet<>(); if (subject.name != null && varNamesRestriction.contains(subject.name) && varNamesInQueryFragment.contains(subject.name)) { ret.add(subject.name); @@ -448,6 +473,26 @@ public boolean hasObject(Variable variable) { return variable.name.equals(object.name); } + public Set getInheritedVarNames() { + return Collections.unmodifiableSet(new HashSet<>(inheritedVarNames)); + } + + public Set getVarNames() { + Set varNames = new HashSet<>(); + + if (subject.name != null) { + varNames.add(subject.name); + } + if (predicate.name != null) { + varNames.add(predicate.name); + } + if (object.name != null) { + varNames.add(object.name); + } + + return Collections.unmodifiableSet(varNames); + } + public static class StableRandomVariableProvider { // We just need a random base that isn't used elsewhere in the ShaclSail, but we don't want it to be stable so @@ -471,9 +516,12 @@ public StableRandomVariableProvider(String prefix) { * increments of one. * * @param inputQuery the query string that should be normalized + * @param union * @return a normalized query string */ - public static String normalize(String inputQuery) { + public static String normalize(String inputQuery, List protectedVars, + List union) { + if (!inputQuery.contains(BASE)) { return inputQuery; } @@ -499,18 +547,30 @@ public static String normalize(String inputQuery) { if (lowest == 0 && incrementsOfOne) { return inputQuery; } + String joinedProtectedVars = protectedVars.stream() + .map(Variable::getName) + .filter(Objects::nonNull) + .filter(s -> s.contains(BASE)) + .collect(Collectors.joining()); - return normalizeRange(inputQuery, lowest, highest); + return normalizeRange(inputQuery, lowest, highest, joinedProtectedVars, union); } - private static String normalizeRange(String inputQuery, int lowest, int highest) { + private static String normalizeRange(String inputQuery, int lowest, int highest, String joinedProtectedVars, + List union) { String normalizedQuery = inputQuery; for (int i = 0; i <= highest; i++) { - if (!normalizedQuery.contains(BASE + i + "_")) { + String replacement = BASE + i + "_"; + if (!normalizedQuery.contains(replacement)) { for (int j = Math.max(i + 1, lowest); j <= highest; j++) { - if (normalizedQuery.contains(BASE + j + "_")) { - normalizedQuery = normalizedQuery.replace(BASE + j + "_", BASE + i + "_"); + String original = BASE + j + "_"; + if (normalizedQuery.contains(original)) { + if (joinedProtectedVars.contains(original)) { + continue; + } + normalizedQuery = normalizedQuery.replace(original, replacement); + replaceInStatementMatcher(union, original, replacement); break; } } @@ -520,6 +580,13 @@ private static String normalizeRange(String inputQuery, int lowest, int highest) return normalizedQuery; } + private static void replaceInStatementMatcher(List statementMatchers, String original, + String replacement) { + for (StatementMatcher statementMatcher : statementMatchers) { + statementMatcher.replaceVariableName(original, replacement); + } + } + public Variable next() { counter++; @@ -538,6 +605,29 @@ public Variable current() { } } + private void replaceVariableName(String original, String replacement) { + + if (subject.name != null && subject.name.contains(original)) { + subject.name = subject.name.replace(original, replacement); + } + if (subject.baseName != null && subject.baseName.contains(original)) { + subject.baseName = subject.baseName.replace(original, replacement); + } + if (predicate.name != null && predicate.name.contains(original)) { + predicate.name = predicate.name.replace(original, replacement); + } + if (predicate.baseName != null && predicate.baseName.contains(original)) { + predicate.baseName = predicate.baseName.replace(original, replacement); + } + if (object.name != null && object.name.contains(original)) { + object.name = object.name.replace(original, replacement); + } + if (object.baseName != null && object.baseName.contains(original)) { + object.baseName = object.baseName.replace(original, replacement); + } + + } + public static class Variable { public static final Variable VALUE = new Variable<>("value"); public static final Variable THIS = new Variable<>("this"); @@ -562,6 +652,12 @@ public Variable(Variable baseVariable, String name) { this.baseName = baseVariable.name; } + public Variable(String name, T value, String baseName) { + this.name = name; + this.value = value; + this.baseName = baseName; + } + public Variable(T value) { this.value = value; } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BindSelect.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BindSelect.java index ffbcb672a36..d802380acf4 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BindSelect.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BindSelect.java @@ -91,7 +91,7 @@ public BindSelect(SailConnection connection, Resource[] dataGraph, SparqlFragmen throw new IllegalStateException(); } - this.query = StatementMatcher.StableRandomVariableProvider.normalize(query.getFragment()); + this.query = StatementMatcher.StableRandomVariableProvider.normalize(query.getFragment(), vars, List.of()); this.prefixes = query.getNamespacesForSparql(); this.direction = direction; this.includePropertyShapeValues = includePropertyShapeValues; diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java index eb0b55ccbf9..75cce9a2bea 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java @@ -12,6 +12,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.planNodes; import java.util.ArrayDeque; +import java.util.List; import java.util.Objects; import java.util.function.Function; @@ -64,7 +65,7 @@ public BulkedExternalInnerJoin(PlanNode leftNode, SailConnection connection, Res this.leftNode = PlanNodeHelper.handleSorting(this, leftNode); this.query = query.getNamespacesForSparql() + StatementMatcher.StableRandomVariableProvider - .normalize(query.getFragment()); + .normalize(query.getFragment(), List.of(), List.of()); this.connection = connection; assert this.connection != null; this.skipBasedOnPreviousConnection = skipBasedOnPreviousConnection; diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java index 0926624ba36..cd11fb066df 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java @@ -12,6 +12,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.planNodes; import java.util.ArrayDeque; +import java.util.List; import java.util.Objects; import java.util.function.Function; @@ -49,7 +50,7 @@ public BulkedExternalLeftOuterJoin(PlanNode leftNode, SailConnection connection, leftNode = PlanNodeHelper.handleSorting(this, leftNode); this.leftNode = leftNode; this.query = query.getNamespacesForSparql() - + StatementMatcher.StableRandomVariableProvider.normalize(query.getFragment()); + + StatementMatcher.StableRandomVariableProvider.normalize(query.getFragment(), List.of(), List.of()); this.connection = connection; assert this.connection != null; this.mapper = mapper; diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ExternalFilterByQuery.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ExternalFilterByQuery.java index a31d8ad3d94..ef9a19f0a6e 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ExternalFilterByQuery.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ExternalFilterByQuery.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.planNodes; +import java.util.List; import java.util.Objects; import java.util.function.Function; @@ -53,7 +54,7 @@ public ExternalFilterByQuery(SailConnection connection, Resource[] dataGraph, Pl this.queryString = queryFragment.getNamespacesForSparql() + StatementMatcher.StableRandomVariableProvider.normalize("SELECT " + queryVariable.asSparqlVariable() - + " WHERE {\n" + queryFragment.getFragment() + "\n}"); + + " WHERE {\n" + queryFragment.getFragment() + "\n}", List.of(queryVariable), List.of()); try { this.query = SparqlQueryParserCache.get(queryString); } catch (MalformedQueryException e) { diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Select.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Select.java index 3d9745c99d3..66d55b1f597 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Select.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Select.java @@ -11,6 +11,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.planNodes; +import java.util.List; import java.util.Objects; import java.util.function.Function; @@ -67,10 +68,11 @@ public Select(SailConnection connection, SparqlFragment queryFragment, String or if (!sorted && fragment.trim().startsWith("select ")) { this.query = queryFragment.getNamespacesForSparql() + "\n" - + StatementMatcher.StableRandomVariableProvider.normalize(fragment); + + StatementMatcher.StableRandomVariableProvider.normalize(fragment, List.of(), List.of()); } else { this.query = queryFragment.getNamespacesForSparql() + "\n" + StatementMatcher.StableRandomVariableProvider - .normalize("select * where {\n" + fragment + "\n}" + (sorted ? " order by " + orderBy : "")); + .normalize("select * where {\n" + fragment + "\n}" + (sorted ? " order by " + orderBy : ""), + List.of(), List.of()); } dataset = PlanNodeHelper.asDefaultGraphDataset(dataGraph); @@ -87,7 +89,7 @@ public Select(SailConnection connection, String query, Function(vars); - vars.add(optional.var); - } + var vars = getVars(includePropertyShapeValues); List varNames = vars.stream().map(StatementMatcher.Variable::getName).collect(Collectors.toList()); @@ -153,7 +150,11 @@ public PlanNode extend(PlanNode source, ConnectionsGroup connectionsGroup, Resou } } - private List> getVars() { + private List> getVars(boolean optional) { + if (optional) { + return Stream.concat(chain.stream(), Stream.of(this.optional)).map(t -> t.var).collect(Collectors.toList()); + } + return chain.stream().map(t -> t.var).collect(Collectors.toList()); } @@ -221,7 +222,7 @@ public boolean couldMatch(ConnectionsGroup connectionsGroup, Resource[] dataGrap public PlanNode getAllTargets(ConnectionsGroup connectionsGroup, Resource[] dataGraph, ConstraintComponent.Scope scope) { - return new AllTargetsPlanNode(connectionsGroup.getBaseConnection(), dataGraph, chain, getVars(), scope); + return new AllTargetsPlanNode(connectionsGroup.getBaseConnection(), dataGraph, chain, getVars(false), scope); } public PlanNode getPlanNode(ConnectionsGroup connectionsGroup, Resource[] dataGraph, @@ -287,7 +288,7 @@ public PlanNode getPlanNode(ConnectionsGroup connectionsGroup, Resource[] dataGr statementMatchersRemoval, optional, fragment, - getVars(), + getVars(false), scope, false); } else { @@ -298,14 +299,14 @@ public PlanNode getPlanNode(ConnectionsGroup connectionsGroup, Resource[] dataGr null, null, fragment, - getVars(), + getVars(false), scope, false); } } else { targetsPlanNode = new AllTargetsPlanNode(connectionsGroup.getBaseConnection(), dataGraph, chain, - getVars(), scope); + getVars(false), scope); } @@ -491,6 +492,10 @@ public Stream getRoot(ConnectionsGroup connectionsGroup, R currentStatementMatcher, List.of(currentStatement)); + if (root == null) { + return null; + } + List collect = root.collect(Collectors.toList()); return collect.stream(); @@ -525,6 +530,14 @@ public StatementMatcher getStatementMatcher() { public boolean hasStatements() { return !statements.isEmpty(); } + + @Override + public String toString() { + return "StatementsAndMatcher{" + + "statements=" + Arrays.toString(statements.toArray()) + + ", statementMatcher=" + statementMatcher + + '}'; + } } static class ActiveTargetTupleMapper implements Function { diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetChainRetriever.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetChainRetriever.java index 6d723115d9d..7ba9f4ffc3e 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetChainRetriever.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetChainRetriever.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -82,8 +83,10 @@ public class TargetChainRetriever implements PlanNode { private final EffectiveTarget.EffectiveTargetFragment removedStatementTarget; private final boolean hasValue; private final Set varNamesInQueryFragment; + private final Set originalStatementMatchers; private StackTraceElement[] stackTrace; + private ValidationExecutionLogger validationExecutionLogger; public TargetChainRetriever(ConnectionsGroup connectionsGroup, @@ -96,7 +99,19 @@ public TargetChainRetriever(ConnectionsGroup connectionsGroup, this.varNames = vars.stream().map(StatementMatcher.Variable::getName).collect(Collectors.toSet()); assert !this.varNames.isEmpty(); this.dataset = PlanNodeHelper.asDefaultGraphDataset(this.dataGraph); + + var union = statementMatchers; + if (removedStatementMatchers != null) { + union = new ArrayList<>(statementMatchers); + union.addAll(removedStatementMatchers); + } + + this.queryFragment = queryFragment.getNamespacesForSparql() + + StatementMatcher.StableRandomVariableProvider.normalize(queryFragment.getFragment(), vars, union); + + this.originalStatementMatchers = new HashSet<>(statementMatchers); this.statementMatchers = StatementMatcher.reduce(statementMatchers); + assert originalStatementMatchers.containsAll(this.statementMatchers); this.scope = scope; @@ -105,11 +120,6 @@ public TargetChainRetriever(ConnectionsGroup connectionsGroup, .reduce((a, b) -> a + " " + b) .orElseThrow(IllegalStateException::new); - this.queryFragment = queryFragment.getNamespacesForSparql() - + StatementMatcher.StableRandomVariableProvider.normalize(queryFragment.getFragment()); - -// this.stackTrace = Thread.currentThread().getStackTrace(); - this.queryParserFactory = QueryParserRegistry.getInstance() .get(QueryLanguage.SPARQL) .get(); @@ -125,12 +135,17 @@ public TargetChainRetriever(ConnectionsGroup connectionsGroup, ? StatementMatcher.reduce(removedStatementMatchers) : Collections.emptyList(); + assert removedStatementMatchers == null || removedStatementMatchers.containsAll(this.removedStatementMatchers); + this.removedStatementTarget = removedStatementTarget; this.hasValue = hasValue; assert scope == ConstraintComponent.Scope.propertyShape || !this.hasValue; + if (logger.isDebugEnabled()) { + this.stackTrace = Thread.currentThread().getStackTrace(); + } } @Override @@ -199,12 +214,19 @@ public void calculateNextStatementMatcher() { removedStatement = true; } - this.sparqlValuesDecl = currentStatementMatcher.getSparqlValuesDecl(varNames, removedStatement, + // we need to add the inherited names if we are going to chase the root of the + // currentStatementMatcher later + boolean addInherited = chaseRoot(); + + this.sparqlValuesDecl = currentStatementMatcher.getSparqlValuesDecl(varNames, addInherited, varNamesInQueryFragment); - this.currentVarNames = currentStatementMatcher.getVarNames(varNames, removedStatement, + this.currentVarNames = currentStatementMatcher.getVarNames(varNames, addInherited, varNamesInQueryFragment); - assert !currentVarNames.isEmpty() : "currentVarNames is empty!"; + if (currentVarNames.isEmpty()) { + logger.error("currentVarNames should not be empty!"); + throw new IllegalStateException("currentVarNames should not be empty!"); + } statements = connection.getStatements( currentStatementMatcher.getSubjectValue(), @@ -217,6 +239,11 @@ public void calculateNextStatementMatcher() { } + private boolean chaseRoot() { + return removedStatementTarget != null && removedStatement + && !originalStatementMatchers.contains(currentStatementMatcher); + } + private void calculateNextResult() { if (next != null) { return; @@ -282,18 +309,21 @@ private void calculateNextResult() { } - private List readStatementsInBulk(Set varNames) { + private List readStatementsInBulk(Set variableNames) { bulk.clear(); while (bulk.size() < BULK_SIZE && statements.hasNext()) { Statement next = statements.next(); Stream rootStatements = Stream .of(new EffectiveTarget.StatementsAndMatcher(List.of(next), currentStatementMatcher)); - if (removedStatement && removedStatementTarget != null) { + if (chaseRoot()) { + // we only need to find the root if the currentStatementMatcher doesn't match anything in the + // query Stream root = removedStatementTarget.getRoot( connectionsGroup, dataGraph, currentStatementMatcher, next); + if (root != null) { rootStatements = root; } @@ -308,7 +338,7 @@ private List readStatementsInBulk(Set varNames) { return statementsAndMatcher.getStatements() .stream() .map(temp -> { - Binding[] bindings = new Binding[varNames.size()]; + Binding[] bindings = new Binding[variableNames.size()]; int j = 0; if (newCurrentStatementMatcher.getSubjectValue() == null @@ -339,7 +369,7 @@ private List readStatementsInBulk(Set varNames) { bindings[0].getValue()); } else { - return new SimpleBindingSet(varNames, bindings); + return new SimpleBindingSet(variableNames, bindings); } }); diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ast/targets/StableQueryGenerationTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ast/targets/StableQueryGenerationTest.java index e4f68e5b36c..04f7ab0033a 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ast/targets/StableQueryGenerationTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ast/targets/StableQueryGenerationTest.java @@ -71,22 +71,28 @@ public void testNormalizationOfQuery() { }; String test1 = names[4] + names[4] + names[9]; - assertEquals(names[0] + names[0] + names[1], StatementMatcher.StableRandomVariableProvider.normalize(test1)); + assertEquals(names[0] + names[0] + names[1], + StatementMatcher.StableRandomVariableProvider.normalize(test1, List.of(), List.of())); String test2 = names[9] + names[4] + names[9]; - assertEquals(names[1] + names[0] + names[1], StatementMatcher.StableRandomVariableProvider.normalize(test2)); + assertEquals(names[1] + names[0] + names[1], + StatementMatcher.StableRandomVariableProvider.normalize(test2, List.of(), List.of())); String test3 = names[0] + names[1] + names[2]; - assertEquals(names[0] + names[1] + names[2], StatementMatcher.StableRandomVariableProvider.normalize(test3)); + assertEquals(names[0] + names[1] + names[2], + StatementMatcher.StableRandomVariableProvider.normalize(test3, List.of(), List.of())); String test4 = names[1] + names[2] + names[3]; - assertEquals(names[0] + names[1] + names[2], StatementMatcher.StableRandomVariableProvider.normalize(test4)); + assertEquals(names[0] + names[1] + names[2], + StatementMatcher.StableRandomVariableProvider.normalize(test4, List.of(), List.of())); String test5 = names[2] + names[4] + names[8]; - assertEquals(names[0] + names[1] + names[2], StatementMatcher.StableRandomVariableProvider.normalize(test5)); + assertEquals(names[0] + names[1] + names[2], + StatementMatcher.StableRandomVariableProvider.normalize(test5, List.of(), List.of())); String test6 = names[8] + names[4] + names[2]; - assertEquals(names[2] + names[1] + names[0], StatementMatcher.StableRandomVariableProvider.normalize(test6)); + assertEquals(names[2] + names[1] + names[0], + StatementMatcher.StableRandomVariableProvider.normalize(test6, List.of(), List.of())); }