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.F;
import fj.Function;
import fj.P;
import fj.P1;
import fj.P2;
import fj.Unit;
import fj.data.Iteratee.Input;
import fj.data.Iteratee.IterV;
/**
* IO monad for processing files, with main methods {@link #enumFileLines(File, Option, IterV)},
* {@link #enumFileChars(File, Option, IterV)} and {@link #enumFileCharChunks(File, Option, IterV)}
* (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
*
* @param the type of the result produced by the wrapped iteratee
*/
public abstract class IO {
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
public static final F> closeReader =
new F>() {
@Override
public IO f(final Reader r) {
return closeReader(r);
}
};
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(File, IterV)} or {@link #enumFileChars(File, IterV)}
* 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(IO.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(IO.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(IO.charChunkReader2(), i));
}
public static IO bufferedReader(final File f, final Option encoding) {
return fileReader(f, encoding).map(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;
}
};
}
/**
* 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 = Function., IterV>apply(input).lazy();
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 =
Function., IterV>apply(input).lazy();
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 abstract A run() throws IOException;
public final IO map(final F f) {
return new IO() {
@Override
public B run() throws IOException {
return f.f(IO.this.run());
}
};
}
public final IO bind(final F> f) {
return new IO() {
@Override
public B run() throws IOException {
return f.f(IO.this.run()).run();
}
};
}
}