001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  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    package org.apache.commons.io;
018    
019    import java.io.IOException;
020    import java.io.OutputStream;
021    
022    /**
023     * Dumps data in hexadecimal format.
024     * <p>
025     * Provides a single function to take an array of bytes and display it
026     * in hexadecimal form.
027     * <p>
028     * Origin of code: POI.
029     *
030     * @author Scott Sanders
031     * @author Marc Johnson
032     * @version $Id: HexDump.java 736507 2009-01-22 00:52:15Z jukka $
033     */
034    public class HexDump {
035    
036        /**
037         * Instances should NOT be constructed in standard programming.
038         */
039        public HexDump() {
040            super();
041        }
042    
043        /**
044         * Dump an array of bytes to an OutputStream. The output is formatted
045         * for human inspection, with a hexadecimal offset followed by the
046         * hexadecimal values of the next 16 bytes of data and the printable ASCII
047         * characters (if any) that those bytes represent printed per each line
048         * of output.
049         * <p>
050         * The offset argument specifies the start offset of the data array
051         * within a larger entity like a file or an incoming stream. For example,
052         * if the data array contains the third kibibyte of a file, then the
053         * offset argument should be set to 2048. The offset value printed
054         * at the beginning of each line indicates where in that larger entity
055         * the first byte on that line is located.
056         * <p>
057         * All bytes between the given index (inclusive) and the end of the
058         * data array are dumped. 
059         *
060         * @param data  the byte array to be dumped
061         * @param offset  offset of the byte array within a larger entity
062         * @param stream  the OutputStream to which the data is to be
063         *               written
064         * @param index initial index into the byte array
065         *
066         * @throws IOException is thrown if anything goes wrong writing
067         *         the data to stream
068         * @throws ArrayIndexOutOfBoundsException if the index is
069         *         outside the data array's bounds
070         * @throws IllegalArgumentException if the output stream is null
071         */
072    
073        public static void dump(byte[] data, long offset,
074                                OutputStream stream, int index)
075                throws IOException, ArrayIndexOutOfBoundsException,
076                IllegalArgumentException {
077            
078            if ((index < 0) || (index >= data.length)) {
079                throw new ArrayIndexOutOfBoundsException(
080                        "illegal index: " + index + " into array of length "
081                        + data.length);
082            }
083            if (stream == null) {
084                throw new IllegalArgumentException("cannot write to nullstream");
085            }
086            long display_offset = offset + index;
087            StringBuilder buffer = new StringBuilder(74);
088    
089            for (int j = index; j < data.length; j += 16) {
090                int chars_read = data.length - j;
091    
092                if (chars_read > 16) {
093                    chars_read = 16;
094                }
095                dump(buffer, display_offset).append(' ');
096                for (int k = 0; k < 16; k++) {
097                    if (k < chars_read) {
098                        dump(buffer, data[k + j]);
099                    } else {
100                        buffer.append("  ");
101                    }
102                    buffer.append(' ');
103                }
104                for (int k = 0; k < chars_read; k++) {
105                    if ((data[k + j] >= ' ') && (data[k + j] < 127)) {
106                        buffer.append((char) data[k + j]);
107                    } else {
108                        buffer.append('.');
109                    }
110                }
111                buffer.append(EOL);
112                stream.write(buffer.toString().getBytes());
113                stream.flush();
114                buffer.setLength(0);
115                display_offset += chars_read;
116            }
117        }
118    
119        /**
120         * The line-separator (initializes to "line.separator" system property.
121         */
122        public static final String EOL =
123                System.getProperty("line.separator");
124        private static final char[] _hexcodes =
125                {
126                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
127                    'A', 'B', 'C', 'D', 'E', 'F'
128                };
129        private static final int[] _shifts =
130                {
131                    28, 24, 20, 16, 12, 8, 4, 0
132                };
133    
134        /**
135         * Dump a long value into a StringBuilder.
136         *
137         * @param _lbuffer the StringBuilder to dump the value in
138         * @param value  the long value to be dumped
139         * @return StringBuilder containing the dumped value.
140         */
141        private static StringBuilder dump(StringBuilder _lbuffer, long value) {
142            for (int j = 0; j < 8; j++) {
143                _lbuffer
144                        .append(_hexcodes[((int) (value >> _shifts[j])) & 15]);
145            }
146            return _lbuffer;
147        }
148    
149        /**
150         * Dump a byte value into a StringBuilder.
151         *
152         * @param _cbuffer the StringBuilder to dump the value in
153         * @param value  the byte value to be dumped
154         * @return StringBuilder containing the dumped value.
155         */
156        private static StringBuilder dump(StringBuilder _cbuffer, byte value) {
157            for (int j = 0; j < 2; j++) {
158                _cbuffer.append(_hexcodes[(value >> _shifts[j + 6]) & 15]);
159            }
160            return _cbuffer;
161        }
162    
163    }