001 /**
002 *
003 * Copyright 2003-2006 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.geronimo.mail.util;
019
020 import java.io.FilterOutputStream;
021 import java.io.IOException;
022 import java.io.OutputStream;
023 import java.io.PrintStream;
024
025 /**
026 * An implementation of a FilterOutputStream that encodes the
027 * stream data in UUencoding format. This version does the
028 * encoding "on the fly" rather than encoding a single block of
029 * data. Since this version is intended for use by the MimeUtilty class,
030 * it also handles line breaks in the encoded data.
031 */
032 public class UUEncoderStream extends FilterOutputStream {
033
034 // default values included on the "begin" prefix of the data stream.
035 protected static final int DEFAULT_MODE = 644;
036 protected static final String DEFAULT_NAME = "encoder.buf";
037
038 protected static final int MAX_CHARS_PER_LINE = 45;
039
040 // the configured name on the "begin" command.
041 protected String name;
042 // the configured mode for the "begin" command.
043 protected int mode;
044
045 // since this is a filtering stream, we need to wait until we have the first byte written for encoding
046 // to write out the "begin" marker. A real pain, but necessary.
047 protected boolean beginWritten = false;
048
049
050 // our encoder utility class.
051 protected UUEncoder encoder = new UUEncoder();
052
053 // Data is generally written out in 45 character lines, so we're going to buffer that amount before
054 // asking the encoder to process this.
055
056 // the buffered byte count
057 protected int bufferedBytes = 0;
058
059 // we'll encode this part once it is filled up.
060 protected byte[] buffer = new byte[45];
061
062 /**
063 * Create a Base64 encoder stream that wraps a specifed stream
064 * using the default line break size.
065 *
066 * @param out The wrapped output stream.
067 */
068 public UUEncoderStream(OutputStream out) {
069 this(out, DEFAULT_NAME, DEFAULT_MODE);
070 }
071
072
073 /**
074 * Create a Base64 encoder stream that wraps a specifed stream
075 * using the default line break size.
076 *
077 * @param out The wrapped output stream.
078 * @param name The filename placed on the "begin" command.
079 */
080 public UUEncoderStream(OutputStream out, String name) {
081 this(out, name, DEFAULT_MODE);
082 }
083
084
085 public UUEncoderStream(OutputStream out, String name, int mode) {
086 super(out);
087 // fill in the name and mode information.
088 this.name = name;
089 this.mode = mode;
090 }
091
092
093 private void checkBegin() throws IOException {
094 if (!beginWritten) {
095 // grumble...OutputStream doesn't directly support writing String data. We'll wrap this in
096 // a PrintStream() to accomplish the task of writing the begin command.
097
098 PrintStream writer = new PrintStream(out);
099 // 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