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 *      https://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
018package org.apache.commons.codec.binary;
019
020import org.apache.commons.codec.BinaryDecoder;
021import org.apache.commons.codec.BinaryEncoder;
022import org.apache.commons.codec.DecoderException;
023import org.apache.commons.codec.EncoderException;
024
025/**
026 * Converts between byte arrays and strings of "0"s and "1"s.
027 *
028 * <p>
029 * This class is immutable and thread-safe.
030 * </p>
031 *
032 * @since 1.3
033 */
034public class BinaryCodec implements BinaryDecoder, BinaryEncoder {
035
036    // TODO may want to add more bit vector functions like and/or/xor/nand TODO: also might be good to generate boolean[] from byte[] et cetera.
037
038    /** Empty char array. */
039    private static final char[] EMPTY_CHAR_ARRAY = {};
040
041    /** Empty byte array. */
042    private static final byte[] EMPTY_BYTE_ARRAY = {};
043
044    /** Mask for bit 0 of a byte. */
045    private static final int BIT_0 = 1;
046
047    /** Mask for bit 1 of a byte. */
048    private static final int BIT_1 = 0x02;
049
050    /** Mask for bit 2 of a byte. */
051    private static final int BIT_2 = 0x04;
052
053    /** Mask for bit 3 of a byte. */
054    private static final int BIT_3 = 0x08;
055
056    /** Mask for bit 4 of a byte. */
057    private static final int BIT_4 = 0x10;
058
059    /** Mask for bit 5 of a byte. */
060    private static final int BIT_5 = 0x20;
061
062    /** Mask for bit 6 of a byte. */
063    private static final int BIT_6 = 0x40;
064
065    /** Mask for bit 7 of a byte. */
066    private static final int BIT_7 = 0x80;
067
068    private static final int[] BITS = { BIT_0, BIT_1, BIT_2, BIT_3, BIT_4, BIT_5, BIT_6, BIT_7 };
069
070    /**
071     * Decodes a byte array where each byte represents an ASCII '0' or '1'.
072     *
073     * <p>
074     * All input must consist of ASCII {@code '0'} and {@code '1'} values. If the input length is not a multiple of 8, the leading
075     * {@code length % 8} values are validated but omitted from the decoded result. Null or empty input produces an empty byte array.
076     * </p>
077     *
078     * @param ascii each byte represents an ASCII '0' or '1'.
079     * @return The raw encoded binary where each bit corresponds to a byte in the byte array argument.
080     * @throws IllegalArgumentException Thrown if the input contains a value other than ASCII '0' or '1'.
081     */
082    public static byte[] fromAscii(final byte[] ascii) {
083        if (isEmpty(ascii)) {
084            return EMPTY_BYTE_ARRAY;
085        }
086        final int asciiLength = ascii.length;
087        // Validate all input, including leading values omitted from the decoded result.
088        for (int i = 0; i < asciiLength; i++) {
089            final byte b = ascii[i];
090            if (b != '0' && b != '1') {
091                throw new IllegalArgumentException("Input contains a value other than ASCII '0' or '1' at index " + i);
092            }
093        }
094        // Decode complete groups of 8, omitting any remaining leading values.
095        final byte[] raw = new byte[asciiLength >> 3];
096        /*
097         * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the loop.
098         */
099        for (int ii = 0, jj = asciiLength - 1; ii < raw.length; ii++, jj -= 8) {
100            for (int bits = 0; bits < BITS.length; ++bits) {
101                if (ascii[jj - bits] == '1') {
102                    raw[ii] |= BITS[bits];
103                }
104            }
105        }
106        return raw;
107    }
108
109    /**
110     * Decodes a char array where each char represents an ASCII '0' or '1'.
111     *
112     * <p>
113     * All input must consist of ASCII {@code '0'} and {@code '1'} values. If the input length is not a multiple of 8, the leading
114     * {@code length % 8} values are validated but omitted from the decoded result. Null or empty input produces an empty byte array.
115     * </p>
116     *
117     * @param ascii each char represents an ASCII '0' or '1'.
118     * @return The raw encoded binary where each bit corresponds to a char in the char array argument.
119     * @throws IllegalArgumentException Thrown if the input contains a value other than ASCII '0' or '1'.
120     */
121    public static byte[] fromAscii(final char[] ascii) {
122        if (ascii == null || ascii.length == 0) {
123            return EMPTY_BYTE_ARRAY;
124        }
125        final int asciiLength = ascii.length;
126        // Validate all input, including leading values omitted from the decoded result.
127        for (int i = 0; i < asciiLength; i++) {
128            final char c = ascii[i];
129            if (c != '0' && c != '1') {
130                throw new IllegalArgumentException("Input contains a value other than ASCII '0' or '1' at index " + i);
131            }
132        }
133        // Decode complete groups of 8, omitting any remaining leading values.
134        final byte[] raw = new byte[asciiLength >> 3];
135        /*
136         * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the loop.
137         */
138        for (int ii = 0, jj = asciiLength - 1; ii < raw.length; ii++, jj -= 8) {
139            for (int bits = 0; bits < BITS.length; ++bits) {
140                if (ascii[jj - bits] == '1') {
141                    raw[ii] |= BITS[bits];
142                }
143            }
144        }
145        return raw;
146    }
147
148    /**
149     * Tests whether the given array is {@code null} or empty (size 0).
150     *
151     * @param array The source array.
152     * @return {@code true} if the given array is {@code null} or empty (size 0.)
153     */
154    static boolean isEmpty(final byte[] array) {
155        return array == null || array.length == 0;
156    }
157
158    /**
159     * Converts an array of raw binary data into an array of ASCII 0 and 1 character bytes - each byte is a truncated char.
160     *
161     * @param raw The raw binary data to convert.
162     * @return An array of 0 and 1 character bytes for each bit of the argument.
163     * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
164     */
165    public static byte[] toAsciiBytes(final byte[] raw) {
166        if (isEmpty(raw)) {
167            return EMPTY_BYTE_ARRAY;
168        }
169        final int rawLength = raw.length;
170        // get 8 times the bytes with 3 bit shifts to the left of the length
171        final byte[] ascii = new byte[rawLength << 3];
172        /*
173         * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the loop.
174         */
175        for (int ii = 0, jj = ascii.length - 1; ii < rawLength; ii++, jj -= 8) {
176            for (int bits = 0; bits < BITS.length; ++bits) {
177                if ((raw[ii] & BITS[bits]) == 0) {
178                    ascii[jj - bits] = '0';
179                } else {
180                    ascii[jj - bits] = '1';
181                }
182            }
183        }
184        return ascii;
185    }
186
187    /**
188     * Converts an array of raw binary data into an array of ASCII 0 and 1 characters.
189     *
190     * @param raw The raw binary data to convert.
191     * @return An array of 0 and 1 characters for each bit of the argument.
192     * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
193     */
194    public static char[] toAsciiChars(final byte[] raw) {
195        if (isEmpty(raw)) {
196            return EMPTY_CHAR_ARRAY;
197        }
198        final int rawLength = raw.length;
199        // get 8 times the bytes with 3 bit shifts to the left of the length
200        final char[] ascii = new char[rawLength << 3];
201        /*
202         * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the loop.
203         */
204        for (int ii = 0, jj = ascii.length - 1; ii < rawLength; ii++, jj -= 8) {
205            for (int bits = 0; bits < BITS.length; ++bits) {
206                if ((raw[ii] & BITS[bits]) == 0) {
207                    ascii[jj - bits] = '0';
208                } else {
209                    ascii[jj - bits] = '1';
210                }
211            }
212        }
213        return ascii;
214    }
215
216    /**
217     * Converts an array of raw binary data into a String of ASCII 0 and 1 characters.
218     *
219     * @param raw The raw binary data to convert.
220     * @return A String of 0 and 1 characters representing the binary data.
221     * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
222     */
223    public static String toAsciiString(final byte[] raw) {
224        return new String(toAsciiChars(raw));
225    }
226
227    /**
228     * Constructs a new instance.
229     */
230    public BinaryCodec() {
231        // empty
232    }
233
234    /**
235     * Decodes a byte array where each byte represents an ASCII '0' or '1'.
236     *
237     * <p>
238     * All input must consist of ASCII {@code '0'} and {@code '1'} values. If the input length is not a multiple of 8, the leading
239     * {@code length % 8} values are validated but omitted from the decoded result. Null or empty input produces an empty byte array.
240     * </p>
241     *
242     * @param ascii each byte represents an ASCII '0' or '1'.
243     * @return The raw encoded binary where each bit corresponds to a byte in the byte array argument.
244     * @throws IllegalArgumentException Thrown if the input contains a value other than ASCII '0' or '1'.
245     * @see org.apache.commons.codec.Decoder#decode(Object)
246     */
247    @Override
248    public byte[] decode(final byte[] ascii) {
249        return fromAscii(ascii);
250    }
251
252    /**
253     * Decodes a byte array where each byte represents an ASCII '0' or '1'.
254     *
255     * <p>
256     * All input must consist of ASCII {@code '0'} and {@code '1'} values. If the input length is not a multiple of 8, the leading
257     * {@code length % 8} values are validated but omitted from the decoded result. Null or empty input produces an empty byte array.
258     * </p>
259     *
260     * @param ascii each byte represents an ASCII '0' or '1'.
261     * @return The raw encoded binary where each bit corresponds to a byte in the byte array argument.
262     * @throws IllegalArgumentException Thrown if the input contains a value other than ASCII '0' or '1'.
263     * @throws DecoderException Thrown if the argument is not a byte[], char[], or String.
264     * @see org.apache.commons.codec.Decoder#decode(Object)
265     */
266    @Override
267    public Object decode(final Object ascii) throws DecoderException {
268        if (ascii == null) {
269            return EMPTY_BYTE_ARRAY;
270        }
271        if (ascii instanceof byte[]) {
272            return fromAscii((byte[]) ascii);
273        }
274        if (ascii instanceof char[]) {
275            return fromAscii((char[]) ascii);
276        }
277        if (ascii instanceof String) {
278            return fromAscii(((String) ascii).toCharArray());
279        }
280        throw new DecoderException("argument not a byte array");
281    }
282
283    /**
284     * Converts an array of raw binary data into an array of ASCII 0 and 1 characters.
285     *
286     * @param raw The raw binary data to convert.
287     * @return 0 and 1 ASCII character bytes one for each bit of the argument.
288     * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
289     */
290    @Override
291    public byte[] encode(final byte[] raw) {
292        return toAsciiBytes(raw);
293    }
294
295    /**
296     * Converts an array of raw binary data into an array of ASCII 0 and 1 chars.
297     *
298     * @param raw The raw binary data to convert.
299     * @return 0 and 1 ASCII character chars one for each bit of the argument.
300     * @throws EncoderException Thrown if the argument is not a byte[].
301     * @see org.apache.commons.codec.Encoder#encode(Object)
302     */
303    @Override
304    public Object encode(final Object raw) throws EncoderException {
305        if (!(raw instanceof byte[])) {
306            throw new EncoderException("argument not a byte array");
307        }
308        return toAsciiChars((byte[]) raw);
309    }
310
311    /**
312     * Decodes a String where each char of the String represents an ASCII '0' or '1'.
313     *
314     * <p>
315     * All input must consist of ASCII {@code '0'} and {@code '1'} values. If the input length is not a multiple of 8, the leading
316     * {@code length % 8} values are validated but omitted from the decoded result. Null or empty input produces an empty byte array.
317     * </p>
318     *
319     * @param ascii String of '0' and '1' characters.
320     * @return The raw encoded binary where each bit corresponds to a byte in the byte array argument.
321     * @throws IllegalArgumentException Thrown if the input contains a value other than ASCII '0' or '1'.
322     * @see org.apache.commons.codec.Decoder#decode(Object)
323     */
324    public byte[] toByteArray(final String ascii) {
325        if (ascii == null) {
326            return EMPTY_BYTE_ARRAY;
327        }
328        return fromAscii(ascii.toCharArray());
329    }
330}