1 | /* |
---|
2 | Copyright 2009 by Sean Luke |
---|
3 | Licensed under the Academic Free License version 3.0 |
---|
4 | See the file "LICENSE" for more information |
---|
5 | */ |
---|
6 | |
---|
7 | |
---|
8 | package ec.util; |
---|
9 | import java.io.*; |
---|
10 | |
---|
11 | /* |
---|
12 | DataPipe is a mechanism which allows you to pipe data from an OutputStream to an InputStream within a single thread. |
---|
13 | This differs from a PipedInputStream/PipedOutputSteam pair in that it permits a single thread (in fact requires it |
---|
14 | due to lack of synchronization for speed). To do this, the DataPipe maintains an extensible buffer which gets as large |
---|
15 | as necessary when filled with data from the InputStream, then expels it out the OutputStream when called. The |
---|
16 | default Input and Output streams of a DataPipe are DataInputStream and DataOutputStream, since reading and writing |
---|
17 | binary objects is its primary function. |
---|
18 | |
---|
19 | <p>The procedure is as follows: create the DataPipe, then start writing to its DataInputStream. When you are done, |
---|
20 | start reading from its DataOutputStream. DataPipe is meant to be one-shot: write a bunch of stuff, then read <i>all</i> of it. |
---|
21 | You shouldn't read and write piecemeal as the DataPipe is not a true circular buffer and will grow without bound with wasted |
---|
22 | space equal to the amount you've read so far. |
---|
23 | |
---|
24 | <p>You <i>can</i>, however, reuse the DataPipe by calling reset() on it. |
---|
25 | Note that this retains the current buffer however large it has grown to. |
---|
26 | */ |
---|
27 | |
---|
28 | public class DataPipe |
---|
29 | { |
---|
30 | // an extensible array which can be pushed into, then pulled out of |
---|
31 | byte[] buffer = new byte[8192]; |
---|
32 | |
---|
33 | // the number of bytes in the array -- in reality this is the number of bytes written to the array so far |
---|
34 | int size = 0; |
---|
35 | |
---|
36 | // the number of bytes read from the array. pull will always trail size |
---|
37 | int pull = 0; |
---|
38 | |
---|
39 | // Double the size of the buffer |
---|
40 | void resize() |
---|
41 | { |
---|
42 | byte[] newbuffer = new byte[buffer.length * 2]; |
---|
43 | System.arraycopy(buffer, 0, newbuffer, 0, buffer.length); |
---|
44 | buffer = newbuffer; |
---|
45 | } |
---|
46 | |
---|
47 | // Add a byte to the buffer |
---|
48 | void push(byte b) |
---|
49 | { |
---|
50 | if (size >= buffer.length) resize(); |
---|
51 | buffer[size++] = b; |
---|
52 | } |
---|
53 | |
---|
54 | // Add bytes to the buffer, read from b[offset]... b[offset+length-1] |
---|
55 | void push(byte[] b, int offset, int length) |
---|
56 | { |
---|
57 | if (size + length > buffer.length) resize(); |
---|
58 | System.arraycopy(b, offset, buffer, size, length); |
---|
59 | size += length; |
---|
60 | } |
---|
61 | |
---|
62 | // Return an unsigned byte read from the buffer. |
---|
63 | // If there are no bytes left to read, -1 is returned. |
---|
64 | int pull() |
---|
65 | { |
---|
66 | if (pull==size) return -1; // EOF |
---|
67 | byte b = buffer[pull++]; |
---|
68 | if (b < 0) return b + 256; |
---|
69 | return b; |
---|
70 | } |
---|
71 | |
---|
72 | // Provide up to *length* bytes from the buffer. They are |
---|
73 | // placed into b[offset] ... b[offset + length - 1]. |
---|
74 | // If there aren't *length* bytes in the buffer, some number |
---|
75 | // less than that is actually provided. The actual number |
---|
76 | // of bytes provided is returned. |
---|
77 | // If there are no bytes left to read at all, -1 is returned. |
---|
78 | int pull(byte[] b, int offset, int length) |
---|
79 | { |
---|
80 | if (pull==size) return -1; |
---|
81 | if (length > size-pull) length = size-pull; |
---|
82 | System.arraycopy(buffer, pull, b, offset, length); |
---|
83 | pull += length; |
---|
84 | return length; |
---|
85 | } |
---|
86 | |
---|
87 | /** The input stream */ |
---|
88 | public DataInputStream input; |
---|
89 | /** The output stream */ |
---|
90 | public DataOutputStream output; |
---|
91 | |
---|
92 | public DataPipe() |
---|
93 | { |
---|
94 | OutputStream outStream = new OutputStream() |
---|
95 | { |
---|
96 | public void write(int b) throws IOException { push((byte)b); } |
---|
97 | public void write(byte[] b, int off, int len) throws IOException { push(b, off, len); } |
---|
98 | public void write(byte[] b) throws IOException { push(b, 0, b.length); } |
---|
99 | }; |
---|
100 | output = new DataOutputStream(outStream); |
---|
101 | |
---|
102 | InputStream inStream = new InputStream() |
---|
103 | { |
---|
104 | public int read() throws IOException { return pull(); } |
---|
105 | public int read(byte[] b, int off, int len) throws IOException { return pull(b, off, len); } |
---|
106 | public int read(byte[] b) throws IOException { return pull(b, 0, b.length); } |
---|
107 | }; |
---|
108 | |
---|
109 | input = new DataInputStream(inStream); |
---|
110 | } |
---|
111 | |
---|
112 | /** Reset the buffer. Does not resize it back to a smaller size -- if it has ballooned it will |
---|
113 | stay large, though it will no longer have wasted space in it. If you wish to make the buffer |
---|
114 | a more manageable size, create a new DataPipe instead. */ |
---|
115 | public void reset() |
---|
116 | { |
---|
117 | pull = size = 0; |
---|
118 | } |
---|
119 | |
---|
120 | /** Returns the total size of the buffer. */ |
---|
121 | public int size() |
---|
122 | { |
---|
123 | return buffer.length; |
---|
124 | } |
---|
125 | |
---|
126 | /** Returns the number of elements written to the buffer so far (after the last reset()). */ |
---|
127 | public int numWritten() |
---|
128 | { |
---|
129 | return size; |
---|
130 | } |
---|
131 | |
---|
132 | /** Returns the number of elements read from the buffer so far (after the last reset()). */ |
---|
133 | public int numRead() |
---|
134 | { |
---|
135 | return pull; |
---|
136 | } |
---|
137 | |
---|
138 | /** A poor-man's clone for serializable but not cloneable objects: |
---|
139 | serializes an object to the pipe, then deserializes it. */ |
---|
140 | public static Object copy(Serializable obj) throws IOException, ClassNotFoundException |
---|
141 | { |
---|
142 | DataPipe pipe = new DataPipe(); |
---|
143 | ObjectOutputStream s = new ObjectOutputStream(pipe.output); |
---|
144 | ObjectInputStream u = new ObjectInputStream(pipe.input); |
---|
145 | s.writeObject(obj); |
---|
146 | return u.readObject(); |
---|
147 | } |
---|
148 | |
---|
149 | public String toString() { return "DataPipe(" + numWritten() + ", " + numRead() + ", " + size() + ")"; } |
---|
150 | } |
---|