Allow StatementWriter to be stateful 11/68811/5
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 27 Feb 2018 08:24:32 +0000 (09:24 +0100)
committerRobert Varga <nite@hq.sk>
Tue, 27 Feb 2018 13:00:12 +0000 (13:00 +0000)
This patch adds a stateful protocol betwen SourceSpecificContext
and StatementStreamSources, so they do not need to allocate/lookup
objects for statements defined in previous phases.

JIRA: YANGTOOLS-854
Change-Id: I600208a384776a601967bc7b5719cc6632b226db
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SourceSpecificContext.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextWriter.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/StatementContextVisitor.java
yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/source/StatementWriter.java

index d4d2e3684ed32ebe1777465e361ed02465081be3..6a526bf581669c78e7c5c91b19143631f6548c0d 100644 (file)
@@ -98,19 +98,23 @@ public class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeh
         return inProgressPhase;
     }
 
-    StatementContextBase<?, ?, ?> createDeclaredChild(final StatementContextBase<?, ?, ?> current, final int childId,
-            final QName name, final String argument, final StatementSourceReference ref) {
-        if (current != null) {
-            // Fast path: we are entering a statement which was emitted in previous phase
-            StatementContextBase<?, ?, ?> existing = current.lookupSubstatement(childId);
-            while (existing != null && StatementSource.CONTEXT == existing.getStatementSource()) {
-                existing = existing.lookupSubstatement(childId);
-            }
-            if (existing != null) {
-                return existing;
-            }
+    Optional<StatementContextBase<?, ?, ?>> lookupDeclaredChild(final StatementContextBase<?, ?, ?> current,
+            final int childId) {
+        if (current == null) {
+            return Optional.empty();
         }
 
+        // Fast path: we are entering a statement which was emitted in previous phase
+        StatementContextBase<?, ?, ?> existing = current.lookupSubstatement(childId);
+        while (existing != null && StatementSource.CONTEXT == existing.getStatementSource()) {
+            existing = existing.lookupSubstatement(childId);
+        }
+
+        return Optional.ofNullable(existing);
+    }
+
+    StatementContextBase<?, ?, ?> createDeclaredChild(final StatementContextBase<?, ?, ?> current, final int childId,
+            final QName name, final String argument, final StatementSourceReference ref) {
         StatementDefinitionContext<?, ?, ?> def = currentContext.getStatementDefinition(getRootVersion(), name);
         if (def == null) {
             def = currentContext.getModelDefinedStatementDefinition(name);
index 4e806ee4ae4178ca23f3f7f12d594dc786895a94..c4307a107f93d027139b76547c405d77d1d76293 100644 (file)
@@ -27,6 +27,7 @@ import java.util.Optional;
 import java.util.Set;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.util.OptionalBoolean;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
@@ -50,6 +51,7 @@ 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.StatementWriter.ResumedStatement;
 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;
@@ -58,7 +60,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
-        extends NamespaceStorageSupport implements Mutable<A, D, E> {
+        extends NamespaceStorageSupport implements Mutable<A, D, E>, ResumedStatement {
     /**
      * Event listener when an item is added to model namespace.
      */
@@ -109,6 +111,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     // BooleanFields value
     private byte supportedByFeatures;
 
+    private boolean fullyDefined;
+
     StatementContextBase(final StatementDefinitionContext<A, D, E> def, final StatementSourceReference ref,
             final String rawArgument) {
         this.definition = Preconditions.checkNotNull(def);
@@ -445,6 +449,18 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return substatements.get(offset);
     }
 
+    final void setFullyDefined() {
+        this.fullyDefined = true;
+    }
+
+    final void walkChildren(final ModelProcessingPhase phase) {
+        Preconditions.checkState(fullyDefined);
+        substatements.values().forEach(stmt -> {
+            stmt.walkChildren(phase);
+            stmt.endDeclared(phase);
+        });
+    }
+
     @Override
     public D buildDeclared() {
         Preconditions.checkArgument(completedPhase == ModelProcessingPhase.FULL_DECLARATION
@@ -546,7 +562,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     /**
      * Ends declared section of current node.
      */
-    void endDeclared(final StatementSourceReference ref, final ModelProcessingPhase phase) {
+    void endDeclared(final ModelProcessingPhase phase) {
         definition().onDeclarationFinished(this, phase);
     }
 
@@ -731,6 +747,22 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         return copy;
     }
 
+
+    @Override
+    public @NonNull StatementDefinition getDefinition() {
+        return getPublicDefinition();
+    }
+
+    @Override
+    public @NonNull StatementSourceReference getSourceReference() {
+        return getStatementSourceReference();
+    }
+
+    @Override
+    public boolean isFullyDefined() {
+        return fullyDefined;
+    }
+
     final void copyTo(final StatementContextBase<?, ?, ?> target, final CopyType typeOfCopy,
             @Nullable final QNameModule targetModule) {
         final Collection<Mutable<?, ?, ?>> buffer = new ArrayList<>(substatements.size() + effective.size());
index 1eddffd82789234e10857290c460726031d5ef36..6a50a43bd0cfb6ca7ee62ecf17a23bf6cbe9b65e 100644 (file)
@@ -9,6 +9,7 @@ package org.opendaylight.yangtools.yang.parser.stmt.reactor;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Verify;
+import java.util.Optional;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementSource;
@@ -27,22 +28,45 @@ final class StatementContextWriter implements StatementWriter {
         this.phase = Preconditions.checkNotNull(phase);
     }
 
+    @Override
+    public Optional<? extends ResumedStatement> resumeStatement(final int childId) {
+        final Optional<StatementContextBase<?, ?, ?>> existing = ctx.lookupDeclaredChild(current, childId);
+        existing.ifPresent(this::resumeStatement);
+        return existing;
+    }
+
+    private void resumeStatement(final StatementContextBase<?, ?, ?> child) {
+        if (child.isFullyDefined()) {
+            child.walkChildren(phase);
+            child.endDeclared(phase);
+        } else {
+            current = child;
+        }
+    }
+
+    @Override
+    public void storeStatement(final int expectedChildren, final boolean fullyDefined) {
+        Preconditions.checkState(current != null);
+        Preconditions.checkArgument(expectedChildren >= 0);
+
+        if (fullyDefined) {
+            current.setFullyDefined();
+        }
+    }
+
     @Override
     public void startStatement(final int childId, @Nonnull final QName name, final String argument,
             @Nonnull final StatementSourceReference ref) {
-        current = Verify.verifyNotNull(ctx.createDeclaredChild(current, childId, name, argument, ref));
+        final Optional<StatementContextBase<?, ?, ?>> existing = ctx.lookupDeclaredChild(current, childId);
+        current = existing.isPresent() ? existing.get()
+                :  Verify.verifyNotNull(ctx.createDeclaredChild(current, childId, name, argument, ref));
     }
 
     @Override
     public void endStatement(@Nonnull final StatementSourceReference ref) {
         Preconditions.checkState(current != null);
-        current.endDeclared(ref, phase);
-        StatementContextBase<?, ?, ?> parentContext = current.getParentContext();
-        while (parentContext != null && StatementSource.CONTEXT == parentContext.getStatementSource()) {
-            parentContext.endDeclared(ref, phase);
-            parentContext = parentContext.getParentContext();
-        }
-        current = parentContext;
+        current.endDeclared(phase);
+        exitStatement();
     }
 
     @Nonnull
@@ -50,4 +74,13 @@ final class StatementContextWriter implements StatementWriter {
     public ModelProcessingPhase getPhase() {
         return phase;
     }
+
+    private void exitStatement() {
+        StatementContextBase<?, ?, ?> parentContext = current.getParentContext();
+        while (parentContext != null && StatementSource.CONTEXT == parentContext.getStatementSource()) {
+            parentContext.endDeclared(phase);
+            parentContext = parentContext.getParentContext();
+        }
+        current = parentContext;
+    }
 }
index aaed74b1c5c1e206225566c9d1eddb947360ab53..adc73571af8dcfac3590a45b30eb3832a0d2407b 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.parser.rfc7950.repo;
 import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
+import java.util.Optional;
 import org.antlr.v4.runtime.tree.ParseTree;
 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser.ArgumentContext;
 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser.KeywordContext;
@@ -25,6 +26,7 @@ import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinit
 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.StatementWriter;
+import org.opendaylight.yangtools.yang.parser.spi.source.StatementWriter.ResumedStatement;
 
 class StatementContextVisitor {
     private final QNameToStatementDefinition stmtDef;
@@ -88,29 +90,45 @@ class StatementContextVisitor {
         return stmtDef.get(QName.create(module, localName));
     }
 
-    private void processStatement(final int myOffset, final StatementContext ctx) {
-        final String keywordTxt = verifyNotNull(ctx.getChild(KeywordContext.class, 0)).getText();
-        final StatementSourceReference ref = DeclarationInTextSource.atPosition(sourceName, ctx.getStart().getLine(),
-            ctx.getStart().getCharPositionInLine());
-        final QName def = getValidStatementDefinition(keywordTxt, ref);
-        if (def == null) {
-            return;
-        }
+    private boolean processStatement(final int myOffset, final StatementContext ctx) {
+        final Optional<? extends ResumedStatement> optResumed = writer.resumeStatement(myOffset);
+        final StatementSourceReference ref;
+        if (optResumed.isPresent()) {
+            final ResumedStatement resumed = optResumed.get();
+            if (resumed.isFullyDefined()) {
+                return true;
+            }
 
-        final ArgumentContext argumentCtx = ctx.getChild(ArgumentContext.class, 0);
-        final String argument = argumentCtx == null ? null
-                : ArgumentContextUtils.stringFromStringContext(argumentCtx, yangVersion, ref);
-        writer.startStatement(myOffset, def, argument, ref);
+            ref = resumed.getSourceReference();
+        } else {
+            final String keywordTxt = verifyNotNull(ctx.getChild(KeywordContext.class, 0)).getText();
+            ref = DeclarationInTextSource.atPosition(sourceName, ctx.getStart().getLine(),
+                ctx.getStart().getCharPositionInLine());
+            final QName def = getValidStatementDefinition(keywordTxt, ref);
+            if (def == null) {
+                return false;
+            }
+
+            final ArgumentContext argumentCtx = ctx.getChild(ArgumentContext.class, 0);
+            final String argument = argumentCtx == null ? null
+                    : ArgumentContextUtils.stringFromStringContext(argumentCtx, yangVersion, ref);
+            writer.startStatement(myOffset, def, argument, ref);
+        }
 
+        int childOffset = 0;
+        boolean fullyDefined = true;
         if (ctx.children != null) {
-            int childOffset = 0;
             for (ParseTree s : ctx.children) {
                 if (s instanceof StatementContext) {
-                    processStatement(childOffset++, (StatementContext) s);
+                    if (!processStatement(childOffset++, (StatementContext) s)) {
+                        fullyDefined = false;
+                    }
                 }
             }
         }
 
+        writer.storeStatement(childOffset, fullyDefined);
         writer.endStatement(ref);
+        return fullyDefined;
     }
 }
index 3a876938ebe2e6275271b732421a9bc2e0cd05b8..e8094c2d987bf87180c8df1d5612a5a2e69c8296 100644 (file)
@@ -7,12 +7,82 @@
  */
 package org.opendaylight.yangtools.yang.parser.spi.source;
 
+import com.google.common.annotations.Beta;
+import java.util.Optional;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
 
 public interface StatementWriter {
+    /**
+     * Resumed statement state.
+     *
+     * @author Robert Varga
+     */
+    @Beta
+    @NonNullByDefault
+    interface ResumedStatement {
+        /**
+         * Return statement definition.
+         *
+         * @return statement definition.
+         */
+        StatementDefinition getDefinition();
+
+        /**
+         * Return statement source reference.
+         *
+         * @return statement source reference.
+         */
+        StatementSourceReference getSourceReference();
+
+        /**
+         * Check if the statement has been fully defined. This implies that all its children have been fully defined.
+         *
+         * @return True if the statement has been fully defined.
+         */
+        boolean isFullyDefined();
+    }
+
+    /**
+     * Attempt to resume a child statement. If the statement has been previously defined, a {@link ResumedStatement}
+     * instance is returned.
+     *
+     * <p>
+     * If an empty optional is returned, the caller is expected to follow-up with
+     * {@link #startStatement(int, QName, String, StatementSourceReference)} to define the statement.
+     *
+     * <p>
+     * If the returned resumed statement indicates {@link ResumedStatement#isFullyDefined()}, the caller should take
+     * no further action with this or any of the child statements. Otherwise this call is equivalent of issuing
+     * {@link #startStatement(int, QName, String, StatementSourceReference)} and the caller is expected to process
+     * any child statements. The caller should call {@link #storeStatement(int, boolean)} before finishing processing
+     * with {@link #endStatement(StatementSourceReference)}.
+     *
+     * @param childId Child
+     * @return A resumed statement or empty if the statement has not previously been defined.
+     */
+    @Beta
+    default Optional<? extends ResumedStatement> resumeStatement(final int childId) {
+        return Optional.empty();
+    }
+
+    /**
+     * Store a defined statement, hinting at the number of children it is expected to have and indicating whether
+     * it has been fully defined. This method should be called before {@link #endStatement(StatementSourceReference)}
+     * when the caller is taking advantage of {@link #resumeStatement(int)}.
+     *
+     * @param expectedChildren Number of expected children, cannot be negative
+     * @param fullyDefined True if the statement and all its descendants have been defined.
+     */
+    @Beta
+    default void storeStatement(final int expectedChildren, final boolean fullyDefined) {
+        // No-op
+    }
+
     /**
      * Starts statement with supplied name and location in source.
      *
@@ -25,17 +95,12 @@ public interface StatementWriter {
      * If statement has substatements, in order to start substatement, call to
      * {@link #startStatement(int, QName, String, StatementSourceReference)} needs to be done for substatement.
      *
-     * @param childId
-     *            Child identifier, unique among siblings
-     * @param name
-     *            Fully qualified name of statement.
-     * @param argument
-     *            String representation of value as appeared in source, null if not present
-     * @param ref
-     *            Identifier of location in source, which will be used for
-     *            reporting in case of statement processing error.
-     * @throws SourceException
-     *             if statement is not valid according to current context.
+     * @param childId Child identifier, unique among siblings
+     * @param name Fully qualified name of statement.
+     * @param argument String representation of value as appeared in source, null if not present
+     * @param ref Identifier of location in source, which will be used for reporting in case of statement processing
+     *            error.
+     * @throws SourceException if statement is not valid according to current context.
      */
     void startStatement(int childId, @Nonnull QName name, @Nullable String argument,
             @Nonnull StatementSourceReference ref);
@@ -43,12 +108,9 @@ public interface StatementWriter {
     /**
      * Ends current opened statement.
      *
-     * @param ref
-     *            Identifier of location in source, which will be used for
-     *            reporting in case of statement processing error.
-     * @throws SourceException
-     *             if closed statement is not valid in current context, or there
-     *             is no such statement
+     * @param ref Identifier of location in source, which will be used for reporting in case of statement processing
+     *            error.
+     * @throws SourceException if closed statement is not valid in current context, or there is no such statement
      */
     void endStatement(@Nonnull StatementSourceReference ref);