/* * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ 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.Streams; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.yangtools.concepts.Immutable; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; 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.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.CopyType; import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStatementState; 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.StatementFactory; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext; 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; /** * A statement which has been inferred to exist. Functionally it is equivalent to a SubstatementContext, but it is not * backed by a declaration (and declared statements). It is backed by a prototype StatementContextBase and has only * effective substatements, which are either transformed from that prototype or added by inference. */ final class InferredStatementContext, E extends EffectiveStatement> extends StatementContextBase implements OnDemandSchemaTreeStorageNode { // An effective copy view, with enough information to decide what to do next private static final class EffectiveCopy implements Immutable { // Original statement private final ReactorStmtCtx orig; // Effective view, if the statement is to be reused it equals to orig private final ReactorStmtCtx copy; EffectiveCopy(final ReactorStmtCtx orig, final ReactorStmtCtx copy) { this.orig = requireNonNull(orig); this.copy = requireNonNull(copy); } boolean isReused() { return orig == copy; } ReactorStmtCtx toChildContext(final @NonNull InferredStatementContext parent) { return isReused() ? orig.replicaAsChildOf(parent) : copy; } ReactorStmtCtx toReusedChild(final @NonNull InferredStatementContext parent) { verify(isReused(), "Attempted to discard copy %s", copy); return orig.replicaAsChildOf(parent); } } private static final Logger LOG = LoggerFactory.getLogger(InferredStatementContext.class); // Sentinel objects for 'substatements', String is a good enough type private static final @NonNull String REUSED_SUBSTATEMENTS = "reused"; private static final @NonNull String SWEPT_SUBSTATEMENTS = "swept"; private final @NonNull StatementContextBase prototype; private final @NonNull StatementContextBase parent; private final @NonNull ReactorStmtCtx originalCtx; private final QNameModule targetModule; private final A argument; /** * Effective substatements, lazily materialized. This field can have four states: *
    *
  • it can be {@code null}, in which case no materialization has taken place
  • *
  • it can be a {@link HashMap}, in which case partial materialization has taken place
  • *
  • it can be a {@link List}, in which case full materialization has taken place
  • *
  • it can be {@link SWEPT_SUBSTATEMENTS}, in which case materialized state is no longer available
  • *
*/ private Object substatements; private InferredStatementContext(final InferredStatementContext original, final StatementContextBase parent) { super(original); this.parent = requireNonNull(parent); this.targetModule = original.targetModule; this.prototype = original.prototype; this.originalCtx = original.originalCtx; this.argument = original.argument; // Substatements are initialized here this.substatements = ImmutableList.of(); } InferredStatementContext(final StatementContextBase parent, final StatementContextBase prototype, final CopyType myCopyType, final CopyType childCopyType, final QNameModule targetModule) { super(prototype, myCopyType, childCopyType); this.parent = requireNonNull(parent); this.prototype = requireNonNull(prototype); this.argument = targetModule == null ? prototype.argument() : prototype.definition().adaptArgumentValue(prototype, targetModule); this.targetModule = targetModule; final var origCtx = prototype.getOriginalCtx().orElse(prototype); verify(origCtx instanceof ReactorStmtCtx, "Unexpected original %s", origCtx); this.originalCtx = (ReactorStmtCtx) origCtx; // Mark prototype as blocking statement cleanup prototype.incRef(); } @Override public Collection> mutableDeclaredSubstatements() { return ImmutableList.of(); } @Override public Collection> mutableEffectiveSubstatements() { return mutableEffectiveSubstatements(ensureEffectiveSubstatements()); } @Override public Iterable> allSubstatements() { // No need to concat with declared return effectiveSubstatements(); } @Override public Stream> allSubstatementsStream() { // No need to concat with declared return effectiveSubstatements().stream(); } @Override public StatementSourceReference sourceReference() { return originalCtx.sourceReference(); } @Override public String rawArgument() { return originalCtx.rawArgument(); } @Override public Optional> getOriginalCtx() { return Optional.of(originalCtx); } @Override public Optional> getPreviousCopyCtx() { return Optional.of(prototype); } @Override public D declared() { /* * Share original instance of declared statement between all effective statements which have been copied or * derived from this original declared statement. */ return originalCtx.declared(); } @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); afterAddEffectiveSubstatement(substatement); } @Override void addEffectiveSubstatementsImpl(final Collection> statements) { substatements = addEffectiveSubstatementsImpl(ensureEffectiveSubstatements(), statements); } @Override InferredStatementContext reparent(final StatementContextBase newParent) { return new InferredStatementContext<>(this, newParent); } @Override E createEffective(final StatementFactory factory) { // If we have not materialized we do not have a difference in effective substatements, hence we can forward // towards the source of the statement. accessSubstatements(); return substatements == null ? tryToReusePrototype(factory) : createInferredEffective(factory); } private @NonNull E createInferredEffective(final @NonNull StatementFactory factory) { return createInferredEffective(factory, this, streamDeclared(), streamEffective()); } @Override E createInferredEffective(final StatementFactory factory, final InferredStatementContext ctx, final Stream> declared, final Stream> effective) { return originalCtx.createInferredEffective(factory, ctx, declared, effective); } private @NonNull E tryToReusePrototype(final @NonNull StatementFactory factory) { final E origEffective = prototype.buildEffective(); final Collection> origSubstatements = origEffective.effectiveSubstatements(); // First check if we can reuse the entire prototype if (!factory.canReuseCurrent(this, prototype, origSubstatements)) { return internAlongCopyAxis(factory, tryToReuseSubstatements(factory, origEffective)); } // We can reuse this statement let's see if all statements agree... // ... no substatements to deal with, we can freely reuse the original if (origSubstatements.isEmpty()) { LOG.debug("Reusing empty: {}", origEffective); substatements = ImmutableList.of(); prototype.decRef(); return origEffective; } // ... all are context independent, reuse the original if (allSubstatementsContextIndependent()) { LOG.debug("Reusing context-independent: {}", origEffective); substatements = noRefs() ? REUSED_SUBSTATEMENTS : reusePrototypeReplicas(); prototype.decRef(); return origEffective; } // ... copy-sensitive check final List declCopy = prototype.streamDeclared() .map(sub -> effectiveCopy((ReactorStmtCtx) sub)) .filter(Objects::nonNull) .collect(Collectors.toUnmodifiableList()); final List effCopy = prototype.streamEffective() .map(sub -> effectiveCopy((ReactorStmtCtx) sub)) .filter(Objects::nonNull) .collect(Collectors.toUnmodifiableList()); // ... are any copy-sensitive? if (allReused(declCopy) && allReused(effCopy)) { LOG.debug("Reusing after substatement check: {}", origEffective); substatements = noRefs() ? REUSED_SUBSTATEMENTS : reusePrototypeReplicas(Streams.concat(declCopy.stream(), effCopy.stream()) .map(copy -> copy.toReusedChild(this))); prototype.decRef(); return origEffective; } // *sigh*, ok, heavy lifting through a shallow copy final List> declared = declCopy.stream() .map(copy -> copy.toChildContext(this)) .collect(ImmutableList.toImmutableList()); final List> effective = effCopy.stream() .map(copy -> copy.toChildContext(this)) .collect(ImmutableList.toImmutableList()); substatements = declared.isEmpty() ? effective : Streams.concat(declared.stream(), effective.stream()).collect(ImmutableList.toImmutableList()); prototype.decRef(); // Values are the effective copies, hence this efficiently deals with recursion. return internAlongCopyAxis(factory, originalCtx.createInferredEffective(factory, this, declared.stream(), effective.stream())); } private @NonNull E tryToReuseSubstatements(final @NonNull StatementFactory factory, final @NonNull E original) { if (allSubstatementsContextIndependent()) { LOG.debug("Reusing substatements of: {}", prototype); substatements = noRefs() ? REUSED_SUBSTATEMENTS : reusePrototypeReplicas(); prototype.decRef(); return factory.copyEffective(this, original); } // Fall back to full instantiation, which populates our substatements. Then check if we should be reusing // the substatement list, as this operation turned out to not affect them. final E effective = createInferredEffective(factory); // Since we have forced instantiation to deal with this case, we also need to reset the 'modified' flag setUnmodified(); if (sameSubstatements(original.effectiveSubstatements(), effective)) { LOG.debug("Reusing unchanged substatements of: {}", prototype); return factory.copyEffective(this, original); } return effective; } private @NonNull E internAlongCopyAxis(final StatementFactory factory, final @NonNull E stmt) { if (!isModified()) { final EffectiveStatementState state = factory.extractEffectiveState(stmt); if (state != null) { return prototype.unmodifiedEffectiveSource().attachEffectiveCopy(state, stmt); } } return stmt; } private List> reusePrototypeReplicas() { return reusePrototypeReplicas(Streams.concat(prototype.streamDeclared(), prototype.streamEffective())); } private List> reusePrototypeReplicas(final Stream> stream) { return stream .map(stmt -> { final ReplicaStatementContext ret = ((ReactorStmtCtx) stmt).replicaAsChildOf(this); ret.buildEffective(); return ret; }) .collect(Collectors.toUnmodifiableList()); } private static boolean sameSubstatements(final Collection original, final EffectiveStatement effective) { final Collection copied = effective.effectiveSubstatements(); if (copied != effective.effectiveSubstatements() || original.size() != copied.size()) { // Do not bother if result is treating substatements as transient return false; } final Iterator oit = original.iterator(); final Iterator cit = copied.iterator(); while (oit.hasNext()) { verify(cit.hasNext()); // Identity comparison on purpose to side-step whatever equality there might be. We want to reuse instances // after all. if (oit.next() != cit.next()) { return false; } } verify(!cit.hasNext()); return true; } private static boolean allReused(final List entries) { return entries.stream().allMatch(EffectiveCopy::isReused); } @Override ReactorStmtCtx unmodifiedEffectiveSource() { return isModified() ? this : prototype.unmodifiedEffectiveSource(); } @Override boolean hasEmptySubstatements() { if (substatements == null) { return prototype.hasEmptySubstatements(); } return substatements instanceof HashMap ? false : ((List) substatements).isEmpty(); } @Override boolean noSensitiveSubstatements() { accessSubstatements(); if (substatements == null) { // No difference, defer to prototype return prototype.allSubstatementsContextIndependent(); } if (substatements instanceof List) { // Fully materialized, walk all statements return noSensitiveSubstatements(castEffective(substatements)); } // Partially-materialized. This case has three distinct outcomes: // - prototype does not have a sensitive statement (1) // - protype has a sensitive substatement, and // - we have not marked is as unsupported (2) // - we have marked it as unsupported (3) // // Determining the outcome between (2) and (3) is a bother, this check errs on the side of false negative side // and treats (3) as (2) -- i.e. even if we marked a sensitive statement as unsupported, we still consider it // as affecting the result. return prototype.allSubstatementsContextIndependent() && noSensitiveSubstatements(castMaterialized(substatements).values()); } @Override > @NonNull Optional findSubstatementArgumentImpl( final @NonNull Class type) { if (substatements instanceof List) { return super.findSubstatementArgumentImpl(type); } final Optional templateArg = prototype.findSubstatementArgument(type); if (templateArg.isEmpty()) { return templateArg; } if (SchemaTreeEffectiveStatement.class.isAssignableFrom(type)) { // X is known to be QName return (Optional) templateArg.map(template -> ((QName) template).bindTo(targetModule)); } return templateArg; } @Override boolean hasSubstatementImpl(final @NonNull Class> type) { return substatements instanceof List ? super.hasSubstatementImpl(type) // We do not allow deletion of partially-materialized statements, hence this is accurate : prototype.hasSubstatement(type); } @Override public , Z extends SchemaTreeEffectiveStatement> StmtContext 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.argument())) .findAny() .orElse(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 ret = (Mutable) copySubstatement((Mutable) template) .orElseThrow( () -> new InferenceException(this, "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. private List> ensureEffectiveSubstatements() { accessSubstatements(); return substatements instanceof List ? castEffective(substatements) : initializeSubstatements(castMaterialized(substatements)); } @Override Iterator> 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). if (substatements == null) { return Collections.emptyIterator(); } accessSubstatements(); if (substatements instanceof HashMap) { return castMaterialized(substatements).values().iterator(); } else { return castEffective(substatements).iterator(); } } @Override Stream> streamDeclared() { return Stream.empty(); } @Override Stream> streamEffective() { return ensureEffectiveSubstatements().stream().filter(StmtContext::isSupportedToBuildEffective); } private void accessSubstatements() { if (substatements instanceof String) { throw new VerifyException("Access to " + substatements + " substatements of " + this); } } @Override void markNoParentRef() { final Object local = substatements; if (local != null) { markNoParentRef(castEffective(local)); } } @Override int sweepSubstatements() { final Object local = substatements; substatements = SWEPT_SUBSTATEMENTS; int count = 0; if (local instanceof List) { final List> list = castEffective(local); sweep(list); count = countUnswept(list); } return count; } private List> initializeSubstatements( final Map, ReactorStmtCtx> materializedSchemaTree) { final Collection> declared = prototype.mutableDeclaredSubstatements(); final Collection> effective = prototype.mutableEffectiveSubstatements(); final List> buffer = new ArrayList<>(declared.size() + effective.size()); for (final Mutable stmtContext : declared) { if (stmtContext.isSupportedByFeatures()) { copySubstatement(stmtContext, buffer, materializedSchemaTree); } } for (final Mutable stmtContext : effective) { copySubstatement(stmtContext, buffer, materializedSchemaTree); } final List> ret = beforeAddEffectiveStatementUnsafe(ImmutableList.of(), buffer.size()); ret.addAll((Collection) buffer); substatements = ret; setModified(); prototype.decRef(); return ret; } // // Statement copy mess starts here. As it turns out, it's not that much of a mess, but it does make your head spin // sometimes. Tread softly because you tread on my dreams. // private EffectiveCopy effectiveCopy(final ReactorStmtCtx stmt) { final ReactorStmtCtx effective = stmt.asEffectiveChildOf(this, childCopyType(), targetModule); return effective == null ? null : new EffectiveCopy(stmt, effective); } private void copySubstatement(final Mutable substatement, final Collection> buffer, final Map, ReactorStmtCtx> materializedSchemaTree) { // 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 ReactorStmtCtx materialized = findMaterialized(materializedSchemaTree, substatement); if (materialized == null) { copySubstatement(substatement).ifPresent(copy -> { ensureCompletedPhase(copy); buffer.add(copy); }); } else { buffer.add(materialized); } } private Optional> copySubstatement(final Mutable substatement) { return substatement.copyAsChildOf(this, childCopyType(), targetModule); } private void addMaterialized(final StmtContext template, final Mutable copy) { final HashMap, ReactorStmtCtx> 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; setModified(); } 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.argument() + " previous result was " + existing); } } private static @Nullable ReactorStmtCtx findMaterialized( final Map, ReactorStmtCtx> materializedSchemaTree, final StmtContext template) { return materializedSchemaTree == null ? null : materializedSchemaTree.get(template); } @SuppressWarnings("unchecked") private static List> castEffective(final Object substatements) { return (List>) substatements; } @SuppressWarnings("unchecked") private static HashMap, ReactorStmtCtx> castMaterialized(final Object substatements) { return (HashMap, ReactorStmtCtx>) substatements; } // Statement copy mess ends here /* * KEEP THINGS ORGANIZED! * * below methods exist in the same form in SubstatementContext. If any adjustment is made here, make sure it is * properly updated there. */ @Override public A argument() { return argument; } @Override public StatementContextBase getParentContext() { return parent; } @Override public StorageNodeType getStorageNodeType() { return StorageNodeType.STATEMENT_LOCAL; } @Override public StatementContextBase getParentNamespaceStorage() { return parent; } @Override public RootStatementContext getRoot() { return parent.getRoot(); } @Override public EffectiveConfig effectiveConfig() { return effectiveConfig(parent); } @Override protected boolean isIgnoringIfFeatures() { return isIgnoringIfFeatures(parent); } @Override protected boolean isIgnoringConfig() { return isIgnoringConfig(parent); } @Override protected boolean isParentSupportedByFeatures() { return parent.isSupportedByFeatures(); } }