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-io.git

commit 7245fbd82735eed3fc5510f8b468b77f1e984fb2
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Sun Nov 3 11:30:18 2024 -0500

    Add ValidatingObjectInputStream.ObjectStreamClassPredicate to allow
    configuration reuse
---
 src/changes/changes.xml                            |   1 +
 .../serialization/ValidatingObjectInputStream.java | 432 +++++++++++++++++++--
 src/site/xdoc/description.xml                      |  14 +-
 .../io/serialization/MoreComplexObjectTest.java    |  18 +-
 .../ValidatingObjectInputStreamTest.java           | 188 +++++++--
 5 files changed, 578 insertions(+), 75 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index edfc12892..75de60bac 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -61,6 +61,7 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add RandomAccessFileMode.io(String).</action>
       <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add FileAlterationObserver.Builder() and deprecate most 
constructors.</action>
       <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add IOUtils.readLines(CharSequence).</action>
+      <action dev="ggregory" type="add"                due-to="Gary 
Gregory">Add ValidatingObjectInputStream.ObjectStreamClassPredicate to allow 
configuration reuse.</action>
       <!-- UPDATE -->
       <action dev="ggregory" type="update"             due-to="Gary 
Gregory">Bump org.apache.commons:commons-parent from 74 to 78 #670, #676, #679, 
#688.</action>
       <action dev="ggregory" type="update"             due-to="Gary 
Gregory">Bump commons.bytebuddy.version from 1.15.1 to 1.15.8 #672, #673, #685, 
#686, #694, #696.</action>
diff --git 
a/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java
 
b/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java
index 06b6841d9..6d701c91b 100644
--- 
a/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java
+++ 
b/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java
@@ -25,6 +25,7 @@ import java.io.ObjectInputStream;
 import java.io.ObjectStreamClass;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Predicate;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
 
@@ -42,10 +43,10 @@ import org.apache.commons.io.build.AbstractStreamBuilder;
  * </p>
  *
  * <pre>{@code
- * // Data
+ * // Defining Object fixture
  * final HashMap<String, Integer> map1 = new HashMap<>();
  * map1.put("1", 1);
- * // Write
+ * // Writing serialized fixture
  * final byte[] byteArray;
  * try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
  *         final ObjectOutputStream oos = new ObjectOutputStream(baos)) {
@@ -53,11 +54,25 @@ import org.apache.commons.io.build.AbstractStreamBuilder;
  *     oos.flush();
  *     byteArray = baos.toByteArray();
  * }
- * // Read
+ * // Reading
  * try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
- *         ValidatingObjectInputStream vois = 
ValidatingObjectInputStream.builder().setInputStream(bais).get()) {
+ *         ValidatingObjectInputStream vois = 
ValidatingObjectInputStream.builder()
+ *             .accept(HashMap.class, Number.class, Integer.class)
+ *             .setInputStream(bais)
+ *             .get()) {
+ *     // String.class is automatically accepted
+ *     final HashMap<String, Integer> map2 = (HashMap<String, Integer>) 
vois.readObject();
+ *     assertEquals(map1, map2);
+ * }
+ * // Reusing a configuration
+ * final ObjectStreamClassPredicate predicate = new 
ObjectStreamClassPredicate()
+ *     .accept(HashMap.class, Number.class, Integer.class);
+ * try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
+ *         ValidatingObjectInputStream vois = 
ValidatingObjectInputStream.builder()
+ *             .setPredicate(predicate)
+ *             .setInputStream(bais)
+ *             .get()) {
  *     // String.class is automatically accepted
- *     vois.accept(HashMap.class, Number.class, Integer.class);
  *     final HashMap<String, Integer> map2 = (HashMap<String, Integer>) 
vois.readObject();
  *     assertEquals(map1, map2);
  * }
