*/
package org.opendaylight.yangtools.yang.parser.stmt.reactor;
+import static com.google.common.base.Verify.verify;
import static java.util.Objects.requireNonNull;
+import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.model.api.SchemaPath;
import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
+import org.opendaylight.yangtools.yang.parser.spi.SchemaTreeNamespace;
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.InferenceException;
import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.OnDemandSchemaTreeStorageNode;
import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.StorageNodeType;
import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextDefaults;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private final QNameModule targetModule;
private final A argument;
+ /**
+ * Effective substatements, lazily materialized. This field can have three states:
+ * <ul>
+ * <li>it can be {@code null}, in which case no materialization has taken place</li>
+ * <li>it can be a {@link HashMap}, in which case partial materialization has taken place</li>
+ * <li>it can be a {@link List}, in which case full materialization has taken place</li>
+ * </ul>
+ */
+ private Object substatements;
+
private InferredStatementContext(final InferredStatementContext<A, D, E> original,
final StatementContextBase<?, ?, ?> parent) {
super(original);
this.prototype = original.prototype;
this.originalCtx = original.originalCtx;
this.argument = original.argument;
- setSubstatementsInitialized();
+ // Substatements are initialized here
+ this.substatements = ImmutableList.of();
}
InferredStatementContext(final StatementContextBase<?, ?, ?> parent, final StatementContextBase<A, D, E> prototype,
this.childCopyType = requireNonNull(childCopyType);
this.targetModule = targetModule;
this.originalCtx = prototype.getOriginalCtx().orElse(prototype);
-
- // Note: substatements from prototype are initialized lazily through ensureSubstatements()
}
@Override
return ImmutableList.of();
}
+ @Override
+ public Collection<? extends Mutable<?, ?, ?>> mutableEffectiveSubstatements() {
+ return mutableEffectiveSubstatements(ensureEffectiveSubstatements());
+ }
+
@Override
public Iterable<? extends StmtContext<?, ?, ?>> allSubstatements() {
// No need to concat with declared
return originalCtx.buildDeclared();
}
+ @Override
+ public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef) {
+ substatements = removeStatementFromEffectiveSubstatements(ensureEffectiveSubstatements(), statementDef);
+ }
+
+ @Override
+ public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef,
+ final String statementArg) {
+ substatements = removeStatementFromEffectiveSubstatements(ensureEffectiveSubstatements(), statementDef,
+ statementArg);
+ }
+
+ @Override
+ public void addEffectiveSubstatement(final Mutable<?, ?, ?> substatement) {
+ substatements = addEffectiveSubstatement(ensureEffectiveSubstatements(), substatement);
+ }
+
+ @Override
+ void addEffectiveSubstatementsImpl(final Collection<? extends Mutable<?, ?, ?>> statements) {
+ substatements = addEffectiveSubstatementsImpl(ensureEffectiveSubstatements(), statements);
+ }
+
@Override
InferredStatementContext<A, D, E> reparent(final StatementContextBase<?, ?, ?> newParent) {
return new InferredStatementContext<>(this, newParent);
@Override
boolean hasEmptySubstatements() {
- ensureEffectiveSubstatements();
- return hasEmptyEffectiveSubstatements();
+ if (substatements == null) {
+ return prototype.hasEmptySubstatements();
+ }
+ return substatements instanceof HashMap ? false : ((List<?>) substatements).isEmpty();
}
@Override
public <X, Z extends EffectiveStatement<X, ?>> @NonNull Optional<X> findSubstatementArgument(
final @NonNull Class<Z> type) {
- if (substatementsInitialized()) {
+ if (substatements instanceof List) {
return StmtContextDefaults.findSubstatementArgument(this, type);
}
@Override
public boolean hasSubstatement(final @NonNull Class<? extends EffectiveStatement<?, ?>> type) {
- return substatementsInitialized() ? StmtContextDefaults.hasSubstatement(prototype, type)
+ return substatements instanceof List ? StmtContextDefaults.hasSubstatement(prototype, type)
+ // We do not allow deletion of partially-materialized statements, hence this is accurate
: prototype.hasSubstatement(type);
}
@Override
- public <D extends DeclaredStatement<QName>, E extends EffectiveStatement<QName, D>>
- StmtContext<QName, D, E> requestSchemaTreeChild(final QName qname) {
- LOG.debug("Materializing on lookup of {}", qname);
- // FIXME: YANGTOOLS-1160: we do not want to force full materialization here
- ensureEffectiveSubstatements();
-
- // Now we have to do a lookup as we do not have access to the namespace being populated (yet). Here we are
- // bypassing additional checks and talk directly to superclass to get the statements.
- for (StmtContext<?, ?, ?> stmt : super.mutableEffectiveSubstatements()) {
- if (stmt.producesEffective(SchemaTreeEffectiveStatement.class)
- && qname.equals(stmt.coerceStatementArgument())) {
- return (StmtContext<QName, D, E>) stmt;
- }
+ public <Y extends DeclaredStatement<QName>, Z extends EffectiveStatement<QName, Y>>
+ StmtContext<QName, Y, Z> requestSchemaTreeChild(final QName qname) {
+ if (substatements instanceof List) {
+ // We have performed materialization, hence we have triggered creation of all our schema tree child
+ // statements.
+ return null;
+ }
+
+ final QName templateQName = qname.bindTo(StmtContextUtils.getRootModuleQName(prototype));
+ LOG.debug("Materializing child {} from {}", qname, templateQName);
+
+ final StmtContext<?, ?, ?> template;
+ if (prototype instanceof InferredStatementContext) {
+ // Note: we need to access namespace here, as the target statement may have already been populated, in which
+ // case we want to obtain the statement in local namespace storage.
+ template = (StmtContext) ((InferredStatementContext<?, ?, ?>) prototype).getFromNamespace(
+ SchemaTreeNamespace.class, templateQName);
+ } else {
+ template = prototype.allSubstatementsStream()
+ .filter(stmt -> stmt.producesEffective(SchemaTreeEffectiveStatement.class)
+ && templateQName.equals(stmt.getStatementArgument()))
+ .findAny()
+ .orElse(null);
}
- return null;
+
+ if (template == null) {
+ // We do not have a template, this child does not exist. It may be added later, but that is someone else's
+ // responsibility.
+ LOG.debug("Child {} does not have a template", qname);
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ final Mutable<QName, Y, Z> ret = (Mutable<QName, Y, Z>) copySubstatement((Mutable<?, ?, ?>) template)
+ .orElseThrow(() -> new InferenceException(getStatementSourceReference(),
+ "Failed to materialize child %s template %s", qname, template));
+ ensureCompletedPhase(ret);
+ addMaterialized(template, ret);
+
+ LOG.debug("Child {} materialized", qname);
+ return ret;
}
// Instantiate this statement's effective substatements. Note this method has side-effects in namespaces and overall
// BuildGlobalContext, hence it must be called at most once.
- @Override
- void ensureEffectiveSubstatements() {
- if (!substatementsInitialized()) {
- initializeSubstatements();
- }
+ private List<StatementContextBase<?, ?, ?>> ensureEffectiveSubstatements() {
+ return substatements instanceof List ? castEffective(substatements)
+ : initializeSubstatements(castMaterialized(substatements));
}
@Override
Iterable<StatementContextBase<?, ?, ?>> effectiveChildrenToComplete() {
// When we have not initialized, there are no statements to catch up: we will catch up when we are copying
- // from prototype (which is already at ModelProcessingPhase.EFFECTIVE_MODEL)
- return substatementsInitialized() ? super.effectiveChildrenToComplete() : List.of();
+ // from prototype (which is already at ModelProcessingPhase.EFFECTIVE_MODEL).
+ if (substatements == null) {
+ return ImmutableList.of();
+ } else if (substatements instanceof HashMap) {
+ return castMaterialized(substatements).values();
+ } else {
+ return castEffective(substatements);
+ }
}
- private void initializeSubstatements() {
+ private List<StatementContextBase<?, ?, ?>> initializeSubstatements(
+ final Map<StmtContext<?, ?, ?>, StatementContextBase<?, ?, ?>> materializedSchemaTree) {
final Collection<? extends StatementContextBase<?, ?, ?>> declared = prototype.mutableDeclaredSubstatements();
final Collection<? extends Mutable<?, ?, ?>> effective = prototype.mutableEffectiveSubstatements();
final List<Mutable<?, ?, ?>> buffer = new ArrayList<>(declared.size() + effective.size());
for (final Mutable<?, ?, ?> stmtContext : declared) {
if (stmtContext.isSupportedByFeatures()) {
- copySubstatement(stmtContext, buffer);
+ copySubstatement(stmtContext, buffer, materializedSchemaTree);
}
}
for (final Mutable<?, ?, ?> stmtContext : effective) {
- copySubstatement(stmtContext, buffer);
+ copySubstatement(stmtContext, buffer, materializedSchemaTree);
}
- // We are bypassing usual safeties here, as this is not introducing new statements but rather just materializing
- // them when the need has arised.
- addInitialEffectiveSubstatements(buffer);
+ final List<StatementContextBase<?, ?, ?>> ret = beforeAddEffectiveStatementUnsafe(ImmutableList.of(),
+ buffer.size());
+ ret.addAll((Collection) buffer);
+ substatements = ret;
+ return ret;
}
// Statement copy mess starts here
YangStmtMapping.TYPEDEF,
YangStmtMapping.USES);
- private void copySubstatement(final Mutable<?, ?, ?> substatement, final Collection<Mutable<?, ?, ?>> buffer) {
+ private void copySubstatement(final Mutable<?, ?, ?> substatement, final Collection<Mutable<?, ?, ?>> buffer,
+ final Map<StmtContext<?, ?, ?>, StatementContextBase<?, ?, ?>> materializedSchemaTree) {
final StatementDefinition def = substatement.getPublicDefinition();
// FIXME: YANGTOOLS-652: formerly known as "isReusedByUses"
return;
}
- substatement.copyAsChildOf(this, childCopyType, targetModule).ifPresent(buffer::add);
+ // Consult materialized substatements. We are in a copy operation and will end up throwing materialized
+ // statements away -- hence we do not perform Map.remove() to save ourselves a mutation operation.
+ //
+ // We could also perform a Map.containsKey() and perform a bulk add, but that would mean the statement order
+ // against parent would change -- and we certainly do not want that to happen.
+ final StatementContextBase<?, ?, ?> materialized = findMaterialized(materializedSchemaTree, substatement);
+ if (materialized == null) {
+ copySubstatement(substatement).ifPresent(copy -> {
+ ensureCompletedPhase(copy);
+ buffer.add(copy);
+ });
+ } else {
+ buffer.add(materialized);
+ }
+ }
+
+ private Optional<? extends Mutable<?, ?, ?>> copySubstatement(final Mutable<?, ?, ?> substatement) {
+ return substatement.copyAsChildOf(this, childCopyType, targetModule);
+ }
+
+ private void addMaterialized(final StmtContext<?, ?, ?> template, final Mutable<?, ?, ?> copy) {
+ final HashMap<StmtContext<?, ?, ?>, StatementContextBase<?, ?, ?>> materializedSchemaTree;
+ if (substatements == null) {
+ // Lazy initialization of backing map. We do not expect this to be used often or multiple times -- each hit
+ // here means an inference along schema tree, such as deviate/augment. HashMap requires power-of-two and
+ // defaults to 0.75 load factor -- we therefore size it to 4, i.e. next two inserts will not cause a
+ // resizing operation.
+ materializedSchemaTree = new HashMap<>(4);
+ substatements = materializedSchemaTree;
+ } else {
+ verify(substatements instanceof HashMap, "Unexpected substatements %s", substatements);
+ materializedSchemaTree = castMaterialized(substatements);
+ }
+
+ final StmtContext<?, ?, ?> existing = materializedSchemaTree.put(template,
+ (StatementContextBase<?, ?, ?>) copy);
+ if (existing != null) {
+ throw new VerifyException(
+ "Unexpected duplicate request for " + copy.getStatementArgument() + " previous result was " + existing);
+ }
+ }
+
+ private static @Nullable StatementContextBase<?, ?, ?> findMaterialized(
+ final Map<StmtContext<?, ?, ?>, StatementContextBase<?, ?, ?>> materializedSchemaTree,
+ final StmtContext<?, ?, ?> template) {
+ return materializedSchemaTree == null ? null : materializedSchemaTree.get(template);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static List<StatementContextBase<?, ?, ?>> castEffective(final Object substatements) {
+ return (List<StatementContextBase<?, ?, ?>>) substatements;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static HashMap<StmtContext<?, ?, ?>, StatementContextBase<?, ?, ?>> castMaterialized(
+ final Object substatements) {
+ return (HashMap<StmtContext<?, ?, ?>, StatementContextBase<?, ?, ?>>) substatements;
}
// Statement copy mess ends here
private Multimap<ModelProcessingPhase, OnPhaseFinished> phaseListeners = ImmutableMultimap.of();
private Multimap<ModelProcessingPhase, ContextMutation> phaseMutation = ImmutableMultimap.of();
- // Note: this field is accessed either directly, or under substatementsInitialized == true
- private List<StatementContextBase<?, ?, ?>> effective = ImmutableList.of();
-
private List<StmtContext<?, ?, ?>> effectOfStatement = ImmutableList.of();
private @Nullable ModelProcessingPhase completedPhase;
// Flag for use with AbstractResumedStatement. This is hiding in the alignment shadow created by above boolean
private boolean fullyDefined;
- // Flag for InferredStatementContext. This is hiding in the alignment shadow created by above boolean.
- private boolean substatementsInitialized;
-
// Flags for use with SubstatementContext. These are hiding in the alignment shadow created by above boolean and
// hence improve memory layout.
private byte flags;
return getBehaviourRegistry().getNamespaceBehaviour(type).getFrom(this, key);
}
- @Override
- public final Collection<? extends Mutable<?, ?, ?>> mutableEffectiveSubstatements() {
- ensureEffectiveSubstatements();
- if (effective instanceof ImmutableCollection) {
- return effective;
- }
-
- return Collections.unmodifiableCollection(effective);
+ static final Collection<? extends Mutable<?, ?, ?>> mutableEffectiveSubstatements(
+ final List<StatementContextBase<?, ?, ?>> effective) {
+ return effective instanceof ImmutableCollection ? effective : Collections.unmodifiableCollection(effective);
}
- private void shrinkEffective() {
- // Initialization guarded by all callers
- if (effective.isEmpty()) {
- effective = ImmutableList.of();
- }
+ private static List<StatementContextBase<?, ?, ?>> shrinkEffective(
+ final List<StatementContextBase<?, ?, ?>> effective) {
+ return effective.isEmpty() ? ImmutableList.of() : effective;
}
- // Note: has side-effect of ensureEffectiveSubstatements()
- public final void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef) {
- ensureEffectiveSubstatements();
+ public abstract void removeStatementFromEffectiveSubstatements(StatementDefinition statementDef);
+
+ static final List<StatementContextBase<?, ?, ?>> removeStatementFromEffectiveSubstatements(
+ final List<StatementContextBase<?, ?, ?>> effective, final StatementDefinition statementDef) {
if (effective.isEmpty()) {
- return;
+ return effective;
}
final Iterator<? extends StmtContext<?, ?, ?>> iterator = effective.iterator();
}
}
- shrinkEffective();
+ return shrinkEffective(effective);
}
/**
* @param statementDef statement definition of the statement context to remove
* @param statementArg statement argument of the statement context to remove
*/
- public final void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef,
+ public abstract void removeStatementFromEffectiveSubstatements(StatementDefinition statementDef,
+ String statementArg);
+
+ static final List<StatementContextBase<?, ?, ?>> removeStatementFromEffectiveSubstatements(
+ final List<StatementContextBase<?, ?, ?>> effective, final StatementDefinition statementDef,
final String statementArg) {
if (statementArg == null) {
- // Note: has side-effect of ensureEffectiveSubstatements()
- removeStatementFromEffectiveSubstatements(statementDef);
- } else {
- ensureEffectiveSubstatements();
+ return removeStatementFromEffectiveSubstatements(effective, statementDef);
}
if (effective.isEmpty()) {
- return;
+ return effective;
}
final Iterator<StatementContextBase<?, ?, ?>> iterator = effective.iterator();
}
}
- shrinkEffective();
+ return shrinkEffective(effective);
}
// YANG example: RPC/action statements always have 'input' and 'output' defined
* 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 final void addEffectiveSubstatement(final Mutable<?, ?, ?> substatement) {
+ public abstract void addEffectiveSubstatement(Mutable<?, ?, ?> substatement);
+
+ final List<StatementContextBase<?, ?, ?>> addEffectiveSubstatement(
+ final List<StatementContextBase<?, ?, ?>> effective, final Mutable<?, ?, ?> substatement) {
verifyStatement(substatement);
- ensureEffectiveSubstatements();
- beforeAddEffectiveStatement(1);
+ final List<StatementContextBase<?, ?, ?>> resized = beforeAddEffectiveStatement(effective, 1);
final StatementContextBase<?, ?, ?> stmt = (StatementContextBase<?, ?, ?>) substatement;
final ModelProcessingPhase phase = completedPhase;
if (phase != null) {
ensureCompletedPhase(stmt, phase);
}
- effective.add(stmt);
+ resized.add(stmt);
+ return resized;
}
/**
public final void addEffectiveSubstatements(final Collection<? extends Mutable<?, ?, ?>> statements) {
if (!statements.isEmpty()) {
statements.forEach(StatementContextBase::verifyStatement);
- ensureEffectiveSubstatements();
- beforeAddEffectiveStatement(statements.size());
- doAddEffectiveSubstatements(statements);
+ addEffectiveSubstatementsImpl(statements);
}
}
- // exposed for InferredStatementContext, which we expect to initialize effective substatements
- void ensureEffectiveSubstatements() {
- // No-op for everything except InferredStatementContext
- }
-
- // Exposed for InferredStatementContextr only, others do not need initialization
- Iterable<StatementContextBase<?, ?, ?>> effectiveChildrenToComplete() {
- return effective;
- }
+ abstract void addEffectiveSubstatementsImpl(Collection<? extends Mutable<?, ?, ?>> statements);
- // exposed for InferredStatementContext only
- final void addInitialEffectiveSubstatements(final Collection<? extends Mutable<?, ?, ?>> statements) {
- verify(!substatementsInitialized, "Attempted to re-initialized statement {} with {}", this, statements);
- substatementsInitialized = true;
-
- if (!statements.isEmpty()) {
- statements.forEach(StatementContextBase::verifyStatement);
- beforeAddEffectiveStatementUnsafe(statements.size());
- doAddEffectiveSubstatements(statements);
- }
- }
-
- private void doAddEffectiveSubstatements(final Collection<? extends Mutable<?, ?, ?>> statements) {
+ final List<StatementContextBase<?, ?, ?>> addEffectiveSubstatementsImpl(
+ final List<StatementContextBase<?, ?, ?>> effective,
+ final Collection<? extends Mutable<?, ?, ?>> statements) {
+ final List<StatementContextBase<?, ?, ?>> resized = beforeAddEffectiveStatement(effective, statements.size());
final Collection<? extends StatementContextBase<?, ?, ?>> casted =
(Collection<? extends StatementContextBase<?, ?, ?>>) statements;
final ModelProcessingPhase phase = completedPhase;
}
}
- // Initialization guarded by all callers
- effective.addAll(casted);
+ resized.addAll(casted);
+ return resized;
+ }
+
+ abstract Iterable<StatementContextBase<?, ?, ?>> effectiveChildrenToComplete();
+
+ // exposed for InferredStatementContext only
+ final void ensureCompletedPhase(final Mutable<?, ?, ?> stmt) {
+ verifyStatement(stmt);
+ final ModelProcessingPhase phase = completedPhase;
+ if (phase != null) {
+ ensureCompletedPhase((StatementContextBase<?, ?, ?>) stmt, phase);
+ }
}
// Make sure target statement has transitioned at least to specified phase. This method is just before we take
verify(stmt instanceof StatementContextBase, "Unexpected statement %s", stmt);
}
- private void beforeAddEffectiveStatement(final int toAdd) {
+ private List<StatementContextBase<?, ?, ?>> beforeAddEffectiveStatement(
+ final List<StatementContextBase<?, ?, ?>> effective, final int toAdd) {
// We cannot allow statement to be further mutated
verify(completedPhase != ModelProcessingPhase.EFFECTIVE_MODEL, "Cannot modify finished statement at %s",
getStatementSourceReference());
- beforeAddEffectiveStatementUnsafe(toAdd);
+ return beforeAddEffectiveStatementUnsafe(effective, toAdd);
}
- private void beforeAddEffectiveStatementUnsafe(final int toAdd) {
+ final List<StatementContextBase<?, ?, ?>> beforeAddEffectiveStatementUnsafe(
+ final List<StatementContextBase<?, ?, ?>> effective, final int toAdd) {
final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase();
checkState(inProgressPhase == ModelProcessingPhase.FULL_DECLARATION
|| inProgressPhase == ModelProcessingPhase.EFFECTIVE_MODEL,
"Effective statement cannot be added in declared phase at: %s", getStatementSourceReference());
- // Initialization guarded by all callers
- if (effective.isEmpty()) {
- effective = new ArrayList<>(toAdd);
- }
+ return effective.isEmpty() ? new ArrayList<>(toAdd) : effective;
}
// These two exists only due to memory optimization, should live in AbstractResumedStatement
fullyDefined = true;
}
- // These two exist only due to memory optimization, should live in InferredStatementContext
- final boolean substatementsInitialized() {
- return substatementsInitialized;
- }
-
- final void setSubstatementsInitialized() {
- substatementsInitialized = true;
- }
-
@Override
public E buildEffective() {
final E existing;
*/
abstract boolean hasEmptySubstatements();
- // Dual use method: AbstractResumedStatement does not use 'initialized' and InferredStatementContext ensures
- // initialization.
- // FIXME: 7.0.0: I think this warrants a separate subclasses, as InferredStatementContext wants to manage these
- // itself. Before we do that, though, we need to analyze size impacts
- final boolean hasEmptyEffectiveSubstatements() {
- return effective.isEmpty();
- }
-
/**
* Config statements are not all that common which means we are performing a recursive search towards the root
* every time {@link #isConfiguration()} is invoked. This is quite expensive because it causes a linear search