--- /dev/null
+package jrw.util;
+
+import jrw.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.nio.charset.*;
+
+public abstract class LazyPChannel implements ReadableByteChannel {
+ private ByteBuffer curbuf = null;
+ private boolean eof = false;
+ private CharsetEncoder enc = null;
+ private Runnable rem = null;
+
+ protected boolean write(byte[] data, int off, int len) {
+ if(rem != null) throw(new IllegalStateException("buffer filled"));
+ int t = Math.min(curbuf.remaining(), len);
+ curbuf.put(data, off, t);
+ if(len > t) {
+ rem = () -> write(data, off + t, len - t);
+ return(true);
+ }
+ return(false);
+ }
+ protected boolean write(byte[] data) {return(write(data, 0, data.length));}
+
+ protected boolean write(CharBuffer buf) {
+ if(rem != null) throw(new IllegalStateException("buffer filled"));
+ if(enc == null)
+ enc = charset().newEncoder();
+ while(true) {
+ int pp = buf.position();
+ CoderResult res = enc.encode(buf, curbuf, false);
+ if(buf.remaining() == 0)
+ return(false);
+ if(res.isUnderflow()) {
+ if(pp == buf.position()) {
+ /* XXX? Not sure if this can be expected to
+ * happen. I'm not aware of any charsets that should
+ * require it, and it would complicate the design
+ * significantly. */
+ throw(new RuntimeException("encoder not consuming input"));
+ }
+ } else if(res.isOverflow()) {
+ rem = () -> write(buf);
+ return(true);
+ } else {
+ try {
+ res.throwException();
+ } catch(CharacterCodingException e) {
+ throw(new RuntimeException(e));
+ }
+ }
+ }
+ }
+
+ protected boolean write(CharSequence chars) {
+ CharBuffer buf = (chars instanceof CharBuffer) ? ((CharBuffer)chars).duplicate() : CharBuffer.wrap(chars);
+ return(write(buf));
+ }
+
+ private void encflush2() {
+ while(true) {
+ CoderResult res = enc.flush(curbuf);
+ if(res.isOverflow()) {
+ rem = this::encflush1;
+ return;
+ } else if(res.isUnderflow()) {
+ return;
+ } else {
+ try {
+ res.throwException();
+ } catch(CharacterCodingException e) {
+ throw(new RuntimeException(e));
+ }
+ }
+ }
+ }
+
+ private void encflush1() {
+ CharBuffer empty = CharBuffer.wrap("");
+ while(true) {
+ CoderResult res = enc.encode(empty, curbuf, true);
+ if(res.isOverflow()) {
+ rem = this::encflush1;
+ return;
+ } else if(res.isUnderflow()) {
+ rem = this::encflush2;
+ return;
+ } else {
+ try {
+ res.throwException();
+ } catch(CharacterCodingException e) {
+ throw(new RuntimeException(e));
+ }
+ }
+ }
+ }
+
+ private void encflush() {
+ if(enc != null)
+ rem = this::encflush1;
+ }
+
+ protected Charset charset() {return(Http.UTF8);}
+
+ protected abstract boolean produce();
+
+ public int read(ByteBuffer buf) {
+ curbuf = buf;
+ try {
+ int op = buf.position();
+ while(buf.remaining() > 0) {
+ Runnable rem = this.rem;
+ this.rem = null;
+ if(rem != null) {
+ rem.run();
+ } else {
+ if(eof) {
+ break;
+ } else if(produce()) {
+ encflush();
+ eof = true;
+ }
+ }
+ }
+ if(eof && (buf.position() == op))
+ return(-1);
+ return(buf.position() - op);
+ } finally {
+ curbuf = null;
+ }
+ }
+
+ public void close() {}
+ public boolean isOpen() {return(true);}
+}