LifeCyclePromisorImpl.java

package io.github.jonloucks.contracts.impl;

import io.github.jonloucks.contracts.api.AutoClose;
import io.github.jonloucks.contracts.api.AutoOpen;
import io.github.jonloucks.contracts.api.Promisor;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import static io.github.jonloucks.contracts.api.Checks.*;
import static java.util.Optional.ofNullable;

/**
 * Implementation for {@link io.github.jonloucks.contracts.api.Promisors#createLifeCyclePromisor(Promisor)}
 * @see io.github.jonloucks.contracts.api.Promisors#createLifeCyclePromisor(Promisor)
 * @param <T> the type of deliverable
 */
final class LifeCyclePromisorImpl<T> implements Promisor<T> {
    
    @Override
    public T demand() {
        final AtomicReference<T> currentDeliverable = new AtomicReference<>();
        if (getCurrentDeliverable(currentDeliverable)) {
            return currentDeliverable.get();
        }
        return createDeliverableIfNeeded();
    }
    
    @Override
    public int incrementUsage() {
        final int currentUsage = usageCounter.incrementAndGet();
        referentPromisor.incrementUsage();
        return currentUsage;
    }
    
    @Override
    public int decrementUsage() {
        final int currentUsage = usageCounter.decrementAndGet();
        try {
            if (currentUsage == 0) {
                closeDeliverable();
            }
        } finally {
            referentPromisor.decrementUsage();
        }
        return currentUsage;
    }
    
    LifeCyclePromisorImpl(Promisor<T> referentPromisor) {
        this.referentPromisor = promisorCheck(referentPromisor);
    }
    
    private boolean getCurrentDeliverable(AtomicReference<T> placeholder) {
        if (usageCounter.get() == 0) {
            throw new IllegalStateException("Usage count is zero.");
        }
        maybeRethrowOpenException();
        if (isDeliverableAcquired.get()) {
            placeholder.set(atomicDeliverable.get());
            return true;
        }
        return false;
    }
    
    private void maybeRethrowOpenException() {
        ofNullable(openException.get())
            .ifPresent(this::rethrowOpenException);
    }
    
    private void rethrowOpenException(Throwable thrown) {
        if (thrown instanceof Error) {
            throw (Error) thrown;
        }
        throw (RuntimeException) thrown;
    }
    
    private T createDeliverableIfNeeded() {
        synchronized (simpleLock) {
            if (isDeliverableAcquired.get()) {
                return atomicDeliverable.get();
            } else {
                return createDeliverable();
            }
        }
    }
    
    private T createDeliverable() {
        openException.set(null);
        final T currentDeliverable = referentPromisor.demand();
        atomicDeliverable.set(currentDeliverable);
        openDeliverable(currentDeliverable);
        isDeliverableAcquired.set(true);
        return currentDeliverable;
    }
    
    private void openDeliverable(final T deliverable) {
        if (deliverable instanceof AutoOpen) {
            try {
                atomicClose.set(((AutoOpen) deliverable).open());
            } catch (RuntimeException | Error thrown) {
                openException.set(thrown);
                isDeliverableAcquired.set(false);
                throw thrown;
            }
        }
    }
    
    private void closeDeliverable() {
        if (isDeliverableAcquired.get()) {
            final T deliverable = atomicDeliverable.get();
            try {
                ofNullable(atomicClose.get()).ifPresent(close -> {
                    atomicClose.set(null);
                    close.close();
                });
            } finally {
                atomicDeliverable.compareAndSet(deliverable, null);
                isDeliverableAcquired.set(false);
            }
        }
    }
    
    private final AtomicInteger usageCounter = new AtomicInteger();
    private final Promisor<T> referentPromisor;
    private final AtomicBoolean isDeliverableAcquired = new AtomicBoolean();
    private final AtomicReference<T> atomicDeliverable = new AtomicReference<>();
    private final AtomicReference<Throwable> openException = new AtomicReference<>();
    private final AtomicReference<AutoClose> atomicClose = new AtomicReference<>();
    private final Object simpleLock = new Object();
}