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 }