@@ -93,9 +108,318 @@ public class ValidatingObjectInputStream extends 
ObjectInputStream {
     // @formatter:on
     public static class Builder extends 
AbstractStreamBuilder<ValidatingObjectInputStream, Builder> {
 
+        private ObjectStreamClassPredicate predicate = new 
ObjectStreamClassPredicate();
+
+        /**
+         * Constructs a new instance.
+         *
+         * @deprecated Use {@link #builder()}.
+         */
+        @Deprecated
+        public Builder() {
+            // empty
+        }
+
+        /**
+         * Accepts the specified classes for deserialization, unless they are 
otherwise rejected.
+         *
+         * @param classes Classes to accept
+         * @return this object
+         * @since 2.18.0
+         */
+        public Builder accept(final Class<?>... classes) {
+            predicate.accept(classes);
+            return this;
+        }
+
+        /**
+         * Accepts class names where the supplied ClassNameMatcher matches for 
deserialization, unless they are otherwise rejected.
+         *
+         * @param matcher a class name matcher to <em>accept</em> objects.
+         * @return this instance.
+         * @since 2.18.0
+         */
+        public Builder accept(final ClassNameMatcher matcher) {
+            predicate.accept(matcher);
+            return this;
+        }
+
+        /**
+         * Accepts class names that match the supplied pattern for 
deserialization, unless they are otherwise rejected.
+         *
+         * @param pattern a Pattern for compiled regular expression.
+         * @return this instance.
+         * @since 2.18.0
+         */
+        public Builder accept(final Pattern pattern) {
+            predicate.accept(pattern);
+            return this;
+        }
+
+        /**
+         * Accepts the wildcard specified classes for deserialization, unless 
they are otherwise rejected.
+         *
+         * @param patterns Wildcard file name patterns as defined by {@link 
org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
+         *                 FilenameUtils.wildcardMatch}
+         * @return this instance.
+         * @since 2.18.0
+         */
+        public Builder accept(final String... patterns) {
+            predicate.accept(patterns);
+            return this;
+        }
+
         @Override
         public ValidatingObjectInputStream get() throws IOException {
-            return new ValidatingObjectInputStream(getInputStream());
+            return new ValidatingObjectInputStream(getInputStream(), 
predicate);
+        }
+
+        /**
+         * Gets the predicate.
+         *
+         * @return the predicate.
+         * @since 2.18.0
+         */
+        public ObjectStreamClassPredicate getPredicate() {
+            return predicate;
+        }
+
+        /**
+         * Rejects the specified classes for deserialization, even if they are 
otherwise accepted.
+         *
+         * @param classes Classes to reject
+         * @return this instance.
+         * @since 2.18.0
+         */
+        public Builder reject(final Class<?>... classes) {
+            predicate.reject(classes);
+            return this;
+        }
+
+        /**
+         * Rejects class names where the supplied ClassNameMatcher matches for 
deserialization, even if they are otherwise accepted.
+         *
+         * @param matcher the matcher to use
+         * @return this instance.
+         * @since 2.18.0
+         */
+        public Builder reject(final ClassNameMatcher matcher) {
+            predicate.reject(matcher);
+            return this;
+        }
+
+        /**
+         * Rejects class names that match the supplied pattern for 
deserialization, even if they are otherwise accepted.
+         *
+         * @param pattern standard Java regexp
+         * @return this instance.
+         * @since 2.18.0
+         */
+        public Builder reject(final Pattern pattern) {
+            predicate.reject(pattern);
+            return this;
+        }
+
+        /**
+         * Rejects the wildcard specified classes for deserialization, even if 
they are otherwise accepted.
+         *
+         * @param patterns Wildcard file name patterns as defined by {@link 
org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
+         *                 FilenameUtils.wildcardMatch}
+         * @return this instance.
+         * @since 2.18.0
+         */
+        public Builder reject(final String... patterns) {
+            predicate.reject(patterns);
+            return this;
+        }
+
+        /**
+         * Sets the predicate, null resets to an empty new 
ObjectStreamClassPredicate.
+         *
+         * @param predicate the predicate.
+         * @return this instance.
+         * @since 2.18.0
+         */
+        public Builder setPredicate(final ObjectStreamClassPredicate 
predicate) {
+            this.predicate = predicate != null ? predicate : new 
ObjectStreamClassPredicate();
+            return this;
+        }
+
+    }
+
+    /**
+     * A predicate (boolean-valued function) of one argument to accept and 
reject classes.
+     * <p>
+     * The reject list takes precedence over the accept list.
+     * </p>
+     *
+     * @since 2.18.0
+     */
+    public static class ObjectStreamClassPredicate implements 
Predicate<ObjectStreamClass> {
+
+        // This is not a Set for now to avoid ClassNameMatchers requiring 
proper implementations of hashCode() and equals().
+        private final List<ClassNameMatcher> acceptMatchers = new 
ArrayList<>();
+
+        // This is not a Set for now to avoid ClassNameMatchers requiring 
proper implementations of hashCode() and equals().
+        private final List<ClassNameMatcher> rejectMatchers = new 
ArrayList<>();
+
+        /**
+         * Constructs a new instance.
+         */
+        public ObjectStreamClassPredicate() {
+            // empty
+        }
+
+        /**
+         * Accepts the specified classes for deserialization, unless they are 
otherwise rejected.
+         * <p>
+         * The reject list takes precedence over the accept list.
+         * </p>
+         *
+         * @param classes Classes to accept
+         * @return this object
+         */
+        public ObjectStreamClassPredicate accept(final Class<?>... classes) {
+            Stream.of(classes).map(c -> new 
FullClassNameMatcher(c.getName())).forEach(acceptMatchers::add);
+            return this;
+        }
+
+        /**
+         * Accepts class names where the supplied ClassNameMatcher matches for 
deserialization, unless they are otherwise rejected.
+         * <p>
+         * The reject list takes precedence over the accept list.
+         * </p>
+         *
+         * @param matcher a class name matcher to <em>accept</em> objects.
+         * @return this instance.
+         */
+        public ObjectStreamClassPredicate accept(final ClassNameMatcher 
matcher) {
+            acceptMatchers.add(matcher);
+            return this;
+        }
+
+        /**
+         * Accepts class names that match the supplied pattern for 
deserialization, unless they are otherwise rejected.
+         * <p>
+         * The reject list takes precedence over the accept list.
+         * </p>
+         *
+         * @param pattern a Pattern for compiled regular expression.
+         * @return this instance.
+         */
+        public ObjectStreamClassPredicate accept(final Pattern pattern) {
+            acceptMatchers.add(new RegexpClassNameMatcher(pattern));
+            return this;
+        }
+
+        /**
+         * Accepts the wildcard specified classes for deserialization, unless 
they are otherwise rejected.
+         * <p>
+         * The reject list takes precedence over the accept list.
+         * </p>
+         *
+         * @param patterns Wildcard file name patterns as defined by {@link 
org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
+         *                 FilenameUtils.wildcardMatch}
+         * @return this instance.
+         */
+        public ObjectStreamClassPredicate accept(final String... patterns) {
+            
Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(acceptMatchers::add);
+            return this;
+        }
+
+        /**
+         * Rejects the specified classes for deserialization, even if they are 
otherwise accepted.
+         * <p>
+         * The reject list takes precedence over the accept list.
+         * </p>
+         *
+         * @param classes Classes to reject
+         * @return this instance.
+         */
+        public ObjectStreamClassPredicate reject(final Class<?>... classes) {
+            Stream.of(classes).map(c -> new 
FullClassNameMatcher(c.getName())).forEach(rejectMatchers::add);
+            return this;
+        }
+
+        /**
+         * Rejects class names where the supplied ClassNameMatcher matches for 
deserialization, even if they are otherwise accepted.
+         * <p>
+         * The reject list takes precedence over the accept list.
+         * </p>
+         *
+         * @param m the matcher to use
+         * @return this instance.
+         */
+        public ObjectStreamClassPredicate reject(final ClassNameMatcher m) {
+            rejectMatchers.add(m);
+            return this;
+        }
+
+        /**
+         * Rejects class names that match the supplied pattern for 
deserialization, even if they are otherwise accepted.
+         * <p>
+         * The reject list takes precedence over the accept list.
+         * </p>
+         *
+         * @param pattern standard Java regexp
+         * @return this instance.
+         */
+        public ObjectStreamClassPredicate reject(final Pattern pattern) {
+            rejectMatchers.add(new RegexpClassNameMatcher(pattern));
+            return this;
+        }
+
+        /**
+         * Rejects the wildcard specified classes for deserialization, even if 
they are otherwise accepted.
+         * <p>
+         * The reject list takes precedence over the accept list.
+         * </p>
+         *
+         * @param patterns Wildcard file name patterns as defined by {@link 
org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
+         *                 FilenameUtils.wildcardMatch}
+         * @return this instance.
+         */
+        public ObjectStreamClassPredicate reject(final String... patterns) {
+            
Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(rejectMatchers::add);
+            return this;
+        }
+
+        /**
+         * Tests that the ObjectStreamClass conforms to requirements.
+         * <p>
+         * The reject list takes precedence over the accept list.
+         * </p>
+         *
+         * @param objectStreamClass The ObjectStreamClass to test.
+         * @return true if the input is accepted, false if rejected, false if 
neither.
+         */
+        @Override
+        public boolean test(final ObjectStreamClass objectStreamClass) {
+            return test(objectStreamClass.getName());
+        }
+
+        /**
+         * Tests that the class name conforms to requirements.
+         * <p>
+         * The reject list takes precedence over the accept list.
+         * </p>
+         *
+         * @param name The class name to test.
+         * @return true if the input is accepted, false if rejected, false if 
neither.
+         */
+        public boolean test(final String name) {
+            // The reject list takes precedence over the accept list.
+            for (final ClassNameMatcher m : rejectMatchers) {
+                if (m.matches(name)) {
+                    return false;
+                }
+            }
+            for (final ClassNameMatcher m : acceptMatchers) {
+                if (m.matches(name)) {
+                    return true;
+                }
+            }
+            return false;
         }
 
     }
@@ -110,8 +434,7 @@ public class ValidatingObjectInputStream extends 
ObjectInputStream {
         return new Builder();
     }
 
-    private final List<ClassNameMatcher> acceptMatchers = new ArrayList<>();
-    private final List<ClassNameMatcher> rejectMatchers = new ArrayList<>();
+    private final ObjectStreamClassPredicate predicate;
 
     /**
      * Constructs an instance to deserialize the specified input stream. At 
least one accept method needs to be called to specify which classes can be
@@ -119,80 +442,94 @@ public class ValidatingObjectInputStream extends 
ObjectInputStream {
      *
      * @param input an input stream
      * @throws IOException if an I/O error occurs while reading stream header
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public ValidatingObjectInputStream(final InputStream input) throws 
IOException {
+        this(input, new ObjectStreamClassPredicate());
+    }
+
+    /**
+     * Constructs an instance to deserialize the specified input stream. At 
least one accept method needs to be called to specify which classes can be
+     * deserialized, as by default no classes are accepted.
+     *
+     * @param input     an input stream.
+     * @param predicate how to accept and reject classes.
+     * @throws IOException if an I/O error occurs while reading stream header.
+     */
+    private ValidatingObjectInputStream(final InputStream input, final 
ObjectStreamClassPredicate predicate) throws IOException {
         super(input);
+        this.predicate = predicate;
     }
 
     /**
      * Accepts the specified classes for deserialization, unless they are 
otherwise rejected.
+     * <p>
+     * The reject list takes precedence over the accept list.
+     * </p>
      *
      * @param classes Classes to accept
-     * @return this object
+     * @return this instance.
      */
     public ValidatingObjectInputStream accept(final Class<?>... classes) {
-        Stream.of(classes).map(c -> new 
FullClassNameMatcher(c.getName())).forEach(acceptMatchers::add);
+        predicate.accept(classes);
         return this;
     }
 
     /**
      * Accepts class names where the supplied ClassNameMatcher matches for 
deserialization, unless they are otherwise rejected.
+     * <p>
+     * The reject list takes precedence over the accept list.
+     * </p>
      *
-     * @param matcher the class name matcher to <em>accept</em> objects.
+     * @param matcher a class name matcher to <em>accept</em> objects.
      * @return this instance.
      */
     public ValidatingObjectInputStream accept(final ClassNameMatcher matcher) {
-        acceptMatchers.add(matcher);
+        predicate.accept(matcher);
         return this;
     }
 
     /**
      * Accepts class names that match the supplied pattern for 
deserialization, unless they are otherwise rejected.
+     * <p>
+     * The reject list takes precedence over the accept list.
+     * </p>
      *
      * @param pattern a Pattern for compiled regular expression.
      * @return this instance.
      */
     public ValidatingObjectInputStream accept(final Pattern pattern) {
-        acceptMatchers.add(new RegexpClassNameMatcher(pattern));
+        predicate.accept(pattern);
         return this;
     }
 
     /**
      * Accepts the wildcard specified classes for deserialization, unless they 
are otherwise rejected.
+     * <p>
+     * The reject list takes precedence over the accept list.
+     * </p>
      *
      * @param patterns Wildcard file name patterns as defined by {@link 
org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
      *                 FilenameUtils.wildcardMatch}.
      * @return this instance.
      */
     public ValidatingObjectInputStream accept(final String... patterns) {
-        
Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(acceptMatchers::add);
+        predicate.accept(patterns);
         return this;
     }
 
     /**
      * Checks that the class name conforms to requirements.
+     * <p>
+     * The reject list takes precedence over the accept list.
+     * </p>
      *
      * @param name The class name to test.
      * @throws InvalidClassException Thrown when a rejected or non-accepted 
class is found.
      */
     private void checkClassName(final String name) throws 
InvalidClassException {
-        // Reject has precedence over accept
-        for (final ClassNameMatcher m : rejectMatchers) {
-            if (m.matches(name)) {
-                invalidClassNameFound(name);
-            }
-        }
-
-        boolean ok = false;
-        for (final ClassNameMatcher m : acceptMatchers) {
-            if (m.matches(name)) {
-                ok = true;
-                break;
-            }
-        }
-        if (!ok) {
+        if (!predicate.test(name)) {
             invalidClassNameFound(name);
         }
     }
@@ -208,48 +545,75 @@ public class ValidatingObjectInputStream extends 
ObjectInputStream {
         throw new InvalidClassException("Class name not accepted: " + 
className);
     }
 
+    /**
+     * Delegates to {@link #readObject()} and casts to the generic {@code T}.
+     *
+     * @param <T> The return type.
+     * @return Result from {@link #readObject()}.
+     * @throws ClassNotFoundException Thrown by {@link #readObject()}.
+     * @throws IOException            Thrown by {@link #readObject()}.
+     * @throws ClassCastException     Thrown when {@link #readObject()} does 
not match {@code T}.
+     * @since 2.18.0
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T readObjectCast() throws ClassNotFoundException, IOException {
+        return (T) super.readObject();
+    }
+
     /**
      * Rejects the specified classes for deserialization, even if they are 
otherwise accepted.
+     * <p>
+     * The reject list takes precedence over the accept list.
+     * </p>
      *
      * @param classes Classes to reject.
      * @return this instance.
      */
     public ValidatingObjectInputStream reject(final Class<?>... classes) {
-        Stream.of(classes).map(c -> new 
FullClassNameMatcher(c.getName())).forEach(rejectMatchers::add);
+        predicate.reject(classes);
         return this;
     }
 
     /**
      * Rejects class names where the supplied ClassNameMatcher matches for 
deserialization, even if they are otherwise accepted.
+     * <p>
+     * The reject list takes precedence over the accept list.
+     * </p>
      *
      * @param matcher a class name matcher to <em>reject</em> objects.
      * @return this instance.
      */
     public ValidatingObjectInputStream reject(final ClassNameMatcher matcher) {
-        rejectMatchers.add(matcher);
+        predicate.reject(matcher);
         return this;
     }
 
     /**
      * Rejects class names that match the supplied pattern for 
deserialization, even if they are otherwise accepted.
+     * <p>
+     * The reject list takes precedence over the accept list.
+     * </p>
      *
      * @param pattern a Pattern for compiled regular expression.
      * @return this instance.
      */
     public ValidatingObjectInputStream reject(final Pattern pattern) {
-        rejectMatchers.add(new RegexpClassNameMatcher(pattern));
+        predicate.reject(pattern);
         return this;
     }
 
     /**
      * Rejects the wildcard specified classes for deserialization, even if 
they are otherwise accepted.
+     * <p>
+     * The reject list takes precedence over the accept list.
+     * </p>
      *
      * @param patterns An array of wildcard file name patterns as defined by 
{@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
      *                 FilenameUtils.wildcardMatch}
      * @return this instance.
      */
     public ValidatingObjectInputStream reject(final String... patterns) {
-        
Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(rejectMatchers::add);
+        predicate.reject(patterns);
         return this;
     }
 
diff --git a/src/site/xdoc/description.xml b/src/site/xdoc/description.xml
index 405b96a14..75fdd18cf 100644
--- a/src/site/xdoc/description.xml
+++ b/src/site/xdoc/description.xml
@@ -245,7 +245,19 @@ limitations under the License.
                 ValidatingObjectInputStream vois = 
ValidatingObjectInputStream.builder().setInputStream(bais).get()) {
             // String.class is automatically accepted
             vois.accept(HashMap.class, Number.class, Integer.class);
-            final HashMap&lt;String, Integer&gt; map2 = (HashMap&lt;String, 
Integer&gt;) vois.readObject();
+            final HashMap&lt;String, Integer&lt; map2 = (HashMap&lt;String, 
Integer&gt;) vois.readObject();
+            assertEquals(map1, map2);
+        }
+        // Reusing a configuration
+        final ObjectStreamClassPredicate predicate = new 
ObjectStreamClassPredicate()
+                .accept(HashMap.class, Number.class, Integer.class);
+        try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
+                ValidatingObjectInputStream vois = 
ValidatingObjectInputStream.builder()
+                        .setPredicate(predicate)
+                        .setInputStream(bais)
+                        .get()) {
+            // String.class is automatically accepted
+            final HashMap&lt;String, Integer&lt; map2 = (HashMap&lt;String, 
Integer&lt;) vois.readObject();
             assertEquals(map1, map2);
         }
       </source>
