View Javadoc

1   /*$Id: TemporaryBuffer.java,v 1.2 2004/11/05 16:52:42 jdt Exp $
2    * Created on 02-Nov-2004
3    *
4    * Copyright (C) AstroGrid. All rights reserved.
5    *
6    * This software is published under the terms of the AstroGrid 
7    * Software License version 1.2, a copy of which has been included 
8    * with this distribution in the LICENSE.txt file.  
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; // 10K I think. good start.
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          //buff.limit(buff.position());
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             // calculate actual number of bytes to request - 
175             // if len is more than what's available, we'll request all thats available instead.
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()) { // test for end of buffer
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             // do nothing.
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             // calculate actual number of bytes to request - 
252             // if len is more than what's available, we'll request all thats available instead.
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             // do nothing.
272         }
273 
274         /***
275          * @see java.io.Writer#flush()
276          */
277         public void flush() throws IOException {
278             // do nothing
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()) { // char is 2 bytes
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()) { // char is 2 bytes
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 $Log: TemporaryBuffer.java,v $
366 Revision 1.2  2004/11/05 16:52:42  jdt
367 Merges from branch nww-itn07-scratchspace
368 
369 Revision 1.1.2.1  2004/11/05 15:45:47  nw
370 added static buffer impleemtation
371  
372 */