public void prefetchForMerge(
     W window, Collection<W> mergingWindows, MergingStateAccessor<?, W> state) {
   if (isFinishedSetNeeded()) {
     for (ValueState<?> value : state.accessInEachMergingWindow(FINISHED_BITS_TAG).values()) {
       value.readLater();
     }
   }
   rootTrigger
       .getSpec()
       .prefetchOnMerge(
           contextFactory.createMergingStateAccessor(window, mergingWindows, rootTrigger));
 }
  private void persistFinishedSet(
      StateAccessor<?> state, FinishedTriggersBitSet modifiedFinishedSet) {
    if (!isFinishedSetNeeded()) {
      return;
    }

    ValueState<BitSet> finishedSetState = state.access(FINISHED_BITS_TAG);
    if (!readFinishedBits(finishedSetState).equals(modifiedFinishedSet)) {
      if (modifiedFinishedSet.getBitSet().isEmpty()) {
        finishedSetState.clear();
      } else {
        finishedSetState.write(modifiedFinishedSet.getBitSet());
      }
    }
  }
 private void clearFinishedBits(ValueState<BitSet> state) {
   if (!isFinishedSetNeeded()) {
     // Nothing to clear.
     return;
   }
   state.clear();
 }
  private FinishedTriggersBitSet readFinishedBits(ValueState<BitSet> state) {
    if (!isFinishedSetNeeded()) {
      // If no trigger in the tree will ever have finished bits, then we don't need to read them.
      // So that the code can be agnostic to that fact, we create a BitSet that is all 0 (not
      // finished) for each trigger in the tree.
      return FinishedTriggersBitSet.emptyWithCapacity(rootTrigger.getFirstIndexAfterSubtree());
    }

    BitSet bitSet = state.read();
    return bitSet == null
        ? FinishedTriggersBitSet.emptyWithCapacity(rootTrigger.getFirstIndexAfterSubtree())
        : FinishedTriggersBitSet.fromBitSet(bitSet);
  }