X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-parser-impl%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fparser%2Fstmt%2Freactor%2FStatementContextBase.java;h=498894cc0a69d4f4e19f4485fbea87ed625737af;hb=30d51f4c77264c9b3904caf3a544d38345f7a462;hp=0f55001b5f2bdec373f003f9e39bcc659809b4cf;hpb=481a692d463636bbcf75f023da71703913e1b605;p=yangtools.git diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java index 0f55001b5f..498894cc0a 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java @@ -7,209 +7,444 @@ */ 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 com.google.common.base.Throwables; -import com.google.common.collect.HashMultimap; +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; import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; import java.util.EventListener; import java.util.Iterator; -import java.util.LinkedHashMap; +import java.util.Optional; +import java.util.Set; import javax.annotation.Nonnull; -import org.opendaylight.yangtools.concepts.Identifiable; +import javax.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; 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.ModelActionBuilder; import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase; import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour; -import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.StorageNodeType; 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.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.ValueAddedListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public abstract class StatementContextBase, E extends EffectiveStatement> extends - NamespaceStorageSupport implements StmtContext.Mutable, Identifiable { - - interface OnNamespaceItemAdded extends EventListener{ - - void namespaceItemAdded(StatementContextBase context, Class namespace, Object key, Object value) throws SourceException; - +public abstract class StatementContextBase, E extends EffectiveStatement> + extends NamespaceStorageSupport implements Mutable { + /** + * Event listener when an item is added to model namespace. + */ + interface OnNamespaceItemAdded extends EventListener { + /** + * Invoked whenever a new item is added to a namespace. + */ + void namespaceItemAdded(StatementContextBase context, Class namespace, Object key, Object value); } - interface OnPhaseFinished extends EventListener{ - - void phaseFinished(StatementContextBase context, ModelProcessingPhase phase) throws SourceException; - + /** + * Event listener when a parsing {@link ModelProcessingPhase} is completed. + */ + interface OnPhaseFinished extends EventListener { + /** + * Invoked whenever a processing phase has finished. + */ + boolean phaseFinished(StatementContextBase context, ModelProcessingPhase phase); } + /** + * Interface for all mutations within an {@link ModelActionBuilder.InferenceAction}. + */ interface ContextMutation { boolean isFinished(); - } - abstract static class ContextBuilder, E extends EffectiveStatement> { + 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; - private final StatementDefinitionContext definition; - private final StatementSourceReference stmtRef; - private String rawArg; - private StatementSourceReference argRef; + // BooleanFields value + private byte supportedByFeatures; - public ContextBuilder(StatementDefinitionContext def, StatementSourceReference sourceRef) { - this.definition = def; - this.stmtRef = sourceRef; - } + 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; + } - public void setArgument(@Nonnull String argument, @Nonnull StatementSourceReference argumentSource) { - Preconditions.checkArgument(definition.hasArgument(), "Statement does not take argument."); - this.rawArg = Preconditions.checkNotNull(argument); - this.argRef = Preconditions.checkNotNull(argumentSource); - } + 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; + } - public String getRawArgument() { - return rawArg; + @Override + public void addAsEffectOfStatement(final StmtContext ctx) { + if (effectOfStatement.isEmpty()) { + effectOfStatement = new ArrayList<>(1); } + effectOfStatement.add(ctx); + } - public StatementSourceReference getStamementSource() { - return stmtRef; + @Override + public void addAsEffectOfStatement(final Collection> ctxs) { + if (ctxs.isEmpty()) { + return; } - public StatementSourceReference getArgumentSource() { - return argRef; + if (effectOfStatement.isEmpty()) { + effectOfStatement = new ArrayList<>(ctxs.size()); } + effectOfStatement.addAll(ctxs); + } - public StatementDefinitionContext getDefinition() { - return definition; + @Override + public boolean isSupportedByFeatures() { + if (OptionalBoolean.isPresent(supportedByFeatures)) { + return OptionalBoolean.get(supportedByFeatures); } - public StatementIdentifier getIdentifier() { - return new StatementIdentifier(definition.getStatementName(), rawArg); + if (isIgnoringIfFeatures()) { + supportedByFeatures = OptionalBoolean.of(true); + return true; } - public abstract StatementContextBase build() throws SourceException; + 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; + } + /* + * 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); + + supportedByFeatures = OptionalBoolean.of(ret); + return ret; } - private final StatementDefinitionContext definition; - private final StatementIdentifier identifier; - private final StatementSourceReference statementDeclSource; + protected abstract boolean isParentSupportedByFeatures(); - private LinkedHashMap > substatements = new LinkedHashMap<>(); + protected abstract boolean isIgnoringIfFeatures(); - private Collection> declared = new ArrayList<>(); - private Collection> effective = new ArrayList<>(); + protected abstract boolean isIgnoringConfig(); - private ModelProcessingPhase completedPhase; + @Override + public boolean isSupportedToBuildEffective() { + return isSupportedToBuildEffective; + } - private Multimap phaseListeners = HashMultimap.create(); - private Multimap phaseMutation = HashMultimap.create(); + @Override + public void setIsSupportedToBuildEffective(final boolean isSupportedToBuildEffective) { + this.isSupportedToBuildEffective = isSupportedToBuildEffective; + } - private D declaredInstance; - private E effectiveInstance; + @Override + public CopyHistory getCopyHistory() { + return copyHistory; + } + @Override + public Optional> getOriginalCtx() { + return Optional.ofNullable(originalCtx); + } - StatementContextBase(@Nonnull ContextBuilder builder) throws SourceException { - this.definition = builder.getDefinition(); - this.identifier = builder.getIdentifier(); - this.statementDeclSource = builder.getStamementSource(); - this.completedPhase = null; + @Override + public ModelProcessingPhase getCompletedPhase() { + return completedPhase; } - StatementContextBase(StatementContextBase original) { - this.definition = original.definition; - this.identifier = original.identifier; - this.statementDeclSource = original.statementDeclSource; - this.completedPhase = null; + @Override + public void setCompletedPhase(final ModelProcessingPhase completedPhase) { + this.completedPhase = completedPhase; } @Override public abstract StatementContextBase getParentContext(); + /** + * Returns the model root for this statement. + * + * @return root context of statement + */ + @Nonnull @Override public abstract RootStatementContext getRoot(); - - @Override - public StatementIdentifier getIdentifier() { - return identifier; - } - + /** + * 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; } @Override - public String rawStatementArgument() { - return identifier.getArgument(); + public final String rawStatementArgument() { + return rawArgument; + } + + @Nonnull + @Override + public Collection> declaredSubstatements() { + return substatements.values(); + } + + @Nonnull + @Override + public Collection> mutableDeclaredSubstatements() { + return substatements.values(); } @Override - public Collection> declaredSubstatements() { - return Collections.unmodifiableCollection(declared); + public Collection> effectiveSubstatements() { + return mutableEffectiveSubstatements(); } + @Nonnull @Override - public Collection> effectiveSubstatements() { + public Collection> mutableEffectiveSubstatements() { + if (effective instanceof ImmutableCollection) { + return effective; + } + return Collections.unmodifiableCollection(effective); } - public void addEffectiveSubstatement(StatementContextBase substatement){ - effective.add(substatement); + public void removeStatementsFromEffectiveSubstatements( + final Collection> substatements) { + if (!effective.isEmpty()) { + effective.removeAll(substatements); + shrinkEffective(); + } } - public void addDeclaredSubstatement(StatementContextBase substatement){ - declared.add(substatement); + private void shrinkEffective() { + if (effective.isEmpty()) { + effective = ImmutableList.of(); + } } - @SuppressWarnings({ "rawtypes", "unchecked" }) - public ContextBuilder substatementBuilder(StatementDefinitionContext def, - StatementSourceReference ref) { - return new ContextBuilder(def, ref) { + public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef) { + if (effective.isEmpty()) { + return; + } - @Override - public StatementContextBase build() throws SourceException { - StatementContextBase potential = substatements.get(getIdentifier()); - if(potential == null) { - potential = new SubstatementContext(StatementContextBase.this, this); - substatements.put(getIdentifier(), potential); - } - potential.resetLists(); - switch (this.getStamementSource().getStatementSource()) { - case DECLARATION: - declared.add(potential); - break; - case CONTEXT: - effective.add(potential); - break; - } - return potential; + final Iterator> iterator = effective.iterator(); + while (iterator.hasNext()) { + final StmtContext next = iterator.next(); + if (statementDef.equals(next.getPublicDefinition())) { + iterator.remove(); + } + } + + shrinkEffective(); + } + + /** + * Removes a statement context from the effective substatements based on its statement definition (i.e statement + * keyword) and raw (in String form) statement argument. The statement context is removed only if both statement + * definition and statement argument match with one of the effective substatements' statement definition + * and argument. + * + *

+ * If the statementArg parameter is null, the statement context is removed based only on its statement definition. + * + * @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, + final String statementArg) { + if (statementArg == null) { + removeStatementFromEffectiveSubstatements(statementDef); + } + + if (effective.isEmpty()) { + return; + } + + final Iterator> iterator = effective.iterator(); + while (iterator.hasNext()) { + final Mutable next = iterator.next(); + if (statementDef.equals(next.getPublicDefinition()) && statementArg.equals(next.rawStatementArgument())) { + iterator.remove(); } - }; + } + + shrinkEffective(); } + /** + * 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 + */ + public void addEffectiveSubstatement(final Mutable substatement) { + beforeAddEffectiveStatement(1); + effective.add(substatement); + } + /** + * Adds an effective statement to collection of substatements. + * + * @param substatements substatements + * @throws IllegalStateException + * if added in declared phase + * @throws NullPointerException + * if statement parameter is null + */ + public void addEffectiveSubstatements(final Collection> substatements) { + if (substatements.isEmpty()) { + return; + } - @Override - public StorageNodeType getStorageNodeType() { - return StorageNodeType.STATEMENT_LOCAL; + substatements.forEach(Preconditions::checkNotNull); + beforeAddEffectiveStatement(substatements.size()); + effective.addAll(substatements); + } + + 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()); + + if (effective.isEmpty()) { + effective = new ArrayList<>(toAdd); + } + } + + /** + * 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 + */ + public final , CE extends EffectiveStatement> + StatementContextBase createSubstatement(final int offset, + final StatementDefinitionContext def, final StatementSourceReference ref, + final String argument) { + final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase(); + Preconditions.checkState(inProgressPhase != ModelProcessingPhase.EFFECTIVE_MODEL, + "Declared statement cannot be added in effective phase at: %s", getStatementSourceReference()); + + final Optional> implicitParent = definition.getImplicitParentFor(def.getPublicView()); + if (implicitParent.isPresent()) { + return createImplicitParent(offset, implicitParent.get(), ref, argument).createSubstatement(offset, def, + ref, argument); + } + + final StatementContextBase ret = new SubstatementContext<>(this, def, ref, argument); + substatements = substatements.put(offset, ret); + def.onStatementAdded(ret); + return ret; + } + + 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); + } + + /** + * Lookup substatement by its offset in this statement. + * + * @param offset Substatement offset + * @return Substatement, or null if substatement does not exist. + */ + final StatementContextBase lookupSubstatement(final int offset) { + return substatements.get(offset); } @Override public D buildDeclared() { - Preconditions.checkArgument(completedPhase == ModelProcessingPhase.FULL_DECLARATION || completedPhase == ModelProcessingPhase.EFFECTIVE_MODEL); + Preconditions.checkArgument(completedPhase == ModelProcessingPhase.FULL_DECLARATION + || completedPhase == ModelProcessingPhase.EFFECTIVE_MODEL); if (declaredInstance == null) { declaredInstance = definition().getFactory().createDeclared(this); } @@ -218,139 +453,291 @@ public abstract class StatementContextBase, E @Override public E buildEffective() { - Preconditions.checkArgument(completedPhase == ModelProcessingPhase.EFFECTIVE_MODEL); if (effectiveInstance == null) { effectiveInstance = definition().getFactory().createEffective(this); } return effectiveInstance; } + /** + * 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) { - void resetLists() { - declared.clear(); - } - - boolean tryToCompletePhase(ModelProcessingPhase phase) throws SourceException { - if(phase.equals(completedPhase)) { - return true; - } - Iterator openMutations = phaseMutation.get(phase).iterator(); boolean finished = true; - while(openMutations.hasNext()) { - ContextMutation current = openMutations.next(); - if(current.isFinished()) { - openMutations.remove(); - } else { - finished = false; + 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; + } + } + + if (openMutations.isEmpty()) { + phaseMutation.removeAll(phase); + if (phaseMutation.isEmpty()) { + phaseMutation = ImmutableMultimap.of(); + } } } - for(StatementContextBase child: declared) { + + for (final StatementContextBase child : substatements.values()) { finished &= child.tryToCompletePhase(phase); } - for(StatementContextBase child: effective) { - finished &= child.tryToCompletePhase(phase); + for (final Mutable child : effective) { + if (child instanceof StatementContextBase) { + finished &= ((StatementContextBase) child).tryToCompletePhase(phase); + } } - if(finished) { + + if (finished) { onPhaseCompleted(phase); return true; } return false; } - - private void onPhaseCompleted(ModelProcessingPhase phase) throws SourceException { + /** + * Occurs on end of {@link ModelProcessingPhase} of source parsing. + * + * @param phase + * that was to be completed (finished) + * @throws SourceException + * when an error occurred in source parsing + */ + private void onPhaseCompleted(final ModelProcessingPhase phase) { completedPhase = phase; - Iterator listener = phaseListeners.get(completedPhase).iterator(); - while(listener.hasNext()) { - listener.next().phaseFinished(this, phase); - listener.remove(); + + final Collection listeners = phaseListeners.get(phase); + if (listeners.isEmpty()) { + return; + } + + final Iterator listener = listeners.iterator(); + while (listener.hasNext()) { + final OnPhaseFinished next = listener.next(); + if (next.phaseFinished(this, phase)) { + listener.remove(); + } + } + + if (listeners.isEmpty()) { + phaseListeners.removeAll(phase); + if (phaseListeners.isEmpty()) { + phaseListeners = ImmutableMultimap.of(); + } } } /** - * * Ends declared section of current node. - * - * @param ref - * @throws SourceException - * */ - void endDeclared(StatementSourceReference ref,ModelProcessingPhase phase) throws SourceException { - definition().onDeclarationFinished(this,phase); + 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; } @Override - protected void checkLocalNamespaceAllowed(Class> type) { + protected void checkLocalNamespaceAllowed(final Class> type) { definition().checkNamespaceAllowed(type); } @Override - protected > void onNamespaceElementAdded(Class type, K key, V value) { - //definition().onNamespaceElementAdded(this, type, key, value); + protected > void onNamespaceElementAdded(final Class type, final K key, + final V value) { + // definition().onNamespaceElementAdded(this, type, key, value); } - > void onNamespaceItemAddedAction(final Class type, K key, final OnNamespaceItemAdded listener) throws SourceException { - Object potential = getFromNamespace(type, key); - if(potential != null) { + > void onNamespaceItemAddedAction(final Class type, final K key, + final OnNamespaceItemAdded listener) throws SourceException { + final Object potential = getFromNamespace(type, key); + if (potential != null) { + LOG.trace("Listener on {} key {} satisfied immediately", type, key); listener.namespaceItemAdded(this, type, key, potential); return; } - NamespaceBehaviour behaviour = getBehaviourRegistry().getNamespaceBehaviour(type); - if(behaviour instanceof NamespaceBehaviourWithListeners) { - NamespaceBehaviourWithListeners casted = (NamespaceBehaviourWithListeners) behaviour; - casted.addValueListener(key, new ValueAddedListener(this) { - @Override - void onValueAdded(Object key, Object value) { - try { - listener.namespaceItemAdded(StatementContextBase.this, type, key, value); - } catch (SourceException e) { - throw Throwables.propagate(e); - } - } - }); - } + + final NamespaceBehaviour behaviour = getBehaviourRegistry().getNamespaceBehaviour(type); + Preconditions.checkArgument(behaviour instanceof NamespaceBehaviourWithListeners, + "Namespace {} does not support listeners", type); + + final NamespaceBehaviourWithListeners casted = (NamespaceBehaviourWithListeners) behaviour; + casted.addValueListener(new ValueAddedListener(this, key) { + @Override + void onValueAdded(final Object key, final Object value) { + listener.namespaceItemAdded(StatementContextBase.this, type, key, value); + } + }); } + /** + * See {@link StatementSupport#getPublicView()}. + */ + @Nonnull @Override public StatementDefinition getPublicDefinition() { return definition().getPublicView(); } @Override - public ModelActionBuilder newInferenceAction(ModelProcessingPhase phase) { + public ModelActionBuilder newInferenceAction(final ModelProcessingPhase phase) { return getRoot().getSourceContext().newInferenceAction(phase); } - void addPhaseCompletedListener(ModelProcessingPhase phase, OnPhaseFinished listener) throws SourceException { + private static Multimap newMultimap() { + return Multimaps.newListMultimap(new EnumMap<>(ModelProcessingPhase.class), () -> new ArrayList<>(1)); + } + + /** + * Adds {@link OnPhaseFinished} listener for a {@link ModelProcessingPhase} end. + */ + 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()); + ModelProcessingPhase finishedPhase = completedPhase; while (finishedPhase != null) { - if(phase.equals(finishedPhase)) { + if (phase.equals(finishedPhase)) { listener.phaseFinished(this, finishedPhase); return; } finishedPhase = finishedPhase.getPreviousPhase(); } + if (phaseListeners.isEmpty()) { + phaseListeners = newMultimap(); + } + phaseListeners.put(phase, listener); } - void addMutation(ModelProcessingPhase phase, ContextMutation mutation) { + /** + * Adds a {@link ContextMutation} to a {@link ModelProcessingPhase}. + * + * @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) { - if(phase.equals(finishedPhase)) { - throw new IllegalStateException("Mutation registered after phase was completed."); - } + Preconditions.checkState(!phase.equals(finishedPhase), + "Mutation registered after phase was completed at: %s", getStatementSourceReference()); finishedPhase = finishedPhase.getPreviousPhase(); } + + if (phaseMutation.isEmpty()) { + phaseMutation = newMultimap(); + } phaseMutation.put(phase, mutation); } @Override - public > void addContext( - Class namepsace, KT key, StmtContext stmt) { - addContextToNamespace(namepsace,(K) key, stmt); + public > void addContext(final Class 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()); + + Preconditions.checkArgument(stmt instanceof SubstatementContext, "Unsupported statement %s", stmt); + + final SubstatementContext original = (SubstatementContext)stmt; + final SubstatementContext copy = new SubstatementContext<>(original, this, type, targetModule); + + original.definition().onStatementAdded(copy); + original.copyTo(copy, type, targetModule); + + return copy; + } + + final void copyTo(final StatementContextBase target, final CopyType typeOfCopy, + @Nullable final QNameModule targetModule) { + final Collection> buffer = new ArrayList<>(substatements.size() + effective.size()); + + for (final Mutable stmtContext : substatements.values()) { + if (stmtContext.isSupportedByFeatures()) { + copySubstatement(stmtContext, target, typeOfCopy, targetModule, buffer); + } + } + + for (final Mutable stmtContext : effective) { + copySubstatement(stmtContext, target, typeOfCopy, targetModule, buffer); + } + + target.addEffectiveSubstatements(buffer); + } + + 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); + } + } + + // 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 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()); + } + + LOG.debug("Will copy {} statement {}", def, stmtContext); + return true; + } + + private static boolean isReusedByUses(final StmtContext stmtContext) { + return REUSED_DEF_SET.contains(stmtContext.getPublicDefinition()); + } + + @Override + public final String toString() { + return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString(); + } + + protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) { + return toStringHelper.add("definition", definition).add("rawArgument", rawArgument); } }