1
2
3
4
5
6
7
8
9
10
11 package org.astrogrid.jes.util;
12
13 import org.astrogrid.component.descriptor.ComponentDescriptor;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
18 import java.io.Reader;
19 import java.io.Writer;
20 import java.nio.ByteBuffer;
21 import java.nio.CharBuffer;
22 import java.nio.charset.Charset;
23
24 import junit.framework.Test;
25
26 /*** this class is an abstraction around a ByteBuffer, and is used to provide temporary working space - e.g. for marshalling / unmarshalling xml documents.
27 * Idea is to reuse this buffer for many marshalling operations, rather than create a new StringReader / StringWriter pair for each. In this way the same section of allocated memory is reused,
28 * hopefully avoiding possible problems with space leaks from many readers and writers.
29 * Obviously, this class is not thread-safe - however, the components of JES operate in a single-threaded manner.
30 * <p>
31 * need to be careful with handling of buffer overflows. Readers and Writers are unbounded in size, while Buffers have a fixed size. Which is a bit of a pain - need to
32 * catch Buffer overflows, and copy contents to a new buffer.
33 * @see org.astrogrid.jes.util.TemporaryBufferTest for tests and exmaples of use.
34 * @author Noel Winstanley nw@jb.man.ac.uk 02-Nov-2004
35 *
36 */
37 public class TemporaryBuffer implements ComponentDescriptor{
38
39 /*** Construct a new WorkingSpace
40 *
41 */
42 public TemporaryBuffer() {
43 super();
44 }
45
46
47 protected final static int START_CAPACITY = 1024 * 20;
48
49 protected ByteBuffer buff = ByteBuffer.allocate(START_CAPACITY);
50
51 protected final InputStream is = new TemporaryBufferInputStream();
52 protected final OutputStream os = new TemporaryBufferOutputStream();
53 protected final Writer w = new TemporaryBufferWriter();
54 protected final TemporaryBufferReader r = new TemporaryBufferReader();
55
56 protected boolean writing = true;
57 /*** call this to place the buffer in <i>read mode</i>. After this, possible to call {@link #getContents}, {@link #getInputStream()} {@link #getReader()}
58 * */
59 public void readMode() {
60
61 buff.flip();
62 writing = false;
63 }
64
65 /*** call this to place the buffer in <i>write mode</i>. After this, possible to call{@link #getOutputStream()}, {@link #getWriter()} */
66 public void writeMode() {
67 buff.clear();
68 writing = true;
69 }
70
71 /*** access a stream from which to read the contents of the buffer
72 * pre: {@link #readMode() */
73 public InputStream getInputStream() {
74 if (writing) {
75 throw new IllegalStateException("Temporary buffer must be in read mode");
76 }
77 buff.rewind();
78 return is;
79 }
80
81 /*** access a reader from which to read the contents of the buffer
82 * pre: {@link #readMode() }
83 * @return
84 *
85 */
86 public Reader getReader() {
87 if (writing) {
88 throw new IllegalStateException("Temporary buffer must be in read mode");
89 }
90 buff.rewind();
91 r.setCBuff(buff.asCharBuffer());
92 return r;
93 }
94
95 /*** access an output stream whcih can be used to write to the buffer
96 * pre: {@link #writeMode}
97 * @return
98 */
99 public OutputStream getOutputStream() {
100 if (!writing) {
101 throw new IllegalStateException("Temporary buffer must be in write mode");
102 }
103 return os;
104 }
105
106 /*** access a writer which can be used to write to the buffer
107 * pre: {@link #writeMode}
108 * @return
109 */
110 public Writer getWriter() {
111 if (!writing) {
112 throw new IllegalStateException("Temporary buffer must be in write mode");
113 }
114 return w;
115 }
116
117 /*** access the contents of the buffer
118 * pre: {@link #readMode()}
119 * @return
120 */
121 public String getContents() {
122 return buff.asCharBuffer().toString();
123 }
124 /***
125 * access the contents of the buffer, decoded according to a particular charset.
126 * pre: {@link #readMode()}
127 * @param charset the decoder to use
128 * @return
129 */
130 public String getContents(String charset) {
131 return Charset.forName(charset).decode(buff).toString();
132 }
133
134
135
136 /***
137 * this is simple - and works. Verified by {@link TemporaryBufferTest#testVariableAssignmentInNestedClasses()}
138 *
139 */
140 protected void growBuffer() {
141 ByteBuffer newBuff = ByteBuffer.allocate(buff.capacity() * 2);
142 buff.flip();
143 newBuff.put(buff);
144 buff = newBuff;
145 }
146 /***
147 * input stream for reading from the buffer.
148 * tries to implement all the inptuStream methods efficiently in terms of the underlying Buffer operations.
149 * There's a lot of congruence here, but there are a few irritating differences between the behaviour of buffers and streams.
150 * Don't really understand why this is - think that there should be methods for creating streams for buffers already in the jdk.
151 * @author Noel Winstanley nw@jb.man.ac.uk 03-Nov-2004
152 *
153 */
154
155 protected class TemporaryBufferInputStream extends InputStream {
156
157 public int available() throws IOException {
158 return buff.remaining();
159 }
160
161 public synchronized void mark(int readlimit) {
162 buff.mark();
163 }
164 public boolean markSupported() {
165 return true;
166 }
167 /*** contract is a little different here
168 * buffer will throw an exceptioin if there isn't enough bytes available to satisfy request.
169 * stream expects it just to provide as many bytes as possible.*/
170 public int read(byte[] b, int off, int len) throws IOException {
171 if (! buff.hasRemaining()){
172 return -1;
173 }
174
175
176 int actualLength = len > buff.remaining() ? buff.remaining() : len;
177 buff.get(b,off,actualLength);
178 return actualLength;
179 }
180
181 /***
182 * @see java.io.InputStream#read()
183 */
184 public int read() throws IOException {
185 if (!buff.hasRemaining()) {
186 return -1;
187 } else {
188 return buff.get();
189 }
190 }
191
192 public synchronized void reset() throws IOException {
193 buff.reset();
194 }
195 /*** think this one is right. */
196 public long skip(long n) throws IOException {
197 int currentPos = buff.position();
198 int toSkip = ((int)n) > buff.remaining() ? buff.remaining() : ((int)n);
199 buff.position(currentPos + toSkip);
200 return toSkip;
201 }
202
203 }
204
205 /*** can use a charBuffer view for this - as each reader / input stream resets the position
206 * - so no need for this class to share state with the parent.*/
207 protected static class TemporaryBufferReader extends Reader {
208 protected CharBuffer cBuff;
209 public void setCBuff(CharBuffer b) {
210 cBuff = b;
211 }
212 public void mark(int readAheadLimit) throws IOException {
213 cBuff.mark();
214 }
215 public boolean markSupported() {
216 return true;
217 }
218 public int read() throws IOException {
219 if (! cBuff.hasRemaining()){
220 return -1;
221 } else {
222 return cBuff.get();
223 }
224 }
225 public boolean ready() throws IOException {
226 return cBuff.hasRemaining();
227 }
228 public void reset() throws IOException {
229 cBuff.reset();
230 }
231 public long skip(long n) throws IOException {
232 int currentPos = cBuff.position();
233 int toSkip = ((int)n) > cBuff.remaining() ? cBuff.remaining() : ((int)n);
234 cBuff.position(currentPos + toSkip );
235 return toSkip;
236 }
237 /***
238 * @see java.io.Reader#close()
239 */
240 public void close() throws IOException {
241
242 }
243
244 /***
245 * @see java.io.Reader#read(char[], int, int)
246 */
247 public int read(char[] cbuf, int off, int len) throws IOException {
248 if (! cBuff.hasRemaining()){
249 return -1;
250 }
251
252
253 int actualLength = len > cBuff.remaining() ? cBuff.remaining() : len;
254
255 cBuff.get(cbuf,off,actualLength);
256 return actualLength;
257 }
258 }
259
260 /*** implemented over the byte buffer, rather than taking a view as a character buffer - as this
261 * would mean that the positions, etc diverge.
262 * @author Noel Winstanley nw@jb.man.ac.uk 03-Nov-2004
263 *
264 */
265 protected class TemporaryBufferWriter extends Writer {
266
267 /***
268 * @see java.io.Writer#close()
269 */
270 public void close() throws IOException {
271
272 }
273
274 /***
275 * @see java.io.Writer#flush()
276 */
277 public void flush() throws IOException {
278
279 }
280
281 /***
282 * @see java.io.Writer#write(char[], int, int)
283 */
284 public void write(char[] cbuf, int off, int len) throws IOException {
285 if (len * 2 > buff.remaining()) {
286 growBuffer();
287 }
288 for (int i = 0; i < len; i++) {
289 buff.putChar(cbuf[off+i]);
290 }
291 }
292
293 public void write(int c) throws IOException {
294 if (buff.position() +1 >= buff.limit()) {
295 growBuffer();
296 }
297 buff.putChar((char)c);
298 }
299
300 }
301
302 protected class TemporaryBufferOutputStream extends OutputStream {
303
304 public TemporaryBufferOutputStream() {
305 }
306
307 public void write(byte[] b, int off, int len) throws IOException {
308 if (len > buff.remaining()) {
309 growBuffer();
310 }
311 buff.put(b,off,len);
312 }
313
314 /***
315 * @see java.io.OutputStream#write(int)
316 */
317 public void write(int b) throws IOException {
318 if (buff.position() >= buff.limit()) {
319 growBuffer();
320 }
321 buff.put((byte)b);
322 }
323
324
325
326 }
327
328 public String toString() {
329 StringBuffer buffer = new StringBuffer();
330 buffer.append("[TemporaryBuffer:");
331 buffer.append(" START_CAPACITY: ");
332 buffer.append(START_CAPACITY);
333 buffer.append(" buff: ");
334 buffer.append(buff);
335 buffer.append(" writing: ");
336 buffer.append(writing);
337 buffer.append("]");
338 return buffer.toString();
339 }
340
341 /***
342 * @see org.astrogrid.component.descriptor.ComponentDescriptor#getName()
343 */
344 public String getName() {
345 return "Temporary Buffer";
346 }
347
348 /***
349 * @see org.astrogrid.component.descriptor.ComponentDescriptor#getDescription()
350 */
351 public String getDescription() {
352 return this.toString();
353 }
354
355 /***
356 * @see org.astrogrid.component.descriptor.ComponentDescriptor#getInstallationTest()
357 */
358 public Test getInstallationTest() {
359 return null;
360 }
361 }
362
363
364
365
366
367
368
369
370
371
372