package au.gov.amsa.risky.format;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.Queue;
import java.util.zip.GZIPInputStream;
import com.github.davidmoten.util.Optional;
import au.gov.amsa.risky.format.BinaryFixesOnSubscribeWithBackp.State;
import rx.Observable;
import rx.Observer;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.observables.SyncOnSubscribe;
public final class BinaryFixesOnSubscribeWithBackp extends SyncOnSubscribe<State, Fix> {
private final InputStream is;
private final Optional<Integer> mmsi;
private final BinaryFixesFormat format;
public BinaryFixesOnSubscribeWithBackp(InputStream is, Optional<Integer> mmsi,
BinaryFixesFormat format) {
this.is = is;
this.mmsi = mmsi;
this.format = format;
}
public final static class State {
final InputStream is;
final Optional<Integer> mmsi;
final Queue<Fix> queue;
public State(InputStream is, Optional<Integer> mmsi, Queue<Fix> queue) {
this.is = is;
this.mmsi = mmsi;
this.queue = queue;
}
}
/**
* Returns stream of fixes from the given file. If the file name ends in
* '.gz' then the file is unzipped before being read.
*
* @param file
* @return fixes stream
*/
public static Observable<Fix> from(final File file, BinaryFixesFormat format) {
Func0<InputStream> resourceFactory = new Func0<InputStream>() {
@Override
public InputStream call() {
try {
if (file.getName().endsWith(".gz"))
return new GZIPInputStream(new FileInputStream(file));
else
return new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
Func1<InputStream, Observable<Fix>> obsFactory = new Func1<InputStream, Observable<Fix>>() {
@Override
public Observable<Fix> call(InputStream is) {
Optional<Integer> mmsi;
if (format == BinaryFixesFormat.WITH_MMSI)
mmsi = Optional.absent();
else
mmsi = Optional.of(BinaryFixesUtil.getMmsi(file));
return Observable.create(new BinaryFixesOnSubscribeWithBackp(is, mmsi, format));
}
};
Action1<InputStream> disposeAction = new Action1<InputStream>() {
@Override
public void call(InputStream is) {
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
return Observable.using(resourceFactory, obsFactory, disposeAction, true);
}
@Override
protected State generateState() {
return new State(is, mmsi, new LinkedList<Fix>());
}
@Override
protected State next(State state, Observer<? super Fix> observer) {
int recordSize = BinaryFixes.recordSize(format);
Fix f = state.queue.poll();
if (f != null)
observer.onNext(f);
else {
byte[] bytes = new byte[4096 * BinaryFixes.recordSize(format)];
int length;
try {
if ((length = state.is.read(bytes)) > 0) {
for (int i = 0; i < length; i += recordSize) {
ByteBuffer bb = ByteBuffer.wrap(bytes, i, recordSize);
final int mmsi;
if (state.mmsi.isPresent()) {
mmsi = state.mmsi.get();
} else {
mmsi = bb.getInt();
}
Fix fix = BinaryFixesUtil.toFix(mmsi, bb);
state.queue.add(fix);
}
observer.onNext(state.queue.remove());
} else
observer.onCompleted();
} catch (IOException e) {
observer.onError(e);
}
}
return state;
}
}