package com.github.davidmoten.rx; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import rx.Observable; import rx.Observer; import rx.functions.Action1; import rx.functions.Func0; import rx.functions.Func1; import rx.observables.SyncOnSubscribe; /** * Utility class for writing Observable streams to ObjectOutputStreams and * reading Observable streams of indeterminate size from ObjectInputStreams. */ public final class Serialized { private static final int DEFAULT_BUFFER_SIZE = 8192; /** * Returns the deserialized objects from the given {@link InputStream} as an * {@link Observable} stream. * * @param ois * the {@link ObjectInputStream} * @param <T> * the generic type of the returned stream * @return the stream of deserialized objects from the {@link InputStream} * as an {@link Observable}. */ public static <T extends Serializable> Observable<T> read(final ObjectInputStream ois) { return Observable.create(new SyncOnSubscribe<ObjectInputStream,T>() { @Override protected ObjectInputStream generateState() { return ois; } @Override protected ObjectInputStream next(ObjectInputStream ois, Observer<? super T> observer) { try { @SuppressWarnings("unchecked") T t = (T) ois.readObject(); observer.onNext(t); } catch (EOFException e) { observer.onCompleted(); } catch (ClassNotFoundException e) { observer.onError(e); } catch (IOException e) { observer.onError(e); } return ois; } }); } /** * Returns the deserialized objects from the given {@link File} as an * {@link Observable} stream. Uses buffer of size <code>bufferSize</code> * buffer reads from the File. * * @param file * the input file * @param bufferSize * the buffer size for reading bytes from the file. * @param <T> * the generic type of the deserialized objects returned in the * stream * @return the stream of deserialized objects from the {@link InputStream} * as an {@link Observable}. */ public static <T extends Serializable> Observable<T> read(final File file, final int bufferSize) { Func0<ObjectInputStream> resourceFactory = new Func0<ObjectInputStream>() { @Override public ObjectInputStream call() { try { return new ObjectInputStream( new BufferedInputStream(new FileInputStream(file), bufferSize)); } catch (FileNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } }; Func1<ObjectInputStream, Observable<? extends T>> observableFactory = new Func1<ObjectInputStream, Observable<? extends T>>() { @Override public Observable<? extends T> call(ObjectInputStream is) { return read(is); } }; Action1<ObjectInputStream> disposeAction = new Action1<ObjectInputStream>() { @Override public void call(ObjectInputStream ois) { try { ois.close(); } catch (IOException e) { throw new RuntimeException(e); } } }; return Observable.using(resourceFactory, observableFactory, disposeAction, true); } /** * Returns the deserialized objects from the given {@link File} as an * {@link Observable} stream. A buffer size of 8192 bytes is used by * default. * * @param file * the input file containing serialized java objects * @param <T> * the generic type of the deserialized objects returned in the * stream * @return the stream of deserialized objects from the {@link InputStream} * as an {@link Observable}. */ public static <T extends Serializable> Observable<T> read(final File file) { return read(file, DEFAULT_BUFFER_SIZE); } /** * Returns a duplicate of the input stream but with the side effect that * emissions from the source are written to the {@link ObjectOutputStream}. * * @param source * the source of objects to write * @param oos * the output stream to write to * @param <T> * the generic type of the objects being serialized * @return re-emits the input stream */ public static <T extends Serializable> Observable<T> write(Observable<T> source, final ObjectOutputStream oos) { return source.doOnNext(new Action1<T>() { @Override public void call(T t) { try { oos.writeObject(t); } catch (IOException e) { throw new RuntimeException(e); } } }); } /** * Writes the source stream to the given file in given append mode and using * the given buffer size. * * @param source * observable stream to write * @param file * file to write to * @param append * if true writes are appended to file otherwise overwrite the * file * @param bufferSize * the buffer size in bytes to use. * @param <T> * the generic type of the input stream * @return re-emits the input stream */ public static <T extends Serializable> Observable<T> write(final Observable<T> source, final File file, final boolean append, final int bufferSize) { Func0<ObjectOutputStream> resourceFactory = new Func0<ObjectOutputStream>() { @Override public ObjectOutputStream call() { try { return new ObjectOutputStream(new BufferedOutputStream( new FileOutputStream(file, append), bufferSize)); } catch (FileNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } }; Func1<ObjectOutputStream, Observable<? extends T>> observableFactory = new Func1<ObjectOutputStream, Observable<? extends T>>() { @Override public Observable<? extends T> call(ObjectOutputStream oos) { return write(source, oos); } }; Action1<ObjectOutputStream> disposeAction = new Action1<ObjectOutputStream>() { @Override public void call(ObjectOutputStream oos) { try { oos.close(); } catch (IOException e) { throw new RuntimeException(e); } } }; return Observable.using(resourceFactory, observableFactory, disposeAction, true); } /** * Writes the source stream to the given file in given append mode and using * the a buffer size of 8192 bytes. * * @param source * observable stream to write * @param file * file to write to * @param append * if true writes are appended to file otherwise overwrite the * file * @param <T> * the generic type of the input stream * @return re-emits the input stream */ public static <T extends Serializable> Observable<T> write(final Observable<T> source, final File file, final boolean append) { return write(source, file, append, DEFAULT_BUFFER_SIZE); } /** * Writes the source stream to the given file in given append mode and using * the a buffer size of 8192 bytes. * * @param source * observable stream to write * @param file * file to write to * @param <T> * the generic type of the input stream * @return re-emits the input stream */ public static <T extends Serializable> Observable<T> write(final Observable<T> source, final File file) { return write(source, file, false, DEFAULT_BUFFER_SIZE); } public static KryoBuilder kryo() { return kryo(new Kryo()); } public static KryoBuilder kryo(Kryo kryo) { return new KryoBuilder(kryo); } public static class KryoBuilder { private static final int DEFAULT_BUFFER_SIZE = 4096; private final Kryo kryo; private KryoBuilder(Kryo kryo) { this.kryo = kryo; } public <T> Observable<T> write(final Observable<T> source, final File file) { return write(source, file, false, DEFAULT_BUFFER_SIZE); } public <T> Observable<T> write(final Observable<T> source, final File file, boolean append) { return write(source, file, append, DEFAULT_BUFFER_SIZE); } public <T> Observable<T> write(final Observable<T> source, final File file, final boolean append, final int bufferSize) { Func0<Output> resourceFactory = new Func0<Output>() { @Override public Output call() { try { return new Output(new FileOutputStream(file, append), bufferSize); } catch (FileNotFoundException e) { throw new RuntimeException(e); } } }; Func1<Output, Observable<? extends T>> observableFactory = new Func1<Output, Observable<? extends T>>() { @Override public Observable<? extends T> call(final Output output) { return source.doOnNext(new Action1<T>() { @Override public void call(T t) { kryo.writeObject(output, t); } }); } }; Action1<Output> disposeAction = new Action1<Output>() { @Override public void call(Output output) { output.close(); } }; return Observable.using(resourceFactory, observableFactory, disposeAction, true); } public <T> Observable<T> read(Class<T> cls, final File file) { return read(cls, file, DEFAULT_BUFFER_SIZE); } public <T> Observable<T> read(final Class<T> cls, final File file, final int bufferSize) { Func0<Input> resourceFactory = new Func0<Input>() { @Override public Input call() { try { return new Input(new FileInputStream(file), bufferSize); } catch (FileNotFoundException e) { throw new RuntimeException(e); } } }; Func1<Input, Observable<? extends T>> observableFactory = new Func1<Input, Observable<? extends T>>() { @Override public Observable<? extends T> call(final Input input) { return read(cls, input, bufferSize); } }; Action1<Input> disposeAction = new Action1<Input>() { @Override public void call(Input input) { input.close(); } }; return Observable.using(resourceFactory, observableFactory, disposeAction, true); } public <T> Observable<T> read(final Class<T> cls, final Input input, final int bufferSize) { return Observable.create(new SyncOnSubscribe<Input,T>() { @Override protected Input generateState() { return input; } @Override protected Input next(Input arg0, Observer<? super T> observer) { if (input.eof()) { observer.onCompleted(); } else { T t = kryo.readObject(input, cls); observer.onNext(t); } return input; } }); } } }