Skip to content

Commit 081756b

Browse files
committed
Add Base16 Input and Output Streams
1 parent f19ec78 commit 081756b

10 files changed

Lines changed: 1929 additions & 7 deletions

File tree

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ limitations under the License.
207207
<role>Submitted Match Rating Approach (MRA) phonetic encoder and tests [CODEC-161]</role>
208208
</roles>
209209
</contributor>
210+
<contributor>
211+
<name>Adam Retter</name>
212+
<organization>Evolved Binary</organization>
213+
<roles>
214+
<role>Base16 Input and Output Streams</role>
215+
</roles>
216+
</contributor>
210217
</contributors>
211218
<!-- Codec only has test dependencies ATM -->
212219
<dependencies>
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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.apache.commons.codec.binary;
19+
20+
import org.apache.commons.codec.DecoderException;
21+
22+
import java.nio.charset.Charset;
23+
24+
/**
25+
* Provides Base16 encoding and decoding.
26+
*
27+
* <p>
28+
* This class is thread-safe.
29+
* </p>
30+
*
31+
* @since 1.15
32+
*/
33+
public class Base16 extends BaseNCodec {
34+
35+
private static final int BYTES_PER_UNENCODED_BLOCK = 1;
36+
private static final int BYTES_PER_ENCODED_BLOCK = 2;
37+
38+
private final boolean toLowerCase;
39+
private final Charset charset;
40+
41+
/**
42+
* Creates a Base16 codec used for decoding and encoding.
43+
*/
44+
protected Base16() {
45+
this(Hex.DEFAULT_CHARSET);
46+
}
47+
48+
/**
49+
* Creates a Base16 codec used for decoding and encoding.
50+
*
51+
* @param charset the charset.
52+
*/
53+
protected Base16(final Charset charset) {
54+
this(true, charset);
55+
}
56+
57+
/**
58+
* Creates a Base16 codec used for decoding and encoding.
59+
*
60+
* @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase.
61+
* @param charset the charset.
62+
*/
63+
protected Base16(final boolean toLowerCase, final Charset charset) {
64+
super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, 0, 0);
65+
this.toLowerCase = toLowerCase;
66+
this.charset = charset;
67+
}
68+
69+
70+
@Override
71+
void encode(final byte[] data, final int offset, final int length, final Context context) {
72+
if (context.eof) {
73+
return;
74+
}
75+
if (length < 0) {
76+
context.eof = true;
77+
return;
78+
}
79+
80+
final char[] chars = Hex.encodeHex(data, offset, length, toLowerCase);
81+
final byte[] encoded = new String(chars).getBytes(charset);
82+
83+
final byte[] buffer = ensureBufferSize(encoded.length, context);
84+
System.arraycopy(encoded, 0, buffer, context.pos, encoded.length);
85+
86+
context.pos += encoded.length;
87+
}
88+
89+
@Override
90+
void decode(final byte[] data, final int offset, final int length, final Context context) {
91+
if (context.eof) {
92+
return;
93+
}
94+
if (length < 0) {
95+
context.eof = true;
96+
return;
97+
}
98+
99+
final int dataLen = Math.min(data.length - offset, length);
100+
final int availableChars = (context.ibitWorkArea > 0 ? 1 : 0) + dataLen;
101+
102+
// small optimisation to short-cut the rest of this method when it is fed byte-by-byte
103+
if (availableChars == 1 && availableChars == dataLen) {
104+
context.ibitWorkArea = data[offset]; // store 1/2 byte for next invocation of decode
105+
return;
106+
}
107+
108+
// NOTE: Each pair of bytes is really a pair of hex-chars, therefore each pair represents one byte
109+
110+
// we must have an even number of chars to decode
111+
final char[] encodedChars = new char[availableChars % 2 == 0 ? availableChars : availableChars - 1];
112+
113+
// copy all (or part of) data into encodedChars
114+
int i = 0;
115+
if (dataLen < availableChars) {
116+
// we have 1/2 byte from previous invocation to decode
117+
encodedChars[i++] = (char)context.ibitWorkArea;
118+
context.ibitWorkArea = -1; // reset for next iteration!
119+
}
120+
final int copyLen = encodedChars.length - i;
121+
for (int j = offset; j < copyLen + offset; j++) {
122+
encodedChars[i++] = (char) data[j];
123+
}
124+
125+
// decode encodedChars into buffer
126+
final byte[] buffer = ensureBufferSize(encodedChars.length / 2, context);
127+
try {
128+
final int written = Hex.decodeHex(encodedChars, buffer, context.pos);
129+
context.pos += written;
130+
} catch (final DecoderException e) {
131+
throw new RuntimeException(e); // this method ensures that this cannot happen at runtime!
132+
}
133+
134+
// we have one char of a hex-pair left over
135+
if (copyLen < dataLen) {
136+
context.ibitWorkArea = data[offset + dataLen - 1]; // store 1/2 byte for next invocation of decode
137+
}
138+
}
139+
140+
@Override
141+
protected boolean isInAlphabet(final byte value) {
142+
if (value >= '0' && value <= '9') {
143+
return true;
144+
}
145+
146+
if (toLowerCase) {
147+
return value >= 'a' && value <= 'f';
148+
} else {
149+
return value >= 'A' && value <= 'F';
150+
}
151+
}
152+
153+
/**
154+
* Returns whether or not the {@code c} is in the base 16 alphabet.
155+
*
156+
* @param c The value to test
157+
* @return {@code true} if the value is defined in the the base 16 alphabet, {@code false} otherwise.
158+
*/
159+
public static boolean isBase16(final char c) {
160+
return
161+
(c >= '0' && c <= '9')
162+
|| (c >= 'A' && c <= 'F')
163+
|| (c >= 'a' && c <= 'f');
164+
}
165+
166+
/**
167+
* Tests a given String to see if it contains only valid characters within the Base16 alphabet.
168+
*
169+
* @param base16 String to test
170+
* @return {@code true} if all characters in the String are valid characters in the Base16 alphabet or if
171+
* the String is empty; {@code false}, otherwise
172+
*/
173+
public static boolean isBase16(final String base16) {
174+
return isBase16(base16.toCharArray());
175+
}
176+
177+
/**
178+
* Tests a given char array to see if it contains only valid characters within the Base16 alphabet.
179+
*
180+
* @param arrayChars char array to test
181+
* @return {@code true} if all chars are valid characters in the Base16 alphabet or if the char array is empty;
182+
* {@code false}, otherwise
183+
*/
184+
public static boolean isBase16(final char[] arrayChars) {
185+
for (int i = 0; i < arrayChars.length; i++) {
186+
if (!isBase16(arrayChars[i])) {
187+
return false;
188+
}
189+
}
190+
return true;
191+
}
192+
193+
/**
194+
* Tests a given char array to see if it contains only valid characters within the Base16 alphabet.
195+
*
196+
* @param arrayChars byte array to test
197+
* @return {@code true} if all chars are valid characters in the Base16 alphabet or if the byte array is empty;
198+
* {@code false}, otherwise
199+
*/
200+
public static boolean isBase16(final byte[] arrayChars) {
201+
for (int i = 0; i < arrayChars.length; i++) {
202+
if (!isBase16((char) arrayChars[i])) {
203+
return false;
204+
}
205+
}
206+
return true;
207+
}
208+
209+
/**
210+
* Encodes binary data using the base16 algorithm.
211+
*
212+
* @param binaryData Array containing binary data to encode.
213+
* @return Base16-encoded data.
214+
*/
215+
public static byte[] encodeBase16(final byte[] binaryData) {
216+
return encodeBase16(binaryData, true, Hex.DEFAULT_CHARSET, Integer.MAX_VALUE);
217+
}
218+
219+
/**
220+
* Encodes binary data using the base16 algorithm.
221+
*
222+
* @param binaryData Array containing binary data to encode.
223+
* @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase.
224+
* @param charset the charset.
225+
* @param maxResultSize The maximum result size to accept.
226+
* @return Base16-encoded data.
227+
* @throws IllegalArgumentException Thrown when the input array needs an output array bigger than maxResultSize
228+
*/
229+
public static byte[] encodeBase16(final byte[] binaryData, final boolean toLowerCase, final Charset charset,
230+
final int maxResultSize) {
231+
if (binaryData == null || binaryData.length == 0) {
232+
return binaryData;
233+
}
234+
235+
// Create this so can use the super-class method
236+
// Also ensures that the same roundings are performed by the ctor and the code
237+
final Base16 b16 = new Base16(toLowerCase, charset);
238+
final long len = b16.getEncodedLength(binaryData);
239+
if (len > maxResultSize) {
240+
throw new IllegalArgumentException("Input array too big, the output array would be bigger (" +
241+
len +
242+
") than the specified maximum size of " +
243+
maxResultSize);
244+
}
245+
246+
return b16.encode(binaryData);
247+
}
248+
249+
/**
250+
* Decodes a Base16 String into octets.
251+
*
252+
* @param base16String String containing Base16 data
253+
* @return Array containing decoded data.
254+
*/
255+
public static byte[] decodeBase16(final String base16String) {
256+
return new Base16().decode(base16String);
257+
}
258+
259+
/**
260+
* Decodes Base16 data into octets.
261+
*
262+
* @param base16Data Byte array containing Base16 data
263+
* @return Array containing decoded data.
264+
*/
265+
public static byte[] decodeBase16(final byte[] base16Data) {
266+
return new Base16().decode(base16Data);
267+
}
268+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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.apache.commons.codec.binary;
19+
20+
import java.io.InputStream;
21+
import java.nio.charset.Charset;
22+
23+
/**
24+
* Provides Base16 encoding and decoding in a streaming fashion (unlimited size).
25+
* <p>
26+
* The default behavior of the Base16InputStream is to DECODE, whereas the default behavior of the
27+
* {@link Base16OutputStream} is to ENCODE, but this behavior can be overridden by using a different constructor.
28+
* </p>
29+
*
30+
* @since 1.15
31+
*/
32+
public class Base16InputStream extends BaseNCodecInputStream {
33+
34+
/**
35+
* Creates a Base16InputStream such that all data read is Base16-decoded from the original provided InputStream.
36+
*
37+
* @param in InputStream to wrap.
38+
*/
39+
public Base16InputStream(final InputStream in) {
40+
this(in, false);
41+
}
42+
43+
/**
44+
* Creates a Base16InputStream such that all data read is either Base16-encoded or Base16-decoded from the original
45+
* provided InputStream.
46+
*
47+
* @param in InputStream to wrap.
48+
* @param doEncode true if we should encode all data read from us, false if we should decode.
49+
*/
50+
public Base16InputStream(final InputStream in, final boolean doEncode) {
51+
this(in, doEncode, true, Hex.DEFAULT_CHARSET);
52+
}
53+
54+
/**
55+
* Creates a Base16InputStream such that all data read is either Base16-encoded or Base16-decoded from the original
56+
* provided InputStream.
57+
*
58+
* @param in InputStream to wrap.
59+
* @param doEncode true if we should encode all data read from us, false if we should decode.
60+
* @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase.
61+
* @param charset the charset.
62+
*/
63+
public Base16InputStream(final InputStream in, final boolean doEncode,
64+
final boolean toLowerCase, final Charset charset) {
65+
super(in, new Base16(toLowerCase, charset), doEncode);
66+
}
67+
}

0 commit comments

Comments
 (0)