View Javadoc

1   /**
2    *
3    * Copyright 2003-2006 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.geronimo.mail.util;
19  
20  import java.io.IOException;
21  import java.io.OutputStream;
22  import java.io.FilterOutputStream;
23  
24  /**
25   * An implementation of a FilterOutputStream that encodes the
26   * stream data in BASE64 encoding format.  This version does the
27   * encoding "on the fly" rather than encoding a single block of
28   * data.  Since this version is intended for use by the MimeUtilty class,
29   * it also handles line breaks in the encoded data.
30   */
31  public class Base64EncoderStream extends FilterOutputStream {
32  
33      // our Filtered stream writes everything out as byte data.  This allows the CRLF sequence to
34      // be written with a single call.
35      protected static final byte[] CRLF = { '\r', '\n' };
36  
37      // our hex encoder utility class.
38      protected Base64Encoder encoder = new Base64Encoder();
39  
40      // our default for line breaks
41      protected static final int DEFAULT_LINEBREAK = 76;
42  
43      // Data can only be written out in complete units of 3 bytes encoded as 4.  Therefore, we need to buffer
44      // as many as 2 bytes to fill out an encoding unit.
45  
46      // the buffered byte count
47      protected int bufferedBytes = 0;
48  
49      // we'll encode this part once it is filled up.
50      protected byte[] buffer = new byte[3];
51  
52  
53      // the size we process line breaks at.  If this is Integer.MAX_VALUE, no line breaks are handled.
54      protected int lineBreak;
55  
56      // the number of encoded characters we've written to the stream, which determines where we
57      // insert line breaks.
58      protected int outputCount;
59  
60      /**
61       * Create a Base64 encoder stream that wraps a specifed stream
62       * using the default line break size.
63       *
64       * @param out    The wrapped output stream.
65       */
66      public Base64EncoderStream(OutputStream out) {
67          this(out, DEFAULT_LINEBREAK);
68      }
69  
70  
71      public Base64EncoderStream(OutputStream out, int lineBreak) {
72          super(out);
73          // lines are processed only in multiple of 4, so round this down.
74          this.lineBreak = (lineBreak / 4) * 4 ;
75      }
76  
77      // in order for this to work, we need to override the 3 different signatures for write
78  
79      public void write(int ch) throws IOException {
80          // store this in the buffer.
81          buffer[bufferedBytes++] = (byte)ch;
82          // if the buffer is filled, encode these bytes
83          if (bufferedBytes == 3) {
84              // check for room in the current line for this character
85              checkEOL(4);
86              // write these directly to the stream.
87              encoder.encode(buffer, 0, 3, out);
88              bufferedBytes = 0;
89              // and update the line length checkers
90              updateLineCount(4);
91          }
92      }
93  
94      public void write(byte [] data) throws IOException {
95          write(data, 0, data.length);
96      }
97  
98      public void write(byte [] data, int offset, int length) throws IOException {
99          // if we have something in the buffer, we need to write enough bytes out to flush
100         // those into the output stream AND continue on to finish off a line.  Once we're done there
101         // we can write additional data out in complete blocks.
102         while ((bufferedBytes > 0 || outputCount > 0) && length > 0) {
103             write(data[offset++]);
104             length--;
105         }
106 
107         if (length > 0) {
108             // no linebreaks requested?  YES!!!!!, we can just dispose of the lot with one call.
109             if (lineBreak == Integer.MAX_VALUE) {
110                 encoder.encode(data, offset, length, out);
111             }
112             else {
113                 // calculate the size of a segment we can encode directly as a line.
114                 int segmentSize = (lineBreak / 4) * 3;
115 
116                 // write this out a block at a time, with separators between.
117                 while (length > segmentSize) {
118                     // encode a segment
119                     encoder.encode(data, offset, segmentSize, out);
120                     // write an EOL marker
121                     out.write(CRLF);
122                     offset += segmentSize;
123                     length -= segmentSize;
124                 }
125 
126                 // any remainder we write out a byte at a time to manage the groupings and
127                 // the line count appropriately.
128                 if (length > 0) {
129                     while (length > 0) {
130                         write(data[offset++]);
131                         length--;
132                     }
133                 }
134             }
135         }
136     }
137 
138     public void close() throws IOException {
139         flush();
140         out.close();
141     }
142 
143     public void flush() throws IOException {
144         if (bufferedBytes > 0) {
145             encoder.encode(buffer, 0, bufferedBytes, out);
146             bufferedBytes = 0;
147         }
148     }
149 
150 
151     /**
152      * Check for whether we're about the reach the end of our
153      * line limit for an update that's about to occur.  If we will
154      * overflow, then a line break is inserted.
155      *
156      * @param required The space required for this pending write.
157      *
158      * @exception IOException
159      */
160     private void checkEOL(int required) throws IOException {
161         if (lineBreak != Integer.MAX_VALUE) {
162             // if this write would exceed the line maximum, add a linebreak to the stream.
163             if (outputCount + required > lineBreak) {
164                 out.write(CRLF);
165                 outputCount = 0;
166             }
167         }
168     }
169 
170     /**
171      * Update the counter of characters on the current working line.
172      * This is conditional if we're not working with a line limit.
173      *
174      * @param added  The number of characters just added.
175      */
176     private void updateLineCount(int added) {
177         if (lineBreak != Integer.MAX_VALUE) {
178             outputCount += added;
179         }
180     }
181 }
182