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

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 delegate1, final ConsumerWhichThrows 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 delegate1, final Consumer 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 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 asConsumerWhichThrows(final Consumer 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}" */ @Deprecated public static ConsumerWhichThrows widen(final ConsumerWhichThrows 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 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 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 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 lineAggregator(final ConsumerWhichThrows delegate) { return new ConsumerWhichThrows() { private final StringBuilder sb = new StringBuilder(); private boolean crPending; @Override public void consume(Character c) throws E { 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); } } }; } /** * @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 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> combineInOrder(final ConsumerWhichThrows 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, 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 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 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 {} 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 cumulate(final ConsumerWhichThrows 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 cumulate(final ConsumerWhichThrows 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 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 delegate, final Predicate 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() { @Override public void consume(Object subject) { ; } }; /** * Wraps the delegate such that its declared exception is caught and ignored. */ public static Consumer ignoreExceptions(final Class exceptionClass, final ConsumerWhichThrows delegate) { return new Consumer() { @Override public void consume(@Nullable T subject) { assert subject != null; try { delegate.consume(subject); } 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()); ; } } }; } /** * Passes the first n elements of the subject to delegate1, and all remaining * elements (if any) to delegate2. *

* This method resembles UNIX's {@code head} command. *

*/ public static void head( Iterable subject, int n, ConsumerWhichThrows delegate1, ConsumerWhichThrows delegate2 ) throws EX { ConsumerUtil.head(subject.iterator(), n, delegate1, delegate2); } /** * Passes the first n products of the subject to delegate1, and all remaining * products (if any) to delegate2. *

* This method resembles UNIX's {@code head} command. *

*/ public static void head( Iterator subject, int n, ConsumerWhichThrows delegate1, ConsumerWhichThrows delegate2 ) throws EX { ConsumerWhichThrows c = ConsumerUtil.head(n, delegate1, delegate2); while (subject.hasNext()) c.consume(subject.next()); } /** * @return A consumer that forwards the first n consumed subjects to delegate1, and all * following consumed subjects to delegate2 */ public static ConsumerWhichThrows head( final int n, final ConsumerWhichThrows delegate1, final ConsumerWhichThrows delegate2 ) { return new ConsumerWhichThrows() { final AtomicInteger count = new AtomicInteger(); @Override public void consume(T subject) throws EX { if (this.count.getAndIncrement() < n) { delegate1.consume(subject); } else { delegate2.consume(subject); } } }; } public static ConsumerWhichThrows head(final int n, final ConsumerWhichThrows delegate) { return new ConsumerWhichThrows() { private final AtomicInteger remaining = new AtomicInteger(n); @Override public void consume(T subject) throws EX { if (this.remaining.getAndDecrement() > 0) delegate.consume(subject); } }; } /** * Passes the first x - n products of the subject to delegate1, and all * remaining products (if any) to delegate2, where x is the number of products of the * subject. *

* This method resembles UNIX's {@code tail} command. *

*/ public static void tail( Iterator subject, int n, ConsumerWhichThrows delegate1, ConsumerWhichThrows delegate2 ) throws EX { List tmp = new ArrayList(); while (subject.hasNext()) tmp.add(subject.next()); ConsumerUtil.tail(tmp, n, delegate1, delegate2); } /** * Passes the first subject{@code .length()} - n elements of the subject * collection to delegate1, and all remaining elements (if any) to delegate2. *

* This method resembles UNIX's {@code tail} command. *

*/ public static void tail( Collection subject, int n, ConsumerWhichThrows delegate1, ConsumerWhichThrows delegate2 ) throws EX { int size = subject.size(); Iterator it = subject.iterator(); for (int i = 0; i < size - n && it.hasNext(); i++) { delegate1.consume(it.next()); } while (it.hasNext()) { delegate2.consume(it.next()); } } /** * @return Counts the number of subjects consumed so far */ public static ConsumerWhichThrows count(final AtomicInteger delegate) { return new ConsumerWhichThrows() { @Override public void consume(T subject) { delegate.incrementAndGet(); } }; } public static ConsumerWhichThrows, EX> getFirstOfTuple2(final ConsumerWhichThrows delegate) { return new ConsumerWhichThrows, EX>() { @Override public void consume(Tuple2 subject) throws EX { delegate.consume(subject.first); } }; } public static ConsumerWhichThrows, EX> getSecondOfTuple2(final ConsumerWhichThrows delegate) { return new ConsumerWhichThrows, EX>() { @Override public void consume(Tuple2 subject) throws EX { delegate.consume(subject.second); } }; } public static ConsumerWhichThrows, EX> getFirstOfTuple3(final ConsumerWhichThrows delegate) { return new ConsumerWhichThrows, EX>() { @Override public void consume(Tuple3 subject) throws EX { delegate.consume(subject.first); } }; } public static ConsumerWhichThrows, EX> getSecondOfTuple3(final ConsumerWhichThrows delegate) { return new ConsumerWhichThrows, EX>() { @Override public void consume(Tuple3 subject) throws EX { delegate.consume(subject.second); } }; } public static ConsumerWhichThrows, EX> getThirdOfTuple3(final ConsumerWhichThrows delegate) { return new ConsumerWhichThrows, EX>() { @Override public void consume(Tuple3 subject) throws EX { delegate.consume(subject.third); } }; } public static ConsumerWhichThrows, EX> put(final Map delegate) { return new ConsumerWhichThrows, EX>() { @Override public void consume(Tuple2 subject) { delegate.put(subject.first, subject.second); } }; } public static Consumer add(final AtomicInteger delegate) { return new Consumer() { @Override public void consume(T subject) { delegate.addAndGet(subject.intValue()); } }; } public static Consumer add(final AtomicLong delegate) { return new Consumer() { @Override public void consume(T subject) { delegate.addAndGet(subject.longValue()); } }; } }