From 817686488c9ca877b5a1c778f5884c7491ff0234 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Wed, 17 Feb 2021 22:18:18 +0100 Subject: [PATCH 1/1] Define ExecutionOrder 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 --- .../stmt/reactor/BuildGlobalContext.java | 3 +- .../parser/stmt/reactor/ReactorStmtCtx.java | 16 ++- .../stmt/reactor/ReplicaStatementContext.java | 8 +- .../stmt/reactor/SourceSpecificContext.java | 11 +- .../stmt/reactor/StatementContextBase.java | 96 ++++++++-------- .../parser/spi/meta/ModelProcessingPhase.java | 107 ++++++++++++++++-- 6 files changed, 176 insertions(+), 65 deletions(-) diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/BuildGlobalContext.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/BuildGlobalContext.java index aca640a1a1..c997cb1221 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/BuildGlobalContext.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/BuildGlobalContext.java @@ -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(); diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java index 8277b9033e..606b3c2180 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReactorStmtCtx.java @@ -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, E extends Effec */ abstract @NonNull ReactorStmtCtx 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); // // diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReplicaStatementContext.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReplicaStatementContext.java index 1837d8d127..392642e0db 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReplicaStatementContext.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/ReplicaStatementContext.java @@ -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, E extends } @Override - public ModelProcessingPhase getCompletedPhase() { - return source.getCompletedPhase(); + byte executionOrder() { + return source.executionOrder(); } @Override @@ -173,7 +172,8 @@ final class ReplicaStatementContext, E extends throw new UnsupportedOperationException(); } - @Override boolean doTryToCompletePhase(final ModelProcessingPhase phase) { + @Override + boolean doTryToCompletePhase(final byte executionOrder) { throw new UnsupportedOperationException(); } diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SourceSpecificContext.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SourceSpecificContext.java index 87245f42e3..cdb2d2ce41 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SourceSpecificContext.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/SourceSpecificContext.java @@ -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 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> 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 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); diff --git a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java index a331d6b6d9..71fdd107bf 100644 --- a/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java +++ b/yang/yang-parser-reactor/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/StatementContextBase.java @@ -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, E // operations, hence we want to keep it close by. private final @NonNull StatementDefinitionContext definition; + // TODO: consider keying by Byte equivalent of ExecutionOrder private Multimap phaseListeners = ImmutableMultimap.of(); private Multimap phaseMutation = ImmutableMultimap.of(); private List> 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 original) { super(original); this.copyHistory = original.copyHistory; this.definition = original.definition; - this.completedPhase = original.completedPhase; + this.executionOrder = original.executionOrder; } StatementContextBase(final StatementDefinitionContext def) { @@ -147,14 +152,14 @@ public abstract class StatementContextBase, 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, E final List> 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, E final List> resized = beforeAddEffectiveStatement(effective, statements.size()); final Collection> casted = (Collection>) 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, 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, E private List> beforeAddEffectiveStatement(final List> 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, E abstract Stream> 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 openMutations = phaseMutation.get(phase); return openMutations.isEmpty() ? true : runMutations(phase, openMutations); } @@ -427,19 +434,20 @@ public abstract class StatementContextBase, 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 listeners = phaseListeners.get(phase); if (!listeners.isEmpty()) { runPhaseListeners(phase, listeners); @@ -609,7 +617,7 @@ public abstract class StatementContextBase, 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, 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(); diff --git a/yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/ModelProcessingPhase.java b/yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/ModelProcessingPhase.java index 196f3ee86f..8da5502607 100644 --- a/yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/ModelProcessingPhase.java +++ b/yang/yang-parser-spi/src/main/java/org/opendaylight/yangtools/yang/parser/spi/meta/ModelProcessingPhase.java @@ -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); + } + } } -- 2.36.6