Expand reactor documentation a bit
[yangtools.git] / yang / yang-parser-reactor / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / StatementContextBase.java
index 7dc216dcd8540b0aa980e3ac62f7a10f1682f9a7..abf79fde20240d82562e8546a13e4257887dacef 100644 (file)
@@ -7,9 +7,14 @@
  */
 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 java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMultimap;
@@ -22,13 +27,13 @@ import java.util.Collections;
 import java.util.EnumMap;
 import java.util.EventListener;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import javax.annotation.Nonnull;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.yangtools.util.OptionalBoolean;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
@@ -37,8 +42,11 @@ import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 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.ConfigStatement;
 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyHistory;
 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ImplicitParentAwareStatementSupport;
+import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
@@ -59,6 +67,13 @@ import org.opendaylight.yangtools.yang.parser.stmt.reactor.NamespaceBehaviourWit
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * Core reactor statement implementation of {@link Mutable}.
+ *
+ * @param <A> Argument type
+ * @param <D> Declared Statement representation
+ * @param <E> Effective Statement representation
+ */
 public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
         extends NamespaceStorageSupport implements Mutable<A, D, E>, ResumedStatement {
     /**
@@ -91,46 +106,87 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
     private static final Logger LOG = LoggerFactory.getLogger(StatementContextBase.class);
 
-    private final StatementDefinitionContext<A, D, E> definition;
-    private final StatementSourceReference statementDeclSource;
+    // Flag bit assignments
+    private static final int IS_SUPPORTED_BY_FEATURES    = 0x01;
+    private static final int HAVE_SUPPORTED_BY_FEATURES  = 0x02;
+    private static final int IS_IGNORE_IF_FEATURE        = 0x04;
+    private static final int HAVE_IGNORE_IF_FEATURE      = 0x08;
+    // Note: these four are related
+    private static final int IS_IGNORE_CONFIG            = 0x10;
+    private static final int HAVE_IGNORE_CONFIG          = 0x20;
+    private static final int IS_CONFIGURATION            = 0x40;
+    private static final int HAVE_CONFIGURATION          = 0x80;
+
+    // Have-and-set flag constants, also used as masks
+    private static final int SET_SUPPORTED_BY_FEATURES = HAVE_SUPPORTED_BY_FEATURES | IS_SUPPORTED_BY_FEATURES;
+    private static final int SET_CONFIGURATION = HAVE_CONFIGURATION | IS_CONFIGURATION;
+    // Note: implies SET_CONFIGURATION, allowing fewer bit operations to be performed
+    private static final int SET_IGNORE_CONFIG = HAVE_IGNORE_CONFIG | IS_IGNORE_CONFIG | SET_CONFIGURATION;
+    private static final int SET_IGNORE_IF_FEATURE = HAVE_IGNORE_IF_FEATURE | IS_IGNORE_IF_FEATURE;
+
+    private final @NonNull StatementDefinitionContext<A, D, E> definition;
+    private final @NonNull StatementSourceReference statementDeclSource;
     private final StmtContext<?, ?, ?> originalCtx;
+    private final StmtContext<?, ?, ?> prevCopyCtx;
     private final CopyHistory copyHistory;
     private final String rawArgument;
 
     private Multimap<ModelProcessingPhase, OnPhaseFinished> phaseListeners = ImmutableMultimap.of();
     private Multimap<ModelProcessingPhase, ContextMutation> phaseMutation = ImmutableMultimap.of();
-    private Collection<Mutable<?, ?, ?>> effective = ImmutableList.of();
-    private Collection<StmtContext<?, ?, ?>> effectOfStatement = ImmutableList.of();
+    private List<Mutable<?, ?, ?>> effective = ImmutableList.of();
+    private List<StmtContext<?, ?, ?>> effectOfStatement = ImmutableList.of();
     private StatementMap substatements = StatementMap.empty();
 
-    private boolean isSupportedToBuildEffective = true;
     private @Nullable ModelProcessingPhase completedPhase;
     private @Nullable D declaredInstance;
     private @Nullable E effectiveInstance;
 
-    // BooleanFields value
-    private byte supportedByFeatures;
-
+    // Common state bits
+    private boolean isSupportedToBuildEffective = true;
     private boolean fullyDefined;
 
+    // Flags for use with SubstatementContext. These are hiding in the alignment shadow created by above booleans and
+    // hence improve memory layout.
+    private byte flags;
+
     StatementContextBase(final StatementDefinitionContext<A, D, E> def, final StatementSourceReference ref,
             final String rawArgument) {
-        this.definition = Preconditions.checkNotNull(def);
-        this.statementDeclSource = Preconditions.checkNotNull(ref);
+        this.definition = requireNonNull(def);
+        this.statementDeclSource = requireNonNull(ref);
         this.rawArgument = def.internArgument(rawArgument);
         this.copyHistory = CopyHistory.original();
         this.originalCtx = null;
+        this.prevCopyCtx = null;
+    }
+
+    StatementContextBase(final StatementDefinitionContext<A, D, E> def, final StatementSourceReference ref,
+        final String rawArgument, final CopyType copyType) {
+        this.definition = requireNonNull(def);
+        this.statementDeclSource = requireNonNull(ref);
+        this.rawArgument = rawArgument;
+        this.copyHistory = CopyHistory.of(copyType, CopyHistory.original());
+        this.originalCtx = null;
+        this.prevCopyCtx = null;
     }
 
     StatementContextBase(final StatementContextBase<A, D, E> original, final CopyType copyType) {
-        this.definition = Preconditions.checkNotNull(original.definition,
-                "Statement context definition cannot be null copying from: %s", original.getStatementSourceReference());
-        this.statementDeclSource = Preconditions.checkNotNull(original.statementDeclSource,
-                "Statement context statementDeclSource cannot be null copying from: %s",
-                original.getStatementSourceReference());
+        this.definition = original.definition;
+        this.statementDeclSource = original.statementDeclSource;
         this.rawArgument = original.rawArgument;
         this.copyHistory = CopyHistory.of(copyType, original.getCopyHistory());
         this.originalCtx = original.getOriginalCtx().orElse(original);
+        this.prevCopyCtx = original;
+    }
+
+    StatementContextBase(final StatementContextBase<A, D, E> original) {
+        this.definition = original.definition;
+        this.statementDeclSource = original.statementDeclSource;
+        this.rawArgument = original.rawArgument;
+        this.copyHistory = original.getCopyHistory();
+        this.originalCtx = original.getOriginalCtx().orElse(original);
+        this.prevCopyCtx = original;
+        this.substatements = original.substatements;
+        this.effective = original.effective;
     }
 
     @Override
@@ -160,46 +216,35 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
     @Override
     public boolean isSupportedByFeatures() {
-        if (OptionalBoolean.isPresent(supportedByFeatures)) {
-            return OptionalBoolean.get(supportedByFeatures);
+        final int fl = flags & SET_SUPPORTED_BY_FEATURES;
+        if (fl != 0) {
+            return fl == SET_SUPPORTED_BY_FEATURES;
         }
-
         if (isIgnoringIfFeatures()) {
-            supportedByFeatures = OptionalBoolean.of(true);
+            flags |= SET_SUPPORTED_BY_FEATURES;
             return true;
         }
 
-        final boolean isParentSupported = isParentSupportedByFeatures();
         /*
-         * If parent is not supported, then this context is also not supported.
-         * So we do not need to check if-features statements of this context and
-         * we can return false immediately.
+         * If parent is supported, we need to check if-features statements of this context.
          */
-        if (!isParentSupported) {
-            supportedByFeatures = OptionalBoolean.of(false);
-            return false;
+        if (isParentSupportedByFeatures()) {
+            // If the set of supported features has not been provided, all features are supported by default.
+            final Set<QName> supportedFeatures = getFromNamespace(SupportedFeaturesNamespace.class,
+                    SupportedFeatures.SUPPORTED_FEATURES);
+            if (supportedFeatures == null || StmtContextUtils.checkFeatureSupport(this, supportedFeatures)) {
+                flags |= SET_SUPPORTED_BY_FEATURES;
+                return true;
+            }
         }
 
-        /*
-         * If parent is supported, we need to check if-features statements of
-         * this context.
-         */
-        // If the set of supported features has not been provided, all features are supported by default.
-        final Set<QName> supportedFeatures = getFromNamespace(SupportedFeaturesNamespace.class,
-                SupportedFeatures.SUPPORTED_FEATURES);
-        final boolean ret = supportedFeatures == null ? true
-                : StmtContextUtils.checkFeatureSupport(this, supportedFeatures);
-
-        supportedByFeatures = OptionalBoolean.of(ret);
-        return ret;
+        // Either parent is not supported or this statement is not supported
+        flags |= HAVE_SUPPORTED_BY_FEATURES;
+        return false;
     }
 
     protected abstract boolean isParentSupportedByFeatures();
 
-    protected abstract boolean isIgnoringIfFeatures();
-
-    protected abstract boolean isIgnoringConfig();
-
     @Override
     public boolean isSupportedToBuildEffective() {
         return isSupportedToBuildEffective;
@@ -220,6 +265,11 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return Optional.ofNullable(originalCtx);
     }
 
+    @Override
+    public Optional<? extends StmtContext<?, ?, ?>> getPreviousCopyCtx() {
+        return Optional.ofNullable(prevCopyCtx);
+    }
+
     @Override
     public ModelProcessingPhase getCompletedPhase() {
         return completedPhase;
@@ -238,27 +288,14 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
      *
      * @return root context of statement
      */
-    @Nonnull
     @Override
     public abstract RootStatementContext<?, ?, ?> getRoot();
 
-    /**
-     * Returns the origin of the statement.
-     *
-     * @return origin of statement
-     */
-    @Nonnull
     @Override
     public StatementSource getStatementSource() {
         return statementDeclSource.getStatementSource();
     }
 
-    /**
-     * Returns a reference to statement source.
-     *
-     * @return reference of statement source
-     */
-    @Nonnull
     @Override
     public StatementSourceReference getStatementSourceReference() {
         return statementDeclSource;
@@ -269,13 +306,11 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return rawArgument;
     }
 
-    @Nonnull
     @Override
     public Collection<? extends StmtContext<?, ?, ?>> declaredSubstatements() {
         return substatements.values();
     }
 
-    @Nonnull
     @Override
     public Collection<? extends Mutable<?, ?, ?>> mutableDeclaredSubstatements() {
         return substatements.values();
@@ -286,7 +321,6 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return mutableEffectiveSubstatements();
     }
 
-    @Nonnull
     @Override
     public Collection<? extends Mutable<?, ?, ?>> mutableEffectiveSubstatements() {
         if (effective instanceof ImmutableCollection) {
@@ -296,6 +330,15 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return Collections.unmodifiableCollection(effective);
     }
 
+    /**
+     * Remove a set of statements from effective statements.
+     *
+     * @param statements statements to be removed
+     * @deprecated This method was used by EffectiveStatementBase to restore proper order of effects of uses statements.
+     *             It is no longer used in that capacity and slated for removal.
+     */
+    // FIXME: 5.0.0: remove this method
+    @Deprecated
     public void removeStatementsFromEffectiveSubstatements(
             final Collection<? extends StmtContext<?, ?, ?>> statements) {
         if (!effective.isEmpty()) {
@@ -387,14 +430,14 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
             return;
         }
 
-        statements.forEach(Preconditions::checkNotNull);
+        statements.forEach(Objects::requireNonNull);
         beforeAddEffectiveStatement(statements.size());
         effective.addAll(statements);
     }
 
     private void beforeAddEffectiveStatement(final int toAdd) {
         final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase();
-        Preconditions.checkState(inProgressPhase == ModelProcessingPhase.FULL_DECLARATION
+        checkState(inProgressPhase == ModelProcessingPhase.FULL_DECLARATION
                 || inProgressPhase == ModelProcessingPhase.EFFECTIVE_MODEL,
                 "Effective statement cannot be added in declared phase at: %s", getStatementSourceReference());
 
@@ -421,7 +464,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
                     final StatementDefinitionContext<X, Y, Z> def, final StatementSourceReference ref,
                     final String argument) {
         final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase();
-        Preconditions.checkState(inProgressPhase != ModelProcessingPhase.EFFECTIVE_MODEL,
+        checkState(inProgressPhase != ModelProcessingPhase.EFFECTIVE_MODEL,
                 "Declared statement cannot be added in effective phase at: %s", getStatementSourceReference());
 
         final Optional<StatementSupport<?, ?, ?>> implicitParent = definition.getImplicitParentFor(def.getPublicView());
@@ -466,7 +509,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     final void walkChildren(final ModelProcessingPhase phase) {
-        Preconditions.checkState(fullyDefined);
+        checkState(fullyDefined);
         substatements.values().forEach(stmt -> {
             stmt.walkChildren(phase);
             stmt.endDeclared(phase);
@@ -475,7 +518,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
     @Override
     public D buildDeclared() {
-        Preconditions.checkArgument(completedPhase == ModelProcessingPhase.FULL_DECLARATION
+        checkArgument(completedPhase == ModelProcessingPhase.FULL_DECLARATION
                 || completedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
         if (declaredInstance == null) {
             declaredInstance = definition().getFactory().createDeclared(this);
@@ -583,7 +626,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
      *
      * @return statement definition
      */
-    protected final StatementDefinitionContext<A, D, E> definition() {
+    protected final @NonNull StatementDefinitionContext<A, D, E> definition() {
         return definition;
     }
 
@@ -644,8 +687,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     final <K, V, N extends IdentifierNamespace<K, V>> void selectMatch(final Class<N> type,
             final NamespaceKeyCriterion<K> criterion, final OnNamespaceItemAdded listener) {
         final Optional<Entry<K, V>> optMatch = getFromNamespace(type, criterion);
-        Preconditions.checkState(optMatch.isPresent(),
-            "Failed to find a match for criterion %s in namespace %s node %s", criterion, type, this);
+        checkState(optMatch.isPresent(), "Failed to find a match for criterion %s in namespace %s node %s", criterion,
+            type, this);
         final Entry<K, V> match = optMatch.get();
         listener.namespaceItemAdded(StatementContextBase.this, type, match.getKey(), match.getValue());
     }
@@ -663,16 +706,12 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     private <K, V, N extends IdentifierNamespace<K, V>> NamespaceBehaviourWithListeners<K, V, N> getBehaviour(
             final Class<N> type) {
         final NamespaceBehaviour<K, V, N> behaviour = getBehaviourRegistry().getNamespaceBehaviour(type);
-        Preconditions.checkArgument(behaviour instanceof NamespaceBehaviourWithListeners,
-            "Namespace %s does not support listeners", type);
+        checkArgument(behaviour instanceof NamespaceBehaviourWithListeners, "Namespace %s does not support listeners",
+            type);
 
         return (NamespaceBehaviourWithListeners<K, V, N>) behaviour;
     }
 
-    /**
-     * See {@link StatementSupport#getPublicView()}.
-     */
-    @Nonnull
     @Override
     public StatementDefinition getPublicDefinition() {
         return definition().getPublicView();
@@ -696,10 +735,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
      * @throws NullPointerException if any of the arguments is null
      */
     void addPhaseCompletedListener(final ModelProcessingPhase phase, final OnPhaseFinished listener) {
-        Preconditions.checkNotNull(phase, "Statement context processing phase cannot be null at: %s",
-                getStatementSourceReference());
-        Preconditions.checkNotNull(listener, "Statement context phase listener cannot be null at: %s",
-                getStatementSourceReference());
+        checkNotNull(phase, "Statement context processing phase cannot be null at: %s", getStatementSourceReference());
+        checkNotNull(listener, "Statement context phase listener cannot be null at: %s", getStatementSourceReference());
 
         ModelProcessingPhase finishedPhase = completedPhase;
         while (finishedPhase != null) {
@@ -725,8 +762,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     void addMutation(final ModelProcessingPhase phase, final ContextMutation mutation) {
         ModelProcessingPhase finishedPhase = completedPhase;
         while (finishedPhase != null) {
-            Preconditions.checkState(!phase.equals(finishedPhase),
-                "Mutation registered after phase was completed at: %s", getStatementSourceReference());
+            checkState(!phase.equals(finishedPhase), "Mutation registered after phase was completed at: %s",
+                getStatementSourceReference());
             finishedPhase = finishedPhase.getPreviousPhase();
         }
 
@@ -745,21 +782,48 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     @Override
     public <X, Y extends DeclaredStatement<X>, Z extends EffectiveStatement<X, Y>> Mutable<X, Y, Z> childCopyOf(
             final StmtContext<X, Y, Z> stmt, final CopyType type, final QNameModule targetModule) {
-        Preconditions.checkState(stmt.getCompletedPhase() == ModelProcessingPhase.EFFECTIVE_MODEL,
+        checkState(stmt.getCompletedPhase() == ModelProcessingPhase.EFFECTIVE_MODEL,
                 "Attempted to copy statement %s which has completed phase %s", stmt, stmt.getCompletedPhase());
 
-        Preconditions.checkArgument(stmt instanceof SubstatementContext, "Unsupported statement %s", stmt);
+        checkArgument(stmt instanceof SubstatementContext, "Unsupported statement %s", stmt);
 
         final SubstatementContext<X, Y, Z> original = (SubstatementContext<X, Y, Z>)stmt;
-        final SubstatementContext<X, Y, Z> copy = new SubstatementContext<>(original, this, type, targetModule);
+        final Optional<StatementSupport<?, ?, ?>> implicitParent = definition.getImplicitParentFor(
+            original.getPublicDefinition());
+
+        final SubstatementContext<X, Y, Z> result;
+        final SubstatementContext<X, Y, Z> copy;
+
+        if (implicitParent.isPresent()) {
+            final StatementDefinitionContext<?, ?, ?> def = new StatementDefinitionContext<>(implicitParent.get());
+            result = new SubstatementContext(this, def, original.getSourceReference(),
+                original.rawStatementArgument(), original.getStatementArgument(), type);
+
+            final CopyType childCopyType;
+            switch (type) {
+                case ADDED_BY_AUGMENTATION:
+                    childCopyType = CopyType.ORIGINAL;
+                    break;
+                case ADDED_BY_USES_AUGMENTATION:
+                    childCopyType = CopyType.ADDED_BY_USES;
+                    break;
+                case ADDED_BY_USES:
+                case ORIGINAL:
+                default:
+                    childCopyType = type;
+            }
+
+            copy = new SubstatementContext<>(original, result, childCopyType, targetModule);
+            result.addEffectiveSubstatement(copy);
+        } else {
+            result = copy = new SubstatementContext<>(original, this, type, targetModule);
+        }
 
         original.definition().onStatementAdded(copy);
         original.copyTo(copy, type, targetModule);
-
-        return copy;
+        return result;
     }
 
-
     @Override
     public @NonNull StatementDefinition getDefinition() {
         return getPublicDefinition();
@@ -775,6 +839,119 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return fullyDefined;
     }
 
+    @Beta
+    public final boolean hasImplicitParentSupport() {
+        return definition.getFactory() instanceof ImplicitParentAwareStatementSupport;
+    }
+
+    @Beta
+    public final StatementContextBase<?, ?, ?> wrapWithImplicit(final StatementContextBase<?, ?, ?> original) {
+        final Optional<StatementSupport<?, ?, ?>> optImplicit = definition.getImplicitParentFor(
+            original.getPublicDefinition());
+        if (!optImplicit.isPresent()) {
+            return original;
+        }
+
+        final StatementDefinitionContext<?, ?, ?> def = new StatementDefinitionContext<>(optImplicit.get());
+        final CopyType type = original.getCopyHistory().getLastOperation();
+        final SubstatementContext<?, ?, ?> result = new SubstatementContext(original.getParentContext(), def,
+            original.getStatementSourceReference(), original.rawStatementArgument(), original.getStatementArgument(),
+            type);
+
+        result.addEffectiveSubstatement(new SubstatementContext<>(original, result));
+        result.setCompletedPhase(original.getCompletedPhase());
+        return result;
+    }
+
+    /**
+     * 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
+     * for the (usually non-existent) config statement.
+     *
+     * <p>
+     * This method maintains a resolution cache, so once we have returned a result, we will keep on returning the same
+     * result without performing any lookups, solely to support {@link SubstatementContext#isConfiguration()}.
+     *
+     * <p>
+     * Note: use of this method implies that {@link #isIgnoringConfig()} is realized with
+     *       {@link #isIgnoringConfig(StatementContextBase)}.
+     */
+    final boolean isConfiguration(final StatementContextBase<?, ?, ?> parent) {
+        final int fl = flags & SET_CONFIGURATION;
+        if (fl != 0) {
+            return fl == SET_CONFIGURATION;
+        }
+        if (isIgnoringConfig(parent)) {
+            // Note: SET_CONFIGURATION has been stored in flags
+            return true;
+        }
+
+        final StmtContext<Boolean, ?, ?> configStatement = StmtContextUtils.findFirstSubstatement(this,
+            ConfigStatement.class);
+        final boolean isConfig;
+        if (configStatement != null) {
+            isConfig = configStatement.coerceStatementArgument();
+            if (isConfig) {
+                // Validity check: if parent is config=false this cannot be a config=true
+                InferenceException.throwIf(!parent.isConfiguration(), getStatementSourceReference(),
+                        "Parent node has config=false, this node must not be specifed as config=true");
+            }
+        } else {
+            // If "config" statement is not specified, the default is the same as the parent's "config" value.
+            isConfig = parent.isConfiguration();
+        }
+
+        // Resolved, make sure we cache this return
+        flags |= isConfig ? SET_CONFIGURATION : HAVE_CONFIGURATION;
+        return isConfig;
+    }
+
+    protected abstract boolean isIgnoringConfig();
+
+    /**
+     * This method maintains a resolution cache for ignore config, so once we have returned a result, we will
+     * keep on returning the same result without performing any lookups. Exists only to support
+     * {@link SubstatementContext#isIgnoringConfig()}.
+     *
+     * <p>
+     * Note: use of this method implies that {@link #isConfiguration()} is realized with
+     *       {@link #isConfiguration(StatementContextBase)}.
+     */
+    final boolean isIgnoringConfig(final StatementContextBase<?, ?, ?> parent) {
+        final int fl = flags & SET_IGNORE_CONFIG;
+        if (fl != 0) {
+            return fl == SET_IGNORE_CONFIG;
+        }
+        if (definition().isIgnoringConfig() || parent.isIgnoringConfig()) {
+            flags |= SET_IGNORE_CONFIG;
+            return true;
+        }
+
+        flags |= HAVE_IGNORE_CONFIG;
+        return false;
+    }
+
+    protected abstract boolean isIgnoringIfFeatures();
+
+    /**
+     * This method maintains a resolution cache for ignore if-feature, so once we have returned a result, we will
+     * keep on returning the same result without performing any lookups. Exists only to support
+     * {@link SubstatementContext#isIgnoringIfFeatures()}.
+     */
+    final boolean isIgnoringIfFeatures(final StatementContextBase<?, ?, ?> parent) {
+        final int fl = flags & SET_IGNORE_IF_FEATURE;
+        if (fl != 0) {
+            return fl == SET_IGNORE_IF_FEATURE;
+        }
+        if (definition().isIgnoringIfFeatures() || parent.isIgnoringIfFeatures()) {
+            flags |= SET_IGNORE_IF_FEATURE;
+            return true;
+        }
+
+        flags |= HAVE_IGNORE_IF_FEATURE;
+        return false;
+    }
+
     final void copyTo(final StatementContextBase<?, ?, ?> target, final CopyType typeOfCopy,
             @Nullable final QNameModule targetModule) {
         final Collection<Mutable<?, ?, ?>> buffer = new ArrayList<>(substatements.size() + effective.size());
@@ -807,11 +984,11 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     // FIXME: revise this, as it seems to be wrong
-    private static final Set<YangStmtMapping> NOCOPY_FROM_GROUPING_SET = ImmutableSet.of(
+    private static final ImmutableSet<YangStmtMapping> NOCOPY_FROM_GROUPING_SET = ImmutableSet.of(
         YangStmtMapping.DESCRIPTION,
         YangStmtMapping.REFERENCE,
         YangStmtMapping.STATUS);
-    private static final Set<YangStmtMapping> REUSED_DEF_SET = ImmutableSet.of(
+    private static final ImmutableSet<YangStmtMapping> REUSED_DEF_SET = ImmutableSet.of(
         YangStmtMapping.TYPE,
         YangStmtMapping.TYPEDEF,
         YangStmtMapping.USES);
@@ -823,7 +1000,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
             return false;
         }
         if (NOCOPY_FROM_GROUPING_SET.contains(def)) {
-            return !YangStmtMapping.GROUPING.equals(stmtContext.getParentContext().getPublicDefinition());
+            return !YangStmtMapping.GROUPING.equals(stmtContext.coerceParentContext().getPublicDefinition());
         }
 
         LOG.debug("Will copy {} statement {}", def, stmtContext);