This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/master by this push:
     new 0065d7c  [CALCITE-4986] Make HepProgram thread-safe
0065d7c is described below

commit 0065d7c179b98698f018f83b0af0845a6698fc54
Author: Julian Hyde <[email protected]>
AuthorDate: Thu Jan 13 21:23:28 2022 -0800

    [CALCITE-4986] Make HepProgram thread-safe
    
    Before this change, HepProgram used mutable fields for its
    working state and was therefore not thread-safe. If two
    threads were planning queries simultaneously and are using
    the same HepProgram instances they might conflict.
    
    This change moves the mutable state out of HepProgram, so
    that HepProgram is fully immutable and therefore thread-safe.
    We make other fields final and not-nullable where possible.
    
    Before using a HepProgram, you now need to call the new
    HepProgram.prepare() method to create an instance of class
    HepState (which contains the state for any program
    instruction, including the whole program), then call the
    method HepState.execute().
    
    Close apache/calcite#2691
---
 .../apache/calcite/plan/hep/HepInstruction.java    | 356 ++++++++++++++++-----
 .../org/apache/calcite/plan/hep/HepPlanner.java    | 230 +++++++------
 .../org/apache/calcite/plan/hep/HepProgram.java    |  79 ++++-
 .../apache/calcite/plan/hep/HepProgramBuilder.java |  95 ++----
 .../java/org/apache/calcite/plan/hep/HepState.java |  45 +++
 .../java/org/apache/calcite/linq4j/Nullness.java   |  61 +++-
 6 files changed, 571 insertions(+), 295 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/plan/hep/HepInstruction.java 
b/core/src/main/java/org/apache/calcite/plan/hep/HepInstruction.java
index 46f1a72..9d8edeb 100644
--- a/core/src/main/java/org/apache/calcite/plan/hep/HepInstruction.java
+++ b/core/src/main/java/org/apache/calcite/plan/hep/HepInstruction.java
@@ -18,13 +18,20 @@ package org.apache.calcite.plan.hep;
 
 import org.apache.calcite.plan.RelOptRule;
 
+import com.google.common.collect.ImmutableList;
+
 import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
+import static org.apache.calcite.linq4j.Nullness.castNonNull;
+
+import static java.util.Objects.requireNonNull;
+
 /**
  * HepInstruction represents one instruction in a HepProgram. The actual
  * instruction set is defined here via inner classes; if these grow too big,
@@ -33,170 +40,345 @@ import java.util.Set;
 abstract class HepInstruction {
   //~ Methods ----------------------------------------------------------------
 
-  void initialize(boolean clearCache) {
-  }
-
-  // typesafe dispatch via the visitor pattern
-  abstract void execute(HepPlanner planner);
+  /** Creates runtime state for this instruction.
+   *
+   * <p>The state is mutable, knows how to execute the instruction, and is
+   * discarded after this execution. See {@link HepState}.
+   *
+   * @param px Preparation context; the state should copy from the context
+   * all information that it will need to execute
+   *
+   * @return Initialized state
+   */
+  abstract HepState prepare(PrepareContext px);
 
   //~ Inner Classes ----------------------------------------------------------
 
