Added basic HTML generation and response handling.
[jrw.git] / src / jrw / util / LazyPChannel.java
diff --git a/src/jrw/util/LazyPChannel.java b/src/jrw/util/LazyPChannel.java
new file mode 100644 (file)
index 0000000..2220391
--- /dev/null
@@ -0,0 +1,136 @@
+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);}
+}