All files / src/impl LifeCyclePromisor.impl.ts

96.77% Statements 60/62
83.33% Branches 15/18
100% Functions 12/12
96.77% Lines 60/62

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139  25x   25x       25x 25x   25x 25x 25x 25x                 25x 5x                       8x 8x 3x   4x       5x 5x 5x       3x 3x 3x 2x     3x   3x       5x       5x       8x 1x   7x 7x 3x 3x   4x       7x 7x           4x     4x         4x 4x 4x 4x 4x 3x       4x 3x 3x 3x   1x 1x 1x           2x 2x 2x 2x   2x 2x         5x   5x 5x 5x 5x        
import { AutoCloseOne } from "@jonloucks/contracts-ts/api/AutoClose";
import { AutoOpen, guard as isAutoOpen } from "@jonloucks/contracts-ts/api/AutoOpen";
import { Promisor } from "@jonloucks/contracts-ts/api/Promisor";
import { OptionalType, RequiredType, isPresent } from "@jonloucks/contracts-ts/api/Types";
import { AtomicBoolean } from "@jonloucks/contracts-ts/auxiliary/AtomicBoolean";
import { AtomicInteger } from "@jonloucks/contracts-ts/auxiliary/AtomicInteger";
import { AtomicReference } from "@jonloucks/contracts-ts/auxiliary/AtomicReference";
import { promisorCheck } from "@jonloucks/contracts-ts/auxiliary/Checks";
import { IllegalStateException } from "@jonloucks/contracts-ts/auxiliary/IllegalStateException";
 
import { create as createAtomicBoolean } from "./AtomicBoolean.impl";
import { create as createAtomicInteger } from "./AtomicInteger.impl";
import { create as createAtomicReference } from "./AtomicReference.impl";
import { create as createAutoCloseOne } from "./AutoCloseOne.impl";
 
/**
 * Factory to create an Life Cycle promisor implementation
 * 
 * @param referent the source promisor
 * @param <T> the type of deliverable
 * @returns the new Life Cycle Promisor implementation
 */
export function create<T>(referent: Promisor<T>): RequiredType<Promisor<T>> {
  return LifeCyclePromisorImpl.internalCreate<T>(referent);
}
 
// ---- Implementation details below ----
 
/**
 * Implementation of a Life Cycle Promisor
 * @param <T> the type of deliverable
 */
class LifeCyclePromisorImpl<T> implements Promisor<T> {
 
  public demand(): OptionalType<T> {
    const currentDeliverable: AtomicReference<T> = createAtomicReference<T>();
    if (this.getCurrentDeliverable(currentDeliverable)) {
      return currentDeliverable.get();
    }
    return this.createDeliverableIfNeeded();
  }
 
  public incrementUsage(): number {
    let currentUsage = this.usageCounter.incrementAndGet();
    this.referent.incrementUsage();
    return currentUsage;
  }
 
  public decrementUsage(): number {
    let currentUsage = this.usageCounter.decrementAndGet();
    try {
      if (currentUsage == 0) {
        this.closeDeliverable();
      }
    } finally {
      this.referent.decrementUsage();
    }
    return currentUsage;
  }
 
  static internalCreate<T>(referentPromisor: Promisor<T>): RequiredType<Promisor<T>> {
    return new LifeCyclePromisorImpl<T>(referentPromisor);
  }
 
  private constructor(referentPromisor: Promisor<T>) {
    this.referent = promisorCheck(referentPromisor);
  }
 
  private getCurrentDeliverable(placeholder: AtomicReference<T>): boolean {
    if (this.usageCounter.get() == 0) {
      throw new IllegalStateException("Usage count is zero.");
    }
    this.maybeRethrowOpenException();
    if (this.isDeliverableAcquired.get()) {
      placeholder.set(this.atomicDeliverable.get());
      return true;
    }
    return false;
  }
 
  private maybeRethrowOpenException(): void {
    const thrown: unknown = this.openException.get();
    Iif (isPresent(thrown)) {
      throw thrown;
    }
  }
 
  private createDeliverableIfNeeded(): OptionalType<T> {
    Iif (this.isDeliverableAcquired.get()) {
      return this.atomicDeliverable.get();
    } else {
      return this.createDeliverable();
    }
  }
 
  private createDeliverable(): OptionalType<T> {
    this.openException.set(null);
    const currentDeliverable: OptionalType<T> = this.referent.demand();
    this.atomicDeliverable.set(currentDeliverable);
    this.isDeliverableAcquired.set(true);
    this.openDeliverable(currentDeliverable);
    return currentDeliverable;
  }
 
  private openDeliverable(deliverable: OptionalType<T>): void {
    if (isPresent(deliverable) && isAutoOpen(deliverable)) {
      const autoOpen: AutoOpen = deliverable;
      try {
        this.closer.set(autoOpen.autoOpen());
      } catch (thrown) {
        this.openException.set(thrown);
        this.isDeliverableAcquired.set(false);
        throw thrown;
      }
    }
  }
 
  private closeDeliverable(): void {
    Eif (this.isDeliverableAcquired.get()) {
      const deliverable: OptionalType<T> | null = this.atomicDeliverable.get();
      try {
        this.closer.close();
      } finally {
        this.atomicDeliverable.compareAndSet(deliverable, null);
        this.isDeliverableAcquired.set(false);
      }
    }
  }
 
  private readonly usageCounter: AtomicInteger = createAtomicInteger();
  private readonly referent: Promisor<T>;
  private readonly isDeliverableAcquired: AtomicBoolean = createAtomicBoolean();
  private readonly atomicDeliverable: AtomicReference<T> = createAtomicReference<T>();
  private readonly openException: AtomicReference<unknown> = createAtomicReference<unknown>();
  private readonly closer: AutoCloseOne = createAutoCloseOne();
}