View Javadoc
1   /*
2    * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  
27  package davmail.ldap;
28  
29  import java.io.ByteArrayInputStream;
30  import java.io.ByteArrayOutputStream;
31  import java.io.InputStream;
32  import java.io.PrintStream;
33  import java.io.OutputStream;
34  import java.io.IOException;
35  import java.nio.ByteBuffer;
36  
37  /**
38   * This class encodes a buffer into the classic: "Hexadecimal Dump" format of
39   * the past. It is useful for analyzing the contents of binary buffers.
40   * The format produced is as follows:
41   * <pre>
42   * xxxx: 00 11 22 33 44 55 66 77   88 99 aa bb cc dd ee ff ................
43   * </pre>
44   * Where xxxx is the offset into the buffer in 16 byte chunks, followed
45   * by ascii coded hexadecimal bytes followed by the ASCII representation of
46   * the bytes or '.' if they are not valid bytes.
47   *
48   * @author      Chuck McManis
49   */
50  
51  public class HexDumpEncoder {
52  
53      private int offset;
54      private int thisLineLength;
55      private int currentByte;
56      private byte[] thisLine = new byte[16];
57  
58      static void hexDigit(PrintStream p, byte x) {
59          char c;
60  
61          c = (char) ((x >> 4) & 0xf);
62          if (c > 9)
63              c = (char) ((c-10) + 'A');
64          else
65              c = (char)(c + '0');
66          p.write(c);
67          c = (char) (x & 0xf);
68          if (c > 9)
69              c = (char)((c-10) + 'A');
70          else
71              c = (char)(c + '0');
72          p.write(c);
73      }
74  
75      protected int bytesPerAtom() {
76          return (1);
77      }
78  
79      protected int bytesPerLine() {
80          return (16);
81      }
82  
83      protected void encodeBufferPrefix(OutputStream o) {
84          offset = 0;
85          pStream = new PrintStream(o);
86      }
87  
88      protected void encodeLinePrefix(int len) {
89          hexDigit(pStream, (byte)((offset >>> 8) & 0xff));
90          hexDigit(pStream, (byte)(offset & 0xff));
91          pStream.print(": ");
92          currentByte = 0;
93          thisLineLength = len;
94      }
95  
96      protected void encodeAtom(byte[] buf, int off) {
97          thisLine[currentByte] = buf[off];
98          hexDigit(pStream, buf[off]);
99          pStream.print(" ");
100         currentByte++;
101         if (currentByte == 8)
102             pStream.print("  ");
103     }
104 
105     protected void encodeLineSuffix() {
106         if (thisLineLength < 16) {
107             for (int i = thisLineLength; i < 16; i++) {
108                 pStream.print("   ");
109                 if (i == 7)
110                     pStream.print("  ");
111             }
112         }
113         pStream.print(" ");
114         for (int i = 0; i < thisLineLength; i++) {
115             if ((thisLine[i] < ' ') || (thisLine[i] > 'z')) {
116                 pStream.print(".");
117             } else {
118                 pStream.write(thisLine[i]);
119             }
120         }
121         pStream.println();
122         offset += thisLineLength;
123     }
124 
125     /** Stream that understands "printing" */
126     protected PrintStream pStream;
127 
128     /**
129      * This method works around the bizarre semantics of BufferedInputStream's
130      * read method.
131      */
132     protected int readFully(InputStream in, byte[] buffer)
133             throws java.io.IOException {
134         for (int i = 0; i < buffer.length; i++) {
135             int q = in.read();
136             if (q == -1)
137                 return i;
138             buffer[i] = (byte)q;
139         }
140         return buffer.length;
141     }
142 
143     /**
144      * Encode bytes from the input stream, and write them as text characters
145      * to the output stream. This method will run until it exhausts the
146      * input stream, but does not print the line suffix for a final
147      * line that is shorter than bytesPerLine().
148      */
149     public void encode(InputStream inStream, OutputStream outStream)
150             throws IOException
151     {
152         int     j;
153         int     numBytes;
154         byte[] tmpbuffer = new byte[bytesPerLine()];
155 
156         encodeBufferPrefix(outStream);
157 
158         while (true) {
159             numBytes = readFully(inStream, tmpbuffer);
160             if (numBytes == 0) {
161                 break;
162             }
163             encodeLinePrefix(numBytes);
164             for (j = 0; j < numBytes; j += bytesPerAtom()) {
165                 encodeAtom(tmpbuffer, j);
166             }
167             if (numBytes < bytesPerLine()) {
168                 break;
169             } else {
170                 encodeLineSuffix();
171             }
172         }
173     }
174 
175     /**
176      * A 'streamless' version of encode that simply takes a buffer of
177      * bytes and returns a string containing the encoded buffer.
178      */
179     public String encode(byte[] aBuffer) {
180         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
181         ByteArrayInputStream    inStream = new ByteArrayInputStream(aBuffer);
182         String retVal;
183         try {
184             encode(inStream, outStream);
185             // explicit ascii->unicode conversion
186             retVal = outStream.toString("ISO-8859-1");
187         } catch (Exception IOException) {
188             // This should never happen.
189             throw new Error("CharacterEncoder.encode internal error");
190         }
191         return (retVal);
192     }
193 
194     /**
195      * Return a byte array from the remaining bytes in this ByteBuffer.
196      * <P>
197      * The ByteBuffer's position will be advanced to ByteBuffer's limit.
198      * <P>
199      * To avoid an extra copy, the implementation will attempt to return the
200      * byte array backing the ByteBuffer.  If this is not possible, a
201      * new byte array will be created.
202      */
203     private byte [] getBytes(ByteBuffer bb) {
204         /*
205          * This should never return a BufferOverflowException, as we're
206          * careful to allocate just the right amount.
207          */
208         byte [] buf = null;
209 
210         /*
211          * If it has a usable backing byte buffer, use it.  Use only
212          * if the array exactly represents the current ByteBuffer.
213          */
214         if (bb.hasArray()) {
215             byte [] tmp = bb.array();
216             if ((tmp.length == bb.capacity()) &&
217                     (tmp.length == bb.remaining())) {
218                 buf = tmp;
219                 bb.position(bb.limit());
220             }
221         }
222 
223         if (buf == null) {
224             /*
225              * This class doesn't have a concept of encode(buf, len, off),
226              * so if we have a partial buffer, we must reallocate
227              * space.
228              */
229             buf = new byte[bb.remaining()];
230 
231             /*
232              * position() automatically updated
233              */
234             bb.get(buf);
235         }
236 
237         return buf;
238     }
239 
240     /**
241      * A 'streamless' version of encode that simply takes a ByteBuffer
242      * and returns a string containing the encoded buffer.
243      * <P>
244      * The ByteBuffer's position will be advanced to ByteBuffer's limit.
245      */
246     public String encode(ByteBuffer aBuffer) {
247         byte [] buf = getBytes(aBuffer);
248         return encode(buf);
249     }
250 
251     /**
252      * Encode bytes from the input stream, and write them as text characters
253      * to the output stream. This method will run until it exhausts the
254      * input stream. It differs from encode in that it will add the
255      * line at the end of a final line that is shorter than bytesPerLine().
256      */
257     public void encodeBuffer(InputStream inStream, OutputStream outStream)
258             throws IOException
259     {
260         int     j;
261         int     numBytes;
262         byte[] tmpbuffer = new byte[bytesPerLine()];
263 
264         encodeBufferPrefix(outStream);
265 
266         while (true) {
267             numBytes = readFully(inStream, tmpbuffer);
268             if (numBytes == 0) {
269                 break;
270             }
271             encodeLinePrefix(numBytes);
272             for (j = 0; j < numBytes; j += bytesPerAtom()) {
273                 encodeAtom(tmpbuffer, j);
274             }
275             encodeLineSuffix();
276             if (numBytes < bytesPerLine()) {
277                 break;
278             }
279         }
280     }
281 
282     /**
283      * Encode the buffer in <i>aBuffer</i> and write the encoded
284      * result to the OutputStream <i>aStream</i>.
285      */
286     public void encodeBuffer(byte[] aBuffer, OutputStream aStream)
287             throws IOException
288     {
289         ByteArrayInputStream inStream = new ByteArrayInputStream(aBuffer);
290         encodeBuffer(inStream, aStream);
291     }
292 
293     /**
294      * A 'streamless' version of encode that simply takes a buffer of
295      * bytes and returns a string containing the encoded buffer.
296      */
297     @SuppressWarnings("unused")
298     public String encodeBuffer(byte[] aBuffer) {
299         ByteArrayOutputStream   outStream = new ByteArrayOutputStream();
300         ByteArrayInputStream    inStream = new ByteArrayInputStream(aBuffer);
301         try {
302             encodeBuffer(inStream, outStream);
303         } catch (Exception IOException) {
304             // This should never happen.
305             throw new Error("CharacterEncoder.encodeBuffer internal error");
306         }
307         return (outStream.toString());
308     }
309 
310     /**
311      * Encode the <i>aBuffer</i> ByteBuffer and write the encoded
312      * result to the OutputStream <i>aStream</i>.
313      * <P>
314      * The ByteBuffer's position will be advanced to ByteBuffer's limit.
315      */
316     @SuppressWarnings("unused")
317     public void encodeBuffer(ByteBuffer aBuffer, OutputStream aStream)
318             throws IOException
319     {
320         byte [] buf = getBytes(aBuffer);
321         encodeBuffer(buf, aStream);
322     }
323 
324 }