View Javadoc

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