diff --git 
a/src/test/java/org/apache/commons/io/serialization/MoreComplexObjectTest.java 
b/src/test/java/org/apache/commons/io/serialization/MoreComplexObjectTest.java
index 7c67d3525..aef6d4218 100644
--- 
a/src/test/java/org/apache/commons/io/serialization/MoreComplexObjectTest.java
+++ 
b/src/test/java/org/apache/commons/io/serialization/MoreComplexObjectTest.java
@@ -61,11 +61,15 @@ public class MoreComplexObjectTest extends 
AbstractCloseableListTest {
      */
     @Test
     public void testTrustJavaIncludingArrays() throws IOException, 
ClassNotFoundException {
+        // @formatter:off
         assertSerialization(addCloseable(
-                new ValidatingObjectInputStream(inputStream)
+                ValidatingObjectInputStream.builder()
+                .setInputStream(inputStream)
                 .accept(MoreComplexObject.class)
                 .accept("java.*", "[Ljava.*")
+                .get()
         ));
+        // @formatter:on
     }
 
     /**
@@ -74,11 +78,15 @@ public class MoreComplexObjectTest extends 
AbstractCloseableListTest {
      */
     @Test
     public void testTrustJavaLang() throws IOException, ClassNotFoundException 
{
+        // @formatter:off
         assertSerialization(addCloseable(
-                new ValidatingObjectInputStream(inputStream)
+                ValidatingObjectInputStream.builder()
+                .setInputStream(inputStream)
                 .accept(MoreComplexObject.class, ArrayList.class, Random.class)
                 .accept("java.lang.*", "[Ljava.lang.*")
+                .get()
         ));
+        // @formatter:on
     }
 
     /**
@@ -94,10 +102,14 @@ public class MoreComplexObjectTest extends 
AbstractCloseableListTest {
                 "org.codehaus.groovy.runtime.MethodClosure",
                 "org.springframework.beans.factory.ObjectFactory"
         };
+        // @formatter:off
         assertSerialization(addCloseable(
-                new ValidatingObjectInputStream(inputStream)
+                ValidatingObjectInputStream.builder()
+                .setInputStream(inputStream)
                 .accept("*")
                 .reject(blacklist)
+                .get()
         ));
+        // @formatter:on
     }
 }
\ No newline at end of file
diff --git 
a/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java
 
b/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java
index 75b204bcb..0d92908bf 100644
--- 
a/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java
+++ 
b/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java
@@ -33,6 +33,9 @@ import java.util.HashMap;
 import java.util.UUID;
 import java.util.regex.Pattern;
 
+import org.apache.commons.io.serialization.ValidatingObjectInputStream.Builder;
+import 
org.apache.commons.io.serialization.ValidatingObjectInputStream.ObjectStreamClassPredicate;
+import org.apache.commons.lang3.SerializationUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -51,8 +54,12 @@ public class ValidatingObjectInputStreamTest extends 
AbstractCloseableListTest {
         assertEquals(testObject, result);
     }
 
+    private Builder newBuilder() {
+        return 
ValidatingObjectInputStream.builder().setInputStream(testStream);
+    }
+
     private ValidatingObjectInputStream newFixture() throws IOException {
-        return 
ValidatingObjectInputStream.builder().setInputStream(testStream).get();
+        return newBuilder().get();
     }
 
     @BeforeEach
@@ -65,54 +72,55 @@ public class ValidatingObjectInputStreamTest extends 
AbstractCloseableListTest {
     }
 
     @Test
-    public void testAcceptCustomMatcher() throws Exception {
+    public void testAcceptCustomMatcherBuilder() throws Exception {
+        
assertSerialization(addCloseable(newBuilder().accept(ALWAYS_TRUE).get()));
+    }
+
+    @Test
+    public void testAcceptCustomMatcherInstance() throws Exception {
         assertSerialization(addCloseable(newFixture()).accept(ALWAYS_TRUE));
     }
 
     @Test
-    public void testAcceptPattern() throws Exception {
-        
assertSerialization(addCloseable(newFixture()).accept(Pattern.compile(".*MockSerializedClass.*")));
+    public void testAcceptOneFail() throws Exception {
+        assertThrows(InvalidClassException.class, () -> 
assertSerialization(addCloseable(newFixture()).accept(Integer.class)));
     }
 
     @Test
-    public void testAcceptWildcard() throws Exception {
-        
assertSerialization(addCloseable(newFixture()).accept("org.apache.commons.io.*"));
+    public void testAcceptOnePassBuilder() throws Exception {
+        
assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).get()));
     }
 
     @Test
-    public void testAcceptOnePass() throws Exception {
+    public void testAcceptOnePassInstance() throws Exception {
         
assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class));
     }
 
     @Test
-    public void testAcceptOneFail() throws Exception {
-        assertThrows(InvalidClassException.class, () -> 
assertSerialization(addCloseable(newFixture()).accept(Integer.class)));
+    public void testAcceptPatternBuilder() throws Exception {
+        
assertSerialization(addCloseable(newBuilder().accept(Pattern.compile(".*MockSerializedClass.*")).get()));
     }
 
-    /**
-     * Javadoc example.
-     */
-    @SuppressWarnings({ "unchecked", "resource" })
     @Test
-    public void testAcceptExample() throws Exception {
-        // Data
-        final HashMap<String, Integer> map1 = new HashMap<>();
-        map1.put("1", 1);
-        // Write
-        final byte[] byteArray;
-        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                final ObjectOutputStream oos = new ObjectOutputStream(baos)) {
-            oos.writeObject(map1);
-            oos.flush();
-            byteArray = baos.toByteArray();
-        }
-        // Read
-        try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
-                ValidatingObjectInputStream vois = 
ValidatingObjectInputStream.builder().setInputStream(bais).get()) {
-            // String.class is automatically accepted
-            vois.accept(HashMap.class, Number.class, Integer.class);
-            final HashMap<String, Integer> map2 = (HashMap<String, Integer>) 
vois.readObject();
-            assertEquals(map1, map2);
+    public void testAcceptPatternInstance() throws Exception {
+        
assertSerialization(addCloseable(newFixture()).accept(Pattern.compile(".*MockSerializedClass.*")));
+    }
+
+    @Test
+    public void testAcceptWildcardBuilder() throws Exception {
+        
assertSerialization(addCloseable(newBuilder().accept("org.apache.commons.io.*").get()));
+    }
+
+    @Test
+    public void testAcceptWildcardInstance() throws Exception {
+        
assertSerialization(addCloseable(newFixture()).accept("org.apache.commons.io.*"));
+    }
+
+    @Test
+    public void testBuildDefault() throws Exception {
+        final byte[] serialized = SerializationUtils.serialize("");
+        try (InputStream is = newBuilder().setInputStream(new 
ByteArrayInputStream(serialized)).get()) {
+            // empty
         }
     }
 
@@ -144,6 +152,49 @@ public class ValidatingObjectInputStreamTest extends 
AbstractCloseableListTest {
         assertTrue(ice.getMessage().contains(name), "Expecting message to 
contain " + name);
     }
 
+    /**
+     * Javadoc example.
+     */
+    @SuppressWarnings({ "unchecked" })
+    @Test
+    public void testJavadocExample() throws Exception {
+        // @formatter:off
+        // Defining Object fixture
+        final HashMap<String, Integer> map1 = new HashMap<>();
+        map1.put("1", 1);
+        // Writing serialized fixture
+        final byte[] byteArray;
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                final ObjectOutputStream oos = new ObjectOutputStream(baos)) {
+            oos.writeObject(map1);
+            oos.flush();
+            byteArray = baos.toByteArray();
+        }
+        // Reading
+        try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
+                ValidatingObjectInputStream vois = 
ValidatingObjectInputStream.builder()
+                        .accept(HashMap.class, Number.class, Integer.class)
+                        .setInputStream(bais)
+                        .get()) {
+            // String.class is automatically accepted
+            final HashMap<String, Integer> map2 = (HashMap<String, Integer>) 
vois.readObject();
+            assertEquals(map1, map2);
+        }
+        // Reusing a configuration
+        final ObjectStreamClassPredicate predicate = new 
ObjectStreamClassPredicate()
+                .accept(HashMap.class, Number.class, Integer.class);
+        try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
+                ValidatingObjectInputStream vois = 
ValidatingObjectInputStream.builder()
+                        .setPredicate(predicate)
+                        .setInputStream(bais)
+                        .get()) {
+            // String.class is automatically accepted
+            final HashMap<String, Integer> map2 = (HashMap<String, Integer>) 
vois.readObject();
+            assertEquals(map1, map2);
+        }
+        // @formatter:on
+    }
+
     @Test
     public void testNoAccept() {
         assertThrows(InvalidClassException.class, () -> 
assertSerialization(addCloseable(newFixture())));
@@ -180,16 +231,28 @@ public class ValidatingObjectInputStreamTest extends 
AbstractCloseableListTest {
     }
 
     @Test
-    public void testReject() {
+    public void testRejectBuilder() {
         assertThrows(InvalidClassException.class,
-                () -> 
assertSerialization(addCloseable(newFixture()).accept(Long.class).reject(MockSerializedClass.class,
 Integer.class)));
+                () -> 
assertSerialization(addCloseable(newBuilder().accept(Long.class).reject(MockSerializedClass.class,
 Integer.class).get())));
+    }
+
+    @Test
+    public void testRejectCustomMatcherBuilder() {
+        assertThrows(InvalidClassException.class,
+                () -> 
assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).reject(ALWAYS_TRUE).get())));
     }
 
     @Test
