de.unkrig.commons.lang.protocol.ConsumerUtil 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.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import de.unkrig.commons.nullanalysis.NotNullByDefault;
import de.unkrig.commons.nullanalysis.Nullable;
/**
* Various {@link Consumer}-related utility methods.
*/
public final
class ConsumerUtil {
private
ConsumerUtil() {}
/**
* @return A consumer that forwards the subjects to both delegate1 and delegate2
*/
public static ConsumerWhichThrows
tee(
final ConsumerWhichThrows super T, ? extends EX> delegate1,
final ConsumerWhichThrows super T, ? extends EX> delegate2
) {
return new ConsumerWhichThrows() {
@Override public void
consume(T subject) throws EX {
delegate1.consume(subject);
delegate2.consume(subject);
}
};
}
/**
* @return A consumer that forwards the subjects to both delegate1 and delegate2
*/
public static Consumer
tee(final Consumer super T> delegate1, final Consumer super T> delegate2) {
return new Consumer() {
@Override public void
consume(T subject) {
delegate1.consume(subject);
delegate2.consume(subject);
}
};
}
/**
* @return A consumer that forwards the subjects to all the delegates
*/
public static ConsumerWhichThrows
tee(final Collection> delegates) {
return new ConsumerWhichThrows() {
@Override public void
consume(T subject) throws EX {
for (ConsumerWhichThrows super T, ? extends EX> delegate : delegates) {
delegate.consume(subject);
}
}
};
}
/**
* Converts the source into a {@link ConsumerWhichThrows ConsumerWhichThrows<T, EX>}.
*
* This is always possible, because the source is only allowed to throw unchecked exceptions.
*
*
* @param The element type
* @param The target consumer's exception
* @deprecated Superseded by {@link #widen2(ConsumerWhichThrows)}.
*/
@Deprecated public static ConsumerWhichThrows super T, ? extends EX>
asConsumerWhichThrows(final Consumer super T> source) {
return new ConsumerWhichThrows() {
@Override public void
consume(T subject) { source.consume(subject); }
};
}
/**
* Converts the source into a {@link ConsumerWhichThrows ConsumerWhichThrows<T, EX>}.
*
* This is always possible, because the source consumes a superclass of T, and the
* source is only allowed to throw a subclass of EX.
*
*
* @param The element type
* @param The target consumer's exception
* @deprecated Not necessary if you declare variables, fields an parameters as "{@code ConsumerWhichThrows super
* consumed-type, ? extends thrown-exception>}"
*/
@Deprecated public static ConsumerWhichThrows super T, ? extends EX>
widen(final ConsumerWhichThrows super T, ? extends EX> source) {
@SuppressWarnings("unchecked") ConsumerWhichThrows result = (ConsumerWhichThrows) source;
return result;
}
/**
* Converts the source into a {@link ConsumerWhichThrows ConsumerWhichThrows<T, EX>}.
*
* This is always possible, because the source consumes a superclass of T, and the
* source is only allowed to throw a {@link RuntimeException}.
*
*
* @param The element type
* @param The target consumer's exception
*/
public static ConsumerWhichThrows
widen2(final ConsumerWhichThrows super T, ? extends RuntimeException> source) {
@SuppressWarnings("unchecked") ConsumerWhichThrows result = (ConsumerWhichThrows) source;
return result;
}
/**
* Converts the source into a {@link Consumer Consumer<T>}.
*
* This is always possible, because both ar allowed to throw {@link RuntimeException}s.
*
*
* @param The element type
* @param The source consumer's exception
*/
public static Consumer
asConsumer(final ConsumerWhichThrows super T, ? extends RuntimeException> source) {
@SuppressWarnings("unchecked") Consumer result = (Consumer) source;
return result;
}
/**
* @return A {@link Writer} which forwards the characters to a {@link ConsumerWhichThrows
* ConsumerWhichThrows<Character, IOException>}
*/
@NotNullByDefault(false) public static Writer
characterConsumerWriter(final ConsumerWhichThrows super Character, ? extends IOException> delegate) {
return new Writer() {
@Override public void
write(int c) throws IOException {
delegate.consume((char) c);
}
@Override public void
write(char[] cbuf, int off, int len) throws IOException {
for (; len > 0; len--) this.write(cbuf[off++]);
}
@Override public void
flush() {
}
@Override public void
close() {
}
};
}
/**
* Creates and returns a {@link Consumer Consumer<Character>} which aggregates characters to lines, which it
* passes to the delegate.
*
* Notice that iff the last consumed character is neither a CR nor an LF (a.k.a. "last line lacks a line
* separator"), then that last line will not be sent to the delegate.
*
*/
public static ConsumerWhichThrows super Character, ? extends EX>
lineAggregator(final ConsumerWhichThrows delegate) {
return new ConsumerWhichThrows() {
private final StringBuilder sb = new StringBuilder();
private boolean crPending;
@Override public void
consume(Character c) throws EX {
if (c == '\r') {
delegate.consume(this.sb.toString());
this.sb.setLength(0);
this.crPending = true;
} else
if (c == '\n') {
if (this.crPending) {
this.crPending = false;
} else {
delegate.consume(this.sb.toString());
this.sb.setLength(0);
}
} else
{
this.crPending = false;
this.sb.append(c);
}
}
};
}
/**
* Invokes lineChar for every non-line-terminator char, and lineComplete for every line
* terminator.
*
* Notice that a line terminator could comprise two chars ({@code '\r', '\n'}); then,
* lineComplete is only invoked once (on the {@code '\r'}).
*
*/
public static ConsumerWhichThrows
lineCounter(
final ConsumerWhichThrows lineChar,
final ConsumerWhichThrows lineComplete
) {
return new ConsumerWhichThrows() {
int lineNumber = 1;
boolean crPending;
@Override public void
consume(Character c) throws EX {
if (c == '\n') {
if (this.crPending) {
this.crPending = false;
} else {
lineComplete.consume(this.lineNumber++);
}
} else
if (c == '\r') {
this.crPending = true;
lineComplete.consume(this.lineNumber++);
} else
{
lineChar.consume(c);
}
}
};
}
/**
* @return A {@link Consumer} that writes lines to the given file with the default character encoding
*/
public static ConsumerWhichThrows
lineConsumer(File file, boolean append) throws IOException {
return ConsumerUtil.lineConsumer(new FileWriter(file, append), true);
}
/**
* @return A {@link Consumer} that writes lines to the given file with the given encoding
*/
public static ConsumerWhichThrows
lineConsumer(File file, String charsetName, boolean append) throws IOException {
return ConsumerUtil.lineConsumer(new FileOutputStream(file, append), charsetName, true);
}
/**
* @return A {@link Consumer} that writes lines to the given {@link OutputStream} with the given encoding
*/
private static ConsumerWhichThrows
lineConsumer(OutputStream stream, String charsetName, boolean closeOnFinalize)
throws UnsupportedEncodingException {
return ConsumerUtil.lineConsumer(new OutputStreamWriter(stream, charsetName), closeOnFinalize);
}
/**
* @return A {@link Consumer} that writes strings to the given {@link Writer}, augmented with a line separator
*/
public static ConsumerWhichThrows
lineConsumer(final Writer writer, final boolean closeOnFinalize) {
return new ConsumerWhichThrows() {
@Override public void
consume(String line) throws IOException {
writer.write(line + ConsumerUtil.LINE_SEPARATOR); // Write line and line separator atomically.
writer.flush();
}
@Override protected void
finalize() throws Throwable { if (closeOnFinalize) writer.close(); }
};
}
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
/**
* @return A {@link Consumer} that writes strings to the given {@link Writer}, augmented with a line separator
*/
public static Consumer
lineConsumer(final PrintStream printStream, final boolean closeOnFinalize) {
return new Consumer() {
@Override public void
consume(String line) { printStream.println(line); }
@Override protected void
finalize() { if (closeOnFinalize) printStream.close(); }
@Override public String
toString() { return printStream.toString(); }
};
}
/**
* The returned producer is a factory for consumers of {@code T}. The subjects sent to these consumers are
* forwarded immediately to the given target.
*
* @see #combineInOrder(ConsumerWhichThrows)
*/
public static Producer>
combine(final Consumer super T> target) {
return new Producer>() {
@Override public Consumer
produce() {
return new Consumer() {
@Override public void
consume(T subject) {
target.consume(subject);
}
};
}
};
}
/**
* The returned producer is a factory for consumers of {@code T}. The first subject sent to each of these consumers
* is passed to the given target in the order the consumers were created (not in the order in
* which the subjects were sent to the consumers).
*
* @see #combine(Consumer)
*/
public static Producer extends ConsumerWhichThrows super T, ? extends EX>>
combineInOrder(final ConsumerWhichThrows super T, ? extends EX> target) {
final Queue>
outstanding = new LinkedList>();
final Map, T>
postponed = new HashMap, T>();
return new Producer>() {
@Override public ConsumerWhichThrows
produce() {
ConsumerWhichThrows consumer = new ConsumerWhichThrows() {
@Override public void
consume(T subject) throws EX {
synchronized (outstanding) {
if (outstanding.isEmpty()) {
throw new IllegalStateException("Can consume only one subject");
}
if (outstanding.peek() == this) {
// The call appeared "at the right time"; pass the subject immediately to the
// target consumer.
outstanding.remove();
target.consume(subject);
// Flush as many postponed results as possible.
while (!outstanding.isEmpty() && postponed.containsKey(outstanding.element())) {
target.consume(postponed.get(outstanding.remove()));
}
} else {
// The invocation happened "too early"; store the subject only to pass it to the the
// target when the time is right.
if (postponed.containsKey(this)) {
throw new IllegalStateException("Can consume only one subject");
}
postponed.put(this, subject);
}
}
}
};
synchronized (outstanding) {
outstanding.add(consumer);
}
return consumer;
}
};
}
/**
* Returns a list of consumers of size n. When all comsumers have consumed their first subject, then
* these subjects are passed to the target consumer; then again when all consumers have consumed their
* second subject, and so on.
*/
public static List>
splice(final int n, final ConsumerWhichThrows super List, ? extends EX> target) {
final List> buffers = new ArrayList>(n);
List>
consumers = new ArrayList>(n);
for (int i = 0; i < n; i++) {
consumers.add(new ConsumerWhichThrows() {
final Queue buffer = new LinkedList();
{ buffers.add(this.buffer); }
@Override public void
consume(T subject) throws EX {
synchronized (buffers) {
this.buffer.add(subject);
// Verify that all buffers contain at least one element.
for (Queue b : buffers) {
if (b.isEmpty()) return;
}
// Move the HEAD elements of all buffers to a temporary list.
List subjects = new ArrayList(n);
for (Queue b : buffers) {
subjects.add(b.remove());
}
// Pass the list to the target consumer.
target.consume(subjects);
}
}
});
}
return consumers;
}
/**
* @return A consumer that adds the subjects it consumes to the given collection
*/
public static Consumer
addToCollection(final Collection super T> drain) {
return new Consumer() {
@Override public void
consume(T subject) { drain.add(subject); }
};
}
/**
* @return A {@link ConsumerWhichThrows} which throws each subject it consumes
*/
public static ConsumerWhichThrows super EX, ? extends EX>
throwsSubject() {
return new ConsumerWhichThrows() {
@Override public void consume(EX throwable) throws EX { throw throwable; }
};
}
/**
* The combination of a {@link Producer} and a {@link Consumer}.
*
* @param The type that the {@link Produmer} produces
* @param The type that the {@link Produmer} consumes
*/
public
interface Produmer
extends ProdumerWhichThrows, Producer, Consumer {}
/**
* The combination of a {@link ProducerWhichThrows} and a {@link ConsumerWhichThrows}.
*
* @param The type that the {@link ProdumerWhichThrows} produces
* @param The exception that {@link #produce()} may throw
* @param The type that the {@link ProdumerWhichThrows} consumes
* @param The exception that {@link #consume(Object)} may throw
*/
public
interface ProdumerWhichThrows
extends ProducerWhichThrows, ConsumerWhichThrows {}
/**
* Equivalent with {@link #store(Object) store(null)}.
*/
public static Produmer
store() { return ConsumerUtil.store(null); }
/**
* The returned {@link Produmer} simply produces the last consumed subject, or initialValue if no
* subject has been consumed yet.
*/
public static Produmer
store(@Nullable final T initialValue) {
return new Produmer() {
@Nullable private T store = initialValue;
@Override public void
consume(T subject) { this.store = subject; }
@Override @Nullable public T
produce() { return this.store; }
};
}
/**
* Equivalent with {@link #cumulate(ConsumerWhichThrows, long) cumulate(delegate)}.
*/
public static ConsumerWhichThrows super Number, ? extends EX>
cumulate(final ConsumerWhichThrows super Long, ? extends EX> delegate) {
return ConsumerUtil.cumulate(delegate, 0);
}
/**
* Creates and returns a {@link Consumer} which forwards the cumulated quantity to the given {@code
* delegate}.
*
* @param initialCount Initial value for the cumulated quantity, usually {@code 0L}
*/
public static ConsumerWhichThrows super Number, ? extends EX>
cumulate(final ConsumerWhichThrows super Long, ? extends EX> delegate, final long initialCount) {
return new ConsumerWhichThrows() {
long count = initialCount;
@Override public void
consume(Number n) throws EX { delegate.consume((this.count += n.longValue())); }
};
}
/**
* Equivalent with {@link #cumulate(long) cumulate(0L)}.
*/
public static Produmer
cumulate() { return ConsumerUtil.cumulate(0L); }
/**
* Creates and returns a {@link Produmer} which adds up the quantities it consumes, and produces the current
* total.
*
* @param initialValue Initial value for the cumulated quantity, usually {@code 0L}
*/
public static Produmer
cumulate(final long initialValue) {
return new Produmer() {
long count = initialValue;
@Override public void
consume(Number n) { this.count += n.longValue(); }
@Override @Nullable public Long
produce() { return this.count; }
};
}
/**
* Creates and returns a {@link Consumer Consumer<Long>} which forwards the quantity to the given {@code
* delegate}, but only if the quantity is equal to or greater than the limit, which starts with {@code
* initialLimit} and increases exponentially.
*/
public static Consumer
compressExponentially(final long initialLimit, final Consumer super Long> delegate) {
return new Consumer() {
long limit = initialLimit;
@Override public void
consume(Long n) {
if (n >= this.limit) {
delegate.consume(n);
do {
this.limit <<= 1;
} while (n >= this.limit);
}
}
};
}
/**
* Forwards each subject it consumes to the given delegate, but only iff the
* subject is not compressable.
*/
public static Consumer
compress(final Consumer delegate, final Predicate compressable) {
return new Consumer() {
@Override public void
consume(T subject) { if (!compressable.evaluate(subject)) delegate.consume(subject); }
};
}
/**
* Replaces sequences of compressable subjects with one compressed subject.
*
* Leading and trailing compressables are discarded.
*
*/
public static Consumer
compress(final Consumer super T> delegate, final Predicate super T> compressable, final T compressed) {
return new Consumer() {
int state;
@Override public void
consume(T subject) {
boolean isCompressable = compressable.evaluate(subject);
if (isCompressable) {
if (this.state == 1) this.state = 2;
} else {
if (this.state == 2) {
delegate.consume(compressed);
}
delegate.consume(subject);
this.state = 1;
}
}
};
}
@SuppressWarnings("unchecked") public static Consumer
nop() { return (Consumer) ConsumerUtil.NOP; }
private static final Consumer> NOP = new Consumer