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.CancellationException;
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 extends T> delegate, final Predicate super ST> 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 extends T> 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 extends T, ? extends RuntimeException> 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 super Integer, T> 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 super Integer, T, EX> 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 extends T1, ? extends EX> delegate,
final TransformerWhichThrows super T1, ? extends T2, EX> 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 extends T1, ? extends EX> delegate,
final FunctionWhichThrows super T1, ? extends T2, EX> 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 extends T, ? extends EX> delegate,
final PredicateWhichThrows super T, ? extends EX> 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 extends T, ? extends EX> delegate,
final Predicate super T> 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 extends T> delegate, final Predicate super T> 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 ProducerUtil.increasing(0); }
/**
* Creates and returns a {@link Producer} that produces initialValue, initialValue{@code +1},
* initialValue{@code + 2}, etc.
*
* 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(final int initialValue) {
return new Producer() {
int value = initialValue;
@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 extends T> delegate1, final Producer extends T> 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)}.
*
*
* The returned producer produces {@code null} if the current thread was interrupted while prefetching.
*
*
* {@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 {
return new ProducerWhichThrows() {
@Nullable Future previous;
@Nullable Future next = prefetch ? delegate.produce() : null;
@Override @Nullable public synchronized T
produce() throws EX {
try {
Future p = this.previous;
Future n = this.next;
if (p == null) {
if (n == null) {
// !prefetch => Wait synchronously for the first value;
return (this.previous = delegate.produce()).get();
}
this.previous = n;
this.next = null;
return n.get();
}
if (n == null) {
if (Boolean.TRUE.equals(invalidationCondition.produce())) {
n = delegate.produce();
assert n != null;
if (n.isDone()) {
this.previous = n;
return n.get();
}
this.next = n;
}
} else {
if (n.isDone()) {
this.previous = n;
this.next = null;
return n.get();
}
}
return p.get();
} catch (ExecutionException ee) {
Throwable cause = ee.getCause();
if (cause instanceof RuntimeException) throw (RuntimeException) cause;
if (cause instanceof Error) throw (Error) cause;
@SuppressWarnings("unchecked") EX ex = (EX) cause;
throw ex;
} catch (CancellationException ce) {
// Should never get here, because we never cancel our Futures.
throw new AssertionError(ce);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
};
}
/**
* 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