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.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;

/**
 * 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 class IOFunctions {

    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;

    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 =
            new F>() {
                @Override
                public IO f(final Reader r) {
                    return closeReader(r);
                }
            };

    /**
     * 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 new IO() {
            @Override
            public Unit run() throws IOException {
                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 IOFunctions.map(fileReader(f, encoding), new F() {
            @Override
            public BufferedReader f(final Reader a) {
                return new BufferedReader(a);
            }
        });
    }

    public static IO fileReader(final File f, final Option encoding) {
        return new IO() {
            @Override
            public Reader run() throws IOException {
                final FileInputStream fis = new FileInputStream(f);
                return encoding.isNone() ? new InputStreamReader(fis) : new InputStreamReader(fis, encoding.some());
            }
        };
    }

    public static final  IO bracket(final IO init, final F> fin, final F> body) {
        return new IO() {
            @Override
            public C run() throws IOException {
                final A a = init.run();
                try {
                    return body.f(a).run();
                } catch (final IOException e) {
                    throw e;
                } finally {
                    fin.f(a);
                }
            }
        };
    }

    public static final  IO unit(final A a) {
        return new IO() {
            @Override
            public A run() throws IOException {
                return a;
            }
        };
    }

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

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

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

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

    public static final  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 new F, IO>>>() {
            @Override
            public F, IO>> f(final BufferedReader r) {
                return 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 new IO>() {
                            @Override
                            public IterV run() throws IOException {
                                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., IterV>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 new F, IO>>>() {
            @Override
            public F, IO>> f(final Reader r) {
                return 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 new IO>() {
                            @Override
                            public IterV run() throws IOException {

                                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., IterV>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 new F, IO>>>() {
            @Override
            public F, IO>> f(final Reader r) {
                return 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 new IO>() {
                            @Override
                            public IterV run() throws IOException {

                                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 (int c = 0; c < buffer.length; c++) {
                                        final Input input = Input.el(buffer[c]);
                                        final F, IterV>, IterV> cont =
                                                Function., IterV>apply(input);
                                        i = i.fold(done, cont);
                                    }
                                }
                                return i;
                            }
                        };
                    }
                };
            }
        };
    }

    public static final  IO map(final IO io, final F f) {
        return new IO() {
            @Override
            public B run() throws IOException {
                return f.f(io.run());
            }
        };
    }

    public static final  IO bind(final IO io, final F> f) {
        return new IO() {
            @Override
            public B run() throws IOException {
                return f.f(io.run()).run();
            }
        };
    }

    /**
     * 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) ->
                IOFunctions.bind(ioList, (xs) -> map(io, x -> List.cons(x, xs)));
        return list.foldRight(f2, IOFunctions.unit(List.nil()));
    }


    public static  IO> sequence(Stream> stream) {
        F2>, IO, IO>> f2 = (ioList, io) ->
                IOFunctions.bind(ioList, (xs) -> map(io, x -> Stream.cons(x, P.lazy(() -> xs))));
        return stream.foldLeft(f2, IOFunctions.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(IOFunctions.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 new IO>() {
            @Override
            public Stream run() throws IOException {
                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 new IO>() {
            @Override
            public Stream run() throws IOException {
                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, P.lazy(() -> s3.run()));
                    }
                }
            }
        };
    }

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

    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((BufferedReader r2) -> r2.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 getContents() {
        Stream> s = Stream.>repeat(() -> (int) stdinBufferedReader.read());
        return IOFunctions.map(sequenceWhile(s, i -> i != -1), s2 -> LazyString.fromStream(s2.map(i -> {
            return (char) i.intValue();
        })));
    }

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

}