// Copyright (c) 2014 Tom Zhou<iwebpp@gmail.com>
package com.iwebpp.node.stream;
import com.iwebpp.node.NodeContext;
import com.iwebpp.node.Util;
public abstract class Transform
extends Duplex {
private TransformState _transformState;
public static class TransformState {
private Transform stream;
private boolean needTransform;
private boolean transforming;
private WriteCB writecb;
private Object writechunk;
private String writeencoding;
public TransformState(Transform stream) {
this.stream = stream;
this.needTransform = false;
this.transforming = false;
this.writecb = null;
this.writechunk = null;
}
@SuppressWarnings("unused")
private TransformState(){}
public void afterTransform(String er, Object data) throws Exception {
afterTransform(stream, er, data);
}
private static void afterTransform(Transform stream, String er, Object data) throws Exception {
TransformState ts = stream._transformState;
ts.transforming = false;
WriteCB cb = ts.writecb;
if (null==cb) {
///return stream.emit("error", new Error('no writecb in Transform class'));
stream.emit("error", "no writecb in Transform class");
return;
}
ts.writechunk = null;
ts.writecb = null;
///if (!util.isNullOrUndefined(data))
if (!Util.isNullOrUndefined(data))
stream.push(data, null);
if (cb != null)
cb.writeDone(er);
State rs = stream.get_readableState();
rs.setReading(false);
if (rs.needReadable || rs.getLength() < rs.highWaterMark) {
stream._read(rs.highWaterMark);
}
}
}
protected Transform(NodeContext ctx, Duplex.Options options) {
super(ctx, options);
// TODO Auto-generated constructor stub
this._transformState = new TransformState(this);
// when the writable side finishes, then flush out anything remaining.
final Transform stream = this;
// start out asking for a readable event once data is transformed.
this.get_readableState().needReadable = true;
// we have implemented the _read method, and done the other things
// that Readable wants before the first _read call, so unset the
// sync guard flag.
this.get_readableState().sync = false;
///this.once("prefinish", function() {
this.once("prefinish", new Listener() {
@Override
public void onEvent(Object data) throws Exception {
/*if (util.isFunction(stream._flush))
this._flush(function(er) {
done(stream, er);
});
else
done(stream);*/
stream._flush(new flushCallback(){
@Override
public void onFlush(String er) throws Exception {
done(stream, er);
}
});
}
});
}
private Transform(){
super(null, null);
}
public boolean push(Object chunk, String encoding) throws Exception {
this._transformState.needTransform = false;
return super.push(chunk, encoding);
}
protected void _write(Object chunk, String encoding, WriteCB cb) throws Exception {
TransformState ts = this._transformState;
ts.writecb = cb;
ts.writechunk = chunk;
ts.writeencoding = encoding;
if (!ts.transforming) {
State rs = this.get_readableState();
if (ts.needTransform ||
rs.needReadable ||
rs.getLength() < rs.highWaterMark)
this._read(rs.highWaterMark);
}
}
// Doesn't matter what the args are here.
// _transform does all the work.
// That we got here means that the readable side wants more data.
protected void _read(int size) throws Exception {
final TransformState ts = this._transformState;
if (!Util.isNull(ts.writechunk) && ts.writecb!=null && !ts.transforming) {
ts.transforming = true;
///this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform);
this._transform(ts.writechunk, ts.writeencoding, new afterTransformCallback(){
@Override
public void afterTransform(String error, Object data) throws Exception {
ts.afterTransform(error, null);
}
});
} else {
// mark that we need a transform, so that any data that comes in
// will get processed, now that we've asked for it.
ts.needTransform = true;
}
};
private static boolean done(Transform stream, String er) throws Exception {
///if (er != null) {
if (!Util.zeroString(er)) {
stream.emit("error", er);
return false;
}
// if there's nothing in the write buffer, then that means
// that nothing more will ever be provided
com.iwebpp.node.stream.Writable2.State ws = stream._writableState;
TransformState ts = stream._transformState;
if (ws.getLength()!=0)
throw new Exception("calling transform done when ws.length != 0");
if (ts.transforming)
throw new Exception("calling transform done when still transforming");
return stream.push(null, null);
}
// This is the part where you do stuff!
// override this function in implementation classes.
// 'chunk' is an input chunk.
//
// Call `push(newChunk)` to pass along transformed output
// to the readable side. You may call 'push' zero or more times.
//
// Call `cb(err)` when you are done with this chunk. If you pass
// an error, then that'll put the hurt on the whole operation. If you
// never call cb(), then you'll never get another chunk.
protected interface afterTransformCallback {
void afterTransform(String error, Object data) throws Exception;
}
protected abstract void _transform(final Object chunk, String encoding,
afterTransformCallback cb) throws Exception;
protected interface flushCallback {
void onFlush(String error) throws Exception;
}
protected abstract void _flush(flushCallback cb) throws Exception;
}