Added basic HTML generation and response handling.
[jrw.git] / src / jrw / util / LazyPChannel.java
CommitLineData
6e0043cc
FT
1package jrw.util;
2
3import jrw.*;
4import java.nio.*;
5import java.nio.channels.*;
6import java.nio.charset.*;
7
8public abstract class LazyPChannel implements ReadableByteChannel {
9 private ByteBuffer curbuf = null;
10 private boolean eof = false;
11 private CharsetEncoder enc = null;
12 private Runnable rem = null;
13
14 protected boolean write(byte[] data, int off, int len) {
15 if(rem != null) throw(new IllegalStateException("buffer filled"));
16 int t = Math.min(curbuf.remaining(), len);
17 curbuf.put(data, off, t);
18 if(len > t) {
19 rem = () -> write(data, off + t, len - t);
20 return(true);
21 }
22 return(false);
23 }
24 protected boolean write(byte[] data) {return(write(data, 0, data.length));}
25
26 protected boolean write(CharBuffer buf) {
27 if(rem != null) throw(new IllegalStateException("buffer filled"));
28 if(enc == null)
29 enc = charset().newEncoder();
30 while(true) {
31 int pp = buf.position();
32 CoderResult res = enc.encode(buf, curbuf, false);
33 if(buf.remaining() == 0)
34 return(false);
35 if(res.isUnderflow()) {
36 if(pp == buf.position()) {
37 /* XXX? Not sure if this can be expected to
38 * happen. I'm not aware of any charsets that should
39 * require it, and it would complicate the design
40 * significantly. */
41 throw(new RuntimeException("encoder not consuming input"));
42 }
43 } else if(res.isOverflow()) {
44 rem = () -> write(buf);
45 return(true);
46 } else {
47 try {
48 res.throwException();
49 } catch(CharacterCodingException e) {
50 throw(new RuntimeException(e));
51 }
52 }
53 }
54 }
55
56 protected boolean write(CharSequence chars) {
57 CharBuffer buf = (chars instanceof CharBuffer) ? ((CharBuffer)chars).duplicate() : CharBuffer.wrap(chars);
58 return(write(buf));
59 }
60
61 private void encflush2() {
62 while(true) {
63 CoderResult res = enc.flush(curbuf);
64 if(res.isOverflow()) {
65 rem = this::encflush1;
66 return;
67 } else if(res.isUnderflow()) {
68 return;
69 } else {
70 try {
71 res.throwException();
72 } catch(CharacterCodingException e) {
73 throw(new RuntimeException(e));
74 }
75 }
76 }
77 }
78
79 private void encflush1() {
80 CharBuffer empty = CharBuffer.wrap("");
81 while(true) {
82 CoderResult res = enc.encode(empty, curbuf, true);
83 if(res.isOverflow()) {
84 rem = this::encflush1;
85 return;
86 } else if(res.isUnderflow()) {
87 rem = this::encflush2;
88 return;
89 } else {
90 try {
91 res.throwException();
92 } catch(CharacterCodingException e) {
93 throw(new RuntimeException(e));
94 }
95 }
96 }
97 }
98
99 private void encflush() {
100 if(enc != null)
101 rem = this::encflush1;
102 }
103
104 protected Charset charset() {return(Http.UTF8);}
105
106 protected abstract boolean produce();
107
108 public int read(ByteBuffer buf) {
109 curbuf = buf;
110 try {
111 int op = buf.position();
112 while(buf.remaining() > 0) {
113 Runnable rem = this.rem;
114 this.rem = null;
115 if(rem != null) {
116 rem.run();
117 } else {
118 if(eof) {
119 break;
120 } else if(produce()) {
121 encflush();
122 eof = true;
123 }
124 }
125 }
126 if(eof && (buf.position() == op))
127 return(-1);
128 return(buf.position() - op);
129 } finally {
130 curbuf = null;
131 }
132 }
133
134 public void close() {}
135 public boolean isOpen() {return(true);}
136}