-    public void testRejectCustomMatcher() {
+    public void testRejectCustomMatcherInstance() {
         assertThrows(InvalidClassException.class, () -> 
assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject(ALWAYS_TRUE)));
     }
 
+    @Test
+    public void testRejectInstance() {
+        assertThrows(InvalidClassException.class,
+                () -> 
assertSerialization(addCloseable(newFixture()).accept(Long.class).reject(MockSerializedClass.class,
 Integer.class)));
+    }
+
     @Test
     public void testRejectOnly() {
         assertThrows(InvalidClassException.class, () -> 
assertSerialization(addCloseable(newFixture()).reject(Integer.class)));
@@ -202,13 +265,64 @@ public class ValidatingObjectInputStreamTest extends 
AbstractCloseableListTest {
     }
 
     @Test
-    public void testRejectPrecedence() {
+    public void testRejectPrecedenceBuilder() {
+        assertThrows(InvalidClassException.class,
+                () -> 
assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).reject(MockSerializedClass.class,
 Integer.class).get())));
+    }
+
+    @Test
+    public void testRejectPrecedenceInstance() {
         assertThrows(InvalidClassException.class,
                 () -> 
assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject(MockSerializedClass.class,
 Integer.class)));
     }
 
     @Test
-    public void testRejectWildcard() {
+    public void testRejectWildcardBuilder() {
+        assertThrows(InvalidClassException.class,
+                () -> 
assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).reject("org.*").get())));
+    }
+
+    @Test
+    public void testRejectWildcardInstance() {
         assertThrows(InvalidClassException.class, () -> 
assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject("org.*")));
     }
