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.FilterOutputStream;
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.io.PrintStream;
24  
25  /**
26   * An implementation of a FilterOutputStream that encodes the
27   * stream data in UUencoding format.  This version does the
28   * encoding "on the fly" rather than encoding a single block of
29   * data.  Since this version is intended for use by the MimeUtilty class,
30   * it also handles line breaks in the encoded data.
31   */
32  public class UUEncoderStream extends FilterOutputStream {
33  
34      // default values included on the "begin" prefix of the data stream.
35      protected static final int DEFAULT_MODE = 644;
36      protected static final String DEFAULT_NAME = "encoder.buf";
37  
38      protected static final int MAX_CHARS_PER_LINE = 45;
39  
40      // the configured name on the "begin" command.
41      protected String name;
42      // the configured mode for the "begin" command.
43      protected int mode;
44  
45      // since this is a filtering stream, we need to wait until we have the first byte written for encoding
46      // to write out the "begin" marker.  A real pain, but necessary.
47      protected boolean beginWritten = false;
48  
49  
50      // our encoder utility class.
51      protected UUEncoder encoder = new UUEncoder();
52  
53      // Data is generally written out in 45 character lines, so we're going to buffer that amount before
54      // asking the encoder to process this.
55  
56      // the buffered byte count
57      protected int bufferedBytes = 0;
58  
59      // we'll encode this part once it is filled up.
60      protected byte[] buffer = new byte[45];
61  
62      /**
63       * Create a Base64 encoder stream that wraps a specifed stream
64       * using the default line break size.
65       *
66       * @param out    The wrapped output stream.
67       */
68      public UUEncoderStream(OutputStream out) {
69          this(out, DEFAULT_NAME, DEFAULT_MODE);
70      }
71  
72  
73      /**
74       * Create a Base64 encoder stream that wraps a specifed stream
75       * using the default line break size.
76       *
77       * @param out    The wrapped output stream.
78       * @param name   The filename placed on the "begin" command.
79       */
80      public UUEncoderStream(OutputStream out, String name) {
81          this(out, name, DEFAULT_MODE);
82      }
83  
84  
85      public UUEncoderStream(OutputStream out, String name, int mode) {
86          super(out);
87          // fill in the name and mode information.
88          this.name = name;
89          this.mode = mode;
90      }
91  
92  
93      private void checkBegin() throws IOException {
94          if (!beginWritten) {
95              // grumble...OutputStream doesn't directly support writing String data.  We'll wrap this in
96              // a PrintStream() to accomplish the task of writing the begin command.
97  
98              PrintStream writer = new PrintStream(out);
99              // write out the stream with a CRLF marker
100             writer.print("begin " + mode + " " + name + "\r\n");
101             writer.flush();
102             beginWritten = true;
103         }
104     }
105 
106     private void writeEnd() throws IOException {
107         PrintStream writer = new PrintStream(out);
108         // write out the stream with a CRLF marker
109         writer.print("\nend\r\n");
110         writer.flush();
111     }
112 
113     private void flushBuffer() throws IOException {
114         // make sure we've written the begin marker first
115         checkBegin();
116         // ask the encoder to encode and write this out.
117         if (bufferedBytes != 0) {
118             encoder.encode(buffer, 0, bufferedBytes, out);
119             // reset the buffer count
120             bufferedBytes = 0;
121         }
122     }
123 
124     private int bufferSpace() {
125         return MAX_CHARS_PER_LINE - bufferedBytes;
126     }
127 
128     private boolean isBufferFull() {
129         return bufferedBytes >= MAX_CHARS_PER_LINE;
130     }
131 
132 
133     // in order for this to work, we need to override the 3 different signatures for write
134 
135     public void write(int ch) throws IOException {
136         // store this in the buffer.
137         buffer[bufferedBytes++] = (byte)ch;
138 
139         // if we filled this up, time to encode and write to the output stream.
140         if (isBufferFull()) {
141             flushBuffer();
142         }
143     }
144 
145     public void write(byte [] data) throws IOException {
146         write(data, 0, data.length);
147     }
148 
149     public void write(byte [] data, int offset, int length) throws IOException {
150         // first check to see how much space we have left in the buffer, and copy that over
151         int copyBytes = Math.min(bufferSpace(), length);
152 
153         System.arraycopy(buffer, bufferedBytes, data, offset, copyBytes);
154         bufferedBytes += copyBytes;
155         offset += copyBytes;
156         length -= copyBytes;
157 
158         // if we filled this up, time to encode and write to the output stream.
159         if (isBufferFull()) {
160             flushBuffer();
161         }
162 
163         // we've flushed the leading part up to the line break.  Now if we have complete lines
164         // of data left, we can have the encoder process all of these lines directly.
165         if (length >= MAX_CHARS_PER_LINE) {
166             int fullLinesLength = (length / MAX_CHARS_PER_LINE) * MAX_CHARS_PER_LINE;
167             // ask the encoder to encode and write this out.
168             encoder.encode(data, offset, fullLinesLength, out);
169             offset += fullLinesLength;
170             length -= fullLinesLength;
171         }
172 
173         // ok, now we're down to a potential trailing bit we need to move into the
174         // buffer for later processing.
175 
176         if (length > 0) {
177             System.arraycopy(buffer, 0, data, offset, length);
178             bufferedBytes += length;
179             offset += length;
180             length -= length;
181         }
182     }
183 
184     public void flush() throws IOException {
185         // flush any unencoded characters we're holding.
186         flushBuffer();
187         // write out the data end marker
188         writeEnd();
189         // and flush the output stream too so that this data is available.
190         out.flush();
191     }
192 
193     public void close() throws IOException {
194         // flush all of the streams and close the target output stream.
195         flush();
196         out.close();
197     }
198 
199 }
200 
201