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

fj.data.IOFunctions Maven / Gradle / Ivy

Go to download

Functional Java is an open source library that supports closures for the Java programming language

There is a newer version: 5.0
Show newest version
package fj.data;

import static fj.Bottom.errorF;
import static fj.Function.constant;
import static fj.Function.partialApply2;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.Arrays;

import fj.*;
import fj.data.Iteratee.Input;
import fj.data.Iteratee.IterV;
import fj.function.Try0;
import fj.function.Try1;

/**
 * IO monad for processing files, with main methods {@link #enumFileLines },
 * {@link #enumFileChars} and {@link #enumFileCharChunks}
 * (the latter one is the fastest as char chunks read from the file are directly passed to the iteratee
 * without indirection in between).
 *
 * @author Martin Grotzke
 */
public final class IOFunctions {

    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;

    private IOFunctions() {
    }

    public static  Try0 toTry(IO io) {
        return io::run;
    }

    public static  P1> p(IO io) {
        return Try.f(toTry(io));
    }

    public static  IO fromF(F0 p) {
        return p::f;
    }

    public static  IO fromTry(Try0 t) {
        return t::f;
    }

    public static final F> closeReader = IOFunctions::closeReader;

    /**
     * Convert io to a SafeIO, throwing any IOException wrapped inside a RuntimeException
     * @param io
     */
    public static  SafeIO toSafe(IO io) {
        return () -> {
            try {
                return io.run();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        };
    }

    /**
     * Run io, rethrowing any IOException wrapped in a RuntimeException
     * @param io
     */
    public static  A runSafe(IO io) {
        return toSafe(io).run();
    }

    public static IO closeReader(final Reader r) {
        return () -> {
            r.close();
            return Unit.unit();
        };
    }

    /**
     * An IO monad that reads lines from the given file (using a {@link BufferedReader}) and passes
     * lines to the provided iteratee. May not be suitable for files with very long
     * lines, consider to use {@link #enumFileCharChunks} or {@link #enumFileChars}
     * as an alternative.
     *
     * @param f        the file to read, must not be null
     * @param encoding the encoding to use, {@link Option#none()} means platform default
     * @param i        the iteratee that is fed with lines read from the file
     */
    public static  IO> enumFileLines(final File f, final Option encoding, final IterV i) {
        return bracket(bufferedReader(f, encoding)
                , Function.vary(closeReader)
                , partialApply2(IOFunctions.lineReader(), i));
    }

    /**
     * An IO monad that reads char chunks from the given file and passes them to the given iteratee.
     *
     * @param f        the file to read, must not be null
     * @param encoding the encoding to use, {@link Option#none()} means platform default
     * @param i        the iteratee that is fed with char chunks read from the file
     */
    public static  IO> enumFileCharChunks(final File f, final Option encoding, final IterV i) {
        return bracket(fileReader(f, encoding)
                , Function.vary(closeReader)
                , partialApply2(IOFunctions.charChunkReader(), i));
    }

    /**
     * An IO monad that reads char chunks from the given file and passes single chars to the given iteratee.
     *
     * @param f        the file to read, must not be null
     * @param encoding the encoding to use, {@link Option#none()} means platform default
     * @param i        the iteratee that is fed with chars read from the file
     */
    public static  IO> enumFileChars(final File f, final Option encoding, final IterV i) {
        return bracket(fileReader(f, encoding)
                , Function.vary(closeReader)
                , partialApply2(IOFunctions.charChunkReader2(), i));
    }

    public static IO bufferedReader(final File f, final Option encoding) {
        return map(fileReader(f, encoding), BufferedReader::new);
    }

    public static IO fileReader(final File f, final Option encoding) {
        return () -> {
            final FileInputStream fis = new FileInputStream(f);
            return encoding.isNone() ? new InputStreamReader(fis) : new InputStreamReader(fis, encoding.some());
        };
    }

    public static  IO bracket(final IO init, final F> fin, final F> body) {
        return () -> {
            final A a = init.run();
            try(Closeable finAsCloseable = fin.f(a)::run) {
                return body.f(a).run();
            }
        };
    }

    public static  IO unit(final A a) {
        return () -> a;
    }

    public static final IO ioUnit = unit(Unit.unit());

    public static  IO lazy(final F0 p) {
        return fromF(p);
    }

    public static  IO lazy(final F f) {
        return () -> f.f(Unit.unit());
    }

    public static  SafeIO lazySafe(final F f) {
        return () -> f.f(Unit.unit());
    }

    public static  SafeIO lazySafe(final F0 f) {
        return f::f;
    }

    /**
     * A function that feeds an iteratee with lines read from a {@link BufferedReader}.
     */
    public static  F, IO>>> lineReader() {
        final F, Boolean> isDone =
                new F, Boolean>() {
                    final F>, P1> done = constant(P.p(true));
                    final F, IterV>, P1> cont = constant(P.p(false));

                    @Override
                    public Boolean f(final IterV i) {
                        return i.fold(done, cont)._1();
                    }
                };

        return r -> new F, IO>>() {
            final F>, P1>> done = errorF("iteratee is done"); //$NON-NLS-1$

            @Override
            public IO> f(final IterV it) {
                // use loop instead of recursion because of missing TCO
                return () -> {
                    IterV i = it;
                    while (!isDone.f(i)) {
                        final String s = r.readLine();
                        if (s == null) {
                            return i;
                        }
                        final Input input = Input.el(s);
                        final F, IterV>, P1>> cont = F1Functions.lazy(Function.apply(input));
                        i = i.fold(done, cont)._1();
                    }
                    return i;
                };
            }
        };
    }

    /**
     * A function that feeds an iteratee with character chunks read from a {@link Reader}
     * (char[] of size {@link #DEFAULT_BUFFER_SIZE}).
     */
    public static  F, IO>>> charChunkReader() {
        final F, Boolean> isDone =
                new F, Boolean>() {
                    final F>, P1> done = constant(P.p(true));
                    final F, IterV>, P1> cont = constant(P.p(false));

                    @Override
                    public Boolean f(final IterV i) {
                        return i.fold(done, cont)._1();
                    }
                };

        return r -> new F, IO>>() {
            final F>, P1>> done = errorF("iteratee is done"); //$NON-NLS-1$

            @Override
            public IO> f(final IterV it) {
                // use loop instead of recursion because of missing TCO
                return () -> {

                    IterV i = it;
                    while (!isDone.f(i)) {
                        char[] buffer = new char[DEFAULT_BUFFER_SIZE];
                        final int numRead = r.read(buffer);
                        if (numRead == -1) {
                            return i;
                        }
                        if (numRead < buffer.length) {
                            buffer = Arrays.copyOfRange(buffer, 0, numRead);
                        }
                        final Input input = Input.el(buffer);
                        final F, IterV>, P1>> cont =
                                F1Functions.lazy(Function.apply(input));
                        i = i.fold(done, cont)._1();
                    }
                    return i;
                };
            }
        };
    }

    /**
     * A function that feeds an iteratee with characters read from a {@link Reader}
     * (chars are read in chunks of size {@link #DEFAULT_BUFFER_SIZE}).
     */
    public static  F, IO>>> charChunkReader2() {
        final F, Boolean> isDone =
                new F, Boolean>() {
                    final F>, P1> done = constant(P.p(true));
                    final F, IterV>, P1> cont = constant(P.p(false));

                    @Override
                    public Boolean f(final IterV i) {
                        return i.fold(done, cont)._1();
                    }
                };

        return r -> new F, IO>>() {
            final F>, IterV> done = errorF("iteratee is done"); //$NON-NLS-1$

            @Override
            public IO> f(final IterV it) {
                // use loop instead of recursion because of missing TCO
                return () -> {

                    IterV i = it;
                    while (!isDone.f(i)) {
                        char[] buffer = new char[DEFAULT_BUFFER_SIZE];
                        final int numRead = r.read(buffer);
                        if (numRead == -1) {
                            return i;
                        }
                        if (numRead < buffer.length) {
                            buffer = Arrays.copyOfRange(buffer, 0, numRead);
                        }
                        for (char c : buffer) {
                            final Input input = Input.el(c);
                            final F, IterV>, IterV> cont =
                                Function.apply(input);
                            i = i.fold(done, cont);
                        }
                    }
                    return i;
                };
            }
        };
    }

    public static  IO map(final IO io, final F f) {
        return () -> f.f(io.run());
    }

    public static  IO as(final IO io, final B b) {
        return map(io, ignored -> b);
    }

    public static  IO voided(final IO  io) {
        return as(io, Unit.unit());
    }

    public static  IO bind(final IO io, final F> f) {
        return () -> f.f(io.run()).run();
    }

    public static IO when(final Boolean b, final IO io) {
        return b ? io : ioUnit;
    }

    public static IO unless(final Boolean b, final IO io) {
        return when(!b, io);
    }

    /**
     * Evaluate each action in the sequence from left to right, and collect the results.
     */
    public static  IO> sequence(List> list) {
        F2, IO>, IO>> f2 = (io, ioList) ->
                bind(ioList, (xs) -> map(io, x -> List.cons(x, xs)));
        return list.foldRight(f2, unit(List.nil()));
    }


    public static  IO> sequence(Stream> stream) {
        F2>, IO, IO>> f2 = (ioList, io) ->
                bind(ioList, (xs) -> map(io, x -> Stream.cons(x, () -> xs)));
        return stream.foldLeft(f2, unit(Stream.nil()));
    }


    public static  IO join(IO> io1) {
        return bind(io1, io2 -> io2);
    }

    public static  SafeIO> toSafeValidation(IO io) {
        return () -> Try.f(io::run)._1();
    }

    public static  IO append(final IO io1, final IO io2) {
        return () -> {
            io1.run();
            return io2.run();
        };
    }

    public static  IO left(final IO io1, final IO io2) {
        return () -> {
            A a = io1.run();
            io2.run();
            return a;
        };
    }

    public static  IO flatMap(final IO io, final F> f) {
        return bind(io, f);
    }

    /**
     * Read lines from stdin until condition is not met, transforming each line and printing
     * the result to stdout.
     * @param condition Read lines until a line does not satisfy condition
     * @param transform Function to change line value
     */
    public static IO interactWhile(F condition, F transform) {
        Stream> s1 = Stream.repeat(stdinReadLine());
        IO> io = sequenceWhile(s1, condition);
        return () -> runSafe(io).foreach(s -> runSafe(stdoutPrintln(transform.f(s))));
    }

    public static  IO> sequenceWhileEager(final Stream> stream, final F f) {
        return () -> {
            boolean loop = true;
            Stream> input = stream;
            Stream result = Stream.nil();
            while (loop) {
                if (input.isEmpty()) {
                    loop = false;
                } else {
                    A a = input.head().run();
                    if (!f.f(a)) {
                        loop = false;
                    } else {
                        input = input.tail()._1();
                        result = result.cons(a);
                    }
                }
            }
            return result.reverse();
        };
    }

    public static  IO> sequenceWhile(final Stream> stream, final F f) {
        return () -> {
            if (stream.isEmpty()) {
                return Stream.nil();
            } else {
                IO io = stream.head();
                A a = io.run();
                if (!f.f(a)) {
                    return Stream.nil();
                } else {
                    IO> io2 = sequenceWhile(stream.tail()._1(), f);
                    SafeIO> s3 = toSafe(io2::run);
                    return Stream.cons(a, s3::run);
                }
            }
        };
    }

    public static  IO apply(IO io, IO> iof) {
        return bind(iof, f -> map(io, f));
    }

    public static  IO liftM2(IO ioa, IO iob, F2 f) {
        return bind(ioa, a -> map(iob, b -> f.f(a, b)));
    }

    public static  IO> replicateM(IO ioa, int n) {
        return sequence(List.replicate(n, ioa));
    }

    public static  IO>> readerState() {
        return () -> State.unit((BufferedReader r) -> P.p(r, Try.f((Try1) BufferedReader::readLine).f(r)));
    }

    public static final BufferedReader stdinBufferedReader = new BufferedReader(new InputStreamReader(System.in));

    public static IO stdinReadLine() {
        return stdinBufferedReader::readLine;
    }

    public static IO stdoutPrintln(final String s) {
        return () -> {
            System.out.println(s);
            return Unit.unit();
        };
    }

    public static IO stdoutPrint(final String s) {
        return () -> {
            System.out.print(s);
            return Unit.unit();
        };
    }

    public static IO getContents() {
        Stream> s = Stream.repeat(() -> stdinBufferedReader.read());
        return map(sequenceWhile(s, i -> i != -1), s2 -> LazyString.fromStream(s2.map(i -> (char) i.intValue())));
    }

    public static IO interact(F f) {
        return bind(getContents(), ls1 -> {
            LazyString ls2 = f.f(ls1);
            return stdoutPrintln(ls2.toString());
        });
    }

}