Define ExecutionOrder 14/95214/13
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 17 Feb 2021 21:18:18 +0000 (22:18 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 18 Feb 2021 10:24:19 +0000 (11:24 +0100)
ModelProcessingPhase really amounts to an execution order, in that
the phases need to be executed serially.

Let's define a space for 127 values of ModelProcessingPhase by
defining ExecutionOrder, which is semantically the same thing (but
different).

Update StatementContextBase to store the execution order as single
byte instead of a reference. Doing so allows us to simplify some
of the verification logic, but most importantly it frees up
some space -- resulting in 3-7 bytes worth of internal space losses.

JIRA: YANGTOOLS-1254
JIRA: YANGTOOLS-1150
Change-Id: I3e5b23cae45798b4508dd9e270a4b0317613ed1b
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/BuildGlobalContext.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java
yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReplicaStatementContext.java
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-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/ModelProcessingPhase.java

index aca640a1a124d79eef3fd3f683778ebce449c278..c997cb1221f41265d84267e515674898340ab85c 100644 (file)
@@ -382,7 +382,8 @@ final class BuildGlobalContext extends NamespaceStorageSupport implements Regist
             while (currentSource.hasNext()) {
                 final SourceSpecificContext nextSourceCtx = currentSource.next();
                 try {
-                    final PhaseCompletionProgress sourceProgress = nextSourceCtx.tryToCompletePhase(currentPhase);
+                    final PhaseCompletionProgress sourceProgress =
+                        nextSourceCtx.tryToCompletePhase(currentPhase.executionOrder());
                     switch (sourceProgress) {
                         case FINISHED:
                             currentSource.remove();
index 8277b9033e2c52afbe2f7cd5fe372b5ff333e843..606b3c218035e9b44052db61fbc84a7e06609790 100644 (file)
@@ -40,6 +40,7 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.EffectiveStmtCtx.Current;
 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
 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.ModelProcessingPhase.ExecutionOrder;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.Registry;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
@@ -437,19 +438,26 @@ abstract class ReactorStmtCtx<A, D extends DeclaredStatement<A>, E extends Effec
      */
     abstract @NonNull ReactorStmtCtx<A, D, E> unmodifiedEffectiveSource();
 
+    @Override
+    public final ModelProcessingPhase getCompletedPhase() {
+        return ModelProcessingPhase.ofExecutionOrder(executionOrder());
+    }
+
+    abstract byte executionOrder();
+
     /**
      * Try to execute current {@link ModelProcessingPhase} of source parsing. If the phase has already been executed,
-     * this method does nothing.
+     * this method does nothing. This must not be called with {@link ExecutionOrder#NULL}.
      *
      * @param phase to be executed (completed)
      * @return true if phase was successfully completed
      * @throws SourceException when an error occurred in source parsing
      */
-    final boolean tryToCompletePhase(final ModelProcessingPhase phase) {
-        return phase.isCompletedBy(getCompletedPhase()) || doTryToCompletePhase(phase);
+    final boolean tryToCompletePhase(final byte executionOrder) {
+        return executionOrder() >= executionOrder || doTryToCompletePhase(executionOrder);
     }
 
-    abstract boolean doTryToCompletePhase(ModelProcessingPhase phase);
+    abstract boolean doTryToCompletePhase(byte targetOrder);
 
     //
     //
index 1837d8d12743cf69b859e410096a41dc44f662b8..392642e0db27d0838db76455a9c76d4f613a169c 100644 (file)
@@ -19,7 +19,6 @@ import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 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.ModelProcessingPhase;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.StorageNodeType;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementNamespace;
@@ -95,8 +94,8 @@ final class ReplicaStatementContext<A, D extends DeclaredStatement<A>, E extends
     }
 
     @Override
-    public ModelProcessingPhase getCompletedPhase() {
-        return source.getCompletedPhase();
+    byte executionOrder() {
+        return source.executionOrder();
     }
 
     @Override
@@ -173,7 +172,8 @@ final class ReplicaStatementContext<A, D extends DeclaredStatement<A>, E extends
         throw new UnsupportedOperationException();
     }
 
-    @Override boolean doTryToCompletePhase(final ModelProcessingPhase phase) {
+    @Override
+    boolean doTryToCompletePhase(final byte executionOrder) {
         throw new UnsupportedOperationException();
     }
 
index 87245f42e34cf8808f4682618491463739d0b933..cdb2d2ce4153636eefba84b112712a100a9cf9fe 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.parser.stmt.reactor;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Verify.verify;
+import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.collect.HashMultimap;
@@ -66,6 +67,7 @@ final class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeha
 
     private static final Logger LOG = LoggerFactory.getLogger(SourceSpecificContext.class);
 
+    // TODO: consider keying by Byte equivalent of ExecutionOrder
     private final Multimap<ModelProcessingPhase, ModifierImpl> modifiers = HashMultimap.create();
     private final QNameToStatementDefinitionMap qnameToStmtDefMap = new QNameToStatementDefinitionMap();
     private final PrefixToModuleMap prefixToModuleMap = new PrefixToModuleMap();
@@ -80,9 +82,10 @@ final class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeha
      * - parent module, declared via 'belongs-to' statement
      */
     private Collection<RootStatementContext<?, ?, ?>> importedNamespaces = ImmutableList.of();
+    private RootStatementContext<?, ?, ?> root;
+    // TODO: consider using ExecutionOrder byte for these two
     private ModelProcessingPhase finishedPhase = ModelProcessingPhase.INIT;
     private ModelProcessingPhase inProgressPhase;
-    private RootStatementContext<?, ?, ?> root;
 
     SourceSpecificContext(final BuildGlobalContext globalContext, final StatementStreamSource source) {
         this.globalContext = requireNonNull(globalContext);
@@ -268,15 +271,17 @@ final class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeha
         return globalContext;
     }
 
-    PhaseCompletionProgress tryToCompletePhase(final ModelProcessingPhase phase) {
+    PhaseCompletionProgress tryToCompletePhase(final byte executionOrder) {
+        final ModelProcessingPhase phase = verifyNotNull(ModelProcessingPhase.ofExecutionOrder(executionOrder));
         final Collection<ModifierImpl> currentPhaseModifiers = modifiers.get(phase);
 
         boolean hasProgressed = tryToProgress(currentPhaseModifiers);
         final boolean phaseCompleted = requireNonNull(root, "Malformed source. Valid root element is missing.")
-                .tryToCompletePhase(phase);
+                .tryToCompletePhase(executionOrder);
 
         hasProgressed |= tryToProgress(currentPhaseModifiers);
 
+        // TODO: use executionOrder instead?
         if (phaseCompleted && currentPhaseModifiers.isEmpty()) {
             finishedPhase = phase;
             LOG.debug("Source {} finished phase {}", source, phase);
index a331d6b6d91619da119fcc3323aa94dee5a478a5..71fdd107bf13c474fb8c3a983c7d0ed01d64415f 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.parser.stmt.reactor;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Verify.verify;
+import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
@@ -29,7 +30,6 @@ import java.util.Map.Entry;
 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.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
@@ -39,6 +39,7 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ImplicitParentAwareStatementSupport;
 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.ModelProcessingPhase.ExecutionOrder;
 import org.opendaylight.yangtools.yang.parser.spi.meta.MutableStatement;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceKeyCriterion;
@@ -99,19 +100,23 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     //       operations, hence we want to keep it close by.
     private final @NonNull StatementDefinitionContext<A, D, E> definition;
 
+    // TODO: consider keying by Byte equivalent of ExecutionOrder
     private Multimap<ModelProcessingPhase, OnPhaseFinished> phaseListeners = ImmutableMultimap.of();
     private Multimap<ModelProcessingPhase, ContextMutation> phaseMutation = ImmutableMultimap.of();
 
     private List<StmtContext<?, ?, ?>> effectOfStatement = ImmutableList.of();
 
-    private @Nullable ModelProcessingPhase completedPhase;
+    /**
+     * {@link ModelProcessingPhase.ExecutionOrder} value of current {@link ModelProcessingPhase} of this statement.
+     */
+    private byte executionOrder;
 
     // Copy constructor used by subclasses to implement reparent()
     StatementContextBase(final StatementContextBase<A, D, E> original) {
         super(original);
         this.copyHistory = original.copyHistory;
         this.definition = original.definition;
-        this.completedPhase = original.completedPhase;
+        this.executionOrder = original.executionOrder;
     }
 
     StatementContextBase(final StatementDefinitionContext<A, D, E> def) {
@@ -147,14 +152,14 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     @Override
-    public final ModelProcessingPhase getCompletedPhase() {
-        return completedPhase;
+    final byte executionOrder() {
+        return executionOrder;
     }
 
     // FIXME: this should be propagated through a correct constructor
     @Deprecated
     final void setCompletedPhase(final ModelProcessingPhase completedPhase) {
-        this.completedPhase = completedPhase;
+        this.executionOrder = completedPhase.executionOrder();
     }
 
     @Override
@@ -256,10 +261,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
         final List<ReactorStmtCtx<?, ?, ?>> resized = beforeAddEffectiveStatement(effective, 1);
         final ReactorStmtCtx<?, ?, ?> stmt = (ReactorStmtCtx<?, ?, ?>) substatement;
-        final ModelProcessingPhase phase = completedPhase;
-        if (phase != null) {
-            ensureCompletedPhase(stmt, phase);
-        }
+        ensureCompletedExecution(stmt);
         resized.add(stmt);
         return resized;
     }
@@ -287,10 +289,9 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         final List<ReactorStmtCtx<?, ?, ?>> resized = beforeAddEffectiveStatement(effective, statements.size());
         final Collection<? extends ReactorStmtCtx<?, ?, ?>> casted =
             (Collection<? extends ReactorStmtCtx<?, ?, ?>>) statements;
-        final ModelProcessingPhase phase = completedPhase;
-        if (phase != null) {
+        if (executionOrder != ExecutionOrder.NULL) {
             for (ReactorStmtCtx<?, ?, ?> stmt : casted) {
-                ensureCompletedPhase(stmt, phase);
+                ensureCompletedExecution(stmt, executionOrder);
             }
         }
 
@@ -303,17 +304,20 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     // exposed for InferredStatementContext only
     final void ensureCompletedPhase(final Mutable<?, ?, ?> stmt) {
         verifyStatement(stmt);
-        final ModelProcessingPhase phase = completedPhase;
-        if (phase != null) {
-            ensureCompletedPhase((ReactorStmtCtx<?, ?, ?>) stmt, phase);
+        ensureCompletedExecution((ReactorStmtCtx<?, ?, ?>) stmt);
+    }
+
+    // Make sure target statement has transitioned at least to our phase (if we have one). This method is just before we
+    // take allow a statement to become our substatement. This is needed to ensure that every statement tree does not
+    // contain any statements which did not complete the same phase as the root statement.
+    private void ensureCompletedExecution(final ReactorStmtCtx<?, ?, ?> stmt) {
+        if (executionOrder != ExecutionOrder.NULL) {
+            ensureCompletedExecution(stmt, executionOrder);
         }
     }
 
-    // Make sure target statement has transitioned at least to specified phase. This method is just before we take
-    // allow a statement to become our substatement. This is needed to ensure that every statement tree does not contain
-    // any statements which did not complete the same phase as the root statement.
-    private static void ensureCompletedPhase(final ReactorStmtCtx<?, ?, ?> stmt, final ModelProcessingPhase phase) {
-        verify(stmt.tryToCompletePhase(phase), "Statement %s cannot complete phase %s", stmt, phase);
+    private static void ensureCompletedExecution(final ReactorStmtCtx<?, ?, ?> stmt, final byte executionOrder) {
+        verify(stmt.tryToCompletePhase(executionOrder), "Statement %s cannot complete phase %s", stmt, executionOrder);
     }
 
     private static void verifyStatement(final Mutable<?, ?, ?> stmt) {
@@ -322,8 +326,10 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
 
     private List<ReactorStmtCtx<?, ?, ?>> beforeAddEffectiveStatement(final List<ReactorStmtCtx<?, ?, ?>> effective,
             final int toAdd) {
-        // We cannot allow statement to be further mutated
-        verify(completedPhase != ModelProcessingPhase.EFFECTIVE_MODEL, "Cannot modify finished statement at %s",
+        // We cannot allow statement to be further mutated.
+        // TODO: we really want to say 'not NULL and not at or after EFFECTIVE_MODEL here. This will matter if we have
+        //       a phase after EFFECTIVE_MODEL
+        verify(executionOrder != ExecutionOrder.EFFECTIVE_MODEL, "Cannot modify finished statement at %s",
             sourceReference());
         return beforeAddEffectiveStatementUnsafe(effective, toAdd);
     }
@@ -376,27 +382,28 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     abstract Stream<? extends @NonNull StmtContext<?, ?, ?>> streamEffective();
 
     @Override
-    final boolean doTryToCompletePhase(final ModelProcessingPhase phase) {
-        final boolean finished = phaseMutation.isEmpty() ? true : runMutations(phase);
-        if (completeChildren(phase) && finished) {
-            onPhaseCompleted(phase);
+    final boolean doTryToCompletePhase(final byte targetOrder) {
+        final boolean finished = phaseMutation.isEmpty() ? true : runMutations(targetOrder);
+        if (completeChildren(targetOrder) && finished) {
+            onPhaseCompleted(targetOrder);
             return true;
         }
         return false;
     }
 
-    private boolean completeChildren(final ModelProcessingPhase phase) {
+    private boolean completeChildren(final byte targetOrder) {
         boolean finished = true;
         for (final StatementContextBase<?, ?, ?> child : mutableDeclaredSubstatements()) {
-            finished &= child.tryToCompletePhase(phase);
+            finished &= child.tryToCompletePhase(targetOrder);
         }
         for (final ReactorStmtCtx<?, ?, ?> child : effectiveChildrenToComplete()) {
-            finished &= child.tryToCompletePhase(phase);
+            finished &= child.tryToCompletePhase(targetOrder);
         }
         return finished;
     }
 
-    private boolean runMutations(final ModelProcessingPhase phase) {
+    private boolean runMutations(final byte targetOrder) {
+        final ModelProcessingPhase phase = verifyNotNull(ModelProcessingPhase.ofExecutionOrder(targetOrder));
         final Collection<ContextMutation> openMutations = phaseMutation.get(phase);
         return openMutations.isEmpty() ? true : runMutations(phase, openMutations);
     }
@@ -427,19 +434,20 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
     }
 
     /**
-     * Occurs on end of {@link ModelProcessingPhase} of source parsing.
+     * Occurs on end of {@link ModelProcessingPhase} of source parsing. This method must not be called with
+     * {@code executionOrder} equal to {@link ExecutionOrder#NULL}.
      *
-     * @param phase
-     *            that was to be completed (finished)
-     * @throws SourceException
-     *             when an error occurred in 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;
-        if (phase == ModelProcessingPhase.EFFECTIVE_MODEL) {
+    private void onPhaseCompleted(final byte completedOrder) {
+        executionOrder = completedOrder;
+        if (completedOrder == ExecutionOrder.EFFECTIVE_MODEL) {
+            // We have completed effective model, substatements are guaranteed not to change
             summarizeSubstatementPolicy();
         }
 
+        final ModelProcessingPhase phase = verifyNotNull(ModelProcessingPhase.ofExecutionOrder(completedOrder));
         final Collection<OnPhaseFinished> listeners = phaseListeners.get(phase);
         if (!listeners.isEmpty()) {
             runPhaseListeners(phase, listeners);
@@ -609,7 +617,7 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
         requireNonNull(phase, "Statement context processing phase cannot be null");
         requireNonNull(listener, "Statement context phase listener cannot be null");
 
-        ModelProcessingPhase finishedPhase = completedPhase;
+        ModelProcessingPhase finishedPhase = ModelProcessingPhase.ofExecutionOrder(executionOrder);
         while (finishedPhase != null) {
             if (phase.equals(finishedPhase)) {
                 listener.phaseFinished(this, finishedPhase);
@@ -630,12 +638,8 @@ public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E
      * @throws IllegalStateException when the mutation was registered after phase was completed
      */
     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",
-                sourceReference());
-            finishedPhase = finishedPhase.getPreviousPhase();
-        }
+        checkState(executionOrder < phase.executionOrder(), "Mutation registered after phase was completed at: %s",
+            sourceReference());
 
         if (phaseMutation.isEmpty()) {
             phaseMutation = newMultimap();
index 196f3ee86f0380e810a79a2ef9a4929c42e394e7..8da5502607f96969216d2815bddc14a9e9c13ddb 100644 (file)
@@ -7,12 +7,14 @@
  */
 package org.opendaylight.yangtools.yang.parser.spi.meta;
 
+import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 
+// FIXME: YANGTOOLS-1150: this should go into yang-reactor-api
 @NonNullByDefault
 public enum ModelProcessingPhase {
     INIT(),
@@ -20,9 +22,9 @@ public enum ModelProcessingPhase {
     /**
      * Preliminary cross-source relationship resolution phase which collects available module names and module
      * namespaces. It is necessary in order to correct resolution of unknown statements used in linkage phase (e.g.
-     * semantic version of yang modules).
+     * semantic version of YANG modules).
      */
-    SOURCE_PRE_LINKAGE(INIT),
+    SOURCE_PRE_LINKAGE(INIT, ExecutionOrder.SOURCE_PRE_LINKAGE),
 
     /**
      * Cross-source relationship resolution phase.
@@ -35,21 +37,88 @@ public enum ModelProcessingPhase {
      * At end of this phase all source related contexts should be bind to their imports and includes to allow
      * visibility of custom defined statements in subsequent phases.
      */
-    SOURCE_LINKAGE(SOURCE_PRE_LINKAGE),
-    STATEMENT_DEFINITION(SOURCE_LINKAGE),
-    FULL_DECLARATION(STATEMENT_DEFINITION),
-    EFFECTIVE_MODEL(FULL_DECLARATION);
+    SOURCE_LINKAGE(SOURCE_PRE_LINKAGE, ExecutionOrder.SOURCE_LINKAGE),
+    STATEMENT_DEFINITION(SOURCE_LINKAGE, ExecutionOrder.STATEMENT_DEFINITION),
+    FULL_DECLARATION(STATEMENT_DEFINITION, ExecutionOrder.FULL_DECLARATION),
+    EFFECTIVE_MODEL(FULL_DECLARATION, ExecutionOrder.EFFECTIVE_MODEL);
+
+    /**
+     * The concept of phase execution order, expressed as non-negative values.
+     */
+    public static final class ExecutionOrder {
+        /**
+         * Equivalent of a {@code null} {@link ModelProcessingPhase}.
+         */
+        public static final byte NULL                 = 0;
+        /**
+         * Corresponds to {@link ModelProcessingPhase#INIT}.
+         */
+        public static final byte INIT                 = 1;
+        /**
+         * Corresponds to {@link ModelProcessingPhase#SOURCE_PRE_LINKAGE}.
+         */
+        public static final byte SOURCE_PRE_LINKAGE   = 2;
+        /**
+         * Corresponds to {@link ModelProcessingPhase#SOURCE_LINKAGE}.
+         */
+        public static final byte SOURCE_LINKAGE       = 3;
+        /**
+         * Corresponds to {@link ModelProcessingPhase#STATEMENT_DEFINITION}.
+         */
+        public static final byte STATEMENT_DEFINITION = 4;
+        /**
+         * Corresponds to {@link ModelProcessingPhase#FULL_DECLARATION}.
+         */
+        public static final byte FULL_DECLARATION     = 5;
+        /**
+         * Corresponds to {@link ModelProcessingPhase#EFFECTIVE_MODEL}.
+         */
+        public static final byte EFFECTIVE_MODEL      = 6;
+
+        private ExecutionOrder() {
+            // Hidden on purpose
+        }
+    }
+
+    /**
+     * Members of this enum at their {@link #executionOrder} offset, with {@code 0} being reserved as {@code null}.
+     */
+    private static final ModelProcessingPhase[] BY_EXECUTION_ORDER;
+
+    // BY_EXECUTION_ORDER initialization. The array has a semantic tie-in on ExectionOrder values, which has to follow
+    // its rules. Since we are one-time indexing, let's make a thorough job of it and verify that everything is declared
+    // as it should be.
+    static {
+        final ModelProcessingPhase[] values = values();
+        final ModelProcessingPhase[] tmp = new ModelProcessingPhase[values.length + 1];
+
+        for (ModelProcessingPhase phase : values) {
+            final byte offset = phase.executionOrder;
+            verify(offset > 0, "Invalid execution order in %s", phase);
+
+            final ModelProcessingPhase existing = tmp[offset];
+            verify(existing == null, "Execution order %s clash with %s", offset, existing);
+            verify(tmp[offset - 1] == phase.previousPhase, "Illegal previous phase of %s", phase);
+            tmp[offset] = phase;
+        }
+
+        BY_EXECUTION_ORDER = tmp;
+    }
 
     private final @Nullable ModelProcessingPhase previousPhase;
+    private final byte executionOrder;
 
     @SuppressFBWarnings(value = "NP_STORE_INTO_NONNULL_FIELD",
         justification = "https://github.com/spotbugs/spotbugs/issues/743")
+    // For INIT only
     ModelProcessingPhase() {
         previousPhase = null;
+        executionOrder = ExecutionOrder.INIT;
     }
 
-    ModelProcessingPhase(final ModelProcessingPhase previousPhase) {
+    ModelProcessingPhase(final ModelProcessingPhase previousPhase, final int executionOrder) {
         this.previousPhase = requireNonNull(previousPhase);
+        this.executionOrder = (byte) executionOrder;
     }
 
     /**
@@ -72,4 +141,28 @@ public enum ModelProcessingPhase {
     public boolean isCompletedBy(final @Nullable ModelProcessingPhase other) {
         return other != null && ordinal() <= other.ordinal();
     }
+
+    /**
+     * Return the execution order, which is a value in range {@code 1..127}.
+     *
+     * @return Execution order
+     */
+    public byte executionOrder() {
+        return executionOrder;
+    }
+
+    /**
+     * Return the {@link ModelProcessingPhase} corresponding to a {@link ExecutionOrder} value.
+     *
+     * @param executionOrder Execution order
+     * @return Corresponding value, or null for {@link ExecutionOrder#NULL}
+     * @throws IllegalArgumentException if the execution order is invalid
+     */
+    public static @Nullable ModelProcessingPhase ofExecutionOrder(final byte executionOrder) {
+        try {
+            return BY_EXECUTION_ORDER[executionOrder];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
 }