All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.unkrig.commons.lang.protocol.ProducerUtil Maven / Gradle / Ivy


/*
 * de.unkrig.commons - A general-purpose Java class library
 *
 * Copyright (c) 2011, Arno Unkrig
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *       following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *       following disclaimer in the documentation and/or other materials provided with the distribution.
 *    3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
 *       products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package de.unkrig.commons.lang.protocol;

import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

import de.unkrig.commons.nullanalysis.Nullable;

/**
 * Various {@link Producer}-related utility methods.
 */
public final
class ProducerUtil {

    private
    ProducerUtil() {}

    /**
     * The returned {@link Producer} calls the {@code delegate} iff the {@code condition} returns {@code true},
     * otherwise it returns the previous product of the {@code delegate}, or {@code null} iff the delegate has
     * not yet been called.
     *
     * @param subject The {@code subject} argument for the {@code condition}
     * @deprecated    Use {@link #cache(ProducerWhichThrows, ProducerWhichThrows)} instead, which has very similar (but
     *                not identical) semantics.
     */
    @Deprecated public static  Producer
    sparingProducer(final Producer delegate, final Predicate condition, final ST subject) {

        return new Producer() {

            @Nullable private T product;

            @Override @Nullable public T
            produce() {
                if (condition.evaluate(subject)) this.product = delegate.produce();
                return this.product;
            }
        };
    }

