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  package org.apache.myfaces.trinidad.util;
20  
21  import java.io.OutputStream;
22  import java.io.Writer;
23  import java.io.IOException;
24  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
25  
26  /**
27   * An OutputStream that encodes data in a base64 representation.
28   * It takes a Writer as its single argument to its constructor and all
29   * bytes written to the stream are correspondingly converted into Base64
30   * and written out to the provided writer.
31   */
32  public class Base64OutputStream extends OutputStream
33  {
34    public Base64OutputStream(Writer out)
35    {
36      _out = out;
37      _numLeftoverBytes = 0;
38      _leftoverBytes = new byte[2];
39    }
40    
41    /**
42     *  Takes a byte writes it out to the writer
43     * 
44     * @param b   a byte
45     */
46    @Override
47    public void write(int b) throws IOException
48    {
49      _single[0] = (byte) b;
50      this.write(_single, 0, 1);
51    } 
52    
53    /**
54     * Writes len bytes from the specified byte array starting at offset off 
55     * to this output stream. The general contract for write(b, off, len) is 
56     * that some of the bytes in the array b are written to the output stream 
57     * in order; element b[off] is the first byte written and b[off+len-1] is 
58     * the last byte written by this operation.
59     * 
60     * The write method of OutputStream calls the write method of one argument 
61     * on each of the bytes to be written out. Subclasses are encouraged to 
62     * override this method and provide a more efficient implementation.
63     * 
64     * If b is null, a NullPointerException is thrown.
65     * 
66     * If off is negative, or len is negative, or off+len is greater than the 
67     * length of the array b, then an IndexOutOfBoundsException is thrown. 
68     * 
69     * 
70     * @param b        the data
71     * @param off      the start offset in the data
72     * @param len      the number of bytes to read
73     */
74    @Override
75    public void write(byte[] b, int off, int len) 
76                                        throws IOException, NullPointerException
77    {
78      if (b==null) 
79      {
80        throw new NullPointerException(_LOG.getMessage(
81          "BYTE_ARRAY_CANNOT_BE_NULL"));
82      }
83        
84      if (off<0 || len<0 || off+len>b.length) 
85      {
86        throw new IndexOutOfBoundsException(_LOG.getMessage(
87          "ACTUAL_LENGTH_OFFSET", new Object[]{b.length,off,len}));
88      }
89      
90      int lengthToProcess = len;
91      int index = off;
92      
93      // base case 1: if only processing one byte from byte array
94      if (lengthToProcess==1) 
95      {
96        if (_numLeftoverBytes==0) 
97        {
98          // remember this byte for next call to write
99          _numLeftoverBytes = 1;
100         _leftoverBytes[0] = b[index];
101       }
102       else if (_numLeftoverBytes==1) 
103       {
104         // remember this byte for next call to write
105         _numLeftoverBytes = 2;
106         _leftoverBytes[1] = b[index];
107       }
108       else if (_numLeftoverBytes==2) 
109       {
110         // this one byte is enough to complete a triplet
111         // so convert triplet into Base64
112         _writeBase64(_leftoverBytes[0], _leftoverBytes[1], b[index]);
113         _numLeftoverBytes=0;
114       }
115       return;
116     }  //end if (lengthToProcess==1)
117     
118     // base case 2: if only processing two bytes from byte array
119     if (lengthToProcess==2) 
120     {
121       if (_numLeftoverBytes==0) 
122       {
123         // not enough to process a triplet, so remember these two bytes 
124         // for next call to write
125         _numLeftoverBytes = 2;
126         _leftoverBytes[0] = b[index];
127         _leftoverBytes[1] = b[index+1];
128       }
129       else if (_numLeftoverBytes==1) 
130       {
131         // these two bytes form triplet combined with the leftover byte
132         _writeBase64(_leftoverBytes[0], b[index], b[index+1]);
133         _numLeftoverBytes = 0;
134       }
135       else if (_numLeftoverBytes==2) 
136       {
137         // two leftover bytes and one new byte form a triplet
138         // the second new byte is remembered for next call to write
139         _writeBase64(_leftoverBytes[0], _leftoverBytes[1], b[index]);
140         _leftoverBytes[0] = b[index+1];
141         _numLeftoverBytes = 1;
142       }
143       return;
144     }  // end if (lengthToProcess==2)
145     
146     // case involving looping
147     if (lengthToProcess>2) 
148     {
149       if (_numLeftoverBytes==1) 
150       {
151         _writeBase64(_leftoverBytes[0], b[index], b[index+1]);
152         _numLeftoverBytes = 0;
153         lengthToProcess -= 2;
154         index += 2;
155         // proceed with loop
156       }      
157       else if (_numLeftoverBytes==2) 
158       {
159         _writeBase64(_leftoverBytes[0], _leftoverBytes[1], b[index]);
160         _numLeftoverBytes = 0;
161         lengthToProcess -= 1;
162         index += 1;
163         // proceed with loop
164       }
165 
166       _processArray(b, index, lengthToProcess);
167     }
168 
169   } //end write(byte[], int off, int len)
170   
171   @Override
172   public void flush() throws IOException 
173   {
174     _out.flush();
175   }
176 
177   /**
178    * Call this method to indicate end of data stream 
179    * and to append any padding characters if necessary.  This method should be 
180    * called only if there will be no subsequent calls to a write method.  
181    * Subsequent calls to the write method will result in incorrect encoding.
182    * 
183    * @deprecated use the close() method instead.
184    */
185   @Deprecated
186   public void finish() throws IOException
187   {
188     close();
189   }
190   
191   /**
192    * Call this method to indicate end of data stream 
193    * and to append any padding characters if necessary.  This method should be 
194    * called only if there will be no subsequent calls to a write method.  
195    * Subsequent calls to the write method will result in incorrect encoding.
196    * 
197    */
198   @Override
199   public void close() throws IOException
200   {
201     if (_numLeftoverBytes==1) 
202     {
203       // grab the one byte from the leftover array
204       byte b1 = _leftoverBytes[0];
205       
206       // convert to two base 64 chars
207       int c1, c2;
208       c1 = (b1>>2)&0x3f;
209       c2 = (b1<<4)&0x3f;
210       
211       char[] encodedChars = _fourChars;
212       
213       encodedChars[0] = _encode(c1);
214       encodedChars[1] = _encode(c2);
215       // append two padding characters
216       encodedChars[2] = '=';
217       encodedChars[3] = '=';
218       
219       _out.write(encodedChars);
220     } 
221     else if (_numLeftoverBytes==2)
222     {
223       // grab the two bytes from the leftovers array
224       byte b1, b2;
225       b1 = _leftoverBytes[0];
226       b2 = _leftoverBytes[1];
227       
228       // convert the two bytes into three base64 chars
229       int c1, c2, c3;
230       c1 = (b1>>2)&0x3f;
231       c2 = (b1<<4 | ((b2>>4)&0x0f))&0x3f;
232       c3 = (b2<<2)&0x3f;
233       
234       char[] encodedChars = _fourChars;
235       
236       encodedChars[0] = _encode(c1);
237       encodedChars[1] = _encode(c2);
238       encodedChars[2] = _encode(c3);  
239       //append one padding character
240       encodedChars[3] = '=';
241       
242       _out.write(encodedChars);
243     } 
244     _out.close();
245   }
246   
247   
248   /**
249    * Encodes three bytes in base64 representation and writes the corresponding 
250    * four base64 characters to the output writer.
251    * 
252    * @param b1  the first byte
253    * @param b2  the second byte
254    * @param b3  the third byte
255    */
256   private void _writeBase64(byte b1, byte b2, byte b3) throws IOException
257   {
258     int c1, c2, c3, c4;
259     char[] encodedChars = _fourChars;
260     
261     c1 = (b1>>2)&0x3f;          // b1.high6
262     c2 = (b1<<4 | ((b2>>4)&0x0f) )&0x3f;  // b1.low2 + b2.high4
263     c3 = (b2<<2 | ((b3>>6)&0x03) )&0x3f;  // b2.low4 + b3.high2
264     c4 = b3&0x3f;               // b3.low6
265     
266     encodedChars[0] = _encode(c1);
267     encodedChars[1] = _encode(c2);
268     encodedChars[2] = _encode(c3);
269     encodedChars[3] = _encode(c4);
270     
271     // write array of chars to writer
272     _out.write(encodedChars);    
273   }
274   
275   /**
276    * Writes lengthToProcess number of bytes from byte array to writer 
277    * in base64 beginning with startIndex. Assumes all leftover bytes from 
278    * previous calls to write have been dealt with.  
279    * 
280    * @param b               the data
281    * @param startIndex      the start offset in the data
282    * @param lengthToProcess the number of bytes to read
283    */
284   private void _processArray(byte[] b, int startIndex, int lengthToProcess) 
285                                                         throws IOException
286   {
287     int index = startIndex;
288   
289     // loop through remaining length of array
290     while(lengthToProcess>0) {
291       // base case: only one byte
292       if (lengthToProcess==1) 
293       {        
294         // save this byte for next call to write
295         _numLeftoverBytes = 1;
296         _leftoverBytes[0] = b[index];
297         return;
298       }
299       // base case: only two bytes
300       else if (lengthToProcess==2) 
301       {     
302         // save these two bytes for next call to write
303         _numLeftoverBytes = 2;
304         _leftoverBytes[0] = b[index];
305         _leftoverBytes[1] = b[index+1]; 
306         return;
307       } 
308       else 
309       {
310         // encode three bytes (24 bits) from input array
311         _writeBase64(b[index],b[index+1],b[index+2]);
312         
313         // finally, make some progress in loop
314         lengthToProcess -= 3;
315         index +=3;
316        }
317     } //end while
318   }
319   
320   
321 
322   /**
323    * Encodes a six-bit pattern into a base-64 character.
324    * 
325    * @param c an integer whose lower 6 bits contain the base64 representation
326    *          all other bits should be zero
327    */
328   private static char _encode(int c) 
329   {
330     if (c < 26)
331       return (char)('A' + c);
332     if (c < 52)
333       return (char)('a' + (c-26));
334     if (c < 62)
335       return (char)('0' + (c-52));
336     if (c == 62)
337       return '+';
338     if (c == 63)
339       return '/';
340       
341     throw new AssertionError("Invalid B64 character code:"+c);
342   }
343 
344   
345   /** stores leftover bytes from previous call to write method **/
346   private final byte[]      _leftoverBytes;
347   
348   /** indicates the number of bytes that were leftover after the last triplet 
349    * was formed in the last call to write method  **/
350   private int         _numLeftoverBytes;
351   
352   // cached four-character array
353   private final char[]      _fourChars = new char[4];
354   // cached single-byte array
355   private final byte[]      _single = new byte[1];
356 
357   // Writer that will receive all completed character output
358   private final Writer      _out;  
359   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
360     Base64OutputStream.class);
361 }