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}