You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2025/10/12 20:22:39 UTC

(commons-codec) branch master updated: Add builder to Base16 streams and deprecate some old constructors

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-codec.git


The following commit(s) were added to refs/heads/master by this push:
     new 18bd3d04 Add builder to Base16 streams and deprecate some old constructors
18bd3d04 is described below

commit 18bd3d041312579486ad12d3f3a803b485d9f5f0
Author: Gary D. Gregory <ga...@gmail.com>
AuthorDate: Sun Oct 12 16:22:33 2025 -0400

    Add builder to Base16 streams and deprecate some old constructors
---
 src/changes/changes.xml                            |  1 +
 .../org/apache/commons/codec/binary/Base16.java    | 88 ++++++++++++++++++----
 .../codec/binary/Base16InputStreamTest.java        |  6 +-
 .../codec/binary/Base16OutputStreamTest.java       |  6 +-
 .../apache/commons/codec/binary/Base16Test.java    |  4 +-
 5 files changed, 82 insertions(+), 23 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 1303b08c..3108b4ce 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -52,6 +52,7 @@ The <action> type attribute can be add,update,fix,remove.
       <!-- ADD -->
       <action type="add" dev="ggregory" due-to="Fredrik Kjellberg, Gary Gregory">Add org.apache.commons.codec.digest.CRC16.</action>
       <action type="add" dev="ggregory" due-to="Gary Gregory">Add builders to org.apache.commons.codec.digest streams and deprecate some old constructors.</action>
+      <action type="add" dev="ggregory" due-to="Gary Gregory">Add builder to Base16 streams and deprecate some old constructors.</action>
       <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>
       <!-- UPDATE -->
       <action type="update" dev="ggregory" due-to="Gary Gregory, Dependabot">Bump org.apache.commons:commons-parent from 85 to 89.</action>
diff --git a/src/main/java/org/apache/commons/codec/binary/Base16.java b/src/main/java/org/apache/commons/codec/binary/Base16.java
index 1de4ff10..99e1f0e6 100644
--- a/src/main/java/org/apache/commons/codec/binary/Base16.java
+++ b/src/main/java/org/apache/commons/codec/binary/Base16.java
@@ -17,6 +17,7 @@
 
 package org.apache.commons.codec.binary;
 
+import java.util.Arrays;
 import java.util.Objects;
 
 import org.apache.commons.codec.CodecPolicy;
