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 * http://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.miloss.fgsms.common.codec;
19
20
21 /**
22 * Converts between byte arrays and strings of "0"s and "1"s.
23 *
24 * <p>This class is immutable and thread-safe.</p>
25 *
26 * TODO: may want to add more bit vector functions like and/or/xor/nand
27 * TODO: also might be good to generate boolean[] from byte[] et cetera.
28 *
29 * @since 1.3
30 * @version $Id$
31 */
32 public class BinaryCodec implements BinaryDecoder, BinaryEncoder {
33 /*
34 * tried to avoid using ArrayUtils to minimize dependencies while using these empty arrays - dep is just not worth
35 * it.
36 */
37 /** Empty char array. */
38 private static final char[] EMPTY_CHAR_ARRAY = new char[0];
39
40 /** Empty byte array. */
41 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
42
43 /** Mask for bit 0 of a byte. */
44 private static final int BIT_0 = 1;
45
46 /** Mask for bit 1 of a byte. */
47 private static final int BIT_1 = 0x02;
48
49 /** Mask for bit 2 of a byte. */
50 private static final int BIT_2 = 0x04;
51
52 /** Mask for bit 3 of a byte. */
53 private static final int BIT_3 = 0x08;
54
55 /** Mask for bit 4 of a byte. */
56 private static final int BIT_4 = 0x10;
57
58 /** Mask for bit 5 of a byte. */
59 private static final int BIT_5 = 0x20;
60
61 /** Mask for bit 6 of a byte. */
62 private static final int BIT_6 = 0x40;
63
64 /** Mask for bit 7 of a byte. */
65 private static final int BIT_7 = 0x80;
66
67 private static final int[] BITS = {BIT_0, BIT_1, BIT_2, BIT_3, BIT_4, BIT_5, BIT_6, BIT_7};
68
69 /**
70 * Converts an array of raw binary data into an array of ASCII 0 and 1 characters.
71 *
72 * @param raw
73 * the raw binary data to convert
74 * @return 0 and 1 ASCII character bytes one for each bit of the argument
75 * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
76 */
77 @Override
78 public byte[] encode(final byte[] raw) {
79 return toAsciiBytes(raw);
80 }
81
82 /**
83 * Converts an array of raw binary data into an array of ASCII 0 and 1 chars.
84 *
85 * @param raw
86 * the raw binary data to convert
87 * @return 0 and 1 ASCII character chars one for each bit of the argument
88 * @throws EncoderException
89 * if the argument is not a byte[]
90 * @see org.apache.commons.codec.Encoder#encode(Object)
91 */
92 @Override
93 public Object encode(final Object raw) throws EncoderException {
94 if (!(raw instanceof byte[])) {
95 throw new EncoderException("argument not a byte array");
96 }
97 return toAsciiChars((byte[]) raw);
98 }
99
100 /**
101 * Decodes a byte array where each byte represents an ASCII '0' or '1'.
102 *
103 * @param ascii
104 * each byte represents an ASCII '0' or '1'
105 * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument
106 * @throws DecoderException
107 * if argument is not a byte[], char[] or String
108 * @see org.apache.commons.codec.Decoder#decode(Object)
109 */
110 @Override
111 public Object decode(final Object ascii) throws DecoderException {
112 if (ascii == null) {
113 return EMPTY_BYTE_ARRAY;
114 }
115 if (ascii instanceof byte[]) {
116 return fromAscii((byte[]) ascii);
117 }
118 if (ascii instanceof char[]) {
119 return fromAscii((char[]) ascii);
120 }
121 if (ascii instanceof String) {
122 return fromAscii(((String) ascii).toCharArray());
123 }
124 throw new DecoderException("argument not a byte array");
125 }
126
127 /**
128 * Decodes a byte array where each byte represents an ASCII '0' or '1'.
129 *
130 * @param ascii
131 * each byte represents an ASCII '0' or '1'
132 * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument
133 * @see org.apache.commons.codec.Decoder#decode(Object)
134 */
135 @Override
136 public byte[] decode(final byte[] ascii) {
137 return fromAscii(ascii);
138 }
139
140 /**
141 * Decodes a String where each char of the String represents an ASCII '0' or '1'.
142 *
143 * @param ascii
144 * String of '0' and '1' characters
145 * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument
146 * @see org.apache.commons.codec.Decoder#decode(Object)
147 */
148 public byte[] toByteArray(final String ascii) {
149 if (ascii == null) {
150 return EMPTY_BYTE_ARRAY;
151 }
152 return fromAscii(ascii.toCharArray());
153 }
154
155 // ------------------------------------------------------------------------
156 //
157 // static codec operations
158 //
159 // ------------------------------------------------------------------------
160 /**
161 * Decodes a char array where each char represents an ASCII '0' or '1'.
162 *
163 * @param ascii
164 * each char represents an ASCII '0' or '1'
165 * @return the raw encoded binary where each bit corresponds to a char in the char array argument
166 */
167 public static byte[] fromAscii(final char[] ascii) {
168 if (ascii == null || ascii.length == 0) {
169 return EMPTY_BYTE_ARRAY;
170 }
171 // get length/8 times bytes with 3 bit shifts to the right of the length
172 final byte[] l_raw = new byte[ascii.length >> 3];
173 /*
174 * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the
175 * loop.
176 */
177 for (int ii = 0, jj = ascii.length - 1; ii < l_raw.length; ii++, jj -= 8) {
178 for (int bits = 0; bits < BITS.length; ++bits) {
179 if (ascii[jj - bits] == '1') {
180 l_raw[ii] |= BITS[bits];
181 }
182 }
183 }
184 return l_raw;
185 }
186
187 /**
188 * Decodes a byte array where each byte represents an ASCII '0' or '1'.
189 *
190 * @param ascii
191 * each byte represents an ASCII '0' or '1'
192 * @return the raw encoded binary where each bit corresponds to a byte in the byte array argument
193 */
194 public static byte[] fromAscii(final byte[] ascii) {
195 if (isEmpty(ascii)) {
196 return EMPTY_BYTE_ARRAY;
197 }
198 // get length/8 times bytes with 3 bit shifts to the right of the length
199 final byte[] l_raw = new byte[ascii.length >> 3];
200 /*
201 * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the
202 * loop.
203 */
204 for (int ii = 0, jj = ascii.length - 1; ii < l_raw.length; ii++, jj -= 8) {
205 for (int bits = 0; bits < BITS.length; ++bits) {
206 if (ascii[jj - bits] == '1') {
207 l_raw[ii] |= BITS[bits];
208 }
209 }
210 }
211 return l_raw;
212 }
213
214 /**
215 * Returns <code>true</code> if the given array is <code>null</code> or empty (size 0.)
216 *
217 * @param array
218 * the source array
219 * @return <code>true</code> if the given array is <code>null</code> or empty (size 0.)
220 */
221 private static boolean isEmpty(final byte[] array) {
222 return array == null || array.length == 0;
223 }
224
225 /**
226 * Converts an array of raw binary data into an array of ASCII 0 and 1 character bytes - each byte is a truncated
227 * char.
228 *
229 * @param raw
230 * the raw binary data to convert
231 * @return an array of 0 and 1 character bytes for each bit of the argument
232 * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
233 */
234 public static byte[] toAsciiBytes(final byte[] raw) {
235 if (isEmpty(raw)) {
236 return EMPTY_BYTE_ARRAY;
237 }
238 // get 8 times the bytes with 3 bit shifts to the left of the length
239 final byte[] l_ascii = new byte[raw.length << 3];
240 /*
241 * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the
242 * loop.
243 */
244 for (int ii = 0, jj = l_ascii.length - 1; ii < raw.length; ii++, jj -= 8) {
245 for (int bits = 0; bits < BITS.length; ++bits) {
246 if ((raw[ii] & BITS[bits]) == 0) {
247 l_ascii[jj - bits] = '0';
248 } else {
249 l_ascii[jj - bits] = '1';
250 }
251 }
252 }
253 return l_ascii;
254 }
255
256 /**
257 * Converts an array of raw binary data into an array of ASCII 0 and 1 characters.
258 *
259 * @param raw
260 * the raw binary data to convert
261 * @return an array of 0 and 1 characters for each bit of the argument
262 * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
263 */
264 public static char[] toAsciiChars(final byte[] raw) {
265 if (isEmpty(raw)) {
266 return EMPTY_CHAR_ARRAY;
267 }
268 // get 8 times the bytes with 3 bit shifts to the left of the length
269 final char[] l_ascii = new char[raw.length << 3];
270 /*
271 * We decr index jj by 8 as we go along to not recompute indices using multiplication every time inside the
272 * loop.
273 */
274 for (int ii = 0, jj = l_ascii.length - 1; ii < raw.length; ii++, jj -= 8) {
275 for (int bits = 0; bits < BITS.length; ++bits) {
276 if ((raw[ii] & BITS[bits]) == 0) {
277 l_ascii[jj - bits] = '0';
278 } else {
279 l_ascii[jj - bits] = '1';
280 }
281 }
282 }
283 return l_ascii;
284 }
285
286 /**
287 * Converts an array of raw binary data into a String of ASCII 0 and 1 characters.
288 *
289 * @param raw
290 * the raw binary data to convert
291 * @return a String of 0 and 1 characters representing the binary data
292 * @see org.apache.commons.codec.BinaryEncoder#encode(byte[])
293 */
294 public static String toAsciiString(final byte[] raw) {
295 return new String(toAsciiChars(raw));
296 }
297 }