    /**
     * Returns a {@code Producer} who's first evaluation result is {@code true}, and each following result is
     * {@code true} iff the last {@code true} result was returned at least the given {@code interval} milliseconds ago.
     * In other words, the interval between two returned {@code true} values is never shorter than {@code interval}
     * milliseconds.
     * 

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static Producer every(final long interval) { return new Producer() { private long expirationTime; @Override @Nullable public synchronized Boolean produce() { long now = System.currentTimeMillis(); if (now >= this.expirationTime) { this.expirationTime = now + interval; return true; } else { return false; } } }; } /** * Converts the source into a {@link ProducerWhichThrows ProducerWhichThrows<T, EX>}. *

* This is always possible, because the source is only allowed to throw {@link RuntimeException}s. *

*

* Notice {@code Producer} extends {@code ProducerWhichThrows}, thus you don't * need this method to convert to {@code ProducerWhichThrows}. *

* * @param The product type * @param The target producer's exception */ public static ProducerWhichThrows asProducerWhichThrows(final Producer source) { @SuppressWarnings("unchecked") ProducerWhichThrows result = (ProducerWhichThrows) source; return result; } /** * Converts the source into a {@link Producer Producer<T>}. *

* This is always possible, because both are only allowed to throw {@link RuntimeException}s. *

* * @param The product type */ public static Producer asProducer(final ProducerWhichThrows source) { @SuppressWarnings("unchecked") Producer result = (Producer) source; return result; } /** * Creates and returns a {@link Producer} that produced the given {@code elements}. * *

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static Producer fromElements(final T... elements) { return new Producer() { int idx; @Override @Nullable public T produce() { return this.idx == elements.length ? null : elements[this.idx++]; } }; } /** * @deprecated Use "{@code fromIterator(}delegate{@code .iterator(), true)}" instead */ @Deprecated public static Producer fromCollection(Collection delegate) { return ProducerUtil.fromIterator(delegate.iterator(), true); } /** * @deprecated Use "{@code fromIterator(}delegate{@code .iterator(), true)}" instead */ @Deprecated public static Producer fromIterable(Iterable delegate, boolean remove) { return ProducerUtil.fromIterator(delegate.iterator(), remove); } /** * Produces the elements of the delegate array, in ascending index order, and after that an infinite * sequence of {@code null}s. *

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static FromArrayProducer fromArray(final T[] delegate) { return ProducerUtil.fromArray(delegate, 0, delegate.length); } /** * Produces the elements from ... to-1 of the {@code delegate} array, and after that an * infinite sequence of {@code null}s. *

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

* * @throws IllegalArgumentException from is less than 0 * @throws IllegalArgumentException to is less than from * @throws IllegalArgumentException to is greater than delegate{@code .length} */ public static FromArrayProducer fromArray(final T[] delegate, final int from, final int to) { if (from < 0 || to < from || to > delegate.length) throw new IllegalArgumentException(); return new FromArrayProducer() { int idx = from; @Override @Nullable public T produce() { return this.idx < to ? delegate[this.idx++] : null; } @Override public int index() { return this.idx; } }; } /** * Extends the concept of the {@link Producer} by an "index". * * @param See {@link Producer} * @see #index() */ public interface FromArrayProducer extends Producer { /** * @return The index of the next element that will be returned by {@link #produce()}; * delegate{@code .length} on end-of-array */ int index(); } /** * Produces the products of the {@code iterator}, or {@code null} iff the {@code iterator} has no more elements. *

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static Producer fromIterator(final Iterator delegate) { return ProducerUtil.fromIterator(delegate, false); } /** * Produces the products of the {@code iterator}, or {@code null} iff the {@code iterator} has no more elements. *

* If remove is {@code true}, then products are removed from the underlying collections as the * elements are iterated. *

*

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static Producer fromIterator(final Iterator delegate, final boolean remove) { return new Producer() { @Override @Nullable public T produce() { if (!delegate.hasNext()) return null; T product = delegate.next(); if (remove) delegate.remove(); return product; } }; } /** * Produces objects based on the number of preceding invocations, i.e. the {@code indexTransformer} is invoked * with subjects '0', '1', '2', ... *

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static Producer fromIndexTransformer(final Transformer indexTransformer) { return new Producer() { private int index; @Override @Nullable public T produce() { return indexTransformer.transform(this.index++); } }; } /** * Produces objects based on the number of preceding invocations, i.e. the {@code indexTransformer} is invoked * with subjects '0', '1', '2', ... *

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static ProducerWhichThrows fromIndexTransformer(final TransformerWhichThrows indexTransformer) { return new ProducerWhichThrows() { private int index; @Override @Nullable public T produce() throws EX { return indexTransformer.transform(this.index++); } }; } /** * Transforms each product of the delegate through the transformer. *

* {@code null} products are not transformed and give a {@code null} product. *

*/ public static ProducerWhichThrows transform( final ProducerWhichThrows delegate, final TransformerWhichThrows transformer ) { return new ProducerWhichThrows() { @Override @Nullable public T2 produce() throws EX { T1 product = delegate.produce(); return product == null ? null : transformer.transform(product); } }; } /** * Transforms each product of the delegate through the function. *

* {@code null} products are not transformed and give a {@code null} product. *

*/ public static ProducerWhichThrows transform( final ProducerWhichThrows delegate, final FunctionWhichThrows function ) { return new ProducerWhichThrows() { @Override @Nullable public T2 produce() throws EX { return function.call(delegate.produce()); } }; } /** * @return A producer which produces bytes through {@code new java.util.Random(seed).nextInt(0x100)} */ public static Producer randomByteProducer(final long seed) { return new Producer() { final Random r = new Random(seed); @Override public Byte produce() { return (byte) this.r.nextInt(0x100); } }; } /** * @return A producer which always produces the constant */ public static Producer constantProducer(final T constant) { return new Producer() { @Override public T produce() { return constant; } }; } /** * Returns a producer which, when invoked, calls the delegate, and returns its product iff the * predicate evaluates to {@code true}. */ public static ProducerWhichThrows filter( final ProducerWhichThrows delegate, final PredicateWhichThrows predicate ) { return new ProducerWhichThrows() { @Override @Nullable public T produce() throws EX { for (;;) { T product = delegate.produce(); if (product == null) return null; if (predicate.evaluate(product)) return product; } } }; } public static ProducerWhichThrows filter( final ProducerWhichThrows delegate, final Predicate predicate ) { return new ProducerWhichThrows() { @Override @Nullable public T produce() throws EX { for (;;) { T product = delegate.produce(); if (product == null) return null; if (predicate.evaluate(product)) return product; } } }; } /** * Discards the elements that the delegate produces while they are compressable. After that, * it produces the elements that the delegate produces and are not compressable, and * reduces sequences of one or more compressable elements to compressed. *

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static Producer compress(final Producer delegate, final Predicate compressable, final T compressed) { return new Producer() { boolean initial = true; @Nullable T lookahead; @Override @Nullable public T produce() { if (this.initial) { T product; do { product = delegate.produce(); if (product == null) return null; } while (compressable.evaluate(product)); this.initial = false; return product; } { T tmp = this.lookahead; if (tmp != null) { this.lookahead = null; return tmp; } } T product = delegate.produce(); if (product == null) return null; if (!compressable.evaluate(product)) return product; do { product = delegate.produce(); if (product == null) return null; } while (compressable.evaluate(product)); this.lookahead = product; return compressed; } }; } /** * Creates and returns a {@link Producer} that produces first, second, first, * second, ... *

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static Producer alternate(final T first, final T second) { return new Producer() { boolean toggle; @Override @Nullable public T produce() { this.toggle = !this.toggle; return this.toggle ? first : second; } }; } /** * Creates and returns a {@link Producer} that produces 0, 1, 2, 3, ... *

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static Producer increasing() { return new Producer() { int value; @Override @Nullable public Integer produce() { return this.value++; } }; } /** * Creates and returns a producer that produces the products of delegate1, and, when that produces * {@code null}, the products of delegate2. *

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static Producer concat(final Producer delegate1, final Producer delegate2) { return new Producer() { boolean second; @Override @Nullable public T produce() { if (this.second) return delegate2.produce(); T product = delegate1.produce(); if (product != null) return product; this.second = true; return delegate2.produce(); } }; } /** * The first product is the first product of the delegate; each following product is the next product * of the delegate if the invalidationCondition evaluates to {@code true}, otherwise it is * the previous product. *

* Example: *

*
ProducerUtil.cache(delegate, ProducerUtil.atMostEvery(milliseconds, false, true))
*

* caches the products of the delegate for milliseconds' time. *

*

* The returned producer is not synchronized and therefore not thread-safe; to get a thread-safe producer, use * {@link ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static ProducerWhichThrows cache( final ProducerWhichThrows delegate, final ProducerWhichThrows invalidationCondition ) { return new ProducerWhichThrows() { @Nullable T cache; boolean isCached; @Override @Nullable public T produce() throws EX { if (this.isCached && !Boolean.TRUE.equals(invalidationCondition.produce())) return this.cache; T tmp = delegate.produce(); this.isCached = true; this.cache = tmp; return tmp; } }; } /** * The first product is the firstProduct; each following product is the next product of the * delegate iff the invalidationCondition evaluates to {@code true}, otherwise it is the * previous product. *

* The returned {@link PredicateWhichThrows} is not synchronized and therefore not thread-safe. *

* * @see #cache(ProducerWhichThrows, ProducerWhichThrows) */ public static ProducerWhichThrows cache( @Nullable final T firstProduct, final ProducerWhichThrows delegate, final ProducerWhichThrows invalidationCondition ) { return new ProducerWhichThrows() { @Nullable T cache = firstProduct; @Override @Nullable public T produce() throws EX { Boolean b = invalidationCondition.produce(); if (b != null && b) return this.cache; return (this.cache = delegate.produce()); } }; } /** * Creates and returns a producer which caches the products of a delegate producer asynchronously. *

* Iff prefetch is {@code true}, then this method uses the delegate immediately to start * the "background fetch". *

*

* On the first call to {@link ProducerWhichThrows#produce() produce()} of the returned producer: *

*
    *
  • * Iff the "background fetch" is not pending: Uses the delegate to start it. *
  • *
  • Waits for the "background fetch" to complete (synchronously!).
  • *
  • Remembers its result as "the cached value" and returns it.
  • *
*

* On all consecutive calls to {@link ProducerWhichThrows#produce() produce()} of the returned producer: *

*
    *
  • *

    Iff the "background fetch" is not pending:

    *
      *
    • Evaluates the invalidationCondition:
    • *
        *
      • *

        Iff {@code false}:

        *
          *
        • Returns the "cached value".
        • *
        *
      • *
      • *

        Iff {@code true}

        *
          *
        • Uses the delegate to start the "background fetch".
        • *
        • Still returns the "cached value".
        • *
        *
      • *
      *
    *
  • *
  • *

    Iff the "background fetch" is pending:

    *
      *
    • *

      Iff it has not yet completed:

      *
        *
      • Returns "the cached value".
      • *
      *
    • *
    • *

      Otherwise, iff the "background fetch" has completed:

      *
        *
      • Retrieves its result (now the "background fetch" is no longer pending).
      • *
      • Remembers the result as "the cached value".
      • *
      • Returns it.
      • *
      *
    • *
    *
  • *
*

* Notice that the invalidationCondition is never evaluated while the "background fetch" is pending. *

*

* The returned producer is not thread-safe; to get a thread-safe asynchronous cache, use {@link * ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*

* {@link #atMostEvery(long, boolean, boolean) atMostEvery(milliseconds, false, false} may be a good choice for * the invalidationCondition to set up an expiration-time-based cache. *

*/ public static ProducerWhichThrows cacheAsynchronously( final ProducerWhichThrows, ? extends EX> delegate, final ProducerWhichThrows invalidationCondition, final boolean prefetch ) throws EX { final Future f = prefetch ? delegate.produce() : null; return new ProducerWhichThrows() { @Nullable T cache; boolean isCached; // FALSE before the first call, TRUE afterwards. @Nullable Future future = f; @Override @Nullable public T produce() throws EX { try { Future f = this.future; if (!this.isCached) { // This is the first invocation. if (f == null) { // Prefetching was not configured, so create the FUTURE for the first product NOW. f = delegate.produce(); assert f != null; this.future = f; } // Wait synchronously until the FUTURE has computed its result. T result = f.get(); // Cache and return the result; this.cache = result; this.isCached = true; this.future = null; return result; } // This is NOT the first invocation. if (f != null) { // A background refetch is currently pending. if (f.isDone()) { // The background refetch has just completed! Fetch, cache and return its result. this.future = null; this.cache = f.get(); } return this.cache; } // Check the "invalidation condition". if (Boolean.TRUE.equals(invalidationCondition.produce())) { // Start the "background refresh". f = delegate.produce(); assert f != null; this.future = f; } return this.cache; } catch (InterruptedException ie) { throw new IllegalStateException(ie); } catch (ExecutionException ee) { throw new IllegalStateException(ee); } } }; } /** * Creates and returns a producer for which the first product is {@code true}, and, for all following * products, the time interval between adjacent {@code true} products will (A) be minimal and (B) never * shorter than milliseconds. *

* Calling this method is equivalent to calling *

*
     *     ProducerUtil.atMostEvery(milliseconds, true, true)
     * 
*

* The returned producer is not thread-safe; to get a thread-safe producer, use {@link * ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

* * @see #atMostEvery(long, boolean, boolean) */ public static Producer atMostEvery(final long milliseconds) { return ProducerUtil.atMostEvery(milliseconds, true, true); } /** * Creates and returns a producer which first produces firstProduct, and afterwards products that are * {@code true} iff they are produced milliseconds or more after the most recent {@code true} product, * or, iff {@code !}startAtTrueProduct, after the first {@code false} product after the most recent * {@code true} product. *

* The returned producer is not thread-safe; to get a thread-safe producer, use {@link * ProducerUtil#synchronizedProducer(ProducerWhichThrows)}. *

*/ public static Producer atMostEvery(final long milliseconds, final boolean firstProduct, final boolean startAtTrueProduct) { return new Producer() { long next = Long.MIN_VALUE; @Override @Nullable public Boolean produce() { long now = System.currentTimeMillis(); if (now < this.next) { // Not yet expired. if (this.next == Long.MAX_VALUE) { this.next = now + milliseconds; } return false; } if (this.next == Long.MIN_VALUE && !firstProduct) { // Let the expiration interval begin NOW. this.next = now + milliseconds; return false; } if (startAtTrueProduct) { // Let the expiration interval begin NOW. this.next = now + milliseconds; } else { // Let the expiration interval begin LATER, when the first "FALSE" product is produced. this.next = Long.MAX_VALUE; } return true; } @Override public String toString() { return ( "At most every " + milliseconds + " ms; next expiration at " + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US) ); } }; } /** * Returns a "{@link Producer}{@code }" that produces {@code true} iff the current time is after the * given expirationTime (in milliseconds). * * @param expirationTime Milliseconds since Jan 1 1970, UTC */ public static Producer after(final long expirationTime) { return new Producer() { @Override @Nullable public Boolean produce() { return System.currentTimeMillis() > expirationTime; } @Override public String toString() { return "After " + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US); } }; } /** * Wraps the delegate such that its declared exception is caught, ignored, and the * defaultValue is returned. */ public static Producer ignoreExceptions(final Class exceptionClass, final ProducerWhichThrows delegate, final T defaultValue) { return new Producer() { @Override @Nullable public T produce() { try { return delegate.produce(); } catch (RuntimeException re) { if (!exceptionClass.isAssignableFrom(re.getClass())) throw re; } catch (Error e) { // SUPPRESS CHECKSTYLE IllegalCatch if (!exceptionClass.isAssignableFrom(e.getClass())) throw e; } catch (Throwable t) { // SUPPRESS CHECKSTYLE IllegalCatch assert exceptionClass.isAssignableFrom(t.getClass()); } return defaultValue; } }; } /** * Creates and returns a proxy to the delegate with a synchronized {@link ProducerWhichThrows#produce() * produce()} method. * * @see ProducerWhichThrows */ public static ProducerWhichThrows synchronizedProducer(final ProducerWhichThrows delegate) { return new ProducerWhichThrows() { @Override @Nullable public synchronized T produce() throws EX { return delegate.produce(); } }; } /** * @return An endless sequence of {@link Boolean}s, where every one-out-of-n is {@code * true} * @param initialFalses How many {@code false} products appear before the first {@code true} product */ public static Producer oneOutOf(final int n, final int initialFalses) { return new Producer() { final AtomicInteger counter = new AtomicInteger(-initialFalses); @Override @Nullable public Boolean produce() { return this.getAndIncrement(this.counter, n) == 0; } private int getAndIncrement(AtomicInteger atomicInteger, int modulo) { for (;;) { int current = atomicInteger.get(); int next = current + 1; if (next >= modulo) next %= modulo; if (atomicInteger.compareAndSet(current, next)) return current; } } }; } }