import static java.util.Objects.requireNonNull;
import com.google.common.annotations.Beta;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.VerifyException;
+import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import java.util.ArrayDeque;
-import java.util.Deque;
+import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.concepts.Mutable;
+import org.opendaylight.yangtools.rfc8040.model.api.YangDataEffectiveStatement;
import org.opendaylight.yangtools.yang.common.AbstractQName;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
-import org.opendaylight.yangtools.yang.common.UnqualifiedQName;
+import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeAwareEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.TypedefEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.TypedefNamespace;
import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveStatementInference;
import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep;
import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step;
import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis;
+import org.slf4j.LoggerFactory;
/**
* A state tracking utility for walking {@link EffectiveModelContext}'s contents along schema/grouping namespaces. This
@Override
public List<EffectiveStatement<?, ?>> statementPath() {
- return ImmutableList.copyOf(deque.descendingIterator());
+ return ImmutableList.copyOf(deque);
}
/**
}
}
+ private static final String VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP =
+ "org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.verifyDefaultSchemaTreeInference";
+ private static final boolean VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE =
+ Boolean.getBoolean(VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP);
+
+ static {
+ if (VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE) {
+ LoggerFactory.getLogger(SchemaInferenceStack.class)
+ .info("SchemaTreeStack.ofInference(DefaultSchemaTreeInference) argument is being verified");
+ }
+ }
+
private final @NonNull EffectiveModelContext effectiveModel;
private final ArrayDeque<EffectiveStatement<?, ?>> deque;
private boolean clean;
private SchemaInferenceStack(final EffectiveModelContext effectiveModel, final int expectedSize) {
- this.deque = new ArrayDeque<>(expectedSize);
+ deque = new ArrayDeque<>(expectedSize);
this.effectiveModel = requireNonNull(effectiveModel);
- this.clean = true;
+ clean = true;
}
private SchemaInferenceStack(final SchemaInferenceStack source) {
- this.deque = source.deque.clone();
- this.effectiveModel = source.effectiveModel;
- this.currentModule = source.currentModule;
- this.groupingDepth = source.groupingDepth;
- this.clean = source.clean;
+ deque = source.deque.clone();
+ effectiveModel = source.effectiveModel;
+ currentModule = source.currentModule;
+ groupingDepth = source.groupingDepth;
+ clean = source.clean;
}
private SchemaInferenceStack(final EffectiveModelContext effectiveModel,
private SchemaInferenceStack(final EffectiveModelContext effectiveModel) {
this.effectiveModel = requireNonNull(effectiveModel);
- this.deque = new ArrayDeque<>();
- this.clean = true;
+ deque = new ArrayDeque<>();
+ clean = true;
}
/**
* @throws IllegalArgumentException if {@code inference} cannot be resolved to a valid stack
*/
public static @NonNull SchemaInferenceStack ofInference(final SchemaTreeInference inference) {
- return of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
+ return inference instanceof DefaultSchemaTreeInference ? ofInference((DefaultSchemaTreeInference) inference)
+ : of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
+ }
+
+ /**
+ * Create a new stack from an {@link DefaultSchemaTreeInference}. The argument is nominally trusted to be an
+ * accurate representation of the schema tree.
+ *
+ * <p>
+ * Run-time verification of {@code inference} can be enabled by setting the
+ * {@value #VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE_PROP} system property to {@code true}.
+ *
+ * @param inference DefaultSchemaTreeInference to use for initialization
+ * @return A new stack
+ * @throws NullPointerException if {@code inference} is null
+ * @throws IllegalArgumentException if {@code inference} refers to a missing module or when verification is enabled
+ * and it does not match its context's scheam tree
+ */
+ public static @NonNull SchemaInferenceStack ofInference(final DefaultSchemaTreeInference inference) {
+ return VERIFY_DEFAULT_SCHEMA_TREE_INFERENCE ? ofUntrusted(inference) : ofTrusted(inference);
+ }
+
+ private static @NonNull SchemaInferenceStack ofTrusted(final DefaultSchemaTreeInference inference) {
+ final var path = inference.statementPath();
+ final var ret = new SchemaInferenceStack(inference.getEffectiveModelContext(), path.size());
+ ret.currentModule = ret.getModule(path.get(0).argument());
+ ret.deque.addAll(path);
+ return ret;
+ }
+
+ @VisibleForTesting
+ static @NonNull SchemaInferenceStack ofUntrusted(final DefaultSchemaTreeInference inference) {
+ final var ret = of(inference.getEffectiveModelContext(), inference.toSchemaNodeIdentifier());
+ if (!Iterables.elementsEqual(ret.deque, inference.statementPath())) {
+ throw new IllegalArgumentException("Provided " + inference + " is not consistent with resolved path "
+ + ret.toSchemaTreeInference());
+ }
+ return ret;
}
/**
/**
* Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute
- * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()}.
+ * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()} interpreted as a schema node identifier.
*
* @param effectiveModel EffectiveModelContext to which this stack is attached
* @return A new stack
return ret;
}
+ /**
+ * Create a new stack backed by an effective model, pointing to specified schema node identified by an absolute
+ * {@link SchemaPath} and its {@link SchemaPath#getPathFromRoot()}, interpreted as a series of steps along primarily
+ * schema tree, with grouping namespace being the alternative lookup.
+ *
+ * @param effectiveModel EffectiveModelContext to which this stack is attached
+ * @return A new stack
+ * @throws NullPointerException {@code effectiveModel} is null
+ * @throws IllegalArgumentException if {@code path} cannot be resolved in the effective model or if it is not an
+ * absolute path.
+ */
+ @Deprecated
+ public static @NonNull SchemaInferenceStack ofSchemaPath(final EffectiveModelContext effectiveModel,
+ final SchemaPath path) {
+ checkArgument(path.isAbsolute(), "Cannot operate on relative path %s", path);
+ final SchemaInferenceStack ret = new SchemaInferenceStack(effectiveModel);
+
+ for (QName step : path.getPathFromRoot()) {
+ try {
+ ret.enterSchemaTree(step);
+ } catch (IllegalArgumentException schemaEx) {
+ try {
+ ret.enterGrouping(step);
+ } catch (IllegalArgumentException ex) {
+ ex.addSuppressed(schemaEx);
+ throw ex;
+ }
+ }
+ }
+
+ return ret;
+ }
+
@Override
public EffectiveModelContext getEffectiveModelContext() {
return effectiveModel;
* @throws IllegalStateException if the stack is empty
*/
public @NonNull EffectiveStatement<?, ?> currentStatement() {
- return checkNonNullState(deque.peekFirst());
+ return checkNonNullState(deque.peekLast());
}
/**
}
/**
- * Check if the stack is in instantiated context. This indicates the stack is non-empty and there are only screma
+ * Check if the stack is in instantiated context. This indicates the stack is non-empty and there are only schema
* tree statements in the stack.
*
* @return False if the stack is empty or contains a statement which is not a {@link SchemaTreeEffectiveStatement},
* @throws IllegalArgumentException if the corresponding choice cannot be found
*/
public @NonNull ChoiceEffectiveStatement enterChoice(final QName nodeIdentifier) {
- final EffectiveStatement<?, ?> parent = deque.peek();
+ final QName nodeId = requireNonNull(nodeIdentifier);
+ final EffectiveStatement<?, ?> parent = deque.peekLast();
if (parent instanceof ChoiceEffectiveStatement) {
- return enterChoice((ChoiceEffectiveStatement) parent, nodeIdentifier);
+ return enterChoice((ChoiceEffectiveStatement) parent, nodeId);
}
// Fall back to schema tree lookup. Note if it results in non-choice, we rewind before reporting an error
- final SchemaTreeEffectiveStatement<?> result = enterSchemaTree(nodeIdentifier);
+ final SchemaTreeEffectiveStatement<?> result = enterSchemaTree(nodeId);
if (result instanceof ChoiceEffectiveStatement) {
return (ChoiceEffectiveStatement) result;
}
exit();
- throw new IllegalArgumentException("Choice " + nodeIdentifier + " not present");
+
+ if (parent != null) {
+ throw notPresent(parent, "Choice", nodeId);
+ }
+ throw new IllegalArgumentException("Choice " + nodeId + " not present");
}
// choice -> choice transition, we have to deal with intermediate case nodes
- private @NonNull ChoiceEffectiveStatement enterChoice(final ChoiceEffectiveStatement parent,
+ private @NonNull ChoiceEffectiveStatement enterChoice(final @NonNull ChoiceEffectiveStatement parent,
final QName nodeIdentifier) {
for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
if (stmt instanceof CaseEffectiveStatement) {
.map(ChoiceEffectiveStatement.class::cast);
if (optMatch.isPresent()) {
final SchemaTreeEffectiveStatement<?> match = optMatch.orElseThrow();
- deque.push(match);
+ deque.addLast(match);
clean = false;
return (ChoiceEffectiveStatement) match;
}
}
}
- throw new IllegalArgumentException("Choice " + nodeIdentifier + " not present");
+ throw notPresent(parent, "Choice", nodeIdentifier);
}
/**
* Lookup a {@code typedef} by its node identifier and push it to the stack.
*
* @param nodeIdentifier Node identifier of the typedef to enter
- * @return Resolved choice
+ * @return Resolved typedef
* @throws NullPointerException if {@code nodeIdentifier} is null
* @throws IllegalArgumentException if the corresponding typedef cannot be found
*/
return pushTypedef(requireNonNull(nodeIdentifier));
}
+ /**
+ * Lookup a {@code rc:yang-data} by the module namespace where it is defined and its template name.
+ *
+ * @param namespace Module namespace in which to lookup the template
+ * @param name Template name
+ * @return Resolved yang-data
+ * @throws NullPointerException if any argument is null
+ * @throws IllegalArgumentException if the corresponding yang-data cannot be found
+ * @throws IllegalStateException if this stack is not empty
+ */
+ public @NonNull YangDataEffectiveStatement enterYangData(final QNameModule namespace, final String name) {
+ final EffectiveStatement<?, ?> parent = deque.peekLast();
+ checkState(parent == null, "Cannot lookup yang-data in a non-empty stack");
+
+ final String templateName = requireNonNull(name);
+ final ModuleEffectiveStatement module = effectiveModel.getModuleStatements().get(requireNonNull(namespace));
+ checkArgument(module != null, "Module for %s not found", namespace);
+
+ final YangDataEffectiveStatement ret = module.streamEffectiveSubstatements(YangDataEffectiveStatement.class)
+ .filter(stmt -> templateName.equals(stmt.argument()))
+ .findFirst()
+ .orElseThrow(
+ () -> new IllegalArgumentException("yang-data " + templateName + " not present in " + namespace));
+ deque.addLast(ret);
+ currentModule = module;
+ return ret;
+ }
+
/**
* Pop the current statement from the stack.
*
* @throws NoSuchElementException if this stack is empty
*/
public @NonNull EffectiveStatement<?, ?> exit() {
- final EffectiveStatement<?, ?> prev = deque.pop();
+ final EffectiveStatement<?, ?> prev = deque.removeLast();
if (prev instanceof GroupingEffectiveStatement) {
--groupingDepth;
}
public @NonNull DataTreeEffectiveStatement<?> exitToDataTree() {
final EffectiveStatement<?, ?> child = exit();
checkState(child instanceof DataTreeEffectiveStatement, "Unexpected current %s", child);
- final EffectiveStatement<?, ?> parent = deque.peekFirst();
+ EffectiveStatement<?, ?> parent = deque.peekLast();
+ while (parent instanceof ChoiceEffectiveStatement || parent instanceof CaseEffectiveStatement) {
+ deque.pollLast();
+ parent = deque.peekLast();
+ }
+
checkState(parent == null || parent instanceof DataTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
return (DataTreeEffectiveStatement<?>) child;
}
-
@Override
public TypeDefinition<?> resolveLeafref(final LeafrefTypeDefinition type) {
final SchemaInferenceStack tmp = copy();
private @NonNull EffectiveStatement<?, ?> resolveLocationPath(final YangLocationPath path) {
// get the default namespace before we clear and loose our deque
- final QNameModule defaultNamespace = deque.isEmpty() ? null : ((QName) deque.peek().argument()).getModule();
+ final QNameModule defaultNamespace = deque.isEmpty() ? null : ((QName) deque.peekLast().argument()).getModule();
if (path.isAbsolute()) {
clear();
}
final QName qname;
if (toResolve instanceof QName) {
qname = (QName) toResolve;
- } else if (toResolve instanceof UnqualifiedQName) {
+ } else if (toResolve instanceof Unqualified) {
checkArgument(defaultNamespace != null, "Can not find target module of step %s", step);
- qname = ((UnqualifiedQName) toResolve).bindTo(defaultNamespace);
+ qname = ((Unqualified) toResolve).bindTo(defaultNamespace);
} else {
throw new VerifyException("Unexpected child step QName " + toResolve);
}
* @throws IllegalStateException if current state cannot be converted to a {@link SchemaTreeInference}
*/
public @NonNull SchemaTreeInference toSchemaTreeInference() {
- return DefaultSchemaTreeInference.of(getEffectiveModelContext(), toSchemaNodeIdentifier());
+ checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
+ final var cleanDeque = clean ? deque : reconstructSchemaInferenceStack().deque;
+ return DefaultSchemaTreeInference.unsafeOf(getEffectiveModelContext(), cleanDeque.stream()
+ .map(stmt -> (SchemaTreeEffectiveStatement<?>) stmt)
+ .collect(ImmutableList.toImmutableList()));
}
/**
*/
public @NonNull Absolute toSchemaNodeIdentifier() {
checkState(inInstantiatedContext(), "Cannot convert uninstantiated context %s", this);
- return Absolute.of(ImmutableList.<QName>builderWithExpectedSize(deque.size())
- .addAll(simplePathFromRoot())
- .build());
+ return Absolute.of(simplePathFromRoot());
}
/**
*/
@Deprecated
public @NonNull SchemaPath toSchemaPath() {
- SchemaPath ret = SchemaPath.ROOT;
- final Iterator<QName> it = simplePathFromRoot();
- while (it.hasNext()) {
- ret = ret.createChild(it.next());
- }
- return ret;
+ return SchemaPath.create(simplePathFromRoot(), true);
}
/**
*/
@Deprecated
public @NonNull Iterator<QName> schemaPathIterator() {
- return Iterators.unmodifiableIterator(simplePathFromRoot());
+ return Iterators.unmodifiableIterator(simplePathFromRoot().iterator());
}
@Override
public String toString() {
- return MoreObjects.toStringHelper(this).add("stack", deque).toString();
+ return MoreObjects.toStringHelper(this).add("path", deque).toString();
}
private @NonNull GroupingEffectiveStatement pushGrouping(final @NonNull QName nodeIdentifier) {
- final EffectiveStatement<?, ?> parent = deque.peekFirst();
+ final EffectiveStatement<?, ?> parent = deque.peekLast();
return parent != null ? pushGrouping(parent, nodeIdentifier) : pushFirstGrouping(nodeIdentifier);
}
final GroupingEffectiveStatement ret = parent.streamEffectiveSubstatements(GroupingEffectiveStatement.class)
.filter(stmt -> nodeIdentifier.equals(stmt.argument()))
.findFirst()
- .orElseThrow(() -> new IllegalArgumentException("Grouping " + nodeIdentifier + " not present"));
- deque.push(ret);
+ .orElseThrow(() -> notPresent(parent, "Grouping", nodeIdentifier));
+ deque.addLast(ret);
++groupingDepth;
return ret;
}
}
private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(final @NonNull QName nodeIdentifier) {
- final EffectiveStatement<?, ?> parent = deque.peekFirst();
+ final EffectiveStatement<?, ?> parent = deque.peekLast();
return parent != null ? pushSchema(parent, nodeIdentifier) : pushFirstSchema(nodeIdentifier);
}
private @NonNull SchemaTreeEffectiveStatement<?> pushSchema(
final @NonNull SchemaTreeAwareEffectiveStatement<?, ?> parent, final @NonNull QName nodeIdentifier) {
- final SchemaTreeEffectiveStatement<?> ret = parent.findSchemaTreeNode(nodeIdentifier).orElseThrow(
- () -> new IllegalArgumentException("Schema tree child " + nodeIdentifier + " not present"));
- deque.push(ret);
+ final SchemaTreeEffectiveStatement<?> ret = parent.findSchemaTreeNode(nodeIdentifier)
+ .orElseThrow(() -> notPresent(parent, "Schema tree child ", nodeIdentifier));
+ deque.addLast(ret);
return ret;
}
}
private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull QName nodeIdentifier) {
- final EffectiveStatement<?, ?> parent = deque.peekFirst();
+ final EffectiveStatement<?, ?> parent = deque.peekLast();
return parent != null ? pushData(parent, nodeIdentifier) : pushFirstData(nodeIdentifier);
}
private @NonNull DataTreeEffectiveStatement<?> pushData(final @NonNull DataTreeAwareEffectiveStatement<?, ?> parent,
final @NonNull QName nodeIdentifier) {
- final DataTreeEffectiveStatement<?> ret = parent.findDataTreeNode(nodeIdentifier).orElseThrow(
- () -> new IllegalArgumentException("Data tree child " + nodeIdentifier + " not present"));
- deque.push(ret);
+ final DataTreeEffectiveStatement<?> ret = parent.findDataTreeNode(nodeIdentifier)
+ .orElseThrow(() -> notPresent(parent, "Data tree child", nodeIdentifier));
+ deque.addLast(ret);
clean = false;
return ret;
}
}
private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull QName nodeIdentifier) {
- final EffectiveStatement<?, ?> parent = deque.peekFirst();
+ final EffectiveStatement<?, ?> parent = deque.peekLast();
return parent != null ? pushTypedef(parent, nodeIdentifier) : pushFirstTypedef(nodeIdentifier);
}
private @NonNull TypedefEffectiveStatement pushTypedef(final @NonNull EffectiveStatement<?, ?> parent,
final @NonNull QName nodeIdentifier) {
- // TODO: 8.0.0: revisit this once we have TypedefNamespace working
- final TypedefEffectiveStatement ret = parent.streamEffectiveSubstatements(TypedefEffectiveStatement.class)
- .filter(stmt -> nodeIdentifier.equals(stmt.argument()))
- .findFirst()
- .orElseThrow(() -> new IllegalArgumentException("Grouping " + nodeIdentifier + " not present"));
- deque.push(ret);
+ final TypedefEffectiveStatement ret = parent.get(TypedefNamespace.class, nodeIdentifier)
+ .orElseThrow(() -> notPresent(parent, "Typedef", nodeIdentifier));
+ deque.addLast(ret);
return ret;
}
// Unified access to queue iteration for addressing purposes. Since we keep 'logical' steps as executed by user
// at this point, conversion to SchemaNodeIdentifier may be needed. We dispatch based on 'clean'.
- private Iterator<QName> simplePathFromRoot() {
- return clean ? iterateQNames() : reconstructQNames();
+ private Collection<QName> simplePathFromRoot() {
+ return clean ? qnames() : reconstructQNames();
}
- private Iterator<QName> iterateQNames() {
- return Iterators.transform(deque.descendingIterator(), stmt -> {
+ private Collection<QName> qnames() {
+ return Collections2.transform(deque, stmt -> {
final Object argument = stmt.argument();
verify(argument instanceof QName, "Unexpected statement %s", stmt);
return (QName) argument;
// So there are some data tree steps in the stack... we essentially need to convert a data tree item into a series
// of schema tree items. This means at least N searches, but after they are done, we get an opportunity to set the
// clean flag.
- private Iterator<QName> reconstructQNames() {
+ private Collection<QName> reconstructQNames() {
+ return reconstructSchemaInferenceStack().qnames();
+ }
+
+ private SchemaInferenceStack reconstructSchemaInferenceStack() {
// Let's walk all statements and decipher them into a temporary stack
final SchemaInferenceStack tmp = new SchemaInferenceStack(effectiveModel, deque.size());
- final Iterator<EffectiveStatement<?, ?>> it = deque.descendingIterator();
+ final Iterator<EffectiveStatement<?, ?>> it = deque.iterator();
while (it.hasNext()) {
final EffectiveStatement<?, ?> stmt = it.next();
// Order of checks is significant
}
// if the sizes match, we did not jump through hoops. let's remember that for future.
- clean = deque.size() == tmp.deque.size();
- return tmp.iterateQNames();
+ if (deque.size() == tmp.deque.size()) {
+ clean = true;
+ return this;
+ }
+ return tmp;
}
private void resolveChoiceSteps(final @NonNull QName nodeIdentifier) {
- final EffectiveStatement<?, ?> parent = deque.peekFirst();
+ final EffectiveStatement<?, ?> parent = deque.peekLast();
if (parent instanceof ChoiceEffectiveStatement) {
resolveChoiceSteps((ChoiceEffectiveStatement) parent, nodeIdentifier);
} else {
final CaseEffectiveStatement caze = (CaseEffectiveStatement) stmt;
final SchemaTreeEffectiveStatement<?> found = caze.findSchemaTreeNode(nodeIdentifier).orElse(null);
if (found instanceof ChoiceEffectiveStatement) {
- deque.push(caze);
- deque.push(found);
+ deque.addLast(caze);
+ deque.addLast(found);
return;
}
}
}
private void resolveDataTreeSteps(final @NonNull QName nodeIdentifier) {
- final EffectiveStatement<?, ?> parent = deque.peekFirst();
+ final EffectiveStatement<?, ?> parent = deque.peekLast();
if (parent != null) {
verify(parent instanceof SchemaTreeAwareEffectiveStatement, "Unexpected parent %s", parent);
resolveDataTreeSteps((SchemaTreeAwareEffectiveStatement<?, ?>) parent, nodeIdentifier);
final SchemaTreeEffectiveStatement<?> found = parent.findSchemaTreeNode(nodeIdentifier).orElse(null);
if (found instanceof DataTreeEffectiveStatement) {
// ... and it did, we are done
- deque.push(found);
+ deque.addLast(found);
return;
}
// Alright, so now it's down to filtering choice/case statements. For that we keep some globally-reused state
// and employ a recursive match.
- final Deque<EffectiveStatement<QName, ?>> match = new ArrayDeque<>();
+ final var match = new ArrayDeque<EffectiveStatement<QName, ?>>();
for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
if (stmt instanceof ChoiceEffectiveStatement
&& searchChoice(match, (ChoiceEffectiveStatement) stmt, nodeIdentifier)) {
- match.descendingIterator().forEachRemaining(deque::push);
+ deque.addAll(match);
return;
}
}
throw new VerifyException("Failed to resolve " + nodeIdentifier + " in " + parent);
}
- private static boolean searchCase(final @NonNull Deque<EffectiveStatement<QName, ?>> result,
+ private static boolean searchCase(final @NonNull ArrayDeque<EffectiveStatement<QName, ?>> result,
final @NonNull CaseEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
- result.push(parent);
+ result.addLast(parent);
for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
if (stmt instanceof DataTreeEffectiveStatement && nodeIdentifier.equals(stmt.argument())) {
- result.push((DataTreeEffectiveStatement<?>) stmt);
+ result.addLast((DataTreeEffectiveStatement<?>) stmt);
return true;
}
if (stmt instanceof ChoiceEffectiveStatement
return true;
}
}
- result.pop();
+ result.removeLast();
return false;
}
- private static boolean searchChoice(final @NonNull Deque<EffectiveStatement<QName, ?>> result,
+ private static boolean searchChoice(final @NonNull ArrayDeque<EffectiveStatement<QName, ?>> result,
final @NonNull ChoiceEffectiveStatement parent, final @NonNull QName nodeIdentifier) {
- result.push(parent);
+ result.addLast(parent);
for (EffectiveStatement<?, ?> stmt : parent.effectiveSubstatements()) {
if (stmt instanceof CaseEffectiveStatement
&& searchCase(result, (CaseEffectiveStatement) stmt, nodeIdentifier)) {
return true;
}
}
- result.pop();
+ result.removeLast();
return false;
}
}
return obj;
}
+
+ private static @NonNull IllegalArgumentException notPresent(final @NonNull EffectiveStatement<?, ?> parent,
+ final @NonNull String name, final QName nodeIdentifier) {
+ return new IllegalArgumentException(name + " " + nodeIdentifier + " not present in " + describeParent(parent));
+ }
+
+ private static @NonNull String describeParent(final @NonNull EffectiveStatement<?, ?> parent) {
+ // Add just enough information to be useful without being overly-verbose. Note we want to expose namespace
+ // information, so that we understand what revisions we are dealing with
+ if (parent instanceof SchemaTreeEffectiveStatement) {
+ return "schema parent " + parent.argument();
+ } else if (parent instanceof GroupingEffectiveStatement) {
+ return "grouping " + parent.argument();
+ } else if (parent instanceof ModuleEffectiveStatement) {
+ final var module = (ModuleEffectiveStatement) parent;
+ return "module " + module.argument().bindTo(module.localQNameModule());
+ } else {
+ // Shorthand for QNames, should provide enough context
+ final Object arg = parent.argument();
+ return "parent " + (arg instanceof QName ? arg : parent);
+ }
+ }
}