SimplePool.java

package space.sunqian.fs.object.pool;

import space.sunqian.annotation.Immutable;
import space.sunqian.annotation.Nonnull;
import space.sunqian.annotation.Nullable;
import space.sunqian.fs.Fs;

import java.time.Duration;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * Simple object pool interface, provides methods for acquiring, releasing objects. If the pool is closed due to some
 * exception, the {@link #unreleasedObjects()} can be still invoked to get the list of unreleased objects.
 *
 * @param <T> the type of objects in the pool
 * @author sunqian
 */
public interface SimplePool<T> {

    /**
     * Returns a builder for {@link SimplePool}.
     *
     * @param <T> the type of objects in the pool
     * @return a builder for {@link SimplePool}
     */
    static <T> @Nonnull Builder<T> newBuilder() {
        return new Builder<>();
    }

    /**
     * Acquires an object from the pool, or {@code null} if no object is available.
     * <p>
     * If any exception occurs during the acquisition process, {@link #close()} will be invoked to close this pool and
     * the {@link #unreleasedObjects()} will return the list of unreleased objects, including idle objects and active
     * objects.
     *
     * @return the acquired object, or {@code null} if no object is available
     * @throws ObjectPoolException if failed to acquire object
     */
    @Nullable
    T get() throws ObjectPoolException;

    /**
     * Releases the given object to the pool. Returns {@code true} if the object is released successfully, {@code false}
     * otherwise. If the object is not acquired from this pool, this method will do nothing just return {@code false}.
     * <p>
     * If any exception occurs during the release process, {@link #close()} will be invoked to close this pool and the
     * {@link #unreleasedObjects()} will return the list of unreleased objects, including idle objects and active
     * objects.
     *
     * @param obj the given object to release
     * @return {@code true} if the object is released successfully, {@code false} otherwise
     * @throws ObjectPoolException if failed to release object
     */
    boolean release(@Nonnull T obj) throws ObjectPoolException;

    /**
     * Cleans the pool, removing idle objects that idle timeout or invalidated by validator, or over the core size,
     * adding new objects up to the core size if necessary. The active objects will not be cleaned.
     * <p>
     * If any exception occurs during the clean process, {@link #close()} will be invoked to close this pool and the
     * {@link #unreleasedObjects()} will return the list of unreleased objects, including idle objects and active
     * objects.
     *
     * @throws ObjectPoolException if any exception occurs during the clean process
     */
    void clean() throws ObjectPoolException;

    /**
     * Closes the pool. The idle objects will be discarded by configured discarder, and the active objects will not be
     * discarded. If this process is failed, the pool will be in a closed state, and the {@link #unreleasedObjects()}
     * will return the list of unreleased objects, including idle objects and active objects.
     */
    void close();

    /**
     * Returns whether the pool is closed.
     *
     * @return {@code true} if the pool is closed, {@code false} otherwise
     */
    boolean isClosed();

    /**
     * Returns the list of unreleased objects after the pool is closed, including idle objects and active objects. If
     * the pool is not closed, this method will return an empty list.
     *
     * @return the list of unreleased objects after the pool is closed, or an empty list if the pool is not closed
     */
    @Nonnull
    @Immutable
    List<@Nonnull T> unreleasedObjects();

    /**
     * Returns the number of objects in the pool.
     *
     * @return the number of objects in the pool
     */
    int size();

    /**
     * Returns the number of idle objects in the pool.
     *
     * @return the number of idle objects in the pool
     */
    int idleSize();

    /**
     * Returns the number of active objects in the pool. If the pool is closed, this method will return the number of
     * unreleased active objects.
     *
     * @return the number of active objects in the pool
     */
    int activeSize();

    /**
     * Builder class for {@link SimplePool}.
     *
     * @param <T> the type of objects in the pool
     */
    class Builder<T> {

        // size:

        private int coreSize = 1;
        private int maxSize = -1;
        private long idleTimeoutMillis = 60000;

        // actions:

        private Supplier<? extends @Nonnull T> supplier;
        private @Nonnull Predicate<? super @Nonnull T> validator = t -> true;
        private @Nonnull Consumer<? super @Nonnull T> discarder = t -> {};

        /**
         * Sets the supplier for creating new objects.
         *
         * @param supplier the supplier for creating new objects
         * @return this builder
         */
        public @Nonnull Builder<T> supplier(@Nonnull Supplier<? extends @Nonnull T> supplier) {
            this.supplier = supplier;
            return this;
        }

        /**
         * Sets the validator for checking object validity.
         *
         * @param validator the validator for checking object validity
         * @return this builder
         */
        public @Nonnull Builder<T> validator(@Nonnull Predicate<? super @Nonnull T> validator) {
            this.validator = validator;
            return this;
        }

        /**
         * Sets the discarder for destroying objects.
         *
         * @param discarder the discarder for destroying objects
         * @return this builder
         */
        public @Nonnull Builder<T> discarder(@Nonnull Consumer<? super @Nonnull T> discarder) {
            this.discarder = discarder;
            return this;
        }

        /**
         * Sets the core size of the pool.
         *
         * @param coreSize the core size of the pool
         * @return this builder
         * @throws IllegalArgumentException if coreSize is less than or equal to 0
         */
        public @Nonnull Builder<T> coreSize(int coreSize) throws IllegalArgumentException {
            if (coreSize <= 0) {
                throw new IllegalArgumentException("coreSize must be greater than 0.");
            }
            this.coreSize = coreSize;
            return this;
        }

        /**
         * Sets the max size of the pool.
         *
         * @param maxSize the max size of the pool
         * @return this builder
         * @throws IllegalArgumentException if maxSize is less than coreSize
         */
        public @Nonnull Builder<T> maxSize(int maxSize) throws IllegalArgumentException {
            if (maxSize < coreSize) {
                throw new IllegalArgumentException("maxSize must be greater than or equal to coreSize.");
            }
            this.maxSize = maxSize;
            return this;
        }

        /**
         * Sets the idle timeout in milliseconds of the pool.
         *
         * @param idleTimeoutMillis the idle timeout in milliseconds of the pool
         * @return this builder
         * @throws IllegalArgumentException if idleTimeout is less than or equal to 0
         */
        public @Nonnull Builder<T> idleTimeout(long idleTimeoutMillis) throws IllegalArgumentException {
            if (idleTimeoutMillis <= 0) {
                throw new IllegalArgumentException("idleTimeout must be greater than 0.");
            }
            this.idleTimeoutMillis = idleTimeoutMillis;
            return this;
        }

        /**
         * Sets the idle timeout in milliseconds of the pool.
         *
         * @param idleTimeout the idle timeout in milliseconds of the pool
         * @return this builder
         * @throws IllegalArgumentException if idleTimeout is less than or equal to 0
         */
        public @Nonnull Builder<T> idleTimeout(@Nonnull Duration idleTimeout) throws IllegalArgumentException {
            if (idleTimeout.isNegative()) {
                throw new IllegalArgumentException("idleTimeout must be greater than 0.");
            }
            this.idleTimeoutMillis = idleTimeout.toMillis();
            return this;
        }

        /**
         * Builds a {@link SimplePool} instance. If some exception occurs during the initialization, a closed pool with
         * unreleased objects (if any) will be returned.
         *
         * @param <T1> the type of objects in the pool, it is used to pass the generic type in method-chaining
         * @return the built {@link SimplePool} instance
         * @throws IllegalArgumentException if supplier is not set
         */
        public <T1 extends T> @Nonnull SimplePool<T1> build() throws IllegalArgumentException {
            if (supplier == null) {
                throw new IllegalArgumentException("Supplier must be set.");
            }
            return Fs.as(new SimplePoolImpl<>(
                coreSize, Math.max(coreSize, maxSize), idleTimeoutMillis, supplier, validator, discarder
            ));
        }
    }
}