Skip to content

Commit f9f750a

Browse files
committed
Add BaseNCodec.AbstractBuilder.setDecodeTable(byte[]) and refactor
subclasses
1 parent 42de020 commit f9f750a

File tree

6 files changed

+122
-71
lines changed

6 files changed

+122
-71
lines changed

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ The <action> type attribute can be add,update,fix,remove.
5454
<action type="add" dev="ggregory" due-to="Gary Gregory">Add builders to org.apache.commons.codec.digest streams and deprecate some old constructors.</action>
5555
<action type="add" dev="ggregory" due-to="Gary Gregory">Add builder to Base16 streams and deprecate some old constructors.</action>
5656
<action type="add" dev="ggregory" due-to="Gary Gregory">Add support for SHAKE128-256 and SHAKE256-512 to `DigestUtils` and `MessageDigestAlgorithms` on Java 25 and up.</action>
57+
<action type="add" dev="ggregory" due-to="Gary Gregory">Add BaseNCodec.AbstractBuilder.setDecodeTable(byte[]) and refactor subclasses.</action>
5758
<!-- UPDATE -->
5859
<action type="update" dev="ggregory" due-to="Gary Gregory, Dependabot">Bump org.apache.commons:commons-parent from 85 to 89.</action>
5960
<action type="update" dev="ggregory" due-to="Gary Gregory">[test] Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0.</action>