+
+    @Test
+    public void testReuseConfiguration() throws Exception {
+        // Defining Object fixture
+        final HashMap<String, Integer> map1 = new HashMap<>();
+        map1.put("1", 1);
+        // Writing serialized fixture
+        final byte[] byteArray;
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                final ObjectOutputStream oos = new ObjectOutputStream(baos)) {
+            oos.writeObject(map1);
+            oos.flush();
+            byteArray = baos.toByteArray();
+        }
+        // Reusing a configuration: ObjectStreamClassPredicate
+        final ObjectStreamClassPredicate predicate = new 
ObjectStreamClassPredicate().accept(HashMap.class, Number.class, Integer.class);
+        try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
+                ValidatingObjectInputStream vois = 
ValidatingObjectInputStream.builder().setPredicate(predicate).setInputStream(bais).get())
 {
+            // String.class is automatically accepted
+            assertEquals(map1, vois.readObjectCast());
+        }
+        try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
+                ValidatingObjectInputStream vois = 
ValidatingObjectInputStream.builder().setPredicate(predicate).setInputStream(bais).get())
 {
+            // String.class is automatically accepted
+            assertEquals(map1, vois.readObjectCast());
+        }
+        // Reusing a configuration: Builder and ObjectStreamClassPredicate
+        final Builder builder = 
ValidatingObjectInputStream.builder().setPredicate(predicate);
+        try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
+                ValidatingObjectInputStream vois = 
builder.setInputStream(bais).get()) {
+            // String.class is automatically accepted
+            assertEquals(map1, vois.readObjectCast());
+        }
+        try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
+                ValidatingObjectInputStream vois = 
builder.setInputStream(bais).get()) {
+            // String.class is automatically accepted
+            assertEquals(map1, vois.readObjectCast());
+        }
+    }
 }


Reply via email to