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=65656d4efa5ba85c3e4541b6cf033756f4170547;hb=5ef3027640586a5e291c18414cade544a89953f7;hp=4e806ee4ae4178ca23f3f7f12d594dc786895a94;hpb=3680e198a70129fafa634eeb3fb49e8401fff010;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 4e806ee4ae..65656d4efa 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 @@ -7,13 +7,17 @@ */ package org.opendaylight.yangtools.yang.parser.stmt.reactor; -import com.google.common.base.MoreObjects; -import com.google.common.base.MoreObjects.ToStringHelper; -import com.google.common.base.Preconditions; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Verify.verify; +import static com.google.common.base.Verify.verifyNotNull; +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.Beta; +import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import java.util.ArrayList; @@ -22,43 +26,46 @@ 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.Optional; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.opendaylight.yangtools.util.OptionalBoolean; -import org.opendaylight.yangtools.yang.common.QName; +import java.util.stream.Stream; +import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.yangtools.yang.common.QNameModule; -import org.opendaylight.yangtools.yang.model.api.YangStmtMapping; import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement; 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.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.ModelActionBuilder; import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase; +import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase.ExecutionOrder; +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.NamespaceKeyCriterion; +import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace; +import org.opendaylight.yangtools.yang.parser.spi.meta.StatementFactory; 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; import org.opendaylight.yangtools.yang.parser.spi.source.ImplicitSubstatement; import org.opendaylight.yangtools.yang.parser.spi.source.SourceException; -import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference; -import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace; -import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace.SupportedFeatures; import org.opendaylight.yangtools.yang.parser.stmt.reactor.NamespaceBehaviourWithListeners.KeyedValueAddedListener; import org.opendaylight.yangtools.yang.parser.stmt.reactor.NamespaceBehaviourWithListeners.PredicateValueAddedListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Core reactor statement implementation of {@link Mutable}. + * + * @param Argument type + * @param Declared Statement representation + * @param Effective Statement representation + */ public abstract class StatementContextBase, E extends EffectiveStatement> - extends NamespaceStorageSupport implements Mutable { + extends ReactorStmtCtx implements CopyHistory { /** * Event listener when an item is added to model namespace. */ @@ -89,237 +96,167 @@ public abstract class StatementContextBase, E private static final Logger LOG = LoggerFactory.getLogger(StatementContextBase.class); - private final StatementDefinitionContext definition; - private final StatementSourceReference statementDeclSource; - private final StmtContext originalCtx; - private final CopyHistory copyHistory; - private final String rawArgument; - - private Multimap phaseListeners = ImmutableMultimap.of(); - private Multimap phaseMutation = ImmutableMultimap.of(); - private Collection> effective = ImmutableList.of(); - private Collection> effectOfStatement = ImmutableList.of(); - private StatementMap substatements = StatementMap.empty(); - - private boolean isSupportedToBuildEffective = true; - private ModelProcessingPhase completedPhase = null; - private D declaredInstance; - private E effectiveInstance; - - // BooleanFields value - private byte supportedByFeatures; - - StatementContextBase(final StatementDefinitionContext def, final StatementSourceReference ref, - final String rawArgument) { - this.definition = Preconditions.checkNotNull(def); - this.statementDeclSource = Preconditions.checkNotNull(ref); - this.rawArgument = def.internArgument(rawArgument); - this.copyHistory = CopyHistory.original(); - this.originalCtx = null; - } - - StatementContextBase(final StatementContextBase 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.rawArgument = original.rawArgument; - this.copyHistory = CopyHistory.of(copyType, original.getCopyHistory()); - this.originalCtx = original.getOriginalCtx().orElse(original); - } - - @Override - public Collection> getEffectOfStatement() { - return effectOfStatement; - } - - @Override - public void addAsEffectOfStatement(final StmtContext ctx) { - if (effectOfStatement.isEmpty()) { - effectOfStatement = new ArrayList<>(1); - } - effectOfStatement.add(ctx); - } + // + // {@link CopyHistory} encoded as a single byte. We still have 4 bits unused. + // + private static final byte COPY_LAST_TYPE_MASK = 0x03; + private static final byte COPY_ADDED_BY_USES = 0x04; + private static final byte COPY_ADDED_BY_AUGMENTATION = 0x08; + private static final byte COPY_ORIGINAL = 0x00; - @Override - public void addAsEffectOfStatement(final Collection> ctxs) { - if (ctxs.isEmpty()) { - return; - } + private final byte copyHistory; - if (effectOfStatement.isEmpty()) { - effectOfStatement = new ArrayList<>(ctxs.size()); - } - effectOfStatement.addAll(ctxs); + static { + final int copyTypes = CopyType.values().length; + // This implies CopyType.ordinal() is <= COPY_TYPE_MASK + verify(copyTypes == COPY_LAST_TYPE_MASK + 1, "Unexpected %s CopyType values", copyTypes); } - @Override - public boolean isSupportedByFeatures() { - if (OptionalBoolean.isPresent(supportedByFeatures)) { - return OptionalBoolean.get(supportedByFeatures); - } + // Note: this field can strictly be derived in InferredStatementContext, but it forms the basis of many of our + // operations, hence we want to keep it close by. + private final @NonNull StatementDefinitionContext definition; - if (isIgnoringIfFeatures()) { - supportedByFeatures = OptionalBoolean.of(true); - return true; - } + // TODO: consider keying by Byte equivalent of ExecutionOrder + private Multimap phaseListeners = ImmutableMultimap.of(); + private Multimap phaseMutation = ImmutableMultimap.of(); - 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 (!isParentSupported) { - supportedByFeatures = OptionalBoolean.of(false); - return false; - } + private List> effectOfStatement = ImmutableList.of(); - /* - * 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 supportedFeatures = getFromNamespace(SupportedFeaturesNamespace.class, - SupportedFeatures.SUPPORTED_FEATURES); - final boolean ret = supportedFeatures == null ? true - : StmtContextUtils.checkFeatureSupport(this, supportedFeatures); + /** + * {@link ModelProcessingPhase.ExecutionOrder} value of current {@link ModelProcessingPhase} of this statement. + */ + private byte executionOrder; - supportedByFeatures = OptionalBoolean.of(ret); - return ret; + // Copy constructor used by subclasses to implement reparent() + StatementContextBase(final StatementContextBase original) { + super(original); + this.copyHistory = original.copyHistory; + this.definition = original.definition; + this.executionOrder = original.executionOrder; } - protected abstract boolean isParentSupportedByFeatures(); - - protected abstract boolean isIgnoringIfFeatures(); - - protected abstract boolean isIgnoringConfig(); - - @Override - public boolean isSupportedToBuildEffective() { - return isSupportedToBuildEffective; + StatementContextBase(final StatementDefinitionContext def) { + this.definition = requireNonNull(def); + this.copyHistory = COPY_ORIGINAL; } - @Override - public void setIsSupportedToBuildEffective(final boolean isSupportedToBuildEffective) { - this.isSupportedToBuildEffective = isSupportedToBuildEffective; + StatementContextBase(final StatementDefinitionContext def, final CopyType copyType) { + this.definition = requireNonNull(def); + this.copyHistory = (byte) copyFlags(copyType); } - @Override - public CopyHistory getCopyHistory() { - return copyHistory; + StatementContextBase(final StatementContextBase prototype, final CopyType copyType) { + this.definition = prototype.definition; + this.copyHistory = (byte) (copyFlags(copyType) | prototype.copyHistory & ~COPY_LAST_TYPE_MASK); } - @Override - public Optional> getOriginalCtx() { - return Optional.ofNullable(originalCtx); + private static int copyFlags(final CopyType copyType) { + return historyFlags(copyType) | copyType.ordinal(); } - @Override - public ModelProcessingPhase getCompletedPhase() { - return completedPhase; + private static byte historyFlags(final CopyType copyType) { + switch (copyType) { + case ADDED_BY_AUGMENTATION: + return COPY_ADDED_BY_AUGMENTATION; + case ADDED_BY_USES: + return COPY_ADDED_BY_USES; + case ADDED_BY_USES_AUGMENTATION: + return COPY_ADDED_BY_AUGMENTATION | COPY_ADDED_BY_USES; + case ORIGINAL: + return COPY_ORIGINAL; + default: + throw new VerifyException("Unhandled type " + copyType); + } } @Override - public void setCompletedPhase(final ModelProcessingPhase completedPhase) { - this.completedPhase = completedPhase; + public Collection> getEffectOfStatement() { + return effectOfStatement; } @Override - public abstract StatementContextBase getParentContext(); + public void addAsEffectOfStatement(final Collection> ctxs) { + if (ctxs.isEmpty()) { + return; + } - /** - * Returns the model root for this statement. - * - * @return root context of statement - */ - @Nonnull - @Override - public abstract RootStatementContext getRoot(); + if (effectOfStatement.isEmpty()) { + effectOfStatement = new ArrayList<>(ctxs.size()); + } + effectOfStatement.addAll(ctxs); + } + + // + // CopyHistory integration + // - /** - * Returns the origin of the statement. - * - * @return origin of statement - */ - @Nonnull @Override - public StatementSource getStatementSource() { - return statementDeclSource.getStatementSource(); + public final CopyHistory history() { + return this; } - /** - * Returns a reference to statement source. - * - * @return reference of statement source - */ - @Nonnull @Override - public StatementSourceReference getStatementSourceReference() { - return statementDeclSource; + public final boolean isAddedByUses() { + return (copyHistory & COPY_ADDED_BY_USES) != 0; } @Override - public final String rawStatementArgument() { - return rawArgument; + public final boolean isAugmenting() { + return (copyHistory & COPY_ADDED_BY_AUGMENTATION) != 0; } - @Nonnull @Override - public Collection> declaredSubstatements() { - return substatements.values(); + public final CopyType getLastOperation() { + return CopyType.values()[copyHistory & COPY_LAST_TYPE_MASK]; } - @Nonnull + // + // Inference completion tracking + // + @Override - public Collection> mutableDeclaredSubstatements() { - return substatements.values(); + final byte executionOrder() { + return executionOrder; } - @Override - public Collection> effectiveSubstatements() { - return mutableEffectiveSubstatements(); + // FIXME: this should be propagated through a correct constructor + @Deprecated + final void setCompletedPhase(final ModelProcessingPhase completedPhase) { + this.executionOrder = completedPhase.executionOrder(); } - @Nonnull @Override - public Collection> mutableEffectiveSubstatements() { - if (effective instanceof ImmutableCollection) { - return effective; - } - - return Collections.unmodifiableCollection(effective); + public final > void addToNs( + final Class<@NonNull N> type, final T key, final U value) { + addToNamespace(type, key, value); } - public void removeStatementsFromEffectiveSubstatements( - final Collection> statements) { - if (!effective.isEmpty()) { - effective.removeAll(statements); - shrinkEffective(); - } + static final Collection> mutableEffectiveSubstatements( + final List> effective) { + return effective instanceof ImmutableCollection ? effective : Collections.unmodifiableCollection(effective); } - private void shrinkEffective() { - if (effective.isEmpty()) { - effective = ImmutableList.of(); - } + private static List> shrinkEffective(final List> effective) { + return effective.isEmpty() ? ImmutableList.of() : effective; } - public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef) { + public abstract void removeStatementFromEffectiveSubstatements(StatementDefinition statementDef); + + static final List> removeStatementFromEffectiveSubstatements( + final List> effective, final StatementDefinition statementDef) { if (effective.isEmpty()) { - return; + return effective; } final Iterator> iterator = effective.iterator(); while (iterator.hasNext()) { final StmtContext next = iterator.next(); - if (statementDef.equals(next.getPublicDefinition())) { + if (statementDef.equals(next.publicDefinition())) { iterator.remove(); } } - shrinkEffective(); + return shrinkEffective(effective); } /** @@ -334,39 +271,62 @@ 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 abstract void removeStatementFromEffectiveSubstatements(StatementDefinition statementDef, + String statementArg); + + static final List> removeStatementFromEffectiveSubstatements( + final List> effective, final StatementDefinition statementDef, final String statementArg) { if (statementArg == null) { - removeStatementFromEffectiveSubstatements(statementDef); + return removeStatementFromEffectiveSubstatements(effective, statementDef); } if (effective.isEmpty()) { - return; + return effective; } - 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())) { + if (statementDef.equals(next.publicDefinition()) && statementArg.equals(next.rawArgument())) { iterator.remove(); } } - shrinkEffective(); + return shrinkEffective(effective); + } + + // YANG example: RPC/action statements always have 'input' and 'output' defined + @Beta + public , Z extends EffectiveStatement> @NonNull Mutable + appendImplicitSubstatement(final StatementSupport support, final String rawArg) { + // FIXME: YANGTOOLS-652: This does not need to be a SubstatementContext, in can be a specialized + // StatementContextBase subclass. + final Mutable ret = new SubstatementContext<>(this, new StatementDefinitionContext<>(support), + ImplicitSubstatement.of(sourceReference()), rawArg); + support.onStatementAdded(ret); + addEffectiveSubstatement(ret); + return ret; } /** * Adds an effective statement to collection of substatements. * * @param substatement substatement - * @throws IllegalStateException - * if added in declared phase - * @throws NullPointerException - * if statement parameter is null + * @throws IllegalStateException if added in declared phase + * @throws NullPointerException if statement parameter is null */ - public void addEffectiveSubstatement(final Mutable substatement) { - beforeAddEffectiveStatement(1); - effective.add(substatement); + public abstract void addEffectiveSubstatement(Mutable substatement); + + final List> addEffectiveSubstatement(final List> effective, + final Mutable substatement) { + verifyStatement(substatement); + + final List> resized = beforeAddEffectiveStatement(effective, 1); + final ReactorStmtCtx stmt = (ReactorStmtCtx) substatement; + ensureCompletedExecution(stmt); + resized.add(stmt); + return resized; } /** @@ -378,155 +338,233 @@ 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); + addEffectiveSubstatementsImpl(statements); + } + } + + abstract void addEffectiveSubstatementsImpl(Collection> statements); + + final List> addEffectiveSubstatementsImpl(final List> effective, + final Collection> statements) { + final List> resized = beforeAddEffectiveStatement(effective, statements.size()); + final Collection> casted = + (Collection>) statements; + if (executionOrder != ExecutionOrder.NULL) { + for (ReactorStmtCtx stmt : casted) { + ensureCompletedExecution(stmt, executionOrder); + } } - statements.forEach(Preconditions::checkNotNull); - beforeAddEffectiveStatement(statements.size()); - effective.addAll(statements); + resized.addAll(casted); + return resized; } - private void beforeAddEffectiveStatement(final int toAdd) { - final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase(); - Preconditions.checkState(inProgressPhase == ModelProcessingPhase.FULL_DECLARATION - || inProgressPhase == ModelProcessingPhase.EFFECTIVE_MODEL, - "Effective statement cannot be added in declared phase at: %s", getStatementSourceReference()); + abstract Iterable> effectiveChildrenToComplete(); - if (effective.isEmpty()) { - effective = new ArrayList<>(toAdd); + // exposed for InferredStatementContext only + final void ensureCompletedPhase(final Mutable stmt) { + verifyStatement(stmt); + ensureCompletedExecution((ReactorStmtCtx) stmt); + } + + // Make sure target statement has transitioned at least to our phase (if we have one). 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 void ensureCompletedExecution(final ReactorStmtCtx stmt) { + if (executionOrder != ExecutionOrder.NULL) { + ensureCompletedExecution(stmt, executionOrder); } } - /** - * Create a new substatement at the specified offset. - * - * @param offset Substatement offset - * @param def definition context - * @param ref source reference - * @param argument statement argument - * @return A new substatement - */ - @SuppressWarnings("checkstyle:methodTypeParameterName") - public final , CE extends EffectiveStatement> - StatementContextBase createSubstatement(final int offset, - final StatementDefinitionContext def, final StatementSourceReference ref, - final String argument) { + private static void ensureCompletedExecution(final ReactorStmtCtx stmt, final byte executionOrder) { + verify(stmt.tryToCompletePhase(executionOrder), "Statement %s cannot complete phase %s", stmt, executionOrder); + } + + private static void verifyStatement(final Mutable stmt) { + verify(stmt instanceof ReactorStmtCtx, "Unexpected statement %s", stmt); + } + + private List> beforeAddEffectiveStatement(final List> effective, + final int toAdd) { + // We cannot allow statement to be further mutated. + // TODO: we really want to say 'not NULL and not at or after EFFECTIVE_MODEL here. This will matter if we have + // a phase after EFFECTIVE_MODEL + verify(executionOrder != ExecutionOrder.EFFECTIVE_MODEL, "Cannot modify finished statement at %s", + sourceReference()); + return beforeAddEffectiveStatementUnsafe(effective, toAdd); + } + + final List> beforeAddEffectiveStatementUnsafe(final List> effective, + final int toAdd) { final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase(); - Preconditions.checkState(inProgressPhase != ModelProcessingPhase.EFFECTIVE_MODEL, - "Declared statement cannot be added in effective phase at: %s", getStatementSourceReference()); + checkState(inProgressPhase == ModelProcessingPhase.FULL_DECLARATION + || inProgressPhase == ModelProcessingPhase.EFFECTIVE_MODEL, + "Effective statement cannot be added in declared phase at: %s", sourceReference()); - final Optional> implicitParent = definition.getImplicitParentFor(def.getPublicView()); - if (implicitParent.isPresent()) { - return createImplicitParent(offset, implicitParent.get(), ref, argument).createSubstatement(offset, def, - ref, argument); + return effective.isEmpty() ? new ArrayList<>(toAdd) : effective; + } + + @Override + final E createEffective() { + final E result = createEffective(definition.getFactory()); + if (result instanceof MutableStatement) { + getRoot().addMutableStmtToSeal((MutableStatement) result); } + return result; + } - final StatementContextBase ret = new SubstatementContext<>(this, def, ref, argument); - substatements = substatements.put(offset, ret); - def.onStatementAdded(ret); - return ret; + @NonNull E createEffective(final StatementFactory factory) { + return createEffective(factory, this); } - private StatementContextBase createImplicitParent(final int offset, - final StatementSupport implicitParent, final StatementSourceReference ref, final String argument) { - final StatementDefinitionContext def = new StatementDefinitionContext<>(implicitParent); - return createSubstatement(offset, def, ImplicitSubstatement.of(ref), argument); + // Creates EffectiveStatement through full materialization + static , E extends EffectiveStatement> @NonNull E createEffective( + final StatementFactory factory, final StatementContextBase ctx) { + return factory.createEffective(ctx, ctx.streamDeclared(), ctx.streamEffective()); } /** - * Lookup substatement by its offset in this statement. + * Return a stream of declared statements which can be built into an {@link EffectiveStatement}, as per + * {@link StmtContext#buildEffective()} contract. * - * @param offset Substatement offset - * @return Substatement, or null if substatement does not exist. + * @return Stream of supported declared statements. */ - final StatementContextBase lookupSubstatement(final int offset) { - return substatements.get(offset); - } + // FIXME: we really want to unify this with streamEffective(), under its name + abstract Stream> streamDeclared(); + + /** + * Return a stream of inferred statements which can be built into an {@link EffectiveStatement}, as per + * {@link StmtContext#buildEffective()} contract. + * + * @return Stream of supported effective statements. + */ + // FIXME: this method is currently a misnomer, but unifying with streamDeclared() would make this accurate again + abstract Stream> streamEffective(); @Override - public D buildDeclared() { - Preconditions.checkArgument(completedPhase == ModelProcessingPhase.FULL_DECLARATION - || completedPhase == ModelProcessingPhase.EFFECTIVE_MODEL); - if (declaredInstance == null) { - declaredInstance = definition().getFactory().createDeclared(this); + final boolean doTryToCompletePhase(final byte targetOrder) { + final boolean finished = phaseMutation.isEmpty() ? true : runMutations(targetOrder); + if (completeChildren(targetOrder) && finished) { + onPhaseCompleted(targetOrder); + return true; } - return declaredInstance; + return false; } - @Override - public E buildEffective() { - if (effectiveInstance == null) { - effectiveInstance = definition().getFactory().createEffective(this); + private boolean completeChildren(final byte targetOrder) { + boolean finished = true; + for (final StatementContextBase child : mutableDeclaredSubstatements()) { + finished &= child.tryToCompletePhase(targetOrder); + } + for (final ReactorStmtCtx child : effectiveChildrenToComplete()) { + finished &= child.tryToCompletePhase(targetOrder); } - return effectiveInstance; + return finished; } - /** - * tries to execute current {@link ModelProcessingPhase} of source parsing. - * - * @param phase - * to be executed (completed) - * @return if phase was successfully completed - * @throws SourceException - * when an error occured in source parsing - */ - boolean tryToCompletePhase(final ModelProcessingPhase phase) { + private boolean runMutations(final byte targetOrder) { + final ModelProcessingPhase phase = verifyNotNull(ModelProcessingPhase.ofExecutionOrder(targetOrder)); + 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 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 Iterator it = openMutations.iterator(); + while (it.hasNext()) { + final ContextMutation current = it.next(); + if (current.isFinished()) { + it.remove(); + } else { + finished = false; } + } - if (openMutations.isEmpty()) { - phaseMutation.removeAll(phase); - if (phaseMutation.isEmpty()) { - phaseMutation = ImmutableMultimap.of(); - } - } + if (openMutations.isEmpty()) { + phaseMutation.removeAll(phase); + cleanupPhaseMutation(); } + return finished; + } - for (final StatementContextBase child : substatements.values()) { - finished &= child.tryToCompletePhase(phase); + private void cleanupPhaseMutation() { + if (phaseMutation.isEmpty()) { + phaseMutation = ImmutableMultimap.of(); } - for (final Mutable child : effective) { - if (child instanceof StatementContextBase) { - finished &= ((StatementContextBase) child).tryToCompletePhase(phase); - } + } + + /** + * Occurs on end of {@link ModelProcessingPhase} of source parsing. This method must not be called with + * {@code executionOrder} equal to {@link ExecutionOrder#NULL}. + * + * @param phase that was to be completed (finished) + * @throws SourceException when an error occurred in source parsing + */ + private void onPhaseCompleted(final byte completedOrder) { + executionOrder = completedOrder; + if (completedOrder == ExecutionOrder.EFFECTIVE_MODEL) { + // We have completed effective model, substatements are guaranteed not to change + summarizeSubstatementPolicy(); } - if (finished) { - onPhaseCompleted(phase); - return true; + final ModelProcessingPhase phase = verifyNotNull(ModelProcessingPhase.ofExecutionOrder(completedOrder)); + final Collection listeners = phaseListeners.get(phase); + if (!listeners.isEmpty()) { + runPhaseListeners(phase, listeners); + } + } + + private void summarizeSubstatementPolicy() { + if (definition().support().copyPolicy() == CopyPolicy.EXACT_REPLICA || noSensitiveSubstatements()) { + setAllSubstatementsContextIndependent(); } - return false; } /** - * Occurs on end of {@link ModelProcessingPhase} of source parsing. + * Determine whether any substatements are copy-sensitive as determined by {@link StatementSupport#copyPolicy()}. + * Only {@link CopyPolicy#CONTEXT_INDEPENDENT}, {@link CopyPolicy#EXACT_REPLICA} and {@link CopyPolicy#IGNORE} are + * copy-insensitive. Note that statements which are not {@link StmtContext#isSupportedToBuildEffective()} are all + * considered copy-insensitive. * - * @param phase - * that was to be completed (finished) - * @throws SourceException - * when an error occurred in source parsing + *

+ * Implementations are expected to call {@link #noSensitiveSubstatements()} to actually traverse substatement sets. + * + * @return True if no substatements require copy-sensitive handling */ - private void onPhaseCompleted(final ModelProcessingPhase phase) { - completedPhase = phase; + abstract boolean noSensitiveSubstatements(); - final Collection listeners = phaseListeners.get(phase); - if (listeners.isEmpty()) { - return; + /** + * Determine whether any of the provided substatements are context-sensitive for purposes of implementing + * {@link #noSensitiveSubstatements()}. + * + * @param substatements Substatements to check + * @return True if no substatements require context-sensitive handling + */ + static boolean noSensitiveSubstatements(final Collection> substatements) { + for (ReactorStmtCtx stmt : substatements) { + if (stmt.isSupportedToBuildEffective()) { + if (!stmt.allSubstatementsContextIndependent()) { + // This is a recursive property + return false; + } + + switch (stmt.definition().support().copyPolicy()) { + case CONTEXT_INDEPENDENT: + case EXACT_REPLICA: + case IGNORE: + break; + default: + return false; + } + } } + return true; + } + private void runPhaseListeners(final ModelProcessingPhase phase, final Collection listeners) { final Iterator listener = listeners.iterator(); while (listener.hasNext()) { final OnPhaseFinished next = listener.next(); @@ -546,31 +584,16 @@ public abstract class StatementContextBase, E /** * Ends declared section of current node. */ - void endDeclared(final StatementSourceReference ref, final ModelProcessingPhase phase) { - definition().onDeclarationFinished(this, phase); - } - - /** - * Return the context in which this statement was defined. - * - * @return statement definition - */ - protected final StatementDefinitionContext definition() { - return definition; + void endDeclared(final ModelProcessingPhase phase) { + definition.onDeclarationFinished(this, phase); } @Override - protected void checkLocalNamespaceAllowed(final Class> type) { - definition().checkNamespaceAllowed(type); - } - - @Override - protected > void onNamespaceElementAdded(final Class type, final K key, - final V value) { - // definition().onNamespaceElementAdded(this, type, key, value); + final StatementDefinitionContext definition() { + return definition; } - final > void onNamespaceItemAddedAction(final Class type, final K key, + final > void onNamespaceItemAddedAction(final Class type, final K key, final OnNamespaceItemAdded listener) { final Object potential = getFromNamespace(type, key); if (potential != null) { @@ -579,7 +602,7 @@ public abstract class StatementContextBase, E return; } - getBehaviour(type).addListener(new KeyedValueAddedListener(this, key) { + getBehaviour(type).addListener(new KeyedValueAddedListener<>(this, key) { @Override void onValueAdded(final Object value) { listener.namespaceItemAdded(StatementContextBase.this, type, key, value); @@ -587,7 +610,7 @@ public abstract class StatementContextBase, E }); } - final > void onNamespaceItemAddedAction(final Class type, + final > void onNamespaceItemAddedAction(final Class type, final ModelProcessingPhase phase, final NamespaceKeyCriterion criterion, final OnNamespaceItemAdded listener) { final Optional> existing = getFromNamespace(type, criterion); @@ -613,16 +636,16 @@ public abstract class StatementContextBase, E }); } - final > void selectMatch(final Class type, + final > void selectMatch(final Class type, final NamespaceKeyCriterion criterion, final OnNamespaceItemAdded listener) { final Optional> 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 match = optMatch.get(); listener.namespaceItemAdded(StatementContextBase.this, type, match.getKey(), match.getValue()); } - final > void waitForPhase(final Object value, final Class type, + final > void waitForPhase(final Object value, final Class type, final ModelProcessingPhase phase, final NamespaceKeyCriterion criterion, final OnNamespaceItemAdded listener) { ((StatementContextBase) value).addPhaseCompletedListener(phase, @@ -632,29 +655,15 @@ public abstract class StatementContextBase, E }); } - private > NamespaceBehaviourWithListeners getBehaviour( + private > NamespaceBehaviourWithListeners getBehaviour( final Class type) { final NamespaceBehaviour 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) behaviour; } - /** - * See {@link StatementSupport#getPublicView()}. - */ - @Nonnull - @Override - public StatementDefinition getPublicDefinition() { - return definition().getPublicView(); - } - - @Override - public ModelActionBuilder newInferenceAction(final ModelProcessingPhase phase) { - return getRoot().getSourceContext().newInferenceAction(phase); - } - private static Multimap newMultimap() { return Multimaps.newListMultimap(new EnumMap<>(ModelProcessingPhase.class), () -> new ArrayList<>(1)); } @@ -668,12 +677,10 @@ public abstract class StatementContextBase, 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()); + requireNonNull(phase, "Statement context processing phase cannot be null"); + requireNonNull(listener, "Statement context phase listener cannot be null"); - ModelProcessingPhase finishedPhase = completedPhase; + ModelProcessingPhase finishedPhase = ModelProcessingPhase.ofExecutionOrder(executionOrder); while (finishedPhase != null) { if (phase.equals(finishedPhase)) { listener.phaseFinished(this, finishedPhase); @@ -691,16 +698,11 @@ 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) { - ModelProcessingPhase finishedPhase = completedPhase; - while (finishedPhase != null) { - Preconditions.checkState(!phase.equals(finishedPhase), - "Mutation registered after phase was completed at: %s", getStatementSourceReference()); - finishedPhase = finishedPhase.getPreviousPhase(); - } + final void addMutation(final ModelProcessingPhase phase, final ContextMutation mutation) { + checkState(executionOrder < phase.executionOrder(), "Mutation registered after phase was completed at: %s", + sourceReference()); if (phaseMutation.isEmpty()) { phaseMutation = newMultimap(); @@ -708,94 +710,157 @@ 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 , Z extends EffectiveStatement> Mutable childCopyOf( - final StmtContext stmt, final CopyType type, final QNameModule targetModule) { - Preconditions.checkState(stmt.getCompletedPhase() == ModelProcessingPhase.EFFECTIVE_MODEL, - "Attempted to copy statement %s which has completed phase %s", stmt, stmt.getCompletedPhase()); + public Optional> copyAsChildOf(final Mutable parent, final CopyType type, + final QNameModule targetModule) { + checkEffectiveModelCompleted(this); + return Optional.ofNullable(copyAsChildOfImpl(parent, type, targetModule)); + } + + private ReactorStmtCtx copyAsChildOfImpl(final Mutable parent, final CopyType type, + final QNameModule targetModule) { + final StatementSupport support = definition.support(); + final CopyPolicy policy = support.copyPolicy(); + switch (policy) { + case EXACT_REPLICA: + return replicaAsChildOf(parent); + case CONTEXT_INDEPENDENT: + if (allSubstatementsContextIndependent()) { + return replicaAsChildOf(parent); + } - Preconditions.checkArgument(stmt instanceof SubstatementContext, "Unsupported statement %s", stmt); + // fall through + case DECLARED_COPY: + // FIXME: ugly cast + return (ReactorStmtCtx) parent.childCopyOf(this, type, targetModule); + case IGNORE: + return null; + case REJECT: + throw new IllegalStateException("Statement " + support.getPublicView() + " should never be copied"); + default: + throw new IllegalStateException("Unhandled policy " + policy); + } + } - final SubstatementContext original = (SubstatementContext)stmt; - final SubstatementContext copy = new SubstatementContext<>(original, this, type, targetModule); + @Override + final ReactorStmtCtx asEffectiveChildOf(final StatementContextBase parent, final CopyType type, + final QNameModule targetModule) { + final ReactorStmtCtx copy = copyAsChildOfImpl(parent, type, targetModule); + if (copy == null) { + // The statement fizzled, this should never happen, perhaps a verify()? + return null; + } - original.definition().onStatementAdded(copy); - original.copyTo(copy, type, targetModule); + parent.ensureCompletedPhase(copy); + return canReuseCurrent(copy) ? this : copy; + } + + private boolean canReuseCurrent(final ReactorStmtCtx copy) { + // Defer to statement factory to see if we can reuse this object. If we can and have only context-independent + // substatements we can reuse the object. More complex cases are handled indirectly via the copy. + return definition.getFactory().canReuseCurrent(copy, this, buildEffective().effectiveSubstatements()) + && allSubstatementsContextIndependent(); + } - return copy; + @Override + public final Mutable childCopyOf(final StmtContext stmt, final CopyType type, + final QNameModule targetModule) { + checkEffectiveModelCompleted(stmt); + checkArgument(stmt instanceof StatementContextBase, "Unsupported statement %s", stmt); + return childCopyOf((StatementContextBase) stmt, type, targetModule); } - final void copyTo(final StatementContextBase target, final CopyType typeOfCopy, - @Nullable final QNameModule targetModule) { - final Collection> buffer = new ArrayList<>(substatements.size() + effective.size()); + private , Z extends EffectiveStatement> Mutable childCopyOf( + final StatementContextBase original, final CopyType type, final QNameModule targetModule) { + final Optional> implicitParent = definition.getImplicitParentFor( + original.publicDefinition()); + + final StatementContextBase result; + final InferredStatementContext copy; - for (final Mutable stmtContext : substatements.values()) { - if (stmtContext.isSupportedByFeatures()) { - copySubstatement(stmtContext, target, typeOfCopy, targetModule, buffer); + if (implicitParent.isPresent()) { + final StatementDefinitionContext def = new StatementDefinitionContext<>(implicitParent.get()); + result = new SubstatementContext(this, def, original.sourceReference(), original.rawArgument(), + original.argument(), 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; } - } - for (final Mutable stmtContext : effective) { - copySubstatement(stmtContext, target, typeOfCopy, targetModule, buffer); + copy = new InferredStatementContext<>(result, original, childCopyType, type, targetModule); + result.addEffectiveSubstatement(copy); + } else { + result = copy = new InferredStatementContext<>(this, original, type, type, targetModule); } - target.addEffectiveSubstatements(buffer); + original.definition.onStatementAdded(copy); + return result; } - private void copySubstatement(final Mutable stmtContext, final Mutable target, - final CopyType typeOfCopy, final QNameModule newQNameModule, final Collection> buffer) { - if (needToCopyByUses(stmtContext)) { - final Mutable copy = target.childCopyOf(stmtContext, typeOfCopy, newQNameModule); - LOG.debug("Copying substatement {} for {} as", stmtContext, this, copy); - buffer.add(copy); - } else if (isReusedByUses(stmtContext)) { - LOG.debug("Reusing substatement {} for {}", stmtContext, this); - buffer.add(stmtContext); - } else { - LOG.debug("Skipping statement {}", stmtContext); - } + @Override + final ReplicaStatementContext replicaAsChildOf(final StatementContextBase parent) { + return new ReplicaStatementContext<>(parent, this); } - // FIXME: revise this, as it seems to be wrong - private static final Set NOCOPY_FROM_GROUPING_SET = ImmutableSet.of( - YangStmtMapping.DESCRIPTION, - YangStmtMapping.REFERENCE, - YangStmtMapping.STATUS); - private static final Set REUSED_DEF_SET = ImmutableSet.of( - YangStmtMapping.TYPE, - YangStmtMapping.TYPEDEF, - YangStmtMapping.USES); + 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); + } - private static boolean needToCopyByUses(final StmtContext stmtContext) { - final StatementDefinition def = stmtContext.getPublicDefinition(); - if (REUSED_DEF_SET.contains(def)) { - LOG.debug("Will reuse {} statement {}", def, stmtContext); - return false; - } - if (NOCOPY_FROM_GROUPING_SET.contains(def)) { - return !YangStmtMapping.GROUPING.equals(stmtContext.getParentContext().getPublicDefinition()); + @Beta + // FIXME: this information should be exposed as a well-known Namespace + public final boolean hasImplicitParentSupport() { + return definition.getFactory() instanceof ImplicitParentAwareStatementSupport; + } + + @Beta + public final StatementContextBase wrapWithImplicit(final StatementContextBase original) { + final Optional> optImplicit = definition.getImplicitParentFor( + original.publicDefinition()); + if (optImplicit.isEmpty()) { + return original; } - LOG.debug("Will copy {} statement {}", def, stmtContext); - return true; - } + final StatementDefinitionContext def = new StatementDefinitionContext<>(optImplicit.get()); + final CopyType type = original.history().getLastOperation(); + final SubstatementContext result = new SubstatementContext(original.getParentContext(), def, + original.sourceReference(), original.rawArgument(), original.argument(), type); - private static boolean isReusedByUses(final StmtContext stmtContext) { - return REUSED_DEF_SET.contains(stmtContext.getPublicDefinition()); + result.addEffectiveSubstatement(original.reparent(result)); + result.setCompletedPhase(original.getCompletedPhase()); + return result; } - @Override - public final String toString() { - return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString(); - } + abstract StatementContextBase reparent(StatementContextBase newParent); - protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) { - return toStringHelper.add("definition", definition).add("rawArgument", rawArgument); - } + /** + * 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(); }