// Copyright (c) 2014 Tom Zhou<iwebpp@gmail.com>
package com.iwebpp.node.stream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.LinkedList;
import java.util.List;
import com.iwebpp.node.EventEmitter;
import com.iwebpp.node.EventEmitter2;
import com.iwebpp.node.NodeContext;
import com.iwebpp.node.Util;
import com.iwebpp.node.others.TripleState;
import android.text.TextUtils;
public abstract class Readable2
extends EventEmitter2
implements Readable {
private final static String TAG = "Readable2";
private State _readableState;
private boolean readable;
private NodeContext context;
public static class Options {
private int highWaterMark;
private boolean objectMode;
private String defaultEncoding;
private String encoding;
public boolean readable;
public Options(
int highWaterMark,
String encoding,
boolean objectMode,
String defaultEncoding,
boolean readable) {
this.highWaterMark = highWaterMark;
this.encoding = encoding;
this.objectMode = objectMode;
this.defaultEncoding = defaultEncoding;
this.readable = readable;
}
/**
* @return the highWaterMark
*/
public int getHighWaterMark() {
return highWaterMark;
}
/**
* @return the objectMode
*/
public boolean isObjectMode() {
return objectMode;
}
/**
* @return the defaultEncoding
*/
public String getDefaultEncoding() {
return defaultEncoding;
}
/**
* @return the encoding
*/
public String getEncoding() {
return encoding;
}
@SuppressWarnings("unused")
private Options() {}
}
public static class State {
boolean objectMode;
int highWaterMark;
List<Object> buffer; // ByteBuffer or String
private int length;
List<Writable> pipes;
int pipesCount;
TripleState flowing;
/**
* @param flowing the flowing to set
*/
public void setFlowing(TripleState flowing) {
this.flowing = flowing;
}
/**
* @return the flowing
*/
public TripleState isFlowing() {
return flowing;
}
boolean ended;
private boolean endEmitted;
private boolean reading;
boolean sync;
boolean needReadable;
boolean emittedReadable;
boolean readableListening;
String defaultEncoding;
int awaitDrain;
boolean readingMore;
CharsetDecoder decoder;
String encoding;
private boolean resumeScheduled;
protected State(Options options, final Readable2 stream) {
///options = options || {};
// object stream flag. Used to make read(n) ignore n and to
// make all the buffer merging and length checks go away
this.setObjectMode(options.objectMode);
// TBD...
///if (stream instanceof Duplex)
/// this.objectMode = this.objectMode || !!options.readableObjectMode;
// the point at which it stops calling _read() to fill the buffer
// Note: 0 is a valid value, means "don't call _read preemptively ever"
int hwm = options.highWaterMark;
int defaultHwm = this.isObjectMode() ? 16 : 16 * 1024;
this.highWaterMark = (hwm >= 0) ? hwm : defaultHwm;
// cast to ints.
///this.highWaterMark = ~~this.highWaterMark;
this.buffer = new LinkedList<Object>();
this.setLength(0);
this.pipes = new LinkedList<Writable>();
this.pipesCount = 0;
this.flowing = TripleState.MAYBE;
this.setEnded(false);
this.setEndEmitted(false);
this.setReading(false);
// a flag to be able to tell if the onwrite cb is called immediately,
// or on a later tick. We set this to true at first, because any
// actions that shouldn't happen until "later" should generally also
// not happen before the first write call.
this.sync = true;
// whenever we return null, then we set a flag to say
// that we're awaiting a 'readable' event emission.
this.needReadable = false;
this.emittedReadable = false;
this.readableListening = false;
// Crypto is kind of old and crusty. Historically, its default string
// encoding is 'binary' so we have to make this configurable.
// Everything else in the universe uses 'utf8', though.
this.defaultEncoding = options.defaultEncoding != null ?
options.defaultEncoding : "UTF-8";
// the number of writers that are awaiting a drain event in .pipe()s
this.awaitDrain = 0;
// if true, a maybeReadMore has been scheduled
this.readingMore = false;
this.setDecoder(null);
this.encoding = null;
if (!Util.zeroString(options.encoding)) {
///if (!StringDecoder)
/// StringDecoder = require('string_decoder').StringDecoder;
///this.decoder = new StringDecoder(options.encoding);
this.setDecoder(Charset.forName(options.encoding).newDecoder());
this.encoding = options.encoding;
}
}
@SuppressWarnings("unused")
private State() {}
/**
* @return the decoder
*/
public CharsetDecoder getDecoder() {
return decoder;
}
/**
* @param decoder the decoder to set
*/
public void setDecoder(CharsetDecoder decoder) {
this.decoder = decoder;
}
/**
* @return the ended
*/
public boolean isEnded() {
return ended;
}
/**
* @param ended the ended to set
*/
public void setEnded(boolean ended) {
this.ended = ended;
}
/**
* @return the objectMode
*/
public boolean isObjectMode() {
return objectMode;
}
/**
* @param objectMode the objectMode to set
*/
public void setObjectMode(boolean objectMode) {
this.objectMode = objectMode;
}
/**
* @return the resumeScheduled
*/
public boolean isResumeScheduled() {
return resumeScheduled;
}
/**
* @param resumeScheduled the resumeScheduled to set
*/
public void setResumeScheduled(boolean resumeScheduled) {
this.resumeScheduled = resumeScheduled;
}
/**
* @return the endEmitted
*/
public boolean isEndEmitted() {
return endEmitted;
}
/**
* @param endEmitted the endEmitted to set
*/
public void setEndEmitted(boolean endEmitted) {
this.endEmitted = endEmitted;
}
/**
* @return the reading
*/
public boolean isReading() {
return reading;
}
/**
* @param reading the reading to set
*/
public void setReading(boolean reading) {
this.reading = reading;
}
/**
* @return the length
*/
public int getLength() {
return length;
}
/**
* @param length the length to set
*/
public void setLength(int length) {
this.length = length;
}
}
protected Readable2(NodeContext context, Options options) {
super();
this.context = context;
///if (!(this instanceof Readable))
/// return new Readable(options);
this._readableState = new State(options, this);
// legacy
this.readable = options.readable;/// true;
}
@SuppressWarnings("unused")
private Readable2() {}
// Unshift should *always* be something directly out of read()
public boolean unshift(Object chunk) throws Exception {
State state = this._readableState;
return readableAddChunk(this, state, chunk, "", true);
}
private boolean readableAddChunk(Readable2 stream, State state,
Object chunk, String encoding, boolean addToFront) throws Exception {
String er = chunkInvalid(state, chunk);
if (er != null) {
stream.emit("error", er);
} else if (chunk == null) {
state.setReading(false);
if (!state.isEnded())
onEofChunk(stream, state);
} else if (state.isObjectMode() || chunk != null && Util.chunkLength(chunk) > 0) {
if (state.isEnded() && !addToFront) {
///var e = new Error('stream.push() after EOF');
String e = "stream.push() after EOF";
stream.emit("error", e);
} else if (state.isEndEmitted() && addToFront) {
////var e = new Error('stream.unshift() after end event');
String e = "stream.unshift() after end event";
stream.emit("error", e);
} else {
if (state.getDecoder()!=null && !addToFront && Util.zeroString(encoding)) {
chunk = state.getDecoder().decode((ByteBuffer) chunk).toString();
debug(TAG, "decoded chunk "+chunk);
}
if (!addToFront)
state.setReading(false);
// if we want the data now, just emit it.
if (state.flowing==TripleState.TRUE && state.getLength() == 0 && !state.sync) {
stream.emit("data", chunk);
stream.read(0);
} else {
// update the buffer info.
state.setLength(state.getLength()
+ (state.isObjectMode() ? 1 : Util.chunkLength(chunk)));
if (addToFront) {
///state.buffer.unshift(chunk);
state.buffer.add(0, chunk);
} else
///state.buffer.push(chunk);
state.buffer.add(chunk);
if (state.needReadable)
emitReadable(stream);
}
maybeReadMore(stream, state);
}
} else if (!addToFront) {
state.setReading(false);
}
return needMoreData(state);
}
//if it's past the high water mark, we can push in some more.
//Also, if we have no data yet, we can stand some
//more bytes. This is to work around cases where hwm=0,
//such as the repl. Also, if the push() triggered a
//readable event, and the user called read(largeNumber) such that
//needReadable was set, then we ought to push more, so that another
//'readable' event will be triggered.
private boolean needMoreData(State state) {
debug(TAG, "needMoreData: state.length:"+
state.getLength()+",state.highWaterMark:"+
state.highWaterMark);
return !state.isEnded() &&
(state.needReadable ||
state.getLength() < state.highWaterMark ||
state.getLength() == 0);
}
// backwards compatibility.
public boolean setEncoding(String enc) {
/*if (!StringDecoder)
StringDecoder = require('string_decoder').StringDecoder;
this._readableState.decoder = new StringDecoder(enc);
this._readableState.encoding = enc;
*/
if (!Util.zeroString(enc)) {
this._readableState.setDecoder(Charset.forName(enc).newDecoder());
this._readableState.encoding = enc;
} else {
this._readableState.setDecoder(null);
this._readableState.encoding = null;
}
return true;
};
//Don't raise the hwm > 128MB
private int MAX_HWM = 0x800000;
private int roundUpToNextPowerOf2(int n) {
if (n >= MAX_HWM) {
n = MAX_HWM;
} else {
// Get the next highest power of 2
n--;
for (int p = 1; p < 32; p <<= 1) n |= n >> p;
n++;
}
return n;
}
private int howMuchToRead(int n, State state) {
if (state.getLength() == 0 && state.isEnded())
return 0;
if (state.isObjectMode())
return n == 0 ? 0 : 1;
///if (util.isNull(n) || isNaN(n)) {
if (n < 0) {
// only flow one buffer at a time
if (state.flowing==TripleState.TRUE && state.buffer.size() > 0)
return Util.chunkLength(state.buffer.get(0));
else
return state.getLength();
}
if (n <= 0)
return 0;
// If we're asking for more than the target buffer level,
// then raise the water mark. Bump up to the next highest
// power of 2, to prevent increasing it excessively in tiny
// amounts.
if (n > state.highWaterMark)
state.highWaterMark = roundUpToNextPowerOf2(n);
// don't have that much. return null, unless we've ended.
if (n > state.getLength()) {
if (!state.isEnded()) {
state.needReadable = true;
return 0;
} else
return state.getLength();
}
return n;
}
//you can override either this method, or the async _read(n) below.
public Object read(int n) throws Exception {
debug(TAG, "read " + n);
State state = this._readableState;
int nOrig = n;
///if (!util.isNumber(n) || n > 0)
if (n > 0 || n < 0)
state.emittedReadable = false;
// if we're doing read(0) to trigger a readable event, but we
// already have a bunch of data in the buffer, then just trigger
// the 'readable' event and move on.
if (n == 0 &&
state.needReadable &&
(state.getLength() >= state.highWaterMark || state.isEnded())) {
debug(TAG, "read: emitReadable" + state.getLength() + "" + state.isEnded());
if (state.getLength() == 0 && state.isEnded())
endReadable(this);
else
emitReadable(this);
return null;
}
n = howMuchToRead(n, state);
// if we've ended, and we're now clear, then finish it up.
if (n == 0 && state.isEnded()) {
if (state.getLength() == 0)
endReadable(this);
return null;
}
// All the actual chunk generation logic needs to be
// *below* the call to _read. The reason is that in certain
// synthetic stream cases, such as passthrough streams, _read
// may be a completely synchronous operation which may change
// the state of the read buffer, providing enough data when
// before there was *not* enough.
//
// So, the steps are:
// 1. Figure out what the state of things will be after we do
// a read from the buffer.
//
// 2. If that resulting state will trigger a _read, then call _read.
// Note that this may be asynchronous, or synchronous. Yes, it is
// deeply ugly to write APIs this way, but that still doesn't mean
// that the Readable class should behave improperly, as streams are
// designed to be sync/async agnostic.
// Take note if the _read call is sync or async (ie, if the read call
// has returned yet), so that we know whether or not it's safe to emit
// 'readable' etc.
//
// 3. Actually pull the requested chunks out of the buffer and return.
// if we need a readable event, then we need to do some reading.
boolean doRead = state.needReadable;
debug(TAG, "need readable " + doRead);
// if we currently have less than the highWaterMark, then also read some
if (state.getLength() == 0 || state.getLength() - n < state.highWaterMark) {
doRead = true;
debug(TAG, "length less than watermark " + doRead);
}
// however, if we've ended, then there's no point, and if we're already
// reading, then it's unnecessary.
if (state.isEnded() || state.isReading()) {
doRead = false;
debug(TAG, "reading or ended " + doRead);
}
if (doRead) {
debug(TAG, "do read");
state.setReading(true);
state.sync = true;
// if the length is currently zero, then we *need* a readable event.
if (state.getLength() == 0)
state.needReadable = true;
// call internal read method
this._read(state.highWaterMark);
state.sync = false;
}
// If _read pushed data synchronously, then `reading` will be false,
// and we need to re-evaluate how much data we can return to the user.
if (doRead && !state.isReading())
n = howMuchToRead(nOrig, state);
Object ret;
if (n > 0)
ret = fromList(n, state);
else
ret = null;
///if (util.isNull(ret)) {
if (Util.isNull(ret)) {
state.needReadable = true;
n = 0;
}
state.setLength(state.getLength() - n);
// If we have nothing in the buffer, then we want to know
// as soon as we *do* get something into the buffer.
if (state.getLength() == 0 && !state.isEnded())
state.needReadable = true;
// If we tried to read() past the EOF, then emit end on the next tick.
if (nOrig != n && state.isEnded() && state.getLength() == 0)
endReadable(this);
///if (!util.isNull(ret))
if (!Util.isNull(ret))
this.emit("data", ret);
return ret;
}
//Pluck off n bytes from an array of buffers.
//Length is the combined lengths of all the buffers in the list.
private Object fromList(int n, State state) throws Exception {
List<Object> list = state.buffer;
int length = state.getLength();
boolean stringMode = state.getDecoder() != null;
boolean objectMode = !!state.isObjectMode();
Object ret;
debug(TAG, "fromList n="+n);
// nothing in the list, definitely empty.
if (list.size() == 0)
return null;
if (length == 0) {
debug(TAG, "length == 0");
ret = null;
} else if (objectMode) {
debug(TAG, "objectMode");
///ret = list.shift();
ret = list.get(0);
} else if (n==0 || n >= length) {
debug(TAG, "n==0 || n >= length");
// read it all, truncate the array.
if (stringMode) {
///ret = list.join('');
ret = TextUtils.join("", list);
debug(TAG, "join.string:"+ret.toString());
} else {
///ret = Buffer.concat(list, length);
ret = Util.concatByteBuffer(list, length);
}
///list.length = 0;
list.clear();
} else {
// read just some of it.
///if (n < list[0].length) {
if (n < Util.chunkLength(list.get(0))) {
debug(TAG, "n < Util.chunkLength(list.get(0))");
// just take a part of the first list item.
// slice is the same for buffers and strings.
Object buf = list.get(0);
///ret = buf.slice(0, n);
ret = Util.chunkSlice(buf, 0, n);
///list[0] = buf.slice(n);
list.set(0, Util.chunkSlice(buf, n));
} else if (n == Util.chunkLength(list.get(0))) {
debug(TAG, "n == Util.chunkLength(list.get(0))");
// first list is a perfect match
///ret = list.shift();
ret = list.remove(0);
} else {
debug(TAG, "complex case");
// complex case.
// we have enough to cover it, but it spans past the first buffer.
if (stringMode) {
debug(TAG, "stringMode");
ret = "";
} else {
///ret = new Buffer(n);
ret = ByteBuffer.allocate(n);
}
int c = 0;
for (int i = 0, l = list.size(); i < l && c < n; i++) {
Object buf = list.get(0);
int cpy = Math.min(n - c, Util.chunkLength(buf));
if (stringMode) {
///ret += buf.slice(0, cpy);
String rs = (String)ret;
rs += (String)(Util.chunkSlice(buf, 0, cpy));
ret = rs;
debug(TAG, "string mode complex buf:"+buf.toString());
debug(TAG, "string mode ret buf:"+ret.toString());
} else {
///buf.copy(ret, c, 0, cpy);
ByteBuffer rb = (ByteBuffer)ret;
rb.put((ByteBuffer)Util.chunkSlice(buf, 0, cpy));
///ret = rb;
}
///if (cpy < buf.length)
if (cpy < Util.chunkLength(buf))
///list[0] = buf.slice(cpy);
list.set(0, Util.chunkSlice(buf, cpy));
else
///list.shift();
list.remove(0);
c += cpy;
}
// flip ByteBuffer
if (!stringMode)
((ByteBuffer)ret).flip();
}
}
return ret;
}
private void endReadable(final Readable2 stream) throws Exception {
final State state = stream._readableState;
// If we get here before consuming all the bytes, then that is a
// bug in node. Should never happen.
if (state.getLength() > 0)
throw new Exception("endReadable called on non-empty stream");
if (!state.isEndEmitted()) {
state.setEnded(true);
///TDB...
///process.nextTick(function() {
context.nextTick(new NodeContext.nextTickListener() {
@Override
public void onNextTick() throws Exception {
// Check that we didn't get one last unshift.
if (!state.isEndEmitted() && state.getLength() == 0) {
state.setEndEmitted(true);
stream.readable = false;
stream.emit("end");
}
}
});
}
}
//at this point, the user has presumably seen the 'readable' event,
//and called read() to consume some data. that may have triggered
//in turn another _read(n) call, in which case reading = true if
//it's in progress.
//However, if we're not ended, or reading, and the length < hwm,
//then go ahead and try to read some more preemptively.
private void maybeReadMore(final Readable2 stream, final State state) throws Exception {
if (!state.readingMore) {
state.readingMore = true;
//TBD...
///process.nextTick(function() {
context.nextTick(new NodeContext.nextTickListener() {
@Override
public void onNextTick() throws Exception {
maybeReadMore_(stream, state);
}
});
}
}
private void maybeReadMore_(Readable2 stream, State state) throws Exception {
int len = state.getLength();
while (!state.isReading() && state.flowing!=TripleState.TRUE && !state.isEnded() &&
state.getLength() < state.highWaterMark) {
debug(TAG, "maybeReadMore read 0");
stream.read(0);
if (len == state.getLength())
// didn't get any data, stop spinning.
break;
else
len = state.getLength();
}
state.readingMore = false;
}
//Don't emit readable right away in sync mode, because this can trigger
//another read() call => stack overflow. This way, it might trigger
//a nextTick recursion warning, but that's not so bad.
private void emitReadable(final Readable2 stream) throws Exception {
State state = stream._readableState;
state.needReadable = false;
if (!state.emittedReadable) {
debug(TAG, "emitReadable " + state.flowing);
state.emittedReadable = true;
if (state.sync) {
//TBD...
///process.nextTick(function() {
context.nextTick(new NodeContext.nextTickListener() {
@Override
public void onNextTick() throws Exception {
emitReadable_(stream);
}
});
} else
emitReadable_(stream);
}
}
private void emitReadable_(Readable2 stream) throws Exception {
debug(TAG, "emit readable");
stream.emit("readable");
flow(stream);
}
private void flow(Readable2 stream) throws Exception {
State state = stream._readableState;
debug(TAG, "flow " + state.flowing);
if (state.flowing==TripleState.TRUE) {
Object chunk;
do {
///var chunk = stream.read();
chunk = stream.read(-1);
} while (null != chunk && state.flowing==TripleState.TRUE);
}
}
private void onEofChunk(Readable2 stream, State state) throws Exception {
if (state.getDecoder()!=null && !state.isEnded()) {
///Object chunk = state.decoder.end();
// Reset decoder anyway
/*
CharBuffer cbuf = CharBuffer.allocate(1024 * 1024);
state.getDecoder().flush(cbuf);
String chunk = cbuf.toString();
if (chunk!=null && Util.chunkLength(chunk)>0) {
state.buffer.add(chunk);
state.length += state.isObjectMode() ? 1 : Util.chunkLength(chunk);
}
*/
state.getDecoder().reset();
}
state.setEnded(true);
// emit 'readable' now to make sure it gets picked up.
emitReadable(stream);
}
private String chunkInvalid(State state, Object chunk) {
String er = null;
/*if (!util.isBuffer(chunk) &&
!util.isString(chunk) &&
!util.isNullOrUndefined(chunk) &&
!state.objectMode) {
er = new TypeError('Invalid non-string/buffer chunk');
}*/
if (!Util.isBuffer(chunk) &&
!Util.isString(chunk) &&
!Util.isNullOrUndefined(chunk) &&
!state.isObjectMode())
er = "Invalid non-string/buffer chunk";
return er;
}
public Writable pipe(final Writable dest) throws Exception {
return pipe(dest, true);
}
public Writable pipe(final Writable dest, boolean pipeOpts) throws Exception {
final Readable2 src = this;
final State state = this._readableState;
/*switch (state.pipesCount) {
case 0:
state.pipes = dest;
break;
case 1:
state.pipes = [state.pipes, dest];
break;
default:
state.pipes.push(dest);
break;
}*/
state.pipes.add(dest);
state.pipesCount += 1;
debug(TAG, "pipe count=" + state.pipesCount + "opts=" + pipeOpts);
/*
boolean doEnd = (!pipeOpts || pipeOpts.end !== false) &&
dest !== process.stdout &&
dest !== process.stderr;
*/
boolean doEnd = pipeOpts;
final Listener onend = new Listener() {
@Override
public void onEvent(Object readable) throws Exception {
debug(TAG, "onend");
dest.end(null, null, null);
}
};
// when the dest drains, it reduces the awaitDrain counter
// on the source. This would be more elegant with a .once()
// handler in flow(), but adding and removing repeatedly is
// too slow.
final Listener ondrain = pipeOnDrain(src);
dest.on("drain", ondrain);
final Listener ondata = new Listener() {
@Override
public void onEvent(Object chunk) throws Exception {
debug(TAG, "ondata");
boolean ret = dest.write(chunk, null, null);
if (false == ret) {
debug(TAG, "false write response, pause " + src._readableState.awaitDrain);
src._readableState.awaitDrain++;
src.pause();
}
}
};
src.on("data", ondata);
// if the dest has an error, then stop piping into it.
// however, don't suppress the throwing behavior for this.
final Listener onerror = new Listener() {
@Override
public void onEvent(Object er) throws Exception {
debug(TAG, "onerror " + er);
unpipe(src, (Writable2) dest);
dest.removeListener("error", this);
if (dest.listenerCount("error") == 0)
dest.emit("error", er);
}
};
// This is a brutally ugly hack to make sure that our error handler
// is attached before any userland ones. NEVER DO THIS.
/*if (!dest._events || !dest._events.error)
dest.on("error", onerror);
else if (Array.isArray(dest._events.error))
dest._events.error.unshift(onerror);
else
dest._events.error = [onerror, dest._events.error];
*/
// TBD...
dest.addListener("error", onerror, 0);
// Both close and finish should trigger unpipe, but only once.
final Listener onclose = new Listener() {
@Override
public void onEvent(Object er) throws Exception {
///dest.removeListener("finish", onfinish);
dest.removeListener("finish");
unpipe(src, (Writable2) dest);
}
};
dest.once("close", onclose);
final Listener onfinish = new Listener() {
@Override
public void onEvent(Object er) throws Exception {
debug(TAG, "onfinish");
dest.removeListener("close", onclose);
unpipe(src, (Writable2) dest);
}
};
dest.once("finish", onfinish);
// tell the dest that it's being piped to
dest.emit("pipe", src);
// start the flow if it hasn't been started already.
if (state.flowing!=TripleState.TRUE) {
debug(TAG, "pipe resume");
src.resume();
}
final Listener cleanup = new Listener() {
@Override
public void onEvent(Object data) throws Exception {
debug(TAG, "cleanup");
// cleanup event handlers once the pipe is broken
dest.removeListener("close", onclose);
dest.removeListener("finish", onfinish);
dest.removeListener("drain", ondrain);
dest.removeListener("error", onerror);
///dest.removeListener("unpipe", onunpipe);
src.removeListener("end", onend);
src.removeListener("end", this);
src.removeListener("data", ondata);
// if the reader is waiting for a drain event from this
// specific writer, then it would cause it to never start
// flowing again.
// So, if this is awaiting a drain, then we just call it now.
// If we don't know, then assume that we are waiting for one.
Writable2 wdest = (Writable2)dest;
///if (state.awaitDrain &&
///(!dest._writableState || dest._writableState.needDrain))
if (state.awaitDrain>0 && wdest.isNeedDrain())
ondrain.onEvent(null);
}
};
final Listener onunpipe = new Listener() {
@Override
public void onEvent(Object readable) throws Exception {
debug(TAG, "onunpipe");
if (readable.equals(src)) {
cleanup.onEvent(null);
}
}
};
dest.once("unpipe", onunpipe);
final Listener endFn = doEnd ? onend : cleanup;
if (state.isEndEmitted())
// TBD...
///process.nextTick(endFn);
context.nextTick(new NodeContext.nextTickListener() {
@Override
public void onNextTick() throws Exception {
endFn.onEvent(null);
}
});
else
src.once("end", endFn);
return dest;
}
private void unpipe(Readable2 src, Writable2 dest) throws Exception {
debug(TAG, "unpipe");
src.unpipe(dest);
}
private Listener pipeOnDrain(final Readable2 src) {
return new Listener () {
@Override
public void onEvent(Object data) throws Exception {
State state = src._readableState;
debug(TAG, "pipeOnDrain "+state.awaitDrain);
if (state.awaitDrain > 0)
state.awaitDrain--;
if (state.awaitDrain == 0 && src.listenerCount("data")>0) {
state.flowing = TripleState.TRUE;
flow(src);
}
}
};
}
@Override
public Readable unpipe(Writable dest) throws Exception {
State state = this._readableState;
debug(TAG, "pipesCount "+state.pipesCount);
// if we're not piping anywhere, then do nothing.
if (state.pipesCount == 0)
return this;
// just one destination. most common case.
if (state.pipesCount == 1) {
// passed in one, but it's not the right one.
if (dest!=null && !state.pipes.contains(dest))
return this;
///if (!dest)
if (dest == null)
dest = state.pipes.get(0);
// got a match.
if (dest != null)
dest.emit("unpipe", this);
state.pipes.clear();
state.pipesCount = 0;
state.flowing = TripleState.FALSE;
return this;
}
// slow case. multiple pipe destinations.
if (dest == null) {
// remove all.
List<Writable> dests = state.pipes;
int len = state.pipesCount;
for (int i = 0; i < len; i++)
dests.get(i).emit("unpipe", this);
state.pipes.clear();
state.pipesCount = 0;
state.flowing = TripleState.FALSE;
return this;
}
// try to find the right one.
int i = state.pipes.indexOf(dest);
if (i == -1)
return this;
///state.pipes.splice(i, 1);
state.pipes.remove(i);
state.pipesCount -= 1;
///if (state.pipesCount == 1)
/// state.pipes = state.pipes[0];
dest.emit("unpipe", this);
return this;
}
// set up data events if they are asked for
// Ensure readable listeners eventually get something
@Override
public EventEmitter on(final String ev, final Listener fn) throws Exception {
EventEmitter res = super.on(ev, fn);
// If listening to data, and it has not explicitly been paused,
// then call resume to start the flow of data on the next tick.
// TBD... always do resume ???
if (ev == "data" && TripleState.FALSE != this._readableState.flowing) {
this.resume();
}
if (ev == "readable" && this.readable) {
State state = this._readableState;
if (!state.readableListening) {
state.readableListening = true;
state.emittedReadable = false;
state.needReadable = true;
if (!state.isReading()) {
final Readable2 self = this;
///TBD...
///process.nextTick(function() {
context.nextTick(new NodeContext.nextTickListener() {
@Override
public void onNextTick() throws Exception {
debug(TAG, "readable nexttick read 0");
self.read(0);
}
});
} else if (state.getLength() > 0) {
emitReadable(this);
}
}
}
return res;
}
// pause() and resume() are remnants of the legacy readable stream API
// If the user uses them, then switch into old mode.
public Readable resume() throws Exception {
State state = this._readableState;
if (state.flowing!=TripleState.TRUE) {
debug(TAG, "resume");
state.flowing = TripleState.TRUE;
resume(this, state);
}
return this;
}
private void resume(final Readable2 stream, final State state) throws Exception {
if (!state.isResumeScheduled()) {
state.setResumeScheduled(true);
// TBD...
///process.nextTick(function() {
context.nextTick(new NodeContext.nextTickListener() {
@Override
public void onNextTick() throws Exception {
resume_(stream, state);
}
});
}
}
private void resume_(Readable2 stream, State state) throws Exception {
if (!state.isReading()) {
debug(TAG, "resume read 0");
stream.read(0);
}
state.setResumeScheduled(false);
stream.emit("resume");
flow(stream);
if (state.flowing==TripleState.TRUE && !state.isReading())
stream.read(0);
}
public Readable pause() throws Exception {
debug(TAG, "call pause flowing=" + this._readableState.flowing);
if (TripleState.FALSE != this._readableState.flowing) {
debug(TAG, "pause");
this._readableState.flowing = TripleState.FALSE;
this.emit("pause");
}
return this;
}
public boolean readable() {
return readable;
}
public void readable(boolean readable) {
this.readable = readable;
}
// wrap an old-style stream as the async data source.
// This is *not* part of the readable stream interface.
// It is an ugly unfortunate mess of history.
public static Readable2 wrap(final NodeContext ctx, final Readable stream, Options options) throws Exception {
return new WrapReadable2(ctx, options, stream);
}
// Manually shove something into the read() buffer.
// This returns true if the highWaterMark has not been hit yet,
// similar to how Writable.write() returns true if you should
// write() some more.
public boolean push(Object chunk, String encoding) throws Exception {
State state = this._readableState;
if (Util.isString(chunk) && !state.isObjectMode()) {
encoding = encoding != null ? encoding : state.defaultEncoding;
if (encoding != state.encoding) {
///chunk = new Buffer(chunk, encoding);
chunk = ByteBuffer.wrap(((String)chunk).getBytes(encoding));
encoding = "";
}
}
return readableAddChunk(this, state, chunk, encoding, false);
}
public boolean push() throws Exception {
return push(null, null);
}
// _read(size)
// abstract method. to be overridden in specific implementation classes.
// call cb(er, data) where data is <= n in length.
// for virtual (non-string, non-buffer) streams, "length" is somewhat
// arbitrary, and perhaps not very meaningful.
protected abstract void _read(int size) throws Exception;
/**
* @return the _readableState
*/
public State get_readableState() {
return _readableState;
}
}