Do not synchronize around ReactorStmtCtx.schemaPath
[yangtools.git] / yang / yang-parser-reactor / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / StatementContextBase.java
index 79419b69b6b775f859ea1334cc5d1d38e9af923b..a9aa4099c3c1ac48d09962bb526a19b5a00153b2 100644 (file)
@@ -44,7 +44,7 @@ import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementSource;
 import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.ConfigStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.ConfigEffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.DeviationStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.RefineStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
@@ -69,7 +69,6 @@ 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;
@@ -141,7 +140,10 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
     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;
@@ -155,13 +157,16 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     // 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;
 
     // SchemaPath cache for use with SubstatementContext and InferredStatementContext. This hurts RootStatementContext
     // a bit in terms of size -- but those are only a few and SchemaPath is on its way out anyway.
-    private volatile SchemaPath schemaPath;
+    private SchemaPath schemaPath;
 
     // Copy constructor used by subclasses to implement reparent()
     StatementContextBase(final StatementContextBase<A, D, E> original) {
@@ -369,7 +374,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     @Override
-    public Collection<? extends Mutable<?, ?, ?>> mutableEffectiveSubstatements() {
+    public final Collection<? extends Mutable<?, ?, ?>> mutableEffectiveSubstatements() {
+        ensureEffectiveSubstatements();
         if (effective instanceof ImmutableCollection) {
             return effective;
         }
@@ -378,12 +384,15 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     private void shrinkEffective() {
+        // Initialization guarded by all callers
         if (effective.isEmpty()) {
             effective = ImmutableList.of();
         }
     }
 
-    public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef) {
+    // Note: has side-effect of ensureEffectiveSubstatements()
+    public final void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef) {
+        ensureEffectiveSubstatements();
         if (effective.isEmpty()) {
             return;
         }
@@ -411,10 +420,13 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, 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 final void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef,
             final String statementArg) {
         if (statementArg == null) {
+            // Note: has side-effect of ensureEffectiveSubstatements()
             removeStatementFromEffectiveSubstatements(statementDef);
+        } else {
+            ensureEffectiveSubstatements();
         }
 
         if (effective.isEmpty()) {
@@ -454,8 +466,9 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
      * @throws NullPointerException
      *             if statement parameter is null
      */
-    public void addEffectiveSubstatement(final Mutable<?, ?, ?> substatement) {
+    public final void addEffectiveSubstatement(final Mutable<?, ?, ?> substatement) {
         verifyStatement(substatement);
+        ensureEffectiveSubstatements();
         beforeAddEffectiveStatement(1);
 
         final StatementContextBase<?, ?, ?> stmt = (StatementContextBase<?, ?, ?>) substatement;
@@ -475,22 +488,49 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
      * @throws NullPointerException
      *             if statement parameter is null
      */
-    public void addEffectiveSubstatements(final Collection<? extends Mutable<?, ?, ?>> statements) {
+    public final void addEffectiveSubstatements(final Collection<? extends Mutable<?, ?, ?>> statements) {
         if (!statements.isEmpty()) {
             statements.forEach(StatementContextBase::verifyStatement);
+            ensureEffectiveSubstatements();
             beforeAddEffectiveStatement(statements.size());
+            doAddEffectiveSubstatements(statements);
+        }
+    }
 
-            final Collection<? extends StatementContextBase<?, ?, ?>> casted =
-                    (Collection<? extends StatementContextBase<?, ?, ?>>) statements;
-            final ModelProcessingPhase phase = completedPhase;
-            if (phase != null) {
-                for (StatementContextBase<?, ?, ?> stmt : casted) {
-                    ensureCompletedPhase(stmt, phase);
-                }
-            }
+    // exposed for InferredStatementContext, which we expect to initialize effective substatements
+    void ensureEffectiveSubstatements() {
+        // No-op for everything except InferredStatementContext
+    }
 
-            effective.addAll(casted);
+    // Exposed for InferredStatementContextr only, others do not need initialization
+    Iterable<StatementContextBase<?, ?, ?>> effectiveChildrenToComplete() {
+        return effective;
+    }
+
+    // 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 Collection<? extends StatementContextBase<?, ?, ?>> casted =
+            (Collection<? extends StatementContextBase<?, ?, ?>>) statements;
+        final ModelProcessingPhase phase = completedPhase;
+        if (phase != null) {
+            for (StatementContextBase<?, ?, ?> stmt : casted) {
+                ensureCompletedPhase(stmt, phase);
+            }
         }
+
+        // Initialization guarded by all callers
+        effective.addAll(casted);
     }
 
     // Make sure target statement has transitioned at least to specified phase. This method is just before we take
@@ -507,29 +547,41 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
     private void beforeAddEffectiveStatement(final int toAdd) {
         // We cannot allow statement to be further mutated
-        final StatementSourceReference ref = getStatementSourceReference();
-        verify(completedPhase != ModelProcessingPhase.EFFECTIVE_MODEL, "Cannot modify finished statement at %s", ref);
+        verify(completedPhase != ModelProcessingPhase.EFFECTIVE_MODEL, "Cannot modify finished statement at %s",
+            getStatementSourceReference());
+        beforeAddEffectiveStatementUnsafe(toAdd);
+    }
 
+    private void beforeAddEffectiveStatementUnsafe(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", ref);
+                "Effective statement cannot be added in declared phase at: %s", getStatementSourceReference());
 
+        // Initialization guarded by all callers
         if (effective.isEmpty()) {
             effective = new ArrayList<>(toAdd);
         }
     }
 
-    // Exists only due to memory optimization
+    // These two exists only due to memory optimization, should live in AbstractResumedStatement
     final boolean fullyDefined() {
         return fullyDefined;
     }
 
-    // Exists only due to memory optimization, should live in AbstractResumedStatement
     final void setFullyDefined() {
         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;
@@ -566,7 +618,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         for (final StatementContextBase<?, ?, ?> child : mutableDeclaredSubstatements()) {
             finished &= child.tryToCompletePhase(phase);
         }
-        for (final StatementContextBase<?, ?, ?> child : effective) {
+        for (final StatementContextBase<?, ?, ?> child : effectiveChildrenToComplete()) {
             finished &= child.tryToCompletePhase(phase);
         }
         return finished;
@@ -591,13 +643,17 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
         if (openMutations.isEmpty()) {
             phaseMutation.removeAll(phase);
-            if (phaseMutation.isEmpty()) {
-                phaseMutation = ImmutableMultimap.of();
-            }
+            cleanupPhaseMutation();
         }
         return finished;
     }
 
+    private void cleanupPhaseMutation() {
+        if (phaseMutation.isEmpty()) {
+            phaseMutation = ImmutableMultimap.of();
+        }
+    }
+
     /**
      * Occurs on end of {@link ModelProcessingPhase} of source parsing.
      *
@@ -774,10 +830,9 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, 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) {
+    final void addMutation(final ModelProcessingPhase phase, final ContextMutation mutation) {
         ModelProcessingPhase finishedPhase = completedPhase;
         while (finishedPhase != null) {
             checkState(!phase.equals(finishedPhase), "Mutation registered after phase was completed at: %s",
@@ -791,6 +846,13 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         phaseMutation.put(phase, mutation);
     }
 
+    final void removeMutation(final ModelProcessingPhase phase, final ContextMutation mutation) {
+        if (!phaseMutation.isEmpty()) {
+            phaseMutation.remove(phase, mutation);
+            cleanupPhaseMutation();
+        }
+    }
+
     @Override
     public <K, KT extends K, N extends StatementNamespace<K, ?, ?>> void addContext(final Class<@NonNull N> namespace,
             final KT key,final StmtContext<?, ?, ?> stmt) {
@@ -913,6 +975,10 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
      */
     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();
     }
@@ -940,11 +1006,10 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
             return true;
         }
 
-        final StmtContext<Boolean, ?, ?> configStatement = StmtContextUtils.findFirstSubstatement(this,
-            ConfigStatement.class);
         final boolean isConfig;
-        if (configStatement != null) {
-            isConfig = configStatement.coerceStatementArgument();
+        final Optional<Boolean> optConfig = findSubstatementArgument(ConfigEffectiveStatement.class);
+        if (optConfig.isPresent()) {
+            isConfig = optConfig.orElseThrow();
             if (isConfig) {
                 // Validity check: if parent is config=false this cannot be a config=true
                 InferenceException.throwIf(!parent.isConfiguration(), getStatementSourceReference(),
@@ -1009,17 +1074,10 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     // Exists only to support {SubstatementContext,InferredStatementContext}.getSchemaPath()
     @Deprecated
     final @NonNull Optional<SchemaPath> substatementGetSchemaPath() {
-        SchemaPath local = schemaPath;
-        if (local == null) {
-            synchronized (this) {
-                local = schemaPath;
-                if (local == null) {
-                    schemaPath = local = createSchemaPath(coerceParentContext());
-                }
-            }
+        if (schemaPath == null) {
+            schemaPath = createSchemaPath(coerceParentContext());
         }
-
-        return Optional.ofNullable(local);
+        return Optional.ofNullable(schemaPath);
     }
 
     @Deprecated