@@ -43,11 +44,59 @@ import org.apache.commons.codec.CodecPolicy;
  */
 public class Base16 extends BaseNCodec {
 
+    /**
+     * Builds {@link Base16} instances.
+     *
+     * <p>
+     * To configure a new instance, use a {@link Builder}. For example:
+     * </p>
+     *
+     * <pre>
+     * Base16 Base16 = Base16.builder()
+     *   .setDecodingPolicy(DecodingPolicy.LENIENT) // default is lenient
+     *   .get()
+     * </pre>
+     *
+     * @since 1.20.0
+     */
+    public static class Builder extends AbstractBuilder<Base16, Builder> {
+
+        /**
+         * Constructs a new instance.
+         */
+        public Builder() {
+            super(null);
+            setEncodedBlockSize(BYTES_PER_ENCODED_BLOCK);
+            setUnencodedBlockSize(BYTES_PER_UNENCODED_BLOCK);
+            setLineLength(0);
+            setLineSeparator(EMPTY_BYTE_ARRAY);
+        }
+
+        @Override
+        public Base16 get() {
+            return new Base16(this);
+        }
+
+        /**
+         * Sets whether to use the the lower-case Base16 alphabet.
+         *
+         * @param lowerCase {@code true} to use the lower-case Base16 alphabet.
+         * @return {@code this} instance.
+         */
+        public Builder setLowerCase(final boolean lowerCase) {
+            setEncodeTableRaw(lowerCase ? LOWER_CASE_ENCODE_TABLE : UPPER_CASE_ENCODE_TABLE);
+            return asThis();
+        }
+
+    }
+
     /**
      * BASE16 characters are 4 bits in length. They are formed by taking an 8-bit group, which is converted into two BASE16 characters.
      */
     private static final int BITS_PER_ENCODED_BYTE = 4;
+
     private static final int BYTES_PER_ENCODED_BLOCK = 2;
+
     private static final int BYTES_PER_UNENCODED_BLOCK = 1;
     /**
      * This array is a lookup table that translates Unicode characters drawn from the "Base16 Alphabet" (as specified in Table 5 of RFC 4648) into their 4-bit
@@ -68,6 +117,7 @@ public class Base16 extends BaseNCodec {
      * 4648.
      */
     private static final byte[] UPPER_CASE_ENCODE_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
     /**
      * This array is a lookup table that translates Unicode characters drawn from the a lower-case "Base16 Alphabet" into their 4-bit positive integer
      * equivalents. Characters that are not in the Base16 alphabet but fall within the bounds of the array are translated to -1.
@@ -88,12 +138,25 @@ public class Base16 extends BaseNCodec {
      * This array is a lookup table that translates 4-bit positive integer index values into their "Base16 Alphabet" lower-case equivalents.
      */
     private static final byte[] LOWER_CASE_ENCODE_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
     /** Mask used to extract 4 bits, used when decoding character. */
     private static final int MASK_4_BITS = 0x0f;
+
+    /**
+     * Constructs a new builder.
+     *
+     * @return a new builder.
+     * @since 1.20.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     /**
      * Decode table to use.
      */
     private final byte[] decodeTable;
+
     /**
      * Encode table to use.
      */
@@ -109,8 +172,10 @@ public class Base16 extends BaseNCodec {
     /**
      * Constructs a Base16 codec used for decoding and encoding.
      *
-     * @param lowerCase if {@code true} then use a lower-case Base16 alphabet.
+     * @param lowerCase {@code true} to use the lower-case Base16 alphabet.
+     * @deprecated Use {@link #builder()} and {@link Builder}.
      */
+    @Deprecated
     public Base16(final boolean lowerCase) {
         this(lowerCase, DECODING_POLICY_DEFAULT);
     }
@@ -118,24 +183,19 @@ public class Base16 extends BaseNCodec {
     /**
      * Constructs a Base16 codec used for decoding and encoding.
      *
-     * @param lowerCase      if {@code true} then use a lower-case Base16 alphabet.
+     * @param lowerCase      {@code true} to use the lower-case Base16 alphabet.
      * @param decodingPolicy Decoding policy.
+     * @deprecated Use {@link #builder()} and {@link Builder}.
      */
+    @Deprecated
     public Base16(final boolean lowerCase, final CodecPolicy decodingPolicy) {
-        this(lowerCase ? LOWER_CASE_ENCODE_TABLE : UPPER_CASE_ENCODE_TABLE, decodingPolicy);
+        this(builder().setEncodeTable(lowerCase ? LOWER_CASE_ENCODE_TABLE : UPPER_CASE_ENCODE_TABLE).setDecodingPolicy(decodingPolicy));
     }
 
-    /**
-     * Constructs a Base16 codec used for decoding and encoding.
-     *
-     * @param encodeTable    the encode table.
-     * @param decodingPolicy Decoding policy.
-     */
-    private Base16(final byte[] encodeTable, final CodecPolicy decodingPolicy) {
-        super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, 0, 0, PAD_DEFAULT, decodingPolicy);
-        Objects.requireNonNull(encodeTable, "encodeTable");
-        this.encodeTable = encodeTable;
-        this.decodeTable = encodeTable == LOWER_CASE_ENCODE_TABLE ? LOWER_CASE_DECODE_TABLE : UPPER_CASE_DECODE_TABLE;
+    private Base16(final Builder builder) {
+        super(builder);
+        this.encodeTable = Objects.requireNonNull(builder.getEncodeTable(), "encodeTable");
+        this.decodeTable = Arrays.equals(encodeTable, LOWER_CASE_ENCODE_TABLE) ? LOWER_CASE_DECODE_TABLE : UPPER_CASE_DECODE_TABLE;
     }
 
     @Override
diff --git a/src/test/java/org/apache/commons/codec/binary/Base16InputStreamTest.java b/src/test/java/org/apache/commons/codec/binary/Base16InputStreamTest.java
index e87e015f..ee727607 100644
--- a/src/test/java/org/apache/commons/codec/binary/Base16InputStreamTest.java
+++ b/src/test/java/org/apache/commons/codec/binary/Base16InputStreamTest.java
@@ -93,7 +93,7 @@ class Base16InputStreamTest {
         decoded = BaseNTestData.DECODED;
         testByChunk(encoded, decoded);
         // test random data of sizes 0 through 150
-        final BaseNCodec codec = new Base16(true);
+        final BaseNCodec codec = Base16.builder().setLowerCase(true).get();
         for (int i = 0; i <= 150; i++) {
             final byte[][] randomData = BaseNTestData.randomData(codec, i);
             encoded = randomData[1];
@@ -122,7 +122,7 @@ class Base16InputStreamTest {
         decoded = BaseNTestData.DECODED;
         testByteByByte(encoded, decoded);
         // test random data of sizes 0 through 150
-        final BaseNCodec codec = new Base16(true);
+        final BaseNCodec codec = Base16.builder().setLowerCase(true).get();
         for (int i = 0; i <= 150; i++) {
             final byte[][] randomData = BaseNTestData.randomData(codec, i);
             encoded = randomData[1];
@@ -225,7 +225,7 @@ class Base16InputStreamTest {
         }
         try (InputStream in = Base16InputStream.builder()
                 .setInputStream(new ByteArrayInputStream(decoded))
-                .setEncode(true).setBaseNCodec(new Base16(lowerCase))
+                .setEncode(true).setBaseNCodec(Base16.builder().setLowerCase(lowerCase).get())
                 .get()) {
             final byte[] output = new byte[encoded.length];
             for (int i = 0; i < output.length; i++) {
diff --git a/src/test/java/org/apache/commons/codec/binary/Base16OutputStreamTest.java b/src/test/java/org/apache/commons/codec/binary/Base16OutputStreamTest.java
index 6f0cc3ff..1ab234bd 100644
--- a/src/test/java/org/apache/commons/codec/binary/Base16OutputStreamTest.java
+++ b/src/test/java/org/apache/commons/codec/binary/Base16OutputStreamTest.java
@@ -65,7 +65,7 @@ class Base16OutputStreamTest {
         testByChunk(encoded, decoded);
 
         // test random data of sizes 0 through 150
-        final BaseNCodec codec = new Base16(true);
+        final BaseNCodec codec = Base16.builder().setLowerCase(true).get();
         for (int i = 0; i <= 150; i++) {
             final byte[][] randomData = BaseNTestData.randomData(codec, i);
             encoded = randomData[1];
@@ -90,7 +90,7 @@ class Base16OutputStreamTest {
         decoded = new byte[] { (byte) 0x41 };
         testByteByByte(encoded, decoded);
         // test random data of sizes 0 through 150
-        final BaseNCodec codec = new Base16(true);
+        final BaseNCodec codec = Base16.builder().setLowerCase(true).get();
         for (int i = 0; i <= 150; i++) {
             final byte[][] randomData = BaseNTestData.randomData(codec, i);
             encoded = randomData[1];
@@ -199,7 +199,7 @@ class Base16OutputStreamTest {
         }
         try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
                 OutputStream out = Base16OutputStream.builder()
-                        .setOutputStream(byteOut).setEncode(true).setBaseNCodec(new Base16(lowerCase))
+                        .setOutputStream(byteOut).setEncode(true).setBaseNCodec(Base16.builder().setLowerCase(lowerCase).get())
                         .get()) {
             for (final byte element : decoded) {
                 out.write(element);
diff --git a/src/test/java/org/apache/commons/codec/binary/Base16Test.java b/src/test/java/org/apache/commons/codec/binary/Base16Test.java
index 98acee1e..7e36f729 100644
--- a/src/test/java/org/apache/commons/codec/binary/Base16Test.java
+++ b/src/test/java/org/apache/commons/codec/binary/Base16Test.java
@@ -248,7 +248,7 @@ class Base16Test {
     @Test
     void testIsInAlphabet() {
         // invalid bounds
-        Base16 b16 = new Base16(true);
+        Base16 b16 = Base16.builder().setLowerCase(true).get();
         assertFalse(b16.isInAlphabet((byte) 0));
         assertFalse(b16.isInAlphabet((byte) 1));
         assertFalse(b16.isInAlphabet((byte) -1));
@@ -256,7 +256,6 @@ class Base16Test {
         assertFalse(b16.isInAlphabet((byte) -16));
         assertFalse(b16.isInAlphabet((byte) 128));
         assertFalse(b16.isInAlphabet((byte) 255));
-
         // lower-case
         b16 = new Base16(true);
         for (char c = '0'; c <= '9'; c++) {
@@ -273,7 +272,6 @@ class Base16Test {
         assertFalse(b16.isInAlphabet((byte) ('a' - 1)));
         assertFalse(b16.isInAlphabet((byte) ('f' + 1)));
         assertFalse(b16.isInAlphabet((byte) ('z' + 1)));
-
         // upper-case
         b16 = new Base16(false);
         for (char c = '0'; c <= '9'; c++) {