Source code: cryptix/openpgp/io/PGPLengthDataOutputStream.java
1 /* $Id: PGPLengthDataOutputStream.java,v 1.2 2005/03/13 17:12:58 woudt Exp $
2 *
3 * Copyright (C) 1999-2005 The Cryptix Foundation Limited.
4 * All rights reserved.
5 *
6 * Use, modification, copying and distribution of this software is subject
7 * the terms and conditions of the Cryptix General Licence. You should have
8 * received a copy of the Cryptix General License along with this library;
9 * if not, you can download a copy from http://www.cryptix.org/ .
10 */
11
12 package cryptix.openpgp.io;
13
14
15 import java.io.ByteArrayOutputStream;
16 import java.io.DataOutputStream;
17 import java.io.OutputStream;
18 import java.io.IOException;
19 import java.io.UnsupportedEncodingException;
20
21 import java.math.BigInteger;
22
23
24
25 /**
26 * Generic outputstream to write PGP formatted packet data that contains
27 * length information
28 *
29 * <p>This class basically has two modes, one where the length of the written
30 * data is (partly) known in advance and one when the length is not known.
31 * In the first mode all data will be written to the underlying outputstream
32 * immediately, which allows for full streaming. In the second mode all output
33 * will be buffered until the close method is called, where all data is then
34 * written in one big chunk to the outputstream.</p>
35 *
36 * <p>If the subclass wants, it can support writing data in chunks. This is
37 * particularly useful when in streaming a lot of data, when you do not know
38 * in advance what the total length will be. In that case you just buffer the
39 * data and write a chunk every time your buffer is full.</p>
40 *
41 * <p>A subclass may decide not to support partial chunks.</p>
42 *
43 * @author Edwin Woudt (edwin@cryptix.org)
44 * @version $Revision: 1.2 $
45 */
46 public abstract class PGPLengthDataOutputStream extends PGPDataOutputStream {
47
48
49 // Instance variables
50 //............................................................................
51
52 /** The underlying outputstream */
53 private OutputStream out;
54 /** Buffer in case we don't know the length */
55 private ByteArrayOutputStream buffer = null;
56 /** If true then length info has been written and we can write directly
57 * to the outputstream, if false then we have to buffer the info */
58 private boolean lengthHasBeenWritten = false;
59 /** If true then we are currently writing a partial length packet */
60 private boolean partial = false;
61 /** The number of bytes written in the current chunk */
62 private long chunkBytesWritten = 0;
63 /** The length of the current chunk if SetLength or setPartialLength has
64 * been used. */
65 private long chunkLength = 0;
66
67
68
69 // Constructor
70 //............................................................................
71
72 /**
73 * Constructor that takes an outputstream
74 *
75 * <p>Subclasses should call this constructor from their own, after doing
76 * their own initialization.</p>
77 */
78 public PGPLengthDataOutputStream(OutputStream out) {
79 this.out = out;
80 }
81
82
83 /**
84 * Emtpy constructor for subclasses that handle their own writing
85 *
86 * <p>If a subclass decides to use this constructor, then the writeDirect
87 * method must be overridden.</p>
88 */
89 public PGPLengthDataOutputStream() {
90 this.out = null;
91 }
92
93
94
95 // Implemented abstract methods
96 //............................................................................
97
98
99 /**
100 * Write one byte directly to the underlying outputstream.
101 */
102 protected void writeDirect(int b) throws IOException {
103
104 out.write(b);
105
106 }
107
108
109 /**
110 * Internal method used by all other methods to write one byte.
111 */
112 protected void writeInternal(int b) throws IOException {
113
114 if (! lengthHasBeenWritten) {
115
116 if (chunkBytesWritten == 0) {
117 buffer = new ByteArrayOutputStream();
118 }
119
120 buffer.write(b);
121 chunkBytesWritten++;
122
123 } else {
124
125 chunkBytesWritten++;
126 if (chunkBytesWritten > chunkLength) {
127 throw new RuntimeException("Tried to write more bytes than "+
128 "set.");
129 }
130
131 writeDirect(b);
132
133 }
134
135 }
136
137
138
139 // Abstract methods
140 //............................................................................
141
142
143 /**
144 * Write a partial length to the outputstream.
145 *
146 * <p>If an implementation does not support partial lengths it should throw
147 * a (subclass of) RuntimeException</p>
148 */
149 protected abstract void writePartialLength(OutputStream out, long len)
150 throws IOException;
151
152
153 /**
154 * Write a length to the outputstream.
155 *
156 * <p>This method will only be called once for every packet. It may be
157 * preceded by several writePartialLength calls.</p>
158 */
159 protected abstract void writeLength(OutputStream out, long len)
160 throws IOException;
161
162
163
164 // Public length control methods
165 //............................................................................
166
167
168 /**
169 * Set the length of the current packet
170 *
171 * <p>This method must only be called once for every packet. It may be
172 * preceded by several setPartialLength calls. If this condition is not met
173 * an IllegalStateException will be thrown.</p>
174 * <p>If no calls have been made setPartialLength then no bytes must have
175 * been written yet, otherwise an IllegalStateException will be thrown.</p>
176 *
177 * <p>This method calls writeLength.</p>
178 *
179 * @throws IOException only if writeLength returns an IOException
180 */
181 public void setLength(long len) throws IOException {
182
183 if ((partial) && (chunkBytesWritten != chunkLength)) {
184
185 throw new IllegalStateException("Partial length has not yet been " +
186 "completed");
187
188 } else if (partial) {
189
190 partial = false;
191
192 } else if (lengthHasBeenWritten) {
193
194 throw new IllegalStateException("Cannot write two non-partial " +
195 "lengths");
196
197 } else if ((!lengthHasBeenWritten) && (chunkBytesWritten != 0)) {
198
199 throw new IllegalStateException("Bytes have already been written");
200
201 }
202
203 chunkLength = len;
204 chunkBytesWritten = 0;
205 lengthHasBeenWritten = true;
206
207 writeLength(out, len);
208
209 }
210
211
212 /**
213 * Set the length of the current packet
214 *
215 * <p>If this is the first call to this method then no bytes must have
216 * been written yet, otherwise an IllegalStateException will be thrown.</p>
217 * <p>After this method has been called one or more times a setLength must
218 * be called for the final chunk. This will be checked in the close method.
219 * </p>
220 *
221 * <p>This method calls writePartialLength.</p>
222 *
223 * @throws IOException only if writePartialLength returns an IOException
224 */
225 public void setPartialLength(long len) throws IOException {
226
227 if ((partial) && (chunkBytesWritten != chunkLength)) {
228
229 throw new IllegalStateException("Partial length has not yet been " +
230 "completed");
231
232 } else if (lengthHasBeenWritten) {
233
234 throw new IllegalStateException("Cannot set a partial length " +
235 "after a normal length");
236
237 } else if ((!lengthHasBeenWritten) && (chunkBytesWritten != 0)) {
238
239 throw new IllegalStateException("Bytes have already been written");
240
241 }
242
243 partial = true;
244 chunkLength = len;
245 chunkBytesWritten = 0;
246 lengthHasBeenWritten = true;
247
248 writePartialLength(out, len);
249
250 }
251
252
253
254 // Close method
255 //............................................................................
256
257
258 /**
259 * Close this inputstream
260 *
261 * <p>If setLength and setPartialLength have been used this method checks
262 * to see if all bytes of the chunk have been written, otherwise an
263 * IllegalStateException will be thrown.</p>
264 * <p>If setLength and setPartialLength have not been used and thus all
265 * data has been buffered, the buffered data will be written after a
266 * call to writeLength.</p>
267 *
268 * <p>This method does not close the underlying outputstream.</p>
269 * <p>This method must always be called.</p>
270 *
271 * @throws IOException if either writeLength throws it or something goes
272 * wrong while writing the buffer.
273 */
274 public void close() throws IOException {
275
276 if (partial) {
277
278 throw new IllegalStateException("Packet cannot end with a partial "+
279 "length");
280
281 } else if ((lengthHasBeenWritten)&&(chunkBytesWritten != chunkLength)) {
282
283 throw new IllegalStateException("Packet has not been completely "+
284 "written");
285
286 } else if (!lengthHasBeenWritten) {
287
288 byte[] b;
289 if (buffer != null) {
290 b = buffer.toByteArray();
291 } else {
292 b = new byte[0];
293 }
294
295 chunkLength = (long)b.length;
296 chunkBytesWritten = 0;
297 lengthHasBeenWritten = true;
298
299 writeLength(out, (long)b.length);
300 writeFully(b);
301
302 }
303
304 }
305
306
307 }