View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.codec.binary;
19  
20  import org.apache.commons.codec.BinaryDecoder;
21  import org.apache.commons.codec.BinaryEncoder;
22  import org.apache.commons.codec.DecoderException;
23  import org.apache.commons.codec.EncoderException;
24  
25  /**
26   * Converts between byte arrays and strings of "0"s and "1"s.
27   *
28   * <p>
29   * This class is immutable and thread-safe.
30   * </p>
31   *
32   * @since 1.3
33   */
34  public class BinaryCodec implements BinaryDecoder, BinaryEncoder {
35  
36      // 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.
37  
38      /** Empty char array. */
39      private static final char[] EMPTY_CHAR_ARRAY = {};
40  
41      /** Empty byte array. */
42      private static final byte[] EMPTY_BYTE_ARRAY = {};
43  
44      /** Mask for bit 0 of a byte. */
45      private static final int BIT_0 = 1;
46  
47      /** Mask for bit 1 of a byte. */
48      private static final int BIT_1 = 0x02;
49  
50      /** Mask for bit 2 of a byte. */
51      private static final int BIT_2 = 0x04;
52  
53      /** Mask for bit 3 of a byte. */
54      private static final int BIT_3 = 0x08;
55  
56      /** Mask for bit 4 of a byte. */
57      private static final int BIT_4 = 0x10;
58  
59      /** Mask for bit 5 of a byte. */
60      private static final int BIT_5 = 0x20;
61  
62      /** Mask for bit 6 of a byte. */
63      private static final int BIT_6 = 0x40;
64  
65      /** Mask for bit 7 of a byte. */
66      private static final int BIT_7 = 0x80;
67  
68      private static final int[] BITS = { BIT_0, BIT_1, BIT_2, BIT_3, BIT_4, BIT_5, BIT_6, BIT_7 };
69  
70      /**
71       * Decodes a byte array where each byte represents an ASCII '0' or '1'.
72       *
73       * <p>
74       * All input must consist of ASCII {@code '0'} and {@code '1'} values. If the input length is not a multiple of 8, the leading
75       * {@code length % 8} values are validated but omitted from the decoded result. Null or empty input produces an empty byte array.
76       * </p>
77       *
78       * @param ascii each byte represents an ASCII '0' or '1'.
79       * @return The raw encoded binary where each bit corresponds to a byte in the byte array argument.
80       * @throws IllegalArgumentException Thrown if the input contains a value other than ASCII '0' or '1'.
81       */
82      public static byte[] fromAscii(final byte[] ascii) {
83          if (isEmpty(ascii)) {
84              return EMPTY_BYTE_ARRAY;
85          }
86          final int asciiLength = ascii.length;
87          // Validate all input, including leading values omitted from the decoded result.
88          for (int i = 0; i < asciiLength; i++) {
89              final byte b = ascii[i];
90              if (b != '0' && b != '1') {
91                  throw new IllegalArgumentException("Input contains a value other than ASCII '0' or '1' at index " + i);
92              }
93          }
94          // Decode complete groups of 8, omitting any remaining leading values.
95          final byte[] raw = new byte[asciiLength >> 3];
96          /*
97           * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the loop.
98           */
99          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 }