001/**
002 * Java Web Archive Toolkit - Software to read and validate ARC, WARC
003 * and GZip files. (http://jwat.org/)
004 * Copyright 2011-2012 Netarkivet.dk (http://netarkivet.dk/)
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jwat.common;
019
020import java.io.ByteArrayOutputStream;
021import java.util.Arrays;
022
023/**
024 * Base16 encoder/decoder implementation based on the specifications in
025 * rfc3548.
026 *
027 * @author nicl
028 */
029public class Base16 {
030
031    /** Ascii table used to encode. */
032    public static String encodeTab = "0123456789ABCDEF";
033
034    /** Table used to decode. */
035    public static byte[] decodeTab = new byte[ 256 ];
036
037    /** Populate decode table. */
038    static {
039        Arrays.fill( decodeTab, (byte)0xff );
040        for ( int i=0; i<encodeTab.length(); ++i ) {
041            decodeTab[ Character.toUpperCase( encodeTab.charAt( i ) ) ] = (byte)i;
042            decodeTab[ Character.toLowerCase( encodeTab.charAt( i ) ) ] = (byte)i;
043        }
044    }
045
046    /**
047     * Static class.
048     */
049    protected Base16() {
050    }
051
052    /**
053     * Decodes an encoded string.
054     * @param in encoded string.
055     * @return decoded string or null
056     */
057    public static String decodeToString(String in) {
058        if (in == null) {
059            return null;
060        }
061        if (in.length() == 0) {
062            return "";
063        }
064
065        StringBuffer out = new StringBuffer( 256 );
066
067        boolean b = true;
068        int idx = 0;
069        char cin;
070        int mod = 0;
071        int cIdx;
072        int cout = 0;
073
074        /*
075         * Loop.
076         */
077
078        while ( b ) {
079            if ( idx < in.length() ) {
080                cin = in.charAt( idx++ );
081                cIdx = decodeTab[ cin ];
082                if ( cIdx != -1 ) {
083                    switch ( mod ) {
084                        case 0:
085                            cout = cIdx << 4;
086                            break;
087                        case 1:
088                            cout |= cIdx;
089                            out.append( (char)cout );
090                            break;
091                    }
092                    mod = ( mod + 1 ) % 2;
093                }
094                else {
095                    return null;
096                }
097            }
098            else {
099                b = false;
100            }
101        }
102
103        /*
104         * Padding.
105         */
106
107        if ( mod != 1 ) {
108            return out.toString();
109        }
110        else {
111            // In state 1 only if decoded a half byte.
112            return null;
113        }
114    }
115
116    /**
117     * Decodes an encoded string.
118     * @param in encoded string.
119     * @return decoded byte array or null
120     */
121    public static byte[] decodeToArray(String in) {
122        if (in == null) {
123            return null;
124        }
125        if (in.length() == 0) {
126            return new byte[0];
127        }
128
129        ByteArrayOutputStream out = new ByteArrayOutputStream();
130
131        boolean b = true;
132        int idx = 0;
133        char cin;
134        int mod = 0;
135        int cIdx;
136        int cout = 0;
137
138        /*
139         * Loop.
140         */
141
142        while ( b ) {
143            if ( idx < in.length() ) {
144                cin = in.charAt( idx++ );
145                cIdx = decodeTab[ cin ];
146                if ( cIdx != -1 ) {
147                    switch ( mod ) {
148                        case 0:
149                            cout = cIdx << 4;
150                            break;
151                        case 1:
152                            cout |= cIdx;
153                            out.write( cout );
154                            break;
155                    }
156                    mod = ( mod + 1 ) % 2;
157                }
158                else {
159                    return null;
160                }
161            }
162            else {
163                b = false;
164            }
165        }
166
167        /*
168         * Padding.
169         */
170
171        if ( mod != 1 ) {
172            return out.toByteArray();
173        }
174        else {
175            // In state 1 only if decoded a half byte.
176            return null;
177        }
178    }
179
180    /**
181     * Encodes a string.
182     * @param in unencoded text string.
183     * @return encoded string or null
184     */
185    public static String encodeString(String in) {
186        if (in == null) {
187            return null;
188        }
189        if (in.length() == 0) {
190            return "";
191        }
192
193        StringBuffer out = new StringBuffer( 256 );
194
195        boolean b = true;
196        int idx = 0;
197        int cin;
198
199        /*
200         * Loop.
201         */
202
203        while ( b ) {
204            if ( idx < in.length() ) {
205                cin = in.charAt( idx++ );
206                if ( cin < 256 ) {
207                    out.append( encodeTab.charAt( (cin >> 4) & 15 ) );
208                    out.append( encodeTab.charAt( cin & 15 ) );
209                }
210                else {
211                    return null;
212                }
213            }
214            else {
215                b = false;
216            }
217        }
218
219        return out.toString();
220    }
221
222    /**
223     * Encodes a byte array.
224     * @param in byte array.
225     * @return encoded string or null
226     */
227    public static String encodeArray(byte[] in) {
228        if (in == null) {
229            return null;
230        }
231        if (in.length == 0) {
232            return "";
233        }
234
235        StringBuffer out = new StringBuffer( 256 );
236
237        boolean b = true;
238        int idx = 0;
239        int cin;
240
241        /*
242         * Loop.
243         */
244
245        while ( b ) {
246            if ( idx < in.length ) {
247                cin = in[ idx++ ] & 255;
248                out.append( encodeTab.charAt( (cin >> 4) & 15 ) );
249                out.append( encodeTab.charAt( cin & 15 ) );
250            }
251            else {
252                b = false;
253            }
254        }
255
256        return out.toString();
257    }
258
259}