|
1 | | -package fj.data; |
2 | | - |
3 | | -import static fj.Bottom.errorF; |
4 | | -import static fj.Function.constant; |
5 | | -import static fj.Function.partialApply2; |
6 | | - |
7 | | -import java.io.BufferedReader; |
8 | | -import java.io.File; |
9 | | -import java.io.FileInputStream; |
10 | | -import java.io.IOException; |
11 | | -import java.io.InputStreamReader; |
12 | | -import java.io.Reader; |
13 | | -import java.nio.charset.Charset; |
14 | | -import java.util.Arrays; |
15 | | - |
16 | | -import fj.F; |
17 | | -import fj.F1Functions; |
18 | | -import fj.Function; |
19 | | -import fj.P; |
20 | | -import fj.P1; |
21 | | -import fj.P2; |
22 | | -import fj.Unit; |
23 | | -import fj.data.Iteratee.Input; |
24 | | -import fj.data.Iteratee.IterV; |
25 | | - |
26 | | -/** |
27 | | - * IO monad for processing files, with main methods {@link #enumFileLines }, |
28 | | - * {@link #enumFileChars} and {@link #enumFileCharChunks} |
29 | | - * (the latter one is the fastest as char chunks read from the file are directly passed to the iteratee |
30 | | - * without indirection in between). |
31 | | - * |
32 | | - * @author Martin Grotzke |
33 | | - * |
34 | | - * @param <A> the type of the result produced by the wrapped iteratee |
35 | | - */ |
36 | | -public abstract class IO<A> { |
37 | | - |
38 | | - private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; |
39 | | - |
40 | | - public static final F<Reader, IO<Unit>> closeReader = |
41 | | - new F<Reader, IO<Unit>>() { |
42 | | - @Override |
43 | | - public IO<Unit> f(final Reader r) { |
44 | | - return closeReader(r); |
45 | | - } |
46 | | - }; |
47 | | - |
48 | | - public static IO<Unit> closeReader(final Reader r) { |
49 | | - return new IO<Unit>() { |
50 | | - @Override |
51 | | - public Unit run() throws IOException { |
52 | | - r.close(); |
53 | | - return Unit.unit(); |
54 | | - } |
55 | | - }; |
56 | | - } |
57 | | - |
58 | | - /** |
59 | | - * An IO monad that reads lines from the given file (using a {@link BufferedReader}) and passes |
60 | | - * lines to the provided iteratee. May not be suitable for files with very long |
61 | | - * lines, consider to use {@link #enumFileCharChunks} or {@link #enumFileChars} |
62 | | - * as an alternative. |
63 | | - * |
64 | | - * @param f the file to read, must not be <code>null</code> |
65 | | - * @param encoding the encoding to use, {@link Option#none()} means platform default |
66 | | - * @param i the iteratee that is fed with lines read from the file |
67 | | - */ |
68 | | - public static <A> IO<IterV<String, A>> enumFileLines(final File f, final Option<Charset> encoding, final IterV<String, A> i) { |
69 | | - return bracket(bufferedReader(f, encoding) |
70 | | - , Function.<BufferedReader, IO<Unit>>vary(closeReader) |
71 | | - , partialApply2(IO.<A>lineReader(), i)); |
72 | | - } |
73 | | - |
74 | | - /** |
75 | | - * An IO monad that reads char chunks from the given file and passes them to the given iteratee. |
76 | | - * |
77 | | - * @param f the file to read, must not be <code>null</code> |
78 | | - * @param encoding the encoding to use, {@link Option#none()} means platform default |
79 | | - * @param i the iteratee that is fed with char chunks read from the file |
80 | | - */ |
81 | | - public static <A> IO<IterV<char[], A>> enumFileCharChunks(final File f, final Option<Charset> encoding, final IterV<char[], A> i) { |
82 | | - return bracket(fileReader(f, encoding) |
83 | | - , Function.<Reader, IO<Unit>>vary(closeReader) |
84 | | - , partialApply2(IO.<A>charChunkReader(), i)); |
85 | | - } |
86 | | - |
87 | | - /** |
88 | | - * An IO monad that reads char chunks from the given file and passes single chars to the given iteratee. |
89 | | - * |
90 | | - * @param f the file to read, must not be <code>null</code> |
91 | | - * @param encoding the encoding to use, {@link Option#none()} means platform default |
92 | | - * @param i the iteratee that is fed with chars read from the file |
93 | | - */ |
94 | | - public static <A> IO<IterV<Character, A>> enumFileChars(final File f, final Option<Charset> encoding, final IterV<Character, A> i) { |
95 | | - return bracket(fileReader(f, encoding) |
96 | | - , Function.<Reader, IO<Unit>>vary(closeReader) |
97 | | - , partialApply2(IO.<A>charChunkReader2(), i)); |
98 | | - } |
99 | | - |
100 | | - public static IO<BufferedReader> bufferedReader(final File f, final Option<Charset> encoding) { |
101 | | - return fileReader(f, encoding).map(new F<Reader, BufferedReader>() { |
102 | | - @Override |
103 | | - public BufferedReader f(final Reader a) { |
104 | | - return new BufferedReader(a); |
105 | | - }}); |
106 | | - } |
107 | | - |
108 | | - public static IO<Reader> fileReader(final File f, final Option<Charset> encoding) { |
109 | | - return new IO<Reader>() { |
110 | | - @Override |
111 | | - public Reader run() throws IOException { |
112 | | - final FileInputStream fis = new FileInputStream(f); |
113 | | - return encoding.isNone() ? new InputStreamReader(fis) : new InputStreamReader(fis, encoding.some()); |
114 | | - } |
115 | | - }; |
116 | | - } |
117 | | - |
118 | | - public static final <A, B, C> IO<C> bracket(final IO<A> init, final F<A, IO<B>> fin, final F<A, IO<C>> body) { |
119 | | - return new IO<C>() { |
120 | | - @Override |
121 | | - public C run() throws IOException { |
122 | | - final A a = init.run(); |
123 | | - try { |
124 | | - return body.f(a).run(); |
125 | | - } catch (final IOException e) { |
126 | | - throw e; |
127 | | - } finally { |
128 | | - fin.f(a); |
129 | | - } |
130 | | - } |
131 | | - }; |
132 | | - } |
133 | | - |
134 | | - public static final <A> IO<A> unit(final A a) { |
135 | | - return new IO<A>() { |
136 | | - @Override |
137 | | - public A run() throws IOException { |
138 | | - return a; |
139 | | - } |
140 | | - }; |
141 | | - } |
142 | | - |
143 | | - /** |
144 | | - * A function that feeds an iteratee with lines read from a {@link BufferedReader}. |
145 | | - */ |
146 | | - public static <A> F<BufferedReader, F<IterV<String, A>, IO<IterV<String, A>>>> lineReader() { |
147 | | - final F<IterV<String, A>, Boolean> isDone = |
148 | | - new F<Iteratee.IterV<String, A>, Boolean>() { |
149 | | - final F<P2<A, Input<String>>, P1<Boolean>> done = constant(P.p(true)); |
150 | | - final F<F<Input<String>, IterV<String, A>>, P1<Boolean>> cont = constant(P.p(false)); |
151 | | - |
152 | | - @Override |
153 | | - public Boolean f(final IterV<String, A> i) { |
154 | | - return i.fold(done, cont)._1(); |
155 | | - } |
156 | | - }; |
157 | | - |
158 | | - return new F<BufferedReader, F<IterV<String, A>, IO<IterV<String, A>>>>() { |
159 | | - @Override |
160 | | - public F<IterV<String, A>, IO<IterV<String, A>>> f(final BufferedReader r) { |
161 | | - return new F<IterV<String, A>, IO<IterV<String, A>>>() { |
162 | | - final F<P2<A, Input<String>>, P1<IterV<String, A>>> done = errorF("iteratee is done"); //$NON-NLS-1$ |
163 | | - |
164 | | - @Override |
165 | | - public IO<IterV<String, A>> f(final IterV<String, A> it) { |
166 | | - // use loop instead of recursion because of missing TCO |
167 | | - return new IO<Iteratee.IterV<String, A>>() { |
168 | | - @Override |
169 | | - public IterV<String, A> run() throws IOException { |
170 | | - IterV<String, A> i = it; |
171 | | - while (!isDone.f(i)) { |
172 | | - final String s = r.readLine(); |
173 | | - if (s == null) { return i; } |
174 | | - final Input<String> input = Input.<String>el(s); |
175 | | - final F<F<Input<String>, IterV<String, A>>, P1<IterV<String, A>>> cont = F1Functions.lazy(Function.<Input<String>, IterV<String, A>>apply(input)); |
176 | | - i = i.fold(done, cont)._1(); |
177 | | - } |
178 | | - return i; |
179 | | - } |
180 | | - }; |
181 | | - } |
182 | | - }; |
183 | | - } |
184 | | - }; |
185 | | - } |
186 | | - |
187 | | - /** |
188 | | - * A function that feeds an iteratee with character chunks read from a {@link Reader} |
189 | | - * (char[] of size {@link #DEFAULT_BUFFER_SIZE}). |
190 | | - */ |
191 | | - public static <A> F<Reader, F<IterV<char[], A>, IO<IterV<char[], A>>>> charChunkReader() { |
192 | | - final F<IterV<char[], A>, Boolean> isDone = |
193 | | - new F<Iteratee.IterV<char[], A>, Boolean>() { |
194 | | - final F<P2<A, Input<char[]>>, P1<Boolean>> done = constant(P.p(true)); |
195 | | - final F<F<Input<char[]>, IterV<char[], A>>, P1<Boolean>> cont = constant(P.p(false)); |
196 | | - |
197 | | - @Override |
198 | | - public Boolean f(final IterV<char[], A> i) { |
199 | | - return i.fold(done, cont)._1(); |
200 | | - } |
201 | | - }; |
202 | | - |
203 | | - return new F<Reader, F<IterV<char[], A>, IO<IterV<char[], A>>>>() { |
204 | | - @Override |
205 | | - public F<IterV<char[], A>, IO<IterV<char[], A>>> f(final Reader r) { |
206 | | - return new F<IterV<char[], A>, IO<IterV<char[], A>>>() { |
207 | | - final F<P2<A, Input<char[]>>, P1<IterV<char[], A>>> done = errorF("iteratee is done"); //$NON-NLS-1$ |
208 | | - |
209 | | - @Override |
210 | | - public IO<IterV<char[], A>> f(final IterV<char[], A> it) { |
211 | | - // use loop instead of recursion because of missing TCO |
212 | | - return new IO<Iteratee.IterV<char[], A>>() { |
213 | | - @Override |
214 | | - public IterV<char[], A> run() throws IOException { |
215 | | - |
216 | | - IterV<char[], A> i = it; |
217 | | - while (!isDone.f(i)) { |
218 | | - char[] buffer = new char[DEFAULT_BUFFER_SIZE]; |
219 | | - final int numRead = r.read(buffer); |
220 | | - if (numRead == -1) { return i; } |
221 | | - if(numRead < buffer.length) { |
222 | | - buffer = Arrays.copyOfRange(buffer, 0, numRead); |
223 | | - } |
224 | | - final Input<char[]> input = Input.<char[]>el(buffer); |
225 | | - final F<F<Input<char[]>, IterV<char[], A>>, P1<IterV<char[], A>>> cont = |
226 | | - F1Functions.lazy(Function.<Input<char[]>, IterV<char[], A>>apply(input)); |
227 | | - i = i.fold(done, cont)._1(); |
228 | | - } |
229 | | - return i; |
230 | | - } |
231 | | - }; |
232 | | - } |
233 | | - }; |
234 | | - } |
235 | | - }; |
236 | | - } |
237 | | - |
238 | | - /** |
239 | | - * A function that feeds an iteratee with characters read from a {@link Reader} |
240 | | - * (chars are read in chunks of size {@link #DEFAULT_BUFFER_SIZE}). |
241 | | - */ |
242 | | - public static <A> F<Reader, F<IterV<Character, A>, IO<IterV<Character, A>>>> charChunkReader2() { |
243 | | - final F<IterV<Character, A>, Boolean> isDone = |
244 | | - new F<Iteratee.IterV<Character, A>, Boolean>() { |
245 | | - final F<P2<A, Input<Character>>, P1<Boolean>> done = constant(P.p(true)); |
246 | | - final F<F<Input<Character>, IterV<Character, A>>, P1<Boolean>> cont = constant(P.p(false)); |
247 | | - |
248 | | - @Override |
249 | | - public Boolean f(final IterV<Character, A> i) { |
250 | | - return i.fold(done, cont)._1(); |
251 | | - } |
252 | | - }; |
253 | | - |
254 | | - return new F<Reader, F<IterV<Character, A>, IO<IterV<Character, A>>>>() { |
255 | | - @Override |
256 | | - public F<IterV<Character, A>, IO<IterV<Character, A>>> f(final Reader r) { |
257 | | - return new F<IterV<Character, A>, IO<IterV<Character, A>>>() { |
258 | | - final F<P2<A, Input<Character>>, IterV<Character, A>> done = errorF("iteratee is done"); //$NON-NLS-1$ |
259 | | - |
260 | | - @Override |
261 | | - public IO<IterV<Character, A>> f(final IterV<Character, A> it) { |
262 | | - // use loop instead of recursion because of missing TCO |
263 | | - return new IO<Iteratee.IterV<Character, A>>() { |
264 | | - @Override |
265 | | - public IterV<Character, A> run() throws IOException { |
266 | | - |
267 | | - IterV<Character, A> i = it; |
268 | | - while (!isDone.f(i)) { |
269 | | - char[] buffer = new char[DEFAULT_BUFFER_SIZE]; |
270 | | - final int numRead = r.read(buffer); |
271 | | - if (numRead == -1) { return i; } |
272 | | - if(numRead < buffer.length) { |
273 | | - buffer = Arrays.copyOfRange(buffer, 0, numRead); |
274 | | - } |
275 | | - for(int c = 0; c < buffer.length; c++) { |
276 | | - final Input<Character> input = Input.el(buffer[c]); |
277 | | - final F<F<Input<Character>, IterV<Character, A>>, IterV<Character, A>> cont = |
278 | | - Function.<Input<Character>, IterV<Character, A>>apply(input); |
279 | | - i = i.fold(done, cont); |
280 | | - } |
281 | | - } |
282 | | - return i; |
283 | | - } |
284 | | - }; |
285 | | - } |
286 | | - }; |
287 | | - } |
288 | | - }; |
289 | | - } |
290 | | - |
291 | | - public abstract A run() throws IOException; |
292 | | - |
293 | | - public final <B> IO<B> map(final F<A, B> f) { |
294 | | - return new IO<B>() { |
295 | | - @Override |
296 | | - public B run() throws IOException { |
297 | | - return f.f(IO.this.run()); |
298 | | - } |
299 | | - }; |
300 | | - } |
301 | | - |
302 | | - public final <B> IO<B> bind(final F<A, IO<B>> f) { |
303 | | - return new IO<B>() { |
304 | | - @Override |
305 | | - public B run() throws IOException { |
306 | | - return f.f(IO.this.run()).run(); |
307 | | - } |
308 | | - }; |
309 | | - } |
310 | | -} |
| 1 | +package fj.data; |
| 2 | + |
| 3 | +/** |
| 4 | + * Created by MarkPerry on 19/06/2014. |
| 5 | + */ |
| 6 | + |
| 7 | +import java.io.IOException; |
| 8 | + |
| 9 | +/** |
| 10 | + * IO monad for processing files |
| 11 | + * |
| 12 | + * @author Martin Grotzke |
| 13 | + * |
| 14 | + * @param <A> the type of the result produced by the IO |
| 15 | + */ |
| 16 | +public interface IO<A> { |
| 17 | + |
| 18 | + public A run() throws IOException; |
| 19 | + |
| 20 | +} |
0 commit comments