-  /** Instruction that executes all rules of a given class.
-   *
-   * @param <R> rule type */
-  static class RuleClass<R extends RelOptRule> extends HepInstruction {
-    @Nullable Class<R> ruleClass;
-
-    /**
-     * Actual rule set instantiated during planning by filtering all of the
-     * planner's rules through ruleClass.
-     */
-    @Nullable Set<RelOptRule> ruleSet;
+  /** Instruction that executes all rules of a given class. */
+  static class RuleClass extends HepInstruction {
+    final Class<? extends RelOptRule> ruleClass;
 
-    @Override void initialize(boolean clearCache) {
-      if (!clearCache) {
-        return;
-      }
+    <R extends RelOptRule> RuleClass(Class<R> ruleClass) {
+      this.ruleClass = requireNonNull(ruleClass, "ruleClass");
+    }
 
-      ruleSet = null;
+    @Override State prepare(PrepareContext px) {
+      return new State(px);
     }
 
-    @Override void execute(HepPlanner planner) {
-      planner.executeInstruction(this);
+    /** State for a {@link RuleClass} instruction. */
+    class State extends HepState {
+      /** Actual rule set instantiated during planning by filtering all the
+       * planner's rules through {@link #ruleClass}. */
+      @Nullable Set<RelOptRule> ruleSet;
+
+      State(PrepareContext px) {
+        super(px);
+      }
+
+      @Override void execute() {
+        planner.executeRuleClass(RuleClass.this, this);
+      }
     }
   }
 
   /** Instruction that executes all rules in a given collection. */
   static class RuleCollection extends HepInstruction {
-    /**
-     * Collection of rules to apply.
-     */
-    @Nullable Collection<RelOptRule> rules;
+    /** Collection of rules to apply. */
+    final List<RelOptRule> rules;
+
+    RuleCollection(Collection<RelOptRule> rules) {
+      this.rules = ImmutableList.copyOf(rules);
+    }
 
-    @Override void execute(HepPlanner planner) {
-      planner.executeInstruction(this);
+    @Override State prepare(PrepareContext px) {
+      return new State(px);
+    }
+
+    /** State for a {@link RuleCollection} instruction. */
+    class State extends HepState {
+      State(PrepareContext px) {
+        super(px);
+      }
+
+      @Override void execute() {
+        planner.executeRuleCollection(RuleCollection.this, this);
+      }
     }
   }
 
   /** Instruction that executes converter rules. */
   static class ConverterRules extends HepInstruction {
-    boolean guaranteed;
+    final boolean guaranteed;
+
+    ConverterRules(boolean guaranteed) {
+      this.guaranteed = guaranteed;
+    }
+
+    @Override State prepare(PrepareContext px) {
+      return new State(px);
+    }
 
-    /**
-     * Actual rule set instantiated during planning by filtering all of the
-     * planner's rules, looking for the desired converters.
-     */
-    @MonotonicNonNull Set<RelOptRule> ruleSet;
+    /** State for a {@link ConverterRules} instruction. */
+    class State extends HepState {
+      /** Actual rule set instantiated during planning by filtering all the
+       * planner's rules, looking for the desired converters. */
+      @MonotonicNonNull Set<RelOptRule> ruleSet;
 
-    @Override void execute(HepPlanner planner) {
-      planner.executeInstruction(this);
+      State(PrepareContext px) {
+        super(px);
+      }
+
+      @Override void execute() {
+        planner.executeConverterRules(ConverterRules.this, this);
+      }
     }
   }
 
   /** Instruction that finds common relational sub-expressions. */
   static class CommonRelSubExprRules extends HepInstruction {
-    @Nullable Set<RelOptRule> ruleSet;
+    @Override State prepare(PrepareContext px) {
+      return new State(px);
+    }
+
+    /** State for a {@link CommonRelSubExprRules} instruction. */
+    class State extends HepState {
+      @Nullable Set<RelOptRule> ruleSet;
+
+      State(PrepareContext px) {
+        super(px);
+      }
 
-    @Override void execute(HepPlanner planner) {
-      planner.executeInstruction(this);
+      @Override void execute() {
+        planner.executeCommonRelSubExprRules(CommonRelSubExprRules.this, this);
+      }
     }
   }
 
   /** Instruction that executes a given rule. */
   static class RuleInstance extends HepInstruction {
-    /**
-     * Description to look for, or null if rule specified explicitly.
-     */
-    @Nullable String ruleDescription;
+    /** Explicitly specified rule. */
+    final RelOptRule rule;
+
+    RuleInstance(RelOptRule rule) {
+      this.rule = requireNonNull(rule, "rule");
+    }
+
+    @Override State prepare(PrepareContext px) {
+      return new State(px);
+    }
+
+    /** State for a {@link RuleInstance} instruction. */
+    class State extends HepState {
+      State(PrepareContext px) {
+        super(px);
+      }
+
+      @Override void execute() {
+        planner.executeRuleInstance(RuleInstance.this, this);
+      }
+    }
+  }
+
+  /** Instruction that executes a rule that is looked up by description. */
+  static class RuleLookup extends HepInstruction {
+    /** Description to look for. */
+    final String ruleDescription;
+
+    RuleLookup(String ruleDescription) {
+      this.ruleDescription = requireNonNull(ruleDescription, 
"ruleDescription");
+    }
 
-    /**
-     * Explicitly specified rule, or rule looked up by planner from
-     * description.
-     */
-    @Nullable RelOptRule rule;
+    @Override State prepare(PrepareContext px) {
+      return new State(px);
+    }
+
+    /** State for a {@link RuleLookup} instruction. */
+    class State extends HepState {
+      /** Rule looked up by planner from description. */
+      @Nullable RelOptRule rule;
 
-    @Override void initialize(boolean clearCache) {
-      if (!clearCache) {
-        return;
+      State(PrepareContext px) {
+        super(px);
       }
 
-      if (ruleDescription != null) {
+      @Override void init() {
         // Look up anew each run.
         rule = null;
       }
-    }
 
-    @Override void execute(HepPlanner planner) {
-      planner.executeInstruction(this);
+      @Override void execute() {
+        planner.executeRuleLookup(RuleLookup.this, this);
+      }
     }
   }
 
   /** Instruction that sets match order. */
   static class MatchOrder extends HepInstruction {
-    @Nullable HepMatchOrder order;
+    final HepMatchOrder order;
+
+    MatchOrder(HepMatchOrder order) {
+      this.order = requireNonNull(order, "order");
+    }
 
-    @Override void execute(HepPlanner planner) {
-      planner.executeInstruction(this);
+    @Override State prepare(PrepareContext px) {
+      return new State(px);
+    }
+
+    /** State for a {@link MatchOrder} instruction. */
+    class State extends HepState {
+      State(PrepareContext px) {
+        super(px);
+      }
+
+      @Override void execute() {
+        planner.executeMatchOrder(MatchOrder.this, this);
+      }
     }
   }
 
   /** Instruction that sets match limit. */
   static class MatchLimit extends HepInstruction {
-    int limit;
+    final int limit;
+
+    MatchLimit(int limit) {
+      this.limit = limit;
+    }
+
+    @Override State prepare(PrepareContext px) {
+      return new State(px);
+    }
 
-    @Override void execute(HepPlanner planner) {
-      planner.executeInstruction(this);
+    /** State for a {@link MatchLimit} instruction. */
+    class State extends HepState {
+      State(PrepareContext px) {
+        super(px);
+      }
+
+      @Override void execute() {
+        planner.executeMatchLimit(MatchLimit.this, this);
+      }
     }
   }
 
   /** Instruction that executes a sub-program. */
-  static class Subprogram extends HepInstruction {
-    @Nullable HepProgram subprogram;
+  static class SubProgram extends HepInstruction {
+    final HepProgram subProgram;
 
-    @Override void initialize(boolean clearCache) {
-      if (subprogram != null) {
-        subprogram.initialize(clearCache);
-      }
+    SubProgram(HepProgram subProgram) {
+      this.subProgram = requireNonNull(subProgram, "subProgram");
+    }
+
+    @Override HepProgram.State prepare(PrepareContext px) {
+      return subProgram.prepare(px);
     }
 
-    @Override void execute(HepPlanner planner) {
-      planner.executeInstruction(this);
+    /** State for a {@link SubProgram} instruction. */
+    class State extends HepState {
+      final HepProgram.State subProgramState;
+
+      State(PrepareContext px) {
+        super(px);
+        subProgramState = subProgram.prepare(px);
+      }
+
+      @Override void init() {
+        subProgramState.init();
+      }
+
+      @Override void execute() {
+        planner.executeSubProgram(SubProgram.this, this);
+      }
     }
   }
 
   /** Instruction that begins a group. */
   static class BeginGroup extends HepInstruction {
-    @Nullable EndGroup endGroup;
+    final EndGroup endGroup;
+
+    BeginGroup(EndGroup endGroup) {
+      this.endGroup = requireNonNull(endGroup, "endGroup");
+    }
+
+    @Override State prepare(PrepareContext px) {
+      return new State(px);
+    }
+
+    /** State for a {@link BeginGroup} instruction. */
+    class State extends HepState {
+      final HepInstruction.EndGroup.State endGroup;
+
+      State(PrepareContext px) {
+        super(px);
+        this.endGroup = requireNonNull(px.endGroupState, "endGroupState");
+      }
 
-    @Override void initialize(boolean clearCache) {
+      @Override void execute() {
+        planner.executeBeginGroup(BeginGroup.this, this);
+      }
     }
+  }
 
-    @Override void execute(HepPlanner planner) {
-      planner.executeInstruction(this);
+  /** Placeholder instruction that marks the beginning of a group under
+   * construction. */
+  static class Placeholder extends HepInstruction {
+    @Override HepState prepare(PrepareContext px) {
+      throw new UnsupportedOperationException();
     }
   }
 
   /** Instruction that ends a group. */
   static class EndGroup extends HepInstruction {
-    /**
-     * Actual rule set instantiated during planning by collecting grouped
-     * rules.
-     */
-    @Nullable Set<RelOptRule> ruleSet;
+    @Override State prepare(PrepareContext px) {
+      return new State(px);
+    }
+
+    /** State for a {@link EndGroup} instruction. */
+    class State extends HepState {
+      /** Actual rule set instantiated during planning by collecting grouped
+       * rules. */
+      final Set<RelOptRule> ruleSet = new HashSet<>();
+
+      boolean collecting = true;
 
-    boolean collecting;
+      State(PrepareContext px) {
+        super(px);
+      }
+
+      @Override void execute() {
+        planner.executeEndGroup(EndGroup.this, this);
+      }
 
-    @Override void initialize(boolean clearCache) {
-      if (!clearCache) {
-        return;
+      @Override void init() {
+        collecting = true;
       }
+    }
+  }
+
+  /** All the information that might be necessary to initialize {@link 
HepState}
+   * for a particular instruction. */
+  static class PrepareContext {
+    final HepPlanner planner;
+    final HepProgram.State programState;
+    final EndGroup.State endGroupState;
+
+    private PrepareContext(HepPlanner planner,
+        HepProgram.State programState, EndGroup.State endGroupState) {
+      this.planner = planner;
+      this.programState = programState;
+      this.endGroupState = endGroupState;
+    }
+
+    static PrepareContext create(HepPlanner planner) {
+      return new PrepareContext(planner, castNonNull(null), castNonNull(null));
+    }
 
-      ruleSet = new HashSet<>();
-      collecting = true;
+    PrepareContext withProgramState(HepProgram.State programState) {
+      return new PrepareContext(planner, programState, endGroupState);
     }
 
-    @Override void execute(HepPlanner planner) {
-      planner.executeInstruction(this);
+    PrepareContext withEndGroupState(EndGroup.State endGroupState) {
+      return new PrepareContext(planner, programState, endGroupState);
     }
   }
 }
diff --git a/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java 
b/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java
index 601aef4..7e39b43 100644
--- a/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java
+++ b/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java
@@ -68,6 +68,8 @@ import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 import static org.apache.calcite.linq4j.Nullness.castNonNull;
 
 import static java.util.Objects.requireNonNull;
@@ -81,8 +83,6 @@ public class HepPlanner extends AbstractRelOptPlanner {
 
   private final HepProgram mainProgram;
 
-  private @Nullable HepProgram currentProgram;
-
   private @Nullable HepRelVertex root;
 
   private @Nullable RelTraitSet requestedRootTraits;
@@ -152,20 +152,18 @@ public class HepPlanner extends AbstractRelOptPlanner {
       @Nullable Function2<RelNode, RelNode, Void> onCopyHook,
       RelOptCostFactory costFactory) {
     super(costFactory, context);
-    this.mainProgram = program;
+    this.mainProgram = requireNonNull(program, "program");
     this.onCopyHook = Util.first(onCopyHook, Functions.ignore2());
     this.noDag = noDag;
   }
 
   //~ Methods ----------------------------------------------------------------
 
-  // implement RelOptPlanner
   @Override public void setRoot(RelNode rel) {
     root = addRelToGraph(rel);
     dumpGraph();
   }
 
-  // implement RelOptPlanner
   @Override public @Nullable RelNode getRoot() {
     return root;
   }
@@ -178,7 +176,6 @@ public class HepPlanner extends AbstractRelOptPlanner {
     this.materializations.clear();
   }
 
-  // implement RelOptPlanner
   @Override public RelNode changeTraits(RelNode rel, RelTraitSet toTraits) {
     // Ignore traits, except for the root, where we remember
     // what the final conversion should be.
@@ -188,9 +185,8 @@ public class HepPlanner extends AbstractRelOptPlanner {
     return rel;
   }
 
-  // implement RelOptPlanner
   @Override public RelNode findBestExp() {
-    assert root != null;
+    requireNonNull(root, "root");
 
     executeProgram(mainProgram);
 
@@ -200,12 +196,19 @@ public class HepPlanner extends AbstractRelOptPlanner {
     return buildFinalPlan(requireNonNull(root, "root"));
   }
 
+  /** Top-level entry point for a program. Initializes state and then invokes
+   * the program. */
   private void executeProgram(HepProgram program) {
-    HepProgram savedProgram = currentProgram;
-    currentProgram = program;
-    currentProgram.initialize(program == mainProgram);
-    for (HepInstruction instruction : currentProgram.instructions) {
-      instruction.execute(this);
+    final HepInstruction.PrepareContext px =
+        HepInstruction.PrepareContext.create(this);
+    final HepState state = program.prepare(px);
+    state.execute();
+  }
+
+  void executeProgram(HepProgram instruction, HepProgram.State state) {
+    state.init();
+    state.instructionStates.forEach(instructionState -> {
+      instructionState.execute();
       int delta = nTransformations - nTransformationsLastGC;
       if (delta > graphSizeLastGC) {
         // The number of transformations performed since the last
@@ -217,87 +220,78 @@ public class HepPlanner extends AbstractRelOptPlanner {
         // proportional to the graph size.
         collectGarbage();
       }
-    }
-    currentProgram = savedProgram;
+    });
   }
 
-  void executeInstruction(
-      HepInstruction.MatchLimit instruction) {
+  void executeMatchLimit(HepInstruction.MatchLimit instruction,
+      HepInstruction.MatchLimit.State state) {
     LOGGER.trace("Setting match limit to {}", instruction.limit);
-    assert currentProgram != null : "currentProgram must not be null";
-    currentProgram.matchLimit = instruction.limit;
+    state.programState.matchLimit = instruction.limit;
   }
 
-  void executeInstruction(
-      HepInstruction.MatchOrder instruction) {
+  void executeMatchOrder(HepInstruction.MatchOrder instruction,
+      HepInstruction.MatchOrder.State state) {
     LOGGER.trace("Setting match order to {}", instruction.order);
-    assert currentProgram != null : "currentProgram must not be null";
-    currentProgram.matchOrder = instruction.order;
+    state.programState.matchOrder = instruction.order;
+  }
+
+  void executeRuleInstance(HepInstruction.RuleInstance instruction,
+      HepInstruction.RuleInstance.State state) {
+    if (state.programState.skippingGroup()) {
+      return;
+    }
+    applyRules(state.programState, ImmutableList.of(instruction.rule), true);
   }
 
-  void executeInstruction(
-      HepInstruction.RuleInstance instruction) {
-    if (skippingGroup()) {
+  void executeRuleLookup(HepInstruction.RuleLookup instruction,
+      HepInstruction.RuleLookup.State state) {
+    if (state.programState.skippingGroup()) {
       return;
     }
-    if (instruction.rule == null) {
-      assert instruction.ruleDescription != null;
-      instruction.rule =
-          getRuleByDescription(instruction.ruleDescription);
+    RelOptRule rule = state.rule;
+    if (rule == null) {
+      state.rule = rule = getRuleByDescription(instruction.ruleDescription);
       LOGGER.trace("Looking up rule with description {}, found {}",
-          instruction.ruleDescription, instruction.rule);
+          instruction.ruleDescription, rule);
     }
-    if (instruction.rule != null) {
-      applyRules(
-          Collections.singleton(instruction.rule),
-          true);
+    if (rule != null) {
+      applyRules(state.programState, ImmutableList.of(rule), true);
     }
   }
 
-  void executeInstruction(
-      HepInstruction.RuleClass<?> instruction) {
-    if (skippingGroup()) {
+  void executeRuleClass(HepInstruction.RuleClass instruction,
+      HepInstruction.RuleClass.State state) {
+    if (state.programState.skippingGroup()) {
       return;
     }
     LOGGER.trace("Applying rule class {}", instruction.ruleClass);
-    Set<RelOptRule> ruleSet = instruction.ruleSet;
+    Set<RelOptRule> ruleSet = state.ruleSet;
     if (ruleSet == null) {
-      instruction.ruleSet = ruleSet = new LinkedHashSet<>();
-      Class<?> ruleClass = requireNonNull(instruction.ruleClass, 
"instruction.ruleClass");
+      state.ruleSet = ruleSet = new LinkedHashSet<>();
+      Class<?> ruleClass = instruction.ruleClass;
       for (RelOptRule rule : mapDescToRule.values()) {
         if (ruleClass.isInstance(rule)) {
           ruleSet.add(rule);
         }
       }
     }
-    applyRules(ruleSet, true);
+    applyRules(state.programState, ruleSet, true);
   }
 
-  void executeInstruction(
-      HepInstruction.RuleCollection instruction) {
-    if (skippingGroup()) {
+  void executeRuleCollection(HepInstruction.RuleCollection instruction,
+      HepInstruction.RuleCollection.State state) {
+    if (state.programState.skippingGroup()) {
       return;
     }
-    assert instruction.rules != null : "instruction.rules must not be null";
-    applyRules(instruction.rules, true);
-  }
-
-  private boolean skippingGroup() {
-    if (currentProgram != null && currentProgram.group != null) {
-      // Skip if we've already collected the ruleset.
-      return !currentProgram.group.collecting;
-    } else {
-      // Not grouping.
-      return false;
-    }
+    applyRules(state.programState, instruction.rules, true);
   }
 
-  void executeInstruction(
-      HepInstruction.ConverterRules instruction) {
-    assert currentProgram != null : "currentProgram must not be null";
-    assert currentProgram.group == null;
-    if (instruction.ruleSet == null) {
-      instruction.ruleSet = new LinkedHashSet<>();
+  void executeConverterRules(HepInstruction.ConverterRules instruction,
+      HepInstruction.ConverterRules.State state) {
+    checkArgument(state.programState.group == null);
+    Set<RelOptRule> ruleSet = state.ruleSet;
+    if (ruleSet == null) {
+      state.ruleSet = ruleSet = new LinkedHashSet<>();
       for (RelOptRule rule : mapDescToRule.values()) {
         if (!(rule instanceof ConverterRule)) {
           continue;
@@ -308,24 +302,25 @@ public class HepPlanner extends AbstractRelOptPlanner {
         }
 
         // Add the rule itself to work top-down
-        instruction.ruleSet.add(converter);
+        ruleSet.add(converter);
         if (!instruction.guaranteed) {
           // Add a TraitMatchingRule to work bottom-up
-          instruction.ruleSet.add(
+          ruleSet.add(
               TraitMatchingRule.config(converter, RelFactories.LOGICAL_BUILDER)
                   .toRule());
         }
       }
     }
-    applyRules(instruction.ruleSet, instruction.guaranteed);
+    applyRules(state.programState, ruleSet, instruction.guaranteed);
   }
 
-  void executeInstruction(HepInstruction.CommonRelSubExprRules instruction) {
-    assert currentProgram != null : "currentProgram must not be null";
-    assert currentProgram.group == null;
-    Set<RelOptRule> ruleSet = instruction.ruleSet;
+  void executeCommonRelSubExprRules(
+      HepInstruction.CommonRelSubExprRules instruction,
+      HepInstruction.CommonRelSubExprRules.State state) {
+    checkArgument(state.programState.group == null);
+    Set<RelOptRule> ruleSet = state.ruleSet;
     if (ruleSet == null) {
-      instruction.ruleSet = ruleSet = new LinkedHashSet<>();
+      state.ruleSet = ruleSet = new LinkedHashSet<>();
       for (RelOptRule rule : mapDescToRule.values()) {
         if (!(rule instanceof CommonRelSubExprRule)) {
           continue;
@@ -333,15 +328,15 @@ public class HepPlanner extends AbstractRelOptPlanner {
         ruleSet.add(rule);
       }
     }
-    applyRules(ruleSet, true);
+    applyRules(state.programState, ruleSet, true);
   }
 
-  void executeInstruction(
-      HepInstruction.Subprogram instruction) {
+  void executeSubProgram(HepInstruction.SubProgram instruction,
+      HepInstruction.SubProgram.State state) {
     LOGGER.trace("Entering subprogram");
     for (;;) {
       int nTransformationsBefore = nTransformations;
-      executeProgram(requireNonNull(instruction.subprogram, 
"instruction.subprogram"));
+      state.programState.execute();
       if (nTransformations == nTransformationsBefore) {
         // Nothing happened this time around.
         break;
@@ -350,26 +345,24 @@ public class HepPlanner extends AbstractRelOptPlanner {
     LOGGER.trace("Leaving subprogram");
   }
 
-  void executeInstruction(
-      HepInstruction.BeginGroup instruction) {
-    assert currentProgram != null : "currentProgram must not be null";
-    assert currentProgram.group == null;
-    currentProgram.group = instruction.endGroup;
+  void executeBeginGroup(HepInstruction.BeginGroup instruction,
+      HepInstruction.BeginGroup.State state) {
+    checkArgument(state.programState.group == null);
+    state.programState.group = state.endGroup;
     LOGGER.trace("Entering group");
   }
 
-  void executeInstruction(
-      HepInstruction.EndGroup instruction) {
-    assert currentProgram != null : "currentProgram must not be null";
-    assert currentProgram.group == instruction;
-    currentProgram.group = null;
-    instruction.collecting = false;
-    applyRules(requireNonNull(instruction.ruleSet, "instruction.ruleSet"), 
true);
+  void executeEndGroup(HepInstruction.EndGroup instruction,
+      HepInstruction.EndGroup.State state) {
+    checkArgument(state.programState.group == state);
+    state.programState.group = null;
+    state.collecting = false;
+    applyRules(state.programState, state.ruleSet, true);
     LOGGER.trace("Leaving group");
   }
 
-  private int depthFirstApply(Iterator<HepRelVertex> iter,
-      Collection<RelOptRule> rules,
+  private int depthFirstApply(HepProgram.State programState,
+      Iterator<HepRelVertex> iter, Collection<RelOptRule> rules,
       boolean forceConversions, int nMatches) {
     while (iter.hasNext()) {
       HepRelVertex vertex = iter.next();
@@ -379,47 +372,46 @@ public class HepPlanner extends AbstractRelOptPlanner {
         if (newVertex == null || newVertex == vertex) {
           continue;
         }
-        assert currentProgram != null : "currentProgram must not be null";
         ++nMatches;
-        if (nMatches >= currentProgram.matchLimit) {
+        if (nMatches >= programState.matchLimit) {
           return nMatches;
         }
         // To the extent possible, pick up where we left
         // off; have to create a new iterator because old
         // one was invalidated by transformation.
-        Iterator<HepRelVertex> depthIter = getGraphIterator(newVertex);
-        nMatches = depthFirstApply(depthIter, rules, forceConversions,
-            nMatches);
+        Iterator<HepRelVertex> depthIter =
+            getGraphIterator(programState, newVertex);
+        nMatches =
+            depthFirstApply(programState, depthIter, rules, forceConversions,
+                nMatches);
         break;
       }
     }
     return nMatches;
   }
 
-  private void applyRules(
-      Collection<RelOptRule> rules,
-      boolean forceConversions) {
-    assert currentProgram != null : "currentProgram must not be null";
-    if (currentProgram.group != null) {
-      assert currentProgram.group.collecting;
-      Set<RelOptRule> ruleSet = requireNonNull(currentProgram.group.ruleSet,
-          "currentProgram.group.ruleSet");
+  private void applyRules(HepProgram.State programState,
+      Collection<RelOptRule> rules, boolean forceConversions) {
+    final HepInstruction.EndGroup.State group = programState.group;
+    if (group != null) {
+      checkArgument(group.collecting);
+      Set<RelOptRule> ruleSet = requireNonNull(group.ruleSet, "group.ruleSet");
       ruleSet.addAll(rules);
       return;
     }
 
     LOGGER.trace("Applying rule set {}", rules);
 
-    requireNonNull(currentProgram, "currentProgram");
-    boolean fullRestartAfterTransformation =
-        currentProgram.matchOrder != HepMatchOrder.ARBITRARY
-        && currentProgram.matchOrder != HepMatchOrder.DEPTH_FIRST;
+    final boolean fullRestartAfterTransformation =
+        programState.matchOrder != HepMatchOrder.ARBITRARY
+            && programState.matchOrder != HepMatchOrder.DEPTH_FIRST;
 
     int nMatches = 0;
 
     boolean fixedPoint;
     do {
-      Iterator<HepRelVertex> iter = getGraphIterator(requireNonNull(root, 
"root"));
+      Iterator<HepRelVertex> iter =
+          getGraphIterator(programState, requireNonNull(root, "root"));
       fixedPoint = true;
       while (iter.hasNext()) {
         HepRelVertex vertex = iter.next();
@@ -430,21 +422,20 @@ public class HepPlanner extends AbstractRelOptPlanner {
             continue;
           }
           ++nMatches;
-          if (nMatches >= requireNonNull(currentProgram, 
"currentProgram").matchLimit) {
+          if (nMatches >= programState.matchLimit) {
             return;
           }
           if (fullRestartAfterTransformation) {
-            iter = getGraphIterator(requireNonNull(root, "root"));
+            iter = getGraphIterator(programState, requireNonNull(root, 
"root"));
           } else {
             // To the extent possible, pick up where we left
             // off; have to create a new iterator because old
             // one was invalidated by transformation.
-            iter = getGraphIterator(newVertex);
-            if (requireNonNull(currentProgram, "currentProgram").matchOrder
-                == HepMatchOrder.DEPTH_FIRST) {
+            iter = getGraphIterator(programState, newVertex);
+            if (programState.matchOrder == HepMatchOrder.DEPTH_FIRST) {
               nMatches =
-                  depthFirstApply(iter, rules, forceConversions, nMatches);
-              if (nMatches >= requireNonNull(currentProgram, 
"currentProgram").matchLimit) {
+                  depthFirstApply(programState, iter, rules, forceConversions, 
nMatches);
+              if (nMatches >= programState.matchLimit) {
                 return;
               }
             }
@@ -458,7 +449,8 @@ public class HepPlanner extends AbstractRelOptPlanner {
     } while (!fixedPoint);
   }
 
-  private Iterator<HepRelVertex> getGraphIterator(HepRelVertex start) {
+  private Iterator<HepRelVertex> getGraphIterator(
+      HepProgram.State programState, HepRelVertex start) {
     // Make sure there's no garbage, because topological sort
     // doesn't start from a specific root, and rules can't
     // deal with firing on garbage.
@@ -469,8 +461,7 @@ public class HepPlanner extends AbstractRelOptPlanner {
     // better optimizer performance.
     collectGarbage();
 
-    assert currentProgram != null : "currentProgram must not be null";
-    switch (requireNonNull(currentProgram.matchOrder, 
"currentProgram.matchOrder")) {
+    switch (requireNonNull(programState.matchOrder, 
"programState.matchOrder")) {
     case ARBITRARY:
     case DEPTH_FIRST:
       return DepthFirstIterator.of(graph, start).iterator();
@@ -786,7 +777,6 @@ public class HepPlanner extends AbstractRelOptPlanner {
     return newVertex;
   }
 
-  // implement RelOptPlanner
   @Override public RelNode register(
       RelNode rel,
       @Nullable RelNode equivRel) {
@@ -799,12 +789,10 @@ public class HepPlanner extends AbstractRelOptPlanner {
     onCopyHook.apply(rel, newRel);
   }
 
-  // implement RelOptPlanner
   @Override public RelNode ensureRegistered(RelNode rel, @Nullable RelNode 
equivRel) {
     return rel;
   }
 
-  // implement RelOptPlanner
   @Override public boolean isRegistered(RelNode rel) {
     return true;
   }
@@ -1059,13 +1047,11 @@ public class HepPlanner extends AbstractRelOptPlanner {
     LOGGER.trace(sb.toString());
   }
 
-  // implement RelOptPlanner
   @Deprecated // to be removed before 2.0
   @Override public void registerMetadataProviders(List<RelMetadataProvider> 
list) {
     list.add(0, new HepRelMetadataProvider());
   }
 
-  // implement RelOptPlanner
   @Deprecated // to be removed before 2.0
   @Override public long getRelMetadataTimestamp(RelNode rel) {
     // TODO jvs 20-Apr-2006: This is overly conservative.  Better would be
diff --git a/core/src/main/java/org/apache/calcite/plan/hep/HepProgram.java 
b/core/src/main/java/org/apache/calcite/plan/hep/HepProgram.java
index 893b240..9b57abd 100644
--- a/core/src/main/java/org/apache/calcite/plan/hep/HepProgram.java
+++ b/core/src/main/java/org/apache/calcite/plan/hep/HepProgram.java
@@ -20,7 +20,14 @@ import com.google.common.collect.ImmutableList;
 
 import org.checkerframework.checker.nullness.qual.Nullable;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import static org.apache.calcite.linq4j.Nullness.castNonNull;
+import static org.apache.calcite.linq4j.Nullness.castToInitialized;
 
 /**
  * HepProgram specifies the order in which rules should be attempted by
@@ -31,7 +38,7 @@ import java.util.List;
  * as read/write during planning, so a program can only be in use by a single
  * planner at a time.
  */
-public class HepProgram {
+public class HepProgram extends HepInstruction {
   //~ Static fields/initializers ---------------------------------------------
 
   /**
@@ -43,12 +50,6 @@ public class HepProgram {
 
   final ImmutableList<HepInstruction> instructions;
 
-  int matchLimit;
-
-  @Nullable HepMatchOrder matchOrder;
-
-  HepInstruction.@Nullable EndGroup group;
-
   //~ Constructors -----------------------------------------------------------
 
   /**
@@ -66,13 +67,65 @@ public class HepProgram {
 
   //~ Methods ----------------------------------------------------------------
 
-  void initialize(boolean clearCache) {
-    matchLimit = MATCH_UNTIL_FIXPOINT;
-    matchOrder = HepMatchOrder.DEPTH_FIRST;
-    group = null;
+  @Override State prepare(PrepareContext px) {
+    return new State(px, instructions);
+  }
+
+  /** State for a {@link HepProgram} instruction. */
+  class State extends HepState {
+    final ImmutableList<HepState> instructionStates;
+    int matchLimit = MATCH_UNTIL_FIXPOINT;
+    HepMatchOrder matchOrder = HepMatchOrder.DEPTH_FIRST;
+    HepInstruction.EndGroup.@Nullable State group;
+
+    State(PrepareContext px, List<HepInstruction> instructions) {
+      super(px);
+      final PrepareContext px2 = px.withProgramState(castToInitialized(this));
+      final List<HepState> states = new ArrayList<>();
+      final Map<HepInstruction, Consumer<HepState>> actions = new HashMap<>();
+      for (HepInstruction instruction : instructions) {
+        final HepState state;
+        if (instruction instanceof BeginGroup) {
+          // The state of a BeginGroup instruction needs the state of the
+          // corresponding EndGroup instruction, which we haven't seen yet.
+          // Temporarily put a placeholder State into the list, and add an
+          // action to replace that State. The action will be invoked when we
+          // reach the EndGroup.
+          final int i = states.size();
+          actions.put(((BeginGroup) instruction).endGroup, state2 ->
+              states.set(i,
+                  instruction.prepare(
+                      px2.withEndGroupState((EndGroup.State) state2))));
+          state = castNonNull(null);
+        } else {
+          state = instruction.prepare(px2);
+          if (actions.containsKey(instruction)) {
+            actions.get(instruction).accept(state);
+          }
+        }
+        states.add(state);
+      }
+      this.instructionStates = ImmutableList.copyOf(states);
+    }
+
+    @Override void init() {
+      matchLimit = MATCH_UNTIL_FIXPOINT;
+      matchOrder = HepMatchOrder.DEPTH_FIRST;
+      group = null;
+    }
+
+    @Override void execute() {
+      planner.executeProgram(HepProgram.this, this);
+    }
 
-    for (HepInstruction instruction : instructions) {
-      instruction.initialize(clearCache);
+    boolean skippingGroup() {
+      if (group != null) {
+        // Skip if we've already collected the ruleset.
+        return !group.collecting;
+      } else {
+        // Not grouping.
+        return false;
+      }
     }
   }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/plan/hep/HepProgramBuilder.java 
b/core/src/main/java/org/apache/calcite/plan/hep/HepProgramBuilder.java
index 977136d..7f7c7eb 100644
--- a/core/src/main/java/org/apache/calcite/plan/hep/HepProgramBuilder.java
+++ b/core/src/main/java/org/apache/calcite/plan/hep/HepProgramBuilder.java
@@ -20,13 +20,11 @@ import org.apache.calcite.plan.CommonRelSubExprRule;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelOptRule;
 
-import org.checkerframework.checker.nullness.qual.Nullable;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
-import static java.util.Objects.requireNonNull;
+import static com.google.common.base.Preconditions.checkArgument;
 
 /**
  * HepProgramBuilder creates instances of {@link HepProgram}.
@@ -36,7 +34,9 @@ public class HepProgramBuilder {
 
   private final List<HepInstruction> instructions = new ArrayList<>();
 
-  private HepInstruction.@Nullable BeginGroup group;
+  /** If a group is under construction, ordinal of the first instruction in the
+   * group; otherwise -1. */
+  private int group = -1;
 
   //~ Constructors -----------------------------------------------------------
 
@@ -53,7 +53,7 @@ public class HepProgramBuilder {
 
   private void clear() {
     instructions.clear();
-    group = null;
+    group = -1;
   }
 
   /**
@@ -72,11 +72,7 @@ public class HepProgramBuilder {
    */
   public <R extends RelOptRule> HepProgramBuilder addRuleClass(
       Class<R> ruleClass) {
-    HepInstruction.RuleClass instruction =
-        new HepInstruction.RuleClass<R>();
-    instruction.ruleClass = ruleClass;
-    instructions.add(instruction);
-    return this;
+    return addInstruction(new HepInstruction.RuleClass(ruleClass));
   }
 
   /**
@@ -95,11 +91,7 @@ public class HepProgramBuilder {
    * @param rules collection of rules to fire
    */
   public HepProgramBuilder addRuleCollection(Collection<RelOptRule> rules) {
-    HepInstruction.RuleCollection instruction =
-        new HepInstruction.RuleCollection();
-    instruction.rules = rules;
-    instructions.add(instruction);
-    return this;
+    return addInstruction(new HepInstruction.RuleCollection(rules));
   }
 
   /**
@@ -113,11 +105,7 @@ public class HepProgramBuilder {
    * @param rule rule to fire
    */
   public HepProgramBuilder addRuleInstance(RelOptRule rule) {
-    HepInstruction.RuleInstance instruction =
-        new HepInstruction.RuleInstance();
-    instruction.rule = requireNonNull(rule, "rule");
-    instructions.add(instruction);
-    return this;
+    return addInstruction(new HepInstruction.RuleInstance(rule));
   }
 
   /**
@@ -134,11 +122,8 @@ public class HepProgramBuilder {
    * @param ruleDescription description of rule to fire
    */
   public HepProgramBuilder addRuleByDescription(String ruleDescription) {
-    HepInstruction.RuleInstance instruction =
-        new HepInstruction.RuleInstance();
-    instruction.ruleDescription = ruleDescription;
-    instructions.add(instruction);
-    return this;
+    return addInstruction(
+        new HepInstruction.RuleLookup(ruleDescription));
   }
 
   /**
@@ -148,11 +133,9 @@ public class HepProgramBuilder {
    * addRuleXXX methods may be called until the next addGroupEnd.
    */
   public HepProgramBuilder addGroupBegin() {
-    assert group == null;
-    HepInstruction.BeginGroup instruction = new HepInstruction.BeginGroup();
-    instructions.add(instruction);
-    group = instruction;
-    return this;
+    checkArgument(group < 0);
+    group = instructions.size();
+    return addInstruction(new HepInstruction.Placeholder());
   }
 
   /**
@@ -162,12 +145,11 @@ public class HepProgramBuilder {
    * whole.
    */
   public HepProgramBuilder addGroupEnd() {
-    assert group != null;
-    HepInstruction.EndGroup instruction = new HepInstruction.EndGroup();
-    instructions.add(instruction);
-    requireNonNull(group, "group").endGroup = instruction;
-    group = null;
-    return this;
+    checkArgument(group >= 0);
+    final HepInstruction.EndGroup endGroup = new HepInstruction.EndGroup();
+    instructions.set(group, new HepInstruction.BeginGroup(endGroup));
+    group = -1;
+    return addInstruction(endGroup);
   }
 
   /**
@@ -179,12 +161,8 @@ public class HepProgramBuilder {
    *                   only non-guaranteed converters
    */
   public HepProgramBuilder addConverters(boolean guaranteed) {
-    assert group == null;
-    HepInstruction.ConverterRules instruction =
-        new HepInstruction.ConverterRules();
-    instruction.guaranteed = guaranteed;
-    instructions.add(instruction);
-    return this;
+    checkArgument(group < 0);
+    return addInstruction(new HepInstruction.ConverterRules(guaranteed));
   }
 
   /**
@@ -193,11 +171,8 @@ public class HepProgramBuilder {
    * than one parent.
    */
   public HepProgramBuilder addCommonRelSubExprInstruction() {
-    assert group == null;
-    HepInstruction.CommonRelSubExprRules instruction =
-        new HepInstruction.CommonRelSubExprRules();
-    instructions.add(instruction);
-    return this;
+    checkArgument(group < 0);
+    return addInstruction(new HepInstruction.CommonRelSubExprRules());
   }
 
   /**
@@ -209,11 +184,8 @@ public class HepProgramBuilder {
    * @param order new match direction to set
    */
   public HepProgramBuilder addMatchOrder(HepMatchOrder order) {
-    assert group == null;
-    HepInstruction.MatchOrder instruction = new HepInstruction.MatchOrder();
-    instruction.order = order;
-    instructions.add(instruction);
-    return this;
+    checkArgument(group < 0);
+    return addInstruction(new HepInstruction.MatchOrder(order));
   }
 
   /**
@@ -225,11 +197,8 @@ public class HepProgramBuilder {
    *              remove limit
    */
   public HepProgramBuilder addMatchLimit(int limit) {
-    assert group == null;
-    HepInstruction.MatchLimit instruction = new HepInstruction.MatchLimit();
-    instruction.limit = limit;
-    instructions.add(instruction);
-    return this;
+    checkArgument(group < 0);
+    return addInstruction(new HepInstruction.MatchLimit(limit));
   }
 
   /**
@@ -244,12 +213,14 @@ public class HepProgramBuilder {
    * (initialized to the defaults every time the subprogram is executed) and
    * any changes it makes to those settings do not affect the parent program.
    *
-   * @param program subprogram to execute
+   * @param program subProgram to execute
    */
   public HepProgramBuilder addSubprogram(HepProgram program) {
-    assert group == null;
-    HepInstruction.Subprogram instruction = new HepInstruction.Subprogram();
-    instruction.subprogram = program;
+    checkArgument(group < 0);
+    return addInstruction(new HepInstruction.SubProgram(program));
+  }
+
+  private HepProgramBuilder addInstruction(HepInstruction instruction) {
     instructions.add(instruction);
     return this;
   }
@@ -261,7 +232,7 @@ public class HepProgramBuilder {
    * @return immutable program
    */
   public HepProgram build() {
-    assert group == null;
+    checkArgument(group < 0);
     HepProgram program = new HepProgram(instructions);
     clear();
     return program;
diff --git a/core/src/main/java/org/apache/calcite/plan/hep/HepState.java 
b/core/src/main/java/org/apache/calcite/plan/hep/HepState.java
new file mode 100644
index 0000000..a3fe2aa
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/plan/hep/HepState.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.plan.hep;
+
+/** Able to execute an instruction or program, and contains all mutable state
+ * for that instruction.
+ *
+ * <p>The goal is that programs are re-entrant - they can be used by more than
+ * one thread at a time. We achieve this by making instructions and programs
+ * immutable. All mutable state is held in the state objects.
+ *
+ * <p>State objects are allocated, just before the program is executed, by
+ * calling {@link HepInstruction#prepare(HepInstruction.PrepareContext)} on the
+ * program and recursively on all of its instructions. */
+abstract class HepState {
+  final HepPlanner planner;
+  final HepProgram.State programState;
+
+  HepState(HepInstruction.PrepareContext px) {
+    this.planner = px.planner;
+    this.programState = px.programState;
+  }
+
+  /** Executes the instruction. */
+  abstract void execute();
+
+  /** Re-initializes the state. (The state was initialized when it was created
+   * via {@link HepInstruction#prepare}.) */
+  void init() {
+  }
+}
diff --git a/linq4j/src/main/java/org/apache/calcite/linq4j/Nullness.java 
b/linq4j/src/main/java/org/apache/calcite/linq4j/Nullness.java
index 1248b63..dd87f0c 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/Nullness.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/Nullness.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.linq4j;
 
+import org.checkerframework.checker.initialization.qual.UnderInitialization;
 import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
 import org.checkerframework.checker.nullness.qual.NonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
@@ -24,28 +25,46 @@ import org.checkerframework.dataflow.qual.Pure;
 /**
  * The methods in this class allow to cast nullable reference to a 
non-nullable one.
  * This is an internal class, and it is not meant to be used as a public API.
+ *
  * <p>The class enables to remove checker-qual runtime dependency, and helps 
IDEs to see
- * the resulting types of {@code castNonNull} better</p>
+ * the resulting types of {@code castNonNull} better.
  */
-@SuppressWarnings({"cast.unsafe", "NullableProblems", 
"contracts.postcondition.not.satisfied"})
+@SuppressWarnings({"cast.unsafe", "RedundantCast", 
"contracts.postcondition.not.satisfied"})
 public class Nullness {
   private Nullness() {
   }
 
   /**
-   * Enables to threat nullable type as non-nullable with no assertions.
+   * Allows you to treat a nullable type as non-nullable with no assertions.
+   *
+   * <p>It is useful in the case you have a nullable lately-initialized field
+   * like the following:
+   *
+   * <pre><code>
+   * class Wrapper&lt;T&gt; {
+   *   &#64;Nullable T value;
+   * }
+   * </code></pre>
+   *
+   * <p>That signature allows you to use {@code Wrapper} with both nullable or
+   * non-nullable types: {@code Wrapper<@Nullable Integer>}
+   * vs {@code Wrapper<Integer>}. Suppose you need to implement
    *
-   * <p>It is useful in the case you have a nullable lately-initialized field 
like the following:
-   * {@code class Wrapper<T> { @Nullable T value; }}.
-   * That signature allows to use {@code Wrapper} with both nullable or 
non-nullable types:
-   * {@code Wrapper<@Nullable Integer>} vs {@code Wrapper<Integer>}. Suppose 
you need to implement
-   * {@code T get() { return value; }} The issue is checkerframework does not 
permit that
-   * because {@code T} has unknown nullability, so the following needs to be 
used:
-   * {@code T get() { return sneakyNull(value); }}</p>
+   * <pre><code>
+   * T get() { return value; }
+   * </code></pre>
+   *
+   * <p>The issue is checkerframework does not permit that because {@code T}
+   * has unknown nullability, so the following needs to be used:
+   *
+   * <pre><code>
+   * T get() { return sneakyNull(value); }
+   * </code></pre>
    *
    * @param <T>     the type of the reference
    * @param ref     a reference of @Nullable type, that is non-null at run time
-   * @return the argument, casted to have the type qualifier @NonNull
+   *
+   * @return the argument, cast to have the type qualifier @NonNull
    */
   @Pure
   public static @EnsuresNonNull("#1")
@@ -54,4 +73,24 @@ public class Nullness {
     //noinspection ConstantConditions
     return (@NonNull T) ref;
   }
+
+  /**
+   * Allows you to treat an uninitialized or under-initialization object as
+   * initialized with no assertions.
+   *
+   * @param <T>     The type of the reference
+   * @param ref     A reference that was @Uninitialized at some point but is
+   *                now fully initialized
+   *
+   * @return the argument, cast to have type qualifier @Initialized
+   */
+  @SuppressWarnings({"unchecked"})
+  @Pure
+  public static <T> T castToInitialized(@UnderInitialization T ref) {
+    // To throw CheckerFramework off the scent, we put the object into an 
array,
+    // cast the array to an Object, and cast back to an array.
+    Object src = new Object[] {ref};
+    Object[] dest = (Object[]) src;
+    return (T) dest[0];
+  }
 }

Reply via email to