src/main/java/org/apache/commons/codec/binary/Base16.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public static class Builder extends AbstractBuilder<Base16, Builder> {
6565
*/
6666
public Builder() {
6767
super(null);
68+
setDecodeTable(UPPER_CASE_DECODE_TABLE);
69+
setEncodeTable(UPPER_CASE_ENCODE_TABLE);
6870
setEncodedBlockSize(BYTES_PER_ENCODED_BLOCK);
6971
setUnencodedBlockSize(BYTES_PER_UNENCODED_BLOCK);
7072
setLineLength(0);
@@ -76,6 +78,12 @@ public Base16 get() {
7678
return new Base16(this);
7779
}
7880

81+
@Override
82+
public Builder setEncodeTable(byte... encodeTable) {
83+
super.setDecodeTableRaw(Arrays.equals(encodeTable, LOWER_CASE_ENCODE_TABLE) ? LOWER_CASE_DECODE_TABLE : UPPER_CASE_DECODE_TABLE);
84+
return super.setEncodeTable(encodeTable);
85+
}
86+
7987
/**
8088
* Sets whether to use the the lower-case Base16 alphabet.
8189
*
@@ -151,11 +159,6 @@ public static Builder builder() {
151159
return new Builder();
152160
}
153161

154-
/**
155-
* Decode table to use.
156-
*/
157-
private final byte[] decodeTable;
158-
159162
/**
160163
* Constructs a Base16 codec used for decoding and encoding.
161164
*/
@@ -188,7 +191,6 @@ public Base16(final boolean lowerCase, final CodecPolicy decodingPolicy) {
188191

189192
private Base16(final Builder builder) {
190193
super(builder);
191-
this.decodeTable = Arrays.equals(encodeTable, LOWER_CASE_ENCODE_TABLE) ? LOWER_CASE_DECODE_TABLE : UPPER_CASE_DECODE_TABLE;
192194
}
193195

194196
@Override

src/main/java/org/apache/commons/codec/binary/Base32.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
package org.apache.commons.codec.binary;
1919

2020
import java.util.Arrays;
21-
import java.util.Objects;
2221

2322
import org.apache.commons.codec.CodecPolicy;
2423

@@ -86,6 +85,8 @@ public static class Builder extends AbstractBuilder<Base32, Builder> {
8685
*/
8786
public Builder() {
8887
super(ENCODE_TABLE);
88+
setDecodeTableRaw(DECODE_TABLE);
89+
setEncodeTableRaw(ENCODE_TABLE);
8990
setEncodedBlockSize(BYTES_PER_ENCODED_BLOCK);
9091
setUnencodedBlockSize(BYTES_PER_UNENCODED_BLOCK);
9192
}
@@ -95,6 +96,12 @@ public Base32 get() {
9596
return new Base32(this);
9697
}
9798

99+
@Override
100+
public Builder setEncodeTable(byte... encodeTable) {
101+
super.setDecodeTableRaw(Arrays.equals(encodeTable, HEX_ENCODE_TABLE) ? HEX_DECODE_TABLE : DECODE_TABLE);
102+
return super.setEncodeTable(encodeTable);
103+
}
104+
98105
/**
99106
* Sets the decode table to use Base32 hexadecimal if {@code true}, otherwise use the Base32 alphabet.
100107
* <p>
@@ -269,11 +276,6 @@ private static byte[] encodeTable(final boolean useHex) {
269276
return useHex ? HEX_ENCODE_TABLE : ENCODE_TABLE;
270277
}
271278

272-
/**
273-
* Decode table to use.
274-
*/
275-
private final byte[] decodeTable;
276-
277279
/**
278280
* Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. {@code encodeSize = {@link
279281
* #BYTES_PER_ENCODED_BLOCK} + lineSeparator.length;}
@@ -338,8 +340,6 @@ public Base32(final boolean useHex, final byte padding) {
338340

339341
private Base32(final Builder builder) {
340342
super(builder);
341-
Objects.requireNonNull(builder.getEncodeTable(), "encodeTable");
342-
this.decodeTable = Arrays.equals(builder.getEncodeTable(), HEX_ENCODE_TABLE) ? HEX_DECODE_TABLE : DECODE_TABLE;
343343
if (builder.getLineLength() > 0) {
344344
final byte[] lineSeparator = builder.getLineSeparator();
345345
// Must be done after initializing the tables
@@ -489,8 +489,15 @@ public Base32(final int lineLength, final byte[] lineSeparator, final boolean us
489489
*/
490490
@Deprecated
491491
public Base32(final int lineLength, final byte[] lineSeparator, final boolean useHex, final byte padding, final CodecPolicy decodingPolicy) {
492-
this(builder().setLineLength(lineLength).setLineSeparator(lineSeparator != null ? lineSeparator : EMPTY_BYTE_ARRAY)
493-
.setEncodeTableRaw(encodeTable(useHex)).setPadding(padding).setDecodingPolicy(decodingPolicy));
492+
// @formatter:off
493+
this(builder()
494+
.setLineLength(lineLength)
495+
.setLineSeparator(lineSeparator != null ? lineSeparator : EMPTY_BYTE_ARRAY)
496+
.setDecodeTable(decodeTable(useHex))
497+
.setEncodeTableRaw(encodeTable(useHex))
498+
.setPadding(padding)
499+
.setDecodingPolicy(decodingPolicy));
500+
// @formatter:on
494501
}
495502

496503
/**

src/main/java/org/apache/commons/codec/binary/Base64.java

Lines changed: 55 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ public static class Builder extends AbstractBuilder<Base64, Builder> {
9898
*/
9999
public Builder() {
100100
super(STANDARD_ENCODE_TABLE);
101+
setDecodeTableRaw(DECODE_TABLE);
102+
setEncodeTableRaw(STANDARD_ENCODE_TABLE);
101103
setEncodedBlockSize(BYTES_PER_ENCODED_BLOCK);
102104
setUnencodedBlockSize(BYTES_PER_UNENCODED_BLOCK);
103105
}
@@ -107,6 +109,14 @@ public Base64 get() {
107109
return new Base64(this);
108110
}
109111

112+
@Override
113+
public Builder setEncodeTable(final byte... encodeTable) {
114+
final boolean isStandardEncodeTable = Arrays.equals(encodeTable, STANDARD_ENCODE_TABLE);
115+
final boolean isUrlSafe = Arrays.equals(encodeTable, URL_SAFE_ENCODE_TABLE);
116+
super.setDecodeTableRaw(isStandardEncodeTable || isUrlSafe ? DECODE_TABLE : calculateDecodeTable(encodeTable));
117+
return super.setEncodeTable(encodeTable);
118+
}
119+
110120
/**
111121
* Sets the URL-safe encoding policy.
112122
*
@@ -222,6 +232,21 @@ public static Builder builder() {
222232
return new Builder();
223233
}
224234

235+
/**
236+
* Calculates a decode table for a given encode table.
237+
*
238+
* @param encodeTable that is used to determine decode lookup table
239+
* @return decodeTable
240+
*/
241+
private static byte[] calculateDecodeTable(final byte[] encodeTable) {
242+
final byte[] decodeTable = new byte[DECODING_TABLE_LENGTH];
243+
Arrays.fill(decodeTable, (byte) -1);
244+
for (int i = 0; i < encodeTable.length; i++) {
245+
decodeTable[encodeTable[i]] = (byte) i;
246+
}
247+
return decodeTable;
248+
}
249+
225250
/**
226251
* Decodes Base64 data into octets.
227252
* <p>
@@ -505,11 +530,6 @@ static byte[] toUrlSafeEncodeTable(final boolean urlSafe) {
505530
return urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;
506531
}
507532

508-
/**
509-
* Decode table to use.
510-
*/
511-
private final byte[] decodeTable;
512-
513533
/**
514534
* Line separator for encoding. Not used when decoding. Only used if lineLength &gt; 0.
515535
*/
@@ -523,8 +543,8 @@ static byte[] toUrlSafeEncodeTable(final boolean urlSafe) {
523543

524544
private final boolean isUrlSafe;
525545

526-
private final boolean isStandardEncodeTable;
527546

547+
private final boolean isStandardEncodeTable;
528548

529549
/**
530550
* Constructs a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
@@ -559,6 +579,35 @@ public Base64(final boolean urlSafe) {
559579
this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe);
560580
}
561581

582+
private Base64(final Builder builder) {
583+
super(builder);
584+
final byte[] encTable = builder.getEncodeTable();
585+
if (encTable.length != STANDARD_ENCODE_TABLE.length) {
586+
throw new IllegalArgumentException("encodeTable must have exactly 64 entries.");
587+
}
588+
this.isStandardEncodeTable = Arrays.equals(encTable, STANDARD_ENCODE_TABLE);
589+
this.isUrlSafe = Arrays.equals(encTable, URL_SAFE_ENCODE_TABLE);
590+
// TODO could be simplified if there is no requirement to reject invalid line sep when length <=0
591+
// @see test case Base64Test.testConstructors()
592+
if (builder.getLineSeparator().length > 0) {
593+
final byte[] lineSeparatorB = builder.getLineSeparator();
594+
if (containsAlphabetOrPad(lineSeparatorB)) {
595+
final String sep = StringUtils.newStringUtf8(lineSeparatorB);
596+
throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]");
597+
}
598+
if (builder.getLineLength() > 0) { // null line-sep forces no chunking rather than throwing IAE
599+
this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparatorB.length;
600+
this.lineSeparator = lineSeparatorB;
601+
} else {
602+
this.encodeSize = BYTES_PER_ENCODED_BLOCK;
603+
this.lineSeparator = null;
604+
}
605+
} else {
606+
this.encodeSize = BYTES_PER_ENCODED_BLOCK;
607+
this.lineSeparator = null;
608+
}
609+
}
610+
562611
/**
563612
* Constructs a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
564613
* <p>
@@ -682,51 +731,6 @@ public Base64(final int lineLength, final byte[] lineSeparator, final boolean ur
682731
.setDecodingPolicy(decodingPolicy));
683732
}
684733

685-
private Base64(final Builder builder) {
686-
super(builder);
687-
Objects.requireNonNull(builder.getEncodeTable(), "encodeTable");
688-
if (builder.getEncodeTable().length != STANDARD_ENCODE_TABLE.length) {
689-
throw new IllegalArgumentException("encodeTable must have exactly 64 entries.");
690-
}
691-
this.isStandardEncodeTable = Arrays.equals(builder.getEncodeTable(), STANDARD_ENCODE_TABLE);
692-
this.isUrlSafe = Arrays.equals(builder.getEncodeTable(), URL_SAFE_ENCODE_TABLE);
693-
this.decodeTable = this.isStandardEncodeTable || this.isUrlSafe ? DECODE_TABLE : calculateDecodeTable(this.encodeTable);
694-
// TODO could be simplified if there is no requirement to reject invalid line sep when length <=0
695-
// @see test case Base64Test.testConstructors()
696-
if (builder.getLineSeparator().length > 0) {
697-
final byte[] lineSeparatorB = builder.getLineSeparator();
698-
if (containsAlphabetOrPad(lineSeparatorB)) {
699-
final String sep = StringUtils.newStringUtf8(lineSeparatorB);
700-
throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]");
701-
}
702-
if (builder.getLineLength() > 0) { // null line-sep forces no chunking rather than throwing IAE
703-
this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparatorB.length;
704-
this.lineSeparator = lineSeparatorB;
705-
} else {
706-
this.encodeSize = BYTES_PER_ENCODED_BLOCK;
707-
this.lineSeparator = null;
708-
}
709-
} else {
710-
this.encodeSize = BYTES_PER_ENCODED_BLOCK;
711-
this.lineSeparator = null;
712-
}
713-
}
714-
715-
/**
716-
* Calculates a decode table for a given encode table.
717-
*
718-
* @param encodeTable that is used to determine decode lookup table
719-
* @return decodeTable
720-
*/
721-
private byte[] calculateDecodeTable(final byte[] encodeTable) {
722-
final byte[] decodeTable = new byte[DECODING_TABLE_LENGTH];
723-
Arrays.fill(decodeTable, (byte) -1);
724-
for (int i = 0; i < encodeTable.length; i++) {
725-
decodeTable[encodeTable[i]] = (byte) i;
726-
}
727-
return decodeTable;
728-
}
729-
730734
/**
731735
* <p>
732736
* Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once

src/main/java/org/apache/commons/codec/binary/BaseNCodec.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public abstract static class AbstractBuilder<T, B extends AbstractBuilder<T, B>>
6565
private byte[] lineSeparator = CHUNK_SEPARATOR;
6666
private final byte[] defaultEncodeTable;
6767
private byte[] encodeTable;
68+
private byte[] decodeTable;
69+
6870
/** Padding byte. */
6971
private byte padding = PAD_DEFAULT;
7072

@@ -90,6 +92,10 @@ B asThis() {
9092
return (B) this;
9193
}
9294

95+
byte[] getDecodeTable() {
96+
return decodeTable;
97+
}
98+
9399
CodecPolicy getDecodingPolicy() {
94100
return decodingPolicy;
95101
}
@@ -118,6 +124,29 @@ int getUnencodedBlockSize() {
118124
return unencodedBlockSize;
119125
}
120126

127+
/**
128+
* Sets the decode table.
129+
*
130+
* @param decodeTable the decode table.
131+
* @return {@code this} instance.
132+
* @since 1.20.0
133+
*/
134+
public B setDecodeTable(final byte[] decodeTable) {
135+
this.decodeTable = decodeTable != null ? decodeTable.clone() : null;
136+
return asThis();
137+
}
138+
139+
/**
140+
* Sets the decode table.
141+
*
142+
* @param decodeTable the decode table, null resets to the default.
143+
* @return {@code this} instance.
144+
*/
145+
B setDecodeTableRaw(final byte[] decodeTable) {
146+
this.decodeTable = decodeTable;
147+
return asThis();
148+
}
149+
121150
/**
122151
* Sets the decoding policy.
123152
*
@@ -133,7 +162,7 @@ public B setDecodingPolicy(final CodecPolicy decodingPolicy) {
133162
* Sets the encoded block size, subclasses normally set this on construction.
134163
*
135164
* @param encodedBlockSize the encoded block size, subclasses normally set this on construction.
136-
* @return this
165+
* @return {@code this} instance.
137166
*/
138167
B setEncodedBlockSize(final int encodedBlockSize) {
139168
this.encodedBlockSize = encodedBlockSize;
@@ -199,7 +228,7 @@ public B setPadding(final byte padding) {
199228
* Sets the unencoded block size, subclasses normally set this on construction.
200229
*
201230
* @param unencodedBlockSize the unencoded block size, subclasses normally set this on construction.
202-
* @return this
231+
* @return {@code this} instance.
203232
*/
204233
B setUnencodedBlockSize(final int unencodedBlockSize) {
205234
this.unencodedBlockSize = unencodedBlockSize;
@@ -444,6 +473,12 @@ private static byte[] resizeBuffer(final Context context, final int minCapacity)
444473
* </p>
445474
*/
446475
private final CodecPolicy decodingPolicy;
476+
477+
/**
478+
* Decode table to use.
479+
*/
480+
final byte[] decodeTable;
481+
447482
/**
448483
* Encode table.
449484
*/
@@ -463,7 +498,8 @@ protected BaseNCodec(final AbstractBuilder<?, ?> builder) {
463498
this.chunkSeparatorLength = builder.lineSeparator.length;
464499
this.pad = builder.padding;
465500
this.decodingPolicy = Objects.requireNonNull(builder.decodingPolicy, "codecPolicy");
466-
this.encodeTable = builder.getEncodeTable();
501+
this.encodeTable = Objects.requireNonNull(builder.getEncodeTable(), "builder.getEncodeTable()");
502+
this.decodeTable = builder.getDecodeTable();
467503
}
468504

469505
/**
@@ -530,6 +566,7 @@ protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, f
530566
this.pad = pad;
531567
this.decodingPolicy = Objects.requireNonNull(decodingPolicy, "codecPolicy");
532568
this.encodeTable = null;
569+
this.decodeTable = null;
533570
}
534571

535572
/**

src/test/java/org/apache/commons/codec/binary/Base32Test.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ void testIsInAlphabet() {
494494
// hex table
495495
b32 = new Base32(true);
496496
for (char c = '0'; c <= '9'; c++) {
497-
assertTrue(b32.isInAlphabet((byte) c));
497+
assertTrue(b32.isInAlphabet((byte) c), String.valueOf(c));
498498
}
499499
for (char c = 'A'; c <= 'V'; c++) {
500500
assertTrue(b32.isInAlphabet((byte) c));

0 commit comments

Comments
 (0)