X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-parser-reactor%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fparser%2Fstmt%2Freactor%2FStatementContextBase.java;h=e51b39706cd78eb36987bab7032bd947cdfae6ba;hb=41ed305a7dce99e21a7b1066711347147dc067d8;hp=0d0a82fc2ea51885fcbcad116fd6cd7039c26748;hpb=3880c99eefb4d8104236fdd17da6d5014f61f63e;p=yangtools.git diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java index 0d0a82fc2e..e51b39706c 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java @@ -8,7 +8,6 @@ package org.opendaylight.yangtools.yang.parser.stmt.reactor; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; import static java.util.Objects.requireNonNull; @@ -28,8 +27,8 @@ import java.util.EnumMap; import java.util.EventListener; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Map.Entry; -import java.util.Objects; import java.util.Optional; import java.util.Set; import org.eclipse.jdt.annotation.NonNull; @@ -44,7 +43,7 @@ import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace; import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition; import org.opendaylight.yangtools.yang.model.api.meta.StatementSource; import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement; -import org.opendaylight.yangtools.yang.model.api.stmt.ConfigStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.ConfigEffectiveStatement; import org.opendaylight.yangtools.yang.model.api.stmt.DeviationStatement; import org.opendaylight.yangtools.yang.model.api.stmt.RefineStatement; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier; @@ -60,8 +59,10 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.MutableStatement; import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour; import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.Registry; import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceKeyCriterion; +import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceNotAvailableException; import org.opendaylight.yangtools.yang.parser.spi.meta.StatementNamespace; import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport; +import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport.CopyPolicy; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils; @@ -138,11 +139,13 @@ public abstract class StatementContextBase, E private Multimap phaseListeners = ImmutableMultimap.of(); private Multimap phaseMutation = ImmutableMultimap.of(); - private List> effective = ImmutableList.of(); + + // Note: this field is accessed either directly, or under substatementsInitialized == true + private List> effective = ImmutableList.of(); + private List> effectOfStatement = ImmutableList.of(); private @Nullable ModelProcessingPhase completedPhase; - private @Nullable D declaredInstance; private @Nullable E effectiveInstance; // Master flag controlling whether this context can yield an effective statement @@ -153,17 +156,26 @@ public abstract class StatementContextBase, E // Flag for use with AbstractResumedStatement. This is hiding in the alignment shadow created by above boolean private boolean fullyDefined; + // Flag for InferredStatementContext. This is hiding in the alignment shadow created by above boolean. + private boolean substatementsInitialized; + // Flags for use with SubstatementContext. These are hiding in the alignment shadow created by above boolean and // hence improve memory layout. private byte flags; // SchemaPath cache for use with SubstatementContext and InferredStatementContext. This hurts RootStatementContext // a bit in terms of size -- but those are only a few and SchemaPath is on its way out anyway. - private volatile SchemaPath schemaPath; + private SchemaPath schemaPath; + // Copy constructor used by subclasses to implement reparent() StatementContextBase(final StatementContextBase original) { this.copyHistory = original.copyHistory; this.definition = original.definition; + + this.isSupportedToBuildEffective = original.isSupportedToBuildEffective; + this.fullyDefined = original.fullyDefined; + this.completedPhase = original.completedPhase; + this.flags = original.flags; } StatementContextBase(final StatementDefinitionContext def) { @@ -252,8 +264,9 @@ public abstract class StatementContextBase, E return completedPhase; } - @Override - public void setCompletedPhase(final ModelProcessingPhase completedPhase) { + // FIXME: this should be propagated through a correct constructor + @Deprecated + final void setCompletedPhase(final ModelProcessingPhase completedPhase) { this.completedPhase = completedPhase; } @@ -308,11 +321,60 @@ public abstract class StatementContextBase, E return getStatementSourceReference().getStatementSource(); } + @Override + public final > Map getAllFromCurrentStmtCtxNamespace( + final Class type) { + return getLocalNamespace(type); + } + + @Override + public final > Map getAllFromNamespace(final Class type) { + return getNamespace(type); + } + + /** + * Associate a value with a key within a namespace. + * + * @param type Namespace type + * @param key Key + * @param value value + * @param namespace key type + * @param namespace value type + * @param namespace type + * @param key type + * @param value type + * @throws NamespaceNotAvailableException when the namespace is not available. + */ + @Override + public final > void addToNs( + final Class<@NonNull N> type, final T key, final U value) { + addToNamespace(type, key, value); + } + @Override public abstract Collection> mutableDeclaredSubstatements(); + /** + * Return a value associated with specified key within a namespace. + * + * @param type Namespace type + * @param key Key + * @param namespace key type + * @param namespace value type + * @param namespace type + * @param key type + * @return Value, or null if there is no element + * @throws NamespaceNotAvailableException when the namespace is not available. + */ @Override - public Collection> mutableEffectiveSubstatements() { + public final > V getFromNamespace( + final Class<@NonNull N> type, final T key) { + return getBehaviourRegistry().getNamespaceBehaviour(type).getFrom(this, key); + } + + @Override + public final Collection> mutableEffectiveSubstatements() { + ensureEffectiveSubstatements(); if (effective instanceof ImmutableCollection) { return effective; } @@ -321,12 +383,15 @@ public abstract class StatementContextBase, E } private void shrinkEffective() { + // Initialization guarded by all callers if (effective.isEmpty()) { effective = ImmutableList.of(); } } - public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef) { + // Note: has side-effect of ensureEffectiveSubstatements() + public final void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef) { + ensureEffectiveSubstatements(); if (effective.isEmpty()) { return; } @@ -354,17 +419,20 @@ public abstract class StatementContextBase, E * @param statementDef statement definition of the statement context to remove * @param statementArg statement argument of the statement context to remove */ - public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef, + public final void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef, final String statementArg) { if (statementArg == null) { + // Note: has side-effect of ensureEffectiveSubstatements() removeStatementFromEffectiveSubstatements(statementDef); + } else { + ensureEffectiveSubstatements(); } if (effective.isEmpty()) { return; } - final Iterator> iterator = effective.iterator(); + final Iterator> iterator = effective.iterator(); while (iterator.hasNext()) { final Mutable next = iterator.next(); if (statementDef.equals(next.getPublicDefinition()) && statementArg.equals(next.rawStatementArgument())) { @@ -397,9 +465,17 @@ public abstract class StatementContextBase, E * @throws NullPointerException * if statement parameter is null */ - public void addEffectiveSubstatement(final Mutable substatement) { + public final void addEffectiveSubstatement(final Mutable substatement) { + verifyStatement(substatement); + ensureEffectiveSubstatements(); beforeAddEffectiveStatement(1); - effective.add(substatement); + + final StatementContextBase stmt = (StatementContextBase) substatement; + final ModelProcessingPhase phase = completedPhase; + if (phase != null) { + ensureCompletedPhase(stmt, phase); + } + effective.add(stmt); } /** @@ -411,101 +487,170 @@ public abstract class StatementContextBase, E * @throws NullPointerException * if statement parameter is null */ - public void addEffectiveSubstatements(final Collection> statements) { - if (statements.isEmpty()) { - return; + public final void addEffectiveSubstatements(final Collection> statements) { + if (!statements.isEmpty()) { + statements.forEach(StatementContextBase::verifyStatement); + ensureEffectiveSubstatements(); + beforeAddEffectiveStatement(statements.size()); + doAddEffectiveSubstatements(statements); + } + } + + // exposed for InferredStatementContext, which we expect to initialize effective substatements + void ensureEffectiveSubstatements() { + // No-op for everything except InferredStatementContext + } + + // Exposed for InferredStatementContextr only, others do not need initialization + Iterable> effectiveChildrenToComplete() { + return effective; + } + + // exposed for InferredStatementContext only + final void addInitialEffectiveSubstatements(final Collection> statements) { + verify(!substatementsInitialized, "Attempted to re-initialized statement {} with {}", this, statements); + substatementsInitialized = true; + + if (!statements.isEmpty()) { + statements.forEach(StatementContextBase::verifyStatement); + beforeAddEffectiveStatementUnsafe(statements.size()); + doAddEffectiveSubstatements(statements); } + } - statements.forEach(Objects::requireNonNull); - beforeAddEffectiveStatement(statements.size()); - effective.addAll(statements); + private void doAddEffectiveSubstatements(final Collection> statements) { + final Collection> casted = + (Collection>) statements; + final ModelProcessingPhase phase = completedPhase; + if (phase != null) { + for (StatementContextBase stmt : casted) { + ensureCompletedPhase(stmt, phase); + } + } + + // Initialization guarded by all callers + effective.addAll(casted); + } + + // Make sure target statement has transitioned at least to specified phase. This method is just before we take + // allow a statement to become our substatement. This is needed to ensure that every statement tree does not contain + // any statements which did not complete the same phase as the root statement. + private static void ensureCompletedPhase(final StatementContextBase stmt, + final ModelProcessingPhase phase) { + verify(stmt.tryToCompletePhase(phase), "Statement %s cannot complete phase %s", stmt, phase); + } + + private static void verifyStatement(final Mutable stmt) { + verify(stmt instanceof StatementContextBase, "Unexpected statement %s", stmt); } private void beforeAddEffectiveStatement(final int toAdd) { + // We cannot allow statement to be further mutated + verify(completedPhase != ModelProcessingPhase.EFFECTIVE_MODEL, "Cannot modify finished statement at %s", + getStatementSourceReference()); + beforeAddEffectiveStatementUnsafe(toAdd); + } + + private void beforeAddEffectiveStatementUnsafe(final int toAdd) { final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase(); checkState(inProgressPhase == ModelProcessingPhase.FULL_DECLARATION || inProgressPhase == ModelProcessingPhase.EFFECTIVE_MODEL, "Effective statement cannot be added in declared phase at: %s", getStatementSourceReference()); + // Initialization guarded by all callers if (effective.isEmpty()) { effective = new ArrayList<>(toAdd); } } - // Exists only due to memory optimization + // These two exists only due to memory optimization, should live in AbstractResumedStatement final boolean fullyDefined() { return fullyDefined; } - // Exists only due to memory optimization, should live in AbstractResumedStatement final void setFullyDefined() { fullyDefined = true; } - @Override - public D buildDeclared() { - checkArgument(completedPhase == ModelProcessingPhase.FULL_DECLARATION - || completedPhase == ModelProcessingPhase.EFFECTIVE_MODEL); - if (declaredInstance == null) { - declaredInstance = definition.getFactory().createDeclared(this); - } - return declaredInstance; + // These two exist only due to memory optimization, should live in InferredStatementContext + final boolean substatementsInitialized() { + return substatementsInitialized; + } + + final void setSubstatementsInitialized() { + substatementsInitialized = true; } @Override public E buildEffective() { - if (effectiveInstance == null) { - effectiveInstance = definition.getFactory().createEffective(this); - } - return effectiveInstance; + final E existing; + return (existing = effectiveInstance) != null ? existing : loadEffective(); + } + + private E loadEffective() { + return effectiveInstance = definition.getFactory().createEffective(this); } /** - * tries to execute current {@link ModelProcessingPhase} of source parsing. + * Try to execute current {@link ModelProcessingPhase} of source parsing. If the phase has already been executed, + * this method does nothing. * - * @param phase - * to be executed (completed) - * @return if phase was successfully completed - * @throws SourceException - * when an error occurred in source parsing + * @param phase to be executed (completed) + * @return true if phase was successfully completed + * @throws SourceException when an error occurred in source parsing */ - boolean tryToCompletePhase(final ModelProcessingPhase phase) { - - boolean finished = true; - final Collection openMutations = phaseMutation.get(phase); - if (!openMutations.isEmpty()) { - final Iterator it = openMutations.iterator(); - while (it.hasNext()) { - final ContextMutation current = it.next(); - if (current.isFinished()) { - it.remove(); - } else { - finished = false; - } - } + final boolean tryToCompletePhase(final ModelProcessingPhase phase) { + return phase.isCompletedBy(completedPhase) || doTryToCompletePhase(phase); + } - if (openMutations.isEmpty()) { - phaseMutation.removeAll(phase); - if (phaseMutation.isEmpty()) { - phaseMutation = ImmutableMultimap.of(); - } - } + private boolean doTryToCompletePhase(final ModelProcessingPhase phase) { + final boolean finished = phaseMutation.isEmpty() ? true : runMutations(phase); + if (completeChildren(phase) && finished) { + onPhaseCompleted(phase); + return true; } + return false; + } + private boolean completeChildren(final ModelProcessingPhase phase) { + boolean finished = true; for (final StatementContextBase child : mutableDeclaredSubstatements()) { finished &= child.tryToCompletePhase(phase); } - for (final Mutable child : effective) { - if (child instanceof StatementContextBase) { - finished &= ((StatementContextBase) child).tryToCompletePhase(phase); + for (final StatementContextBase child : effectiveChildrenToComplete()) { + finished &= child.tryToCompletePhase(phase); + } + return finished; + } + + private boolean runMutations(final ModelProcessingPhase phase) { + final Collection openMutations = phaseMutation.get(phase); + return openMutations.isEmpty() ? true : runMutations(phase, openMutations); + } + + private boolean runMutations(final ModelProcessingPhase phase, final Collection openMutations) { + boolean finished = true; + final Iterator it = openMutations.iterator(); + while (it.hasNext()) { + final ContextMutation current = it.next(); + if (current.isFinished()) { + it.remove(); + } else { + finished = false; } } - if (finished) { - onPhaseCompleted(phase); - return true; + if (openMutations.isEmpty()) { + phaseMutation.removeAll(phase); + cleanupPhaseMutation(); + } + return finished; + } + + private void cleanupPhaseMutation() { + if (phaseMutation.isEmpty()) { + phaseMutation = ImmutableMultimap.of(); } - return false; } /** @@ -520,10 +665,12 @@ public abstract class StatementContextBase, E completedPhase = phase; final Collection listeners = phaseListeners.get(phase); - if (listeners.isEmpty()) { - return; + if (!listeners.isEmpty()) { + runPhaseListeners(phase, listeners); } + } + private void runPhaseListeners(final ModelProcessingPhase phase, final Collection listeners) { final Iterator listener = listeners.iterator(); while (listener.hasNext()) { final OnPhaseFinished next = listener.next(); @@ -661,8 +808,8 @@ public abstract class StatementContextBase, E * @throws NullPointerException if any of the arguments is null */ void addPhaseCompletedListener(final ModelProcessingPhase phase, final OnPhaseFinished listener) { - checkNotNull(phase, "Statement context processing phase cannot be null at: %s", getStatementSourceReference()); - checkNotNull(listener, "Statement context phase listener cannot be null at: %s", getStatementSourceReference()); + requireNonNull(phase, "Statement context processing phase cannot be null"); + requireNonNull(listener, "Statement context phase listener cannot be null"); ModelProcessingPhase finishedPhase = completedPhase; while (finishedPhase != null) { @@ -682,10 +829,9 @@ public abstract class StatementContextBase, E /** * Adds a {@link ContextMutation} to a {@link ModelProcessingPhase}. * - * @throws IllegalStateException - * when the mutation was registered after phase was completed + * @throws IllegalStateException when the mutation was registered after phase was completed */ - void addMutation(final ModelProcessingPhase phase, final ContextMutation mutation) { + final void addMutation(final ModelProcessingPhase phase, final ContextMutation mutation) { ModelProcessingPhase finishedPhase = completedPhase; while (finishedPhase != null) { checkState(!phase.equals(finishedPhase), "Mutation registered after phase was completed at: %s", @@ -699,17 +845,55 @@ public abstract class StatementContextBase, E phaseMutation.put(phase, mutation); } + final void removeMutation(final ModelProcessingPhase phase, final ContextMutation mutation) { + if (!phaseMutation.isEmpty()) { + phaseMutation.remove(phase, mutation); + cleanupPhaseMutation(); + } + } + @Override - public > void addContext(final Class namespace, + public > void addContext(final Class<@NonNull N> namespace, final KT key,final StmtContext stmt) { addContextToNamespace(namespace, key, stmt); } + @Override + public Optional> copyAsChildOf(final Mutable parent, final CopyType type, + final QNameModule targetModule) { + checkEffectiveModelCompleted(this); + + final StatementSupport support = definition.support(); + final CopyPolicy policy = support.applyCopyPolicy(this, parent, type, targetModule); + switch (policy) { + case CONTEXT_INDEPENDENT: + if (hasEmptySubstatements()) { + // This statement is context-independent and has no substatements -- hence it can be freely shared. + return Optional.of(this); + } + // FIXME: YANGTOOLS-694: filter out all context-independent substatements, eliminate fall-through + // fall through + case DECLARED_COPY: + // FIXME: YANGTOOLS-694: this is still to eager, we really want to copy as a lazily-instantiated + // context, so that we can support building an effective statement without copying + // anything -- we will typically end up not being inferred against. In that case, + // this slim context should end up dealing with differences at buildContext() + // time. This is a YANGTOOLS-1067 prerequisite (which will deal with what can and + // cannot be shared across instances). + return Optional.of(parent.childCopyOf(this, type, targetModule)); + case IGNORE: + return Optional.empty(); + case REJECT: + throw new IllegalStateException("Statement " + support.getPublicView() + " should never be copied"); + default: + throw new IllegalStateException("Unhandled policy " + policy); + } + } + @Override public final Mutable childCopyOf(final StmtContext stmt, final CopyType type, final QNameModule targetModule) { - checkState(stmt.getCompletedPhase() == ModelProcessingPhase.EFFECTIVE_MODEL, - "Attempted to copy statement %s which has completed phase %s", stmt, stmt.getCompletedPhase()); + checkEffectiveModelCompleted(stmt); checkArgument(stmt instanceof StatementContextBase, "Unsupported statement %s", stmt); return childCopyOf((StatementContextBase) stmt, type, targetModule); } @@ -751,6 +935,12 @@ public abstract class StatementContextBase, E return result; } + private static void checkEffectiveModelCompleted(final StmtContext stmt) { + final ModelProcessingPhase phase = stmt.getCompletedPhase(); + checkState(phase == ModelProcessingPhase.EFFECTIVE_MODEL, + "Attempted to copy statement %s which has completed phase %s", stmt, phase); + } + @Beta public final boolean hasImplicitParentSupport() { return definition.getFactory() instanceof ImplicitParentAwareStatementSupport; @@ -777,6 +967,21 @@ public abstract class StatementContextBase, E abstract StatementContextBase reparent(StatementContextBase newParent); + /** + * Indicate that the set of substatements is empty. This is a preferred shortcut to substatement stream filtering. + * + * @return True if {@link #allSubstatements()} and {@link #allSubstatementsStream()} would return an empty stream. + */ + abstract boolean hasEmptySubstatements(); + + // Dual use method: AbstractResumedStatement does not use 'initialized' and InferredStatementContext ensures + // initialization. + // FIXME: 7.0.0: I think this warrants a separate subclasses, as InferredStatementContext wants to manage these + // itself. Before we do that, though, we need to analyze size impacts + final boolean hasEmptyEffectiveSubstatements() { + return effective.isEmpty(); + } + /** * Config statements are not all that common which means we are performing a recursive search towards the root * every time {@link #isConfiguration()} is invoked. This is quite expensive because it causes a linear search @@ -800,11 +1005,10 @@ public abstract class StatementContextBase, E return true; } - final StmtContext configStatement = StmtContextUtils.findFirstSubstatement(this, - ConfigStatement.class); final boolean isConfig; - if (configStatement != null) { - isConfig = configStatement.coerceStatementArgument(); + final Optional optConfig = findSubstatementArgument(ConfigEffectiveStatement.class); + if (optConfig.isPresent()) { + isConfig = optConfig.orElseThrow(); if (isConfig) { // Validity check: if parent is config=false this cannot be a config=true InferenceException.throwIf(!parent.isConfiguration(), getStatementSourceReference(), @@ -836,7 +1040,7 @@ public abstract class StatementContextBase, E if (fl != 0) { return fl == SET_IGNORE_CONFIG; } - if (definition.isIgnoringConfig() || parent.isIgnoringConfig()) { + if (definition.support().isIgnoringConfig() || parent.isIgnoringConfig()) { flags |= SET_IGNORE_CONFIG; return true; } @@ -857,7 +1061,7 @@ public abstract class StatementContextBase, E if (fl != 0) { return fl == SET_IGNORE_IF_FEATURE; } - if (definition.isIgnoringIfFeatures() || parent.isIgnoringIfFeatures()) { + if (definition.support().isIgnoringIfFeatures() || parent.isIgnoringIfFeatures()) { flags |= SET_IGNORE_IF_FEATURE; return true; } @@ -866,22 +1070,16 @@ public abstract class StatementContextBase, E return false; } - // Exists only to support SubstatementContext/InferredStatementContext + // Exists only to support {SubstatementContext,InferredStatementContext}.getSchemaPath() + @Deprecated final @NonNull Optional substatementGetSchemaPath() { - SchemaPath local = schemaPath; - if (local == null) { - synchronized (this) { - local = schemaPath; - if (local == null) { - local = createSchemaPath(coerceParentContext()); - schemaPath = local; - } - } + if (schemaPath == null) { + schemaPath = createSchemaPath(coerceParentContext()); } - - return Optional.ofNullable(local); + return Optional.ofNullable(schemaPath); } + @Deprecated private SchemaPath createSchemaPath(final Mutable parent) { final Optional maybeParentPath = parent.getSchemaPath(); verify(maybeParentPath.isPresent(), "Parent %s does not have a SchemaPath", parent); @@ -893,7 +1091,7 @@ public abstract class StatementContextBase, E final Object argument = getStatementArgument(); if (argument instanceof QName) { final QName qname = (QName) argument; - if (StmtContextUtils.producesDeclared(this, UsesStatement.class)) { + if (producesDeclared(UsesStatement.class)) { return maybeParentPath.orElse(null); } @@ -901,16 +1099,15 @@ public abstract class StatementContextBase, E } if (argument instanceof String) { // FIXME: This may yield illegal argument exceptions - final Optional> originalCtx = getOriginalCtx(); + final Optional> originalCtx = getOriginalCtx(); final QName qname = StmtContextUtils.qnameFromArgument(originalCtx.orElse(this), (String) argument); return parentPath.createChild(qname); } if (argument instanceof SchemaNodeIdentifier - && (StmtContextUtils.producesDeclared(this, AugmentStatement.class) - || StmtContextUtils.producesDeclared(this, RefineStatement.class) - || StmtContextUtils.producesDeclared(this, DeviationStatement.class))) { + && (producesDeclared(AugmentStatement.class) || producesDeclared(RefineStatement.class) + || producesDeclared(DeviationStatement.class))) { - return parentPath.createChild(((SchemaNodeIdentifier) argument).getPathFromRoot()); + return parentPath.createChild(((SchemaNodeIdentifier) argument).getNodeIdentifiers()); } // FIXME: this does not look right