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

elecharny pushed a commit to branch 2.2.X
in repository https://gitbox.apache.org/repos/asf/mina.git

commit 691a9df5a0aff0dddeedc5181f6e5832ee90dcea
Author: emmanuel lecharny <elecha...@apache.org>
AuthorDate: Mon Nov 4 12:06:06 2024 +0100

    pom.xml
---
 mina-core/pom.xml                                  |   2 +-
 .../apache/mina/core/buffer/AbstractIoBuffer.java  |  79 +++-
 .../java/org/apache/mina/core/buffer/IoBuffer.java |  30 ++
 .../apache/mina/core/buffer/IoBufferWrapper.java   |  29 ++
 .../mina/core/buffer/matcher/ClassNameMatcher.java |  32 ++
 .../mina/core/buffer/matcher/FileSystem.java       | 526 +++++++++++++++++++++
 .../mina/core/buffer/matcher/FilenameUtils.java    | 174 +++++++
 .../core/buffer/matcher/FullClassNameMatcher.java  |  48 ++
 .../apache/mina/core/buffer/matcher/IOCase.java    | 275 +++++++++++
 .../buffer/matcher/RegexpClassNameMatcher.java     |  56 +++
 .../buffer/matcher/WildcardClassNameMatcher.java   |  45 ++
 .../org/apache/mina/core/buffer/IoBufferTest.java  |   6 +-
 pom.xml                                            |  13 +-
 13 files changed, 1308 insertions(+), 7 deletions(-)

diff --git a/mina-core/pom.xml b/mina-core/pom.xml
index 73c89ef53..c3d5a1b20 100644
--- a/mina-core/pom.xml
+++ b/mina-core/pom.xml
@@ -32,6 +32,7 @@
   <packaging>bundle</packaging>
 
   <dependencies>
+    <!-- Test dependencies -->
     <dependency>
       <groupId>org.easymock</groupId>
       <artifactId>easymock</artifactId>
@@ -112,4 +113,3 @@
     </plugins>
   </build>
 </project>
-
diff --git 
a/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java 
b/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java
index 54d068c4f..bd80469e9 100644
--- a/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java
+++ b/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java
@@ -43,8 +43,18 @@ import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.CoderResult;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.EnumSet;
+import java.util.List;
 import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import org.apache.mina.core.buffer.matcher.ClassNameMatcher;
+import org.apache.mina.core.buffer.matcher.FullClassNameMatcher;
+import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher;
+import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher;
+
 
 /**
  * A base implementation of {@link IoBuffer}. This implementation assumes that
@@ -80,6 +90,9 @@ public abstract class AbstractIoBuffer extends IoBuffer {
     /** A mask for an int */
     private static final long INT_MASK = 0xFFFFFFFFL;
 
+    private final List<ClassNameMatcher> acceptMatchers = new ArrayList<>();
+    private final List<ClassNameMatcher> rejectMatchers = new ArrayList<>();
+
     /**
      * We don't have any access to Buffer.markValue(), so we need to track it 
down,
      * which will cause small extra overhead.
@@ -2182,6 +2195,8 @@ public abstract class AbstractIoBuffer extends IoBuffer {
             @Override
             protected Class<?> resolveClass(ObjectStreamClass desc) throws 
IOException, ClassNotFoundException {
                 Class<?> clazz = desc.forClass();
+                
+                String[] classes = new String[] {"java.util.Date", "long", 
"java.util.ArrayList"};
 
                 if (clazz == null) {
                     String name = desc.getName();
@@ -2191,10 +2206,25 @@ public abstract class AbstractIoBuffer extends IoBuffer 
{
                         return super.resolveClass(desc);
                     }
                 } else {
-                    return clazz;
+                    boolean found = false;
+                    String className = desc.getName();
+                    
+                    for (ClassNameMatcher matcher : acceptMatchers) {
+                        if (matcher.matches(className)) {
+                            found = true;
+                            break;
+                        }
+                    }
+
+                    if (found) {
+                        return clazz;
+                    }
+                    
+                    throw new ClassNotFoundException();
                 }
             }
         }) {
+            //((ValidatingObjectInputStream)in).accept(Date.class, long.class, 
ArrayList.class);
             return in.readObject();
         } catch (IOException e) {
             throw new BufferDataException(e);
@@ -2747,4 +2777,51 @@ public abstract class AbstractIoBuffer extends IoBuffer {
             throw new IllegalArgumentException("fieldSize cannot be negative: 
" + fieldSize);
         }
     }
+
+    /**
+     * Accept the specified classes for deserialization, unless they
+     * are otherwise rejected.
+     *
+     * @param classes Classes to accept
+     * @return this object
+     */
+    public IoBuffer accept(Class<?>... classes) {
+        for (Class<?> clazz:classes) {
+            acceptMatchers.add(new FullClassNameMatcher(clazz.getName()));
+        }
+
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IoBuffer accept(ClassNameMatcher m) {
+        acceptMatchers.add(m);
+        
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IoBuffer accept(Pattern pattern) {
+        acceptMatchers.add(new RegexpClassNameMatcher(pattern));
+        
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IoBuffer accept(String... patterns) {
+        for (String pattern:patterns) {
+            acceptMatchers.add(new WildcardClassNameMatcher(pattern));
+        }
+        
+        return this;
+    }
 }
diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java 
b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java
index 1ac600d85..6cda800cb 100644
--- a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java
+++ b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java
@@ -36,7 +36,9 @@ import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CharsetEncoder;
 import java.util.EnumSet;
 import java.util.Set;
+import java.util.regex.Pattern;
 
+import org.apache.mina.core.buffer.matcher.ClassNameMatcher;
 import org.apache.mina.core.session.IoSession;
 
 /**
@@ -2107,4 +2109,32 @@ public abstract class IoBuffer implements 
Comparable<IoBuffer> {
      * @return the modified IoBuffer
      */
     public abstract <E extends Enum<E>> IoBuffer putEnumSetLong(int index, 
Set<E> set);
+    
+    /**
+     * Accept class names where the supplied ClassNameMatcher matches for
+     * deserialization, unless they are otherwise rejected.
+     *
+     * @param m the matcher to use
+     * @return this object
+     */
+    public abstract IoBuffer accept(ClassNameMatcher m);
+
+    /**
+     * Accept class names that match the supplied pattern for
+     * deserialization, unless they are otherwise rejected.
+     *
+     * @param pattern standard Java regexp
+     * @return this object
+     */
+    public abstract IoBuffer accept(Pattern pattern);
+
+    /**
+     * Accept 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 object
+     */
+    public abstract IoBuffer accept(String... patterns);
 }
diff --git 
a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java 
b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java
index 437483fb8..e53081103 100644
--- a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java
+++ b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java
@@ -34,6 +34,11 @@ import java.nio.charset.CharacterCodingException;
 import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CharsetEncoder;
 import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.apache.mina.core.buffer.matcher.ClassNameMatcher;
+import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher;
+import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher;
 
 /**
  * A {@link IoBuffer} that wraps a buffer and proxies any operations to it.
@@ -1535,4 +1540,28 @@ public class IoBufferWrapper extends IoBuffer {
         buf.putUnsigned(index, value);
         return this;
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IoBuffer accept(ClassNameMatcher m) {
+        return buf.accept(m);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IoBuffer accept(Pattern pattern) {
+        return buf.accept(pattern);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IoBuffer accept(String... patterns) {
+        return buf.accept(patterns);
+    }
 }
diff --git 
a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/ClassNameMatcher.java
 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/ClassNameMatcher.java
new file mode 100644
index 000000000..44da8ff77
--- /dev/null
+++ 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/ClassNameMatcher.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.mina.core.buffer.matcher;
+
+/**
+ * An object that matches a Class name to a condition.
+ */
+public interface ClassNameMatcher {
+    /**
+     * Returns {@code true} if the supplied class name matches this object's 
condition.
+     *
+     * @param className fully qualified class name
+     * @return {@code true} if the class name matches this object's condition
+     */
+    boolean matches(String className);
+}
\ No newline at end of file
diff --git 
a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FileSystem.java 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FileSystem.java
new file mode 100644
index 000000000..38212c791
--- /dev/null
+++ 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FileSystem.java
@@ -0,0 +1,526 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.mina.core.buffer.matcher;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Abstracts an OS' file system details, currently supporting the single use 
case of converting a file name String to a
+ * legal file name with {@link #toLegalFileName(String, char)}.
+ * <p>
+ * The starting point of any operation is {@link #getCurrent()} which gets you 
the enum for the file system that matches
+ * the OS hosting the running JVM.
+ * </p>
+ *
+ * @since 2.7
+ */
+public enum FileSystem {
+
+    /**
+     * Generic file system.
+     */
+    GENERIC(4096, false, false, Integer.MAX_VALUE, Integer.MAX_VALUE, new 
int[] { 0 }, new String[] {}, false, false, '/'),
+
+    /**
+     * Linux file system.
+     */
+    LINUX(8192, true, true, 255, 4096, new int[] {
+            // KEEP THIS ARRAY SORTED!
+            // @formatter:off
+            // ASCII NUL
+            0,
+             '/'
+            // @formatter:on
+    }, new String[] {}, false, false, '/'),
+
+    /**
+     * MacOS file system.
+     */
+    MAC_OSX(4096, true, true, 255, 1024, new int[] {
+            // KEEP THIS ARRAY SORTED!
+            // @formatter:off
+            // ASCII NUL
+            0,
+            '/',
+             ':'
+            // @formatter:on
+    }, new String[] {}, false, false, '/'),
+
+    /**
+     * Windows file system.
+     * <p>
+     * The reserved characters are defined in the
+     * <a 
href="https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file";>Naming
 Conventions
+     * (microsoft.com)</a>.
+     * </p>
+     *
+     * @see <a 
href="https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file";>Naming
 Conventions
+     *      (microsoft.com)</a>
+     * @see <a 
href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#consoles";>
+     *      CreateFileA function - Consoles (microsoft.com)</a>
+     */
+    WINDOWS(4096, false, true,
+            255, 32000, // KEEP THIS ARRAY SORTED!
+            new int[] {
+                    // KEEP THIS ARRAY SORTED!
+                    // @formatter:off
+                    // ASCII NUL
+                    0,
+                    // 1-31 may be allowed in file streams
+                    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
+                    29, 30, 31,
+                    '"', '*', '/', ':', '<', '>', '?', '\\', '|'
+                    // @formatter:on
+            }, new String[] { "AUX", "COM1", "COM2", "COM3", "COM4", "COM5", 
"COM6", "COM7", "COM8", "COM9", "CON", "CONIN$", "CONOUT$",
+                            "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", 
"LPT7", "LPT8", "LPT9", "NUL", "PRN" }, true, true, '\\');
+
+    /**
+     * <p>
+     * Is {@code true} if this is Linux.
+     * </p>
+     * <p>
+     * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+     * </p>
+     */
+    private static final boolean IS_OS_LINUX = getOsMatchesName("Linux");
+
+    /**
+     * <p>
+     * Is {@code true} if this is Mac.
+     * </p>
+     * <p>
+     * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+     * </p>
+     */
+    private static final boolean IS_OS_MAC = getOsMatchesName("Mac");
+
+    /**
+     * The prefix String for all Windows OS.
+     */
+    private static final String OS_NAME_WINDOWS_PREFIX = "Windows";
+
+    /**
+     * <p>
+     * Is {@code true} if this is Windows.
+     * </p>
+     * <p>
+     * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+     * </p>
+     */
+    private static final boolean IS_OS_WINDOWS = 
getOsMatchesName(OS_NAME_WINDOWS_PREFIX);
+
+    /**
+     * The current FileSystem.
+     */
+    private static final FileSystem CURRENT = current();
+
+    /**
+     * Gets the current file system.
+     *
+     * @return the current file system
+     */
+    private static FileSystem current() {
+        if (IS_OS_LINUX) {
+            return LINUX;
+        }
+        if (IS_OS_MAC) {
+            return MAC_OSX;
+        }
+        if (IS_OS_WINDOWS) {
+            return WINDOWS;
+        }
+        return GENERIC;
+    }
+
+    /**
+     * Gets the current file system.
+     *
+     * @return the current file system
+     */
+    public static FileSystem getCurrent() {
+        return CURRENT;
+    }
+
+    /**
+     * Decides if the operating system matches.
+     *
+     * @param osNamePrefix
+     *            the prefix for the os name
+     * @return true if matches, or false if not or can't determine
+     */
+    private static boolean getOsMatchesName(final String osNamePrefix) {
+        return isOsNameMatch(getSystemProperty("os.name"), osNamePrefix);
+    }
+
+    /**
+     * <p>
+     * Gets a System property, defaulting to {@code null} if the property 
cannot be read.
+     * </p>
+     * <p>
+     * If a {@link SecurityException} is caught, the return value is {@code 
null} and a message is written to
+     * {@code System.err}.
+     * </p>
+     *
+     * @param property
+     *            the system property name
+     * @return the system property value or {@code null} if a security problem 
occurs
+     */
+    private static String getSystemProperty(final String property) {
+        try {
+            return System.getProperty(property);
+        } catch (final SecurityException ex) {
+            // we are not allowed to look at this property
+            System.err.println("Caught a SecurityException reading the system 
property '" + property
+                    + "'; the SystemUtils property value will default to 
null.");
+            return null;
+        }
+    }
+
+    /**
+     * Copied from Apache Commons Lang CharSequenceUtils.
+     *
+     * Returns the index within {@code cs} of the first occurrence of the
+     * specified character, starting the search at the specified index.
+     * <p>
+     * If a character with value {@code searchChar} occurs in the
+     * character sequence represented by the {@code cs}
+     * object at an index no smaller than {@code start}, then
+     * the index of the first such occurrence is returned. For values
+     * of {@code searchChar} in the range from 0 to 0xFFFF (inclusive),
+     * this is the smallest value <i>k</i> such that:
+     * </p>
+     * <blockquote><pre>
+     * (this.charAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &gt;= start)
+     * </pre></blockquote>
+     * is true. For other values of {@code searchChar}, it is the
+     * smallest value <i>k</i> such that:
+     * <blockquote><pre>
+     * (this.codePointAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &gt;= 
start)
+     * </pre></blockquote>
+     * <p>
+     * is true. In either case, if no such character occurs inm {@code cs}
+     * at or after position {@code start}, then
+     * {@code -1} is returned.
+     * </p>
+     * <p>
+     * There is no restriction on the value of {@code start}. If it
+     * is negative, it has the same effect as if it were zero: the entire
+     * {@link CharSequence} may be searched. If it is greater than
+     * the length of {@code cs}, it has the same effect as if it were
+     * equal to the length of {@code cs}: {@code -1} is returned.
+     * </p>
+     * <p>All indices are specified in {@code char} values
+     * (Unicode code units).
+     * </p>
+     *
+     * @param cs  the {@link CharSequence} to be processed, not null
+     * @param searchChar  the char to be searched for
+     * @param start  the start index, negative starts at the string start
+     * @return the index where the search char was found, -1 if not found
+     * @since 3.6 updated to behave more like {@link String}
+     */
+    private static int indexOf(final CharSequence cs, final int searchChar, 
int start) {
+        if (cs instanceof String) {
+            return ((String) cs).indexOf(searchChar, start);
+        }
+        final int sz = cs.length();
+        if (start < 0) {
+            start = 0;
+        }
+        if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
+            for (int i = start; i < sz; i++) {
+                if (cs.charAt(i) == searchChar) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+        //supplementary characters (LANG1300)
+        if (searchChar <= Character.MAX_CODE_POINT) {
+            final char[] chars = Character.toChars(searchChar);
+            for (int i = start; i < sz - 1; i++) {
+                final char high = cs.charAt(i);
+                final char low = cs.charAt(i + 1);
+                if (high == chars[0] && low == chars[1]) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Decides if the operating system matches.
+     * <p>
+     * This method is package private instead of private to support unit test 
invocation.
+     * </p>
+     *
+     * @param osName
+     *            the actual OS name
+     * @param osNamePrefix
+     *            the prefix for the expected OS name
+     * @return true if matches, or false if not or can't determine
+     */
+    private static boolean isOsNameMatch(final String osName, final String 
osNamePrefix) {
+        if (osName == null) {
+            return false;
+        }
+        return 
osName.toUpperCase(Locale.ROOT).startsWith(osNamePrefix.toUpperCase(Locale.ROOT));
+    }
+
+    /**
+     * Null-safe replace.
+     *
+     * @param path the path to be changed, null ignored.
+     * @param oldChar the old character.
+     * @param newChar the new character.
+     * @return the new path.
+     */
+    private static String replace(final String path, final char oldChar, final 
char newChar) {
+        return path == null ? null : path.replace(oldChar, newChar);
+    }
+
+    private final int blockSize;
+    private final boolean casePreserving;
+    private final boolean caseSensitive;
+    private final int[] illegalFileNameChars;
+    private final int maxFileNameLength;
+    private final int maxPathLength;
+    private final String[] reservedFileNames;
+    private final boolean reservedFileNamesExtensions;
+    private final boolean supportsDriveLetter;
+    private final char nameSeparator;
+    private final char nameSeparatorOther;
+
+    /**
+     * Constructs a new instance.
+     *
+     * @param blockSize file allocation block size in bytes.
+     * @param caseSensitive Whether this file system is case-sensitive.
+     * @param casePreserving Whether this file system is case-preserving.
+     * @param maxFileLength The maximum length for file names. The file name 
does not include folders.
+     * @param maxPathLength The maximum length of the path to a file. This can 
include folders.
+     * @param illegalFileNameChars Illegal characters for this file system.
+     * @param reservedFileNames The reserved file names.
+     * @param reservedFileNamesExtensions TODO
+     * @param supportsDriveLetter Whether this file system support driver 
letters.
+     * @param nameSeparator The name separator, '\\' on Windows, '/' on Linux.
+     */
+    FileSystem(final int blockSize, final boolean caseSensitive, final boolean 
casePreserving,
+        final int maxFileLength, final int maxPathLength, final int[] 
illegalFileNameChars,
+        final String[] reservedFileNames, final boolean 
reservedFileNamesExtensions, final boolean supportsDriveLetter, final char 
nameSeparator) {
+        this.blockSize = blockSize;
+        this.maxFileNameLength = maxFileLength;
+        this.maxPathLength = maxPathLength;
+        this.illegalFileNameChars = 
Objects.requireNonNull(illegalFileNameChars, "illegalFileNameChars");
+        this.reservedFileNames = Objects.requireNonNull(reservedFileNames, 
"reservedFileNames");
+        this.reservedFileNamesExtensions = reservedFileNamesExtensions;
+        this.caseSensitive = caseSensitive;
+        this.casePreserving = casePreserving;
+        this.supportsDriveLetter = supportsDriveLetter;
+        this.nameSeparator = nameSeparator;
+        this.nameSeparatorOther = FilenameUtils.flipSeparator(nameSeparator);
+    }
+
+    /**
+     * Gets the file allocation block size in bytes.
+     * @return the file allocation block size in bytes.
+     *
+     * @since 2.12.0
+     */
+    public int getBlockSize() {
+        return blockSize;
+    }
+
+    /**
+     * Gets a cloned copy of the illegal characters for this file system.
+     *
+     * @return the illegal characters for this file system.
+     */
+    public char[] getIllegalFileNameChars() {
+        final char[] chars = new char[illegalFileNameChars.length];
+        for (int i = 0; i < illegalFileNameChars.length; i++) {
+            chars[i] = (char) illegalFileNameChars[i];
+        }
+        return chars;
+    }
+
+    /**
+     * Gets a cloned copy of the illegal code points for this file system.
+     *
+     * @return the illegal code points for this file system.
+     * @since 2.12.0
+     */
+    public int[] getIllegalFileNameCodePoints() {
+        return this.illegalFileNameChars.clone();
+    }
+
+    /**
+     * Gets the maximum length for file names. The file name does not include 
folders.
+     *
+     * @return the maximum length for file names.
+     */
+    public int getMaxFileNameLength() {
+        return maxFileNameLength;
+    }
+
+    /**
+     * Gets the maximum length of the path to a file. This can include folders.
+     *
+     * @return the maximum length of the path to a file.
+     */
+    public int getMaxPathLength() {
+        return maxPathLength;
+    }
+
+    /**
+     * Gets the name separator, '\\' on Windows, '/' on Linux.
+     *
+     * @return '\\' on Windows, '/' on Linux.
+     *
+     * @since 2.12.0
+     */
+    public char getNameSeparator() {
+        return nameSeparator;
+    }
+
+    /**
+     * Gets a cloned copy of the reserved file names.
+     *
+     * @return the reserved file names.
+     */
+    public String[] getReservedFileNames() {
+        return reservedFileNames.clone();
+    }
+
+    /**
+     * Tests whether this file system preserves case.
+     *
+     * @return Whether this file system preserves case.
+     */
+    public boolean isCasePreserving() {
+        return casePreserving;
+    }
+
+    /**
+     * Tests whether this file system is case-sensitive.
+     *
+     * @return Whether this file system is case-sensitive.
+     */
+    public boolean isCaseSensitive() {
+        return caseSensitive;
+    }
+
+    /**
+     * Tests if the given character is illegal in a file name, {@code false} 
otherwise.
+     *
+     * @param c
+     *            the character to test
+     * @return {@code true} if the given character is illegal in a file name, 
{@code false} otherwise.
+     */
+    private boolean isIllegalFileNameChar(final int c) {
+        return Arrays.binarySearch(illegalFileNameChars, c) >= 0;
+    }
+
+    /**
+     * Tests if a candidate file name (without a path) such as {@code 
"filename.ext"} or {@code "filename"} is a
+     * potentially legal file name. If the file name length exceeds {@link 
#getMaxFileNameLength()}, or if it contains
+     * an illegal character then the check fails.
+     *
+     * @param candidate
+     *            a candidate file name (without a path) like {@code 
"filename.ext"} or {@code "filename"}
+     * @return {@code true} if the candidate name is legal
+     */
+    public boolean isLegalFileName(final CharSequence candidate) {
+        if (candidate == null || candidate.length() == 0 || candidate.length() 
> maxFileNameLength) {
+            return false;
+        }
+        if (isReservedFileName(candidate)) {
+            return false;
+        }
+        return candidate.chars().noneMatch(this::isIllegalFileNameChar);
+    }
+
+    /**
+     * Tests whether the given string is a reserved file name.
+     *
+     * @param candidate
+     *            the string to test
+     * @return {@code true} if the given string is a reserved file name.
+     */
+    public boolean isReservedFileName(final CharSequence candidate) {
+        final CharSequence test = reservedFileNamesExtensions ? 
trimExtension(candidate) : candidate;
+        return Arrays.binarySearch(reservedFileNames, test) >= 0;
+    }
+
+    /**
+     * Converts all separators to the Windows separator of backslash.
+     *
+     * @param path the path to be changed, null ignored
+     * @return the updated path
+     * @since 2.12.0
+     */
+    public String normalizeSeparators(final String path) {
+        return replace(path, nameSeparatorOther, nameSeparator);
+    }
+
+    /**
+     * Tests whether this file system support driver letters.
+     * <p>
+     * Windows supports driver letters as do other operating systems. Whether 
these other OS's still support Java like
+     * OS/2, is a different matter.
+     * </p>
+     *
+     * @return whether this file system support driver letters.
+     * @since 2.9.0
+     * @see <a 
href="https://en.wikipedia.org/wiki/Drive_letter_assignment";>Operating systems 
that use drive letter
+     *      assignment</a>
+     */
+    public boolean supportsDriveLetter() {
+        return supportsDriveLetter;
+    }
+
+    /**
+     * Converts a candidate file name (without a path) like {@code 
"filename.ext"} or {@code "filename"} to a legal file
+     * name. Illegal characters in the candidate name are replaced by the 
{@code replacement} character. If the file
+     * name length exceeds {@link #getMaxFileNameLength()}, then the name is 
truncated to
+     * {@link #getMaxFileNameLength()}.
+     *
+     * @param candidate
+     *            a candidate file name (without a path) like {@code 
"filename.ext"} or {@code "filename"}
+     * @param replacement
+     *            Illegal characters in the candidate name are replaced by 
this character
+     * @return a String without illegal characters
+     */
+    public String toLegalFileName(final String candidate, final char 
replacement) {
+        if (isIllegalFileNameChar(replacement)) {
+            // %s does not work properly with NUL
+            throw new IllegalArgumentException(String.format("The replacement 
character '%s' cannot be one of the %s illegal characters: %s",
+                replacement == '\0' ? "\\0" : replacement, name(), 
Arrays.toString(illegalFileNameChars)));
+        }
+        final String truncated = candidate.length() > maxFileNameLength ? 
candidate.substring(0, maxFileNameLength) : candidate;
+        final int[] array = truncated.chars().map(i -> 
isIllegalFileNameChar(i) ? replacement : i).toArray();
+        return new String(array, 0, array.length);
+    }
+
+    CharSequence trimExtension(final CharSequence cs) {
+        final int index = indexOf(cs, '.', 0);
+        return index < 0 ? cs : cs.subSequence(0, index);
+    }
+}
diff --git 
a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FilenameUtils.java
 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FilenameUtils.java
new file mode 100644
index 000000000..9ff67ca05
--- /dev/null
+++ 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FilenameUtils.java
@@ -0,0 +1,174 @@
+package org.apache.mina.core.buffer.matcher;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+
+public class FilenameUtils
+{
+    private static final int NOT_FOUND = -1;
+
+    private static final String[] EMPTY_STRING_ARRAY = {};
+
+    /**
+     * The Unix separator character.
+     */
+    private static final char UNIX_NAME_SEPARATOR = '/';
+
+    /**
+     * The Windows separator character.
+     */
+    private static final char WINDOWS_NAME_SEPARATOR = '\\';
+
+    /**
+     * Checks a fileName to see if it matches the specified wildcard matcher
+     * allowing control over case-sensitivity.
+     * <p>
+     * The wildcard matcher uses the characters '?' and '*' to represent a
+     * single or multiple (zero or more) wildcard characters.
+     * N.B. the sequence "*?" does not work properly at present in match 
strings.
+     *
+     * @param fileName  the fileName to match on
+     * @param wildcardMatcher  the wildcard string to match against
+     * @param ioCase  what case sensitivity rule to use, null means 
case-sensitive
+     * @return true if the fileName matches the wildcard string
+     * @since 1.3
+     */
+    public static boolean wildcardMatch(final String fileName, final String 
wildcardMatcher, IOCase ioCase) {
+        if (fileName == null && wildcardMatcher == null) {
+            return true;
+        }
+        if (fileName == null || wildcardMatcher == null) {
+            return false;
+        }
+        ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
+        final String[] wcs = splitOnTokens(wildcardMatcher);
+        boolean anyChars = false;
+        int textIdx = 0;
+        int wcsIdx = 0;
+        final Deque<int[]> backtrack = new ArrayDeque<>(wcs.length);
+
+        // loop around a backtrack stack, to handle complex * matching
+        do {
+            if (!backtrack.isEmpty()) {
+                final int[] array = backtrack.pop();
+                wcsIdx = array[0];
+                textIdx = array[1];
+                anyChars = true;
+            }
+
+            // loop whilst tokens and text left to process
+            while (wcsIdx < wcs.length) {
+
+                if (wcs[wcsIdx].equals("?")) {
+                    // ? so move to next text char
+                    textIdx++;
+                    if (textIdx > fileName.length()) {
+                        break;
+                    }
+                    anyChars = false;
+
+                } else if (wcs[wcsIdx].equals("*")) {
+                    // set any chars status
+                    anyChars = true;
+                    if (wcsIdx == wcs.length - 1) {
+                        textIdx = fileName.length();
+                    }
+
+                } else {
+                    // matching text token
+                    if (anyChars) {
+                        // any chars then try to locate text token
+                        textIdx = ioCase.checkIndexOf(fileName, textIdx, 
wcs[wcsIdx]);
+                        if (textIdx == NOT_FOUND) {
+                            // token not found
+                            break;
+                        }
+                        final int repeat = ioCase.checkIndexOf(fileName, 
textIdx + 1, wcs[wcsIdx]);
+                        if (repeat >= 0) {
+                            backtrack.push(new int[] {wcsIdx, repeat});
+                        }
+                    } else if (!ioCase.checkRegionMatches(fileName, textIdx, 
wcs[wcsIdx])) {
+                        // matching from current position
+                        // couldn't match token
+                        break;
+                    }
+
+                    // matched text token, move text index to end of matched 
token
+                    textIdx += wcs[wcsIdx].length();
+                    anyChars = false;
+                }
+
+                wcsIdx++;
+            }
+
+            // full match
+            if (wcsIdx == wcs.length && textIdx == fileName.length()) {
+                return true;
+            }
+
+        } while (!backtrack.isEmpty());
+
+        return false;
+    }
+
+
+    /**
+     * Splits a string into a number of tokens.
+     * The text is split by '?' and '*'.
+     * Where multiple '*' occur consecutively they are collapsed into a single 
'*'.
+     *
+     * @param text  the text to split
+     * @return the array of tokens, never null
+     */
+    static String[] splitOnTokens(final String text) {
+        // used by wildcardMatch
+        // package level so a unit test may run on this
+
+        if (text.indexOf('?') == NOT_FOUND && text.indexOf('*') == NOT_FOUND) {
+            return new String[] { text };
+        }
+
+        final char[] array = text.toCharArray();
+        final ArrayList<String> list = new ArrayList<>();
+        final StringBuilder buffer = new StringBuilder();
+        char prevChar = 0;
+        for (final char ch : array) {
+            if (ch == '?' || ch == '*') {
+                if (buffer.length() != 0) {
+                    list.add(buffer.toString());
+                    buffer.setLength(0);
+                }
+                if (ch == '?') {
+                    list.add("?");
+                } else if (prevChar != '*') {// ch == '*' here; check if 
previous char was '*'
+                    list.add("*");
+                }
+            } else {
+                buffer.append(ch);
+            }
+            prevChar = ch;
+        }
+        if (buffer.length() != 0) {
+            list.add(buffer.toString());
+        }
+
+        return list.toArray(EMPTY_STRING_ARRAY);
+    }
+
+    /**
+     * Flips the Windows name separator to Linux and vice-versa.
+     *
+     * @param ch The Windows or Linux name separator.
+     * @return The Windows or Linux name separator.
+     */
+    static char flipSeparator(final char ch) {
+        if (ch == UNIX_NAME_SEPARATOR) {
+            return WINDOWS_NAME_SEPARATOR;
+        }
+        if (ch == WINDOWS_NAME_SEPARATOR) {
+            return UNIX_NAME_SEPARATOR;
+        }
+        throw new IllegalArgumentException(String.valueOf(ch));
+    }
+}
diff --git 
a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FullClassNameMatcher.java
 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FullClassNameMatcher.java
new file mode 100644
index 000000000..1f4d07775
--- /dev/null
+++ 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FullClassNameMatcher.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.mina.core.buffer.matcher;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A {@link ClassNameMatcher} that matches on full class names.
+ * <p>
+ * This object is immutable and thread-safe.
+ * </p>
+ */
+public final class FullClassNameMatcher implements ClassNameMatcher {
+    private final Set<String> classesSet;
+
+    /**
+     * Constructs an object based on the specified class names.
+     *
+     * @param classes a list of class names
+     */
+    public FullClassNameMatcher(String... classes) {
+        classesSet = Collections.unmodifiableSet(new 
HashSet<>(Arrays.asList(classes)));
+    }
+
+    @Override
+    public boolean matches(String className) {
+        return classesSet.contains(className);
+    }
+}
\ No newline at end of file
diff --git 
a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/IOCase.java 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/IOCase.java
new file mode 100644
index 000000000..b2a1c89cd
--- /dev/null
+++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/IOCase.java
@@ -0,0 +1,275 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.mina.core.buffer.matcher;
+
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * Enumeration of IO case sensitivity.
+ * <p>
+ * Different filing systems have different rules for case-sensitivity.
+ * Windows is case-insensitive, Unix is case-sensitive.
+ * </p>
+ * <p>
+ * This class captures that difference, providing an enumeration to
+ * control how file name comparisons should be performed. It also provides
+ * methods that use the enumeration to perform comparisons.
+ * </p>
+ * <p>
+ * Wherever possible, you should use the {@code check} methods in this
+ * class to compare file names.
+ * </p>
+ *
+ * @since 1.3
+ */
+public enum IOCase {
+
+    /**
+     * The constant for case-sensitive regardless of operating system.
+     */
+    SENSITIVE("Sensitive", true),
+
+    /**
+     * The constant for case-insensitive regardless of operating system.
+     */
+    INSENSITIVE("Insensitive", false),
+
+    /**
+     * The constant for case sensitivity determined by the current operating 
system.
+     * Windows is case-insensitive when comparing file names, Unix is 
case-sensitive.
+     * <p>
+     * <strong>Note:</strong> This only caters for Windows and Unix. Other 
operating
+     * systems (e.g. OSX and OpenVMS) are treated as case-sensitive if they 
use the
+     * Unix file separator and case-insensitive if they use the Windows file 
separator
+     * (see {@link java.io.File#separatorChar}).
+     * </p>
+     * <p>
+     * If you serialize this constant on Windows, and deserialize on Unix, or 
vice
+     * versa, then the value of the case-sensitivity flag will change.
+     * </p>
+     */
+    SYSTEM("System", FileSystem.getCurrent().isCaseSensitive());
+
+    /** Serialization version. */
+    private static final long serialVersionUID = -6343169151696340687L;
+
+    /**
+     * Factory method to create an IOCase from a name.
+     *
+     * @param name  the name to find
+     * @return the IOCase object
+     * @throws IllegalArgumentException if the name is invalid
+     */
+    public static IOCase forName(final String name) {
+        return Stream.of(IOCase.values()).filter(ioCase -> 
ioCase.getName().equals(name)).findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("Illegal 
IOCase name: " + name));
+    }
+
+    /**
+     * Tests for cases sensitivity in a null-safe manner.
+     *
+     * @param ioCase an IOCase.
+     * @return true if the input is non-null and {@link #isCaseSensitive()}.
+     * @since 2.10.0
+     */
+    public static boolean isCaseSensitive(final IOCase ioCase) {
+        return ioCase != null && ioCase.isCaseSensitive();
+    }
+
+    /**
+     * Returns the given value if not-null, the defaultValue if null.
+     *
+     * @param value the value to test.
+     * @param defaultValue the default value.
+     * @return the given value if not-null, the defaultValue if null.
+     * @since 2.12.0
+     */
+    public static IOCase value(final IOCase value, final IOCase defaultValue) {
+        return value != null ? value : defaultValue;
+    }
+
+    /** The enumeration name. */
+    private final String name;
+
+    /** The sensitivity flag. */
+    private final transient boolean sensitive;
+
+    /**
+     * Constructs a new instance.
+     *
+     * @param name  the name
+     * @param sensitive  the sensitivity
+     */
+    IOCase(final String name, final boolean sensitive) {
+        this.name = name;
+        this.sensitive = sensitive;
+    }
+
+    /**
+     * Compares two strings using the case-sensitivity rule.
+     * <p>
+     * This method mimics {@link String#compareTo} but takes case-sensitivity
+     * into account.
+     * </p>
+     *
+     * @param str1  the first string to compare, not null
+     * @param str2  the second string to compare, not null
+     * @return true if equal using the case rules
+     * @throws NullPointerException if either string is null
+     */
+    public int checkCompareTo(final String str1, final String str2) {
+        Objects.requireNonNull(str1, "str1");
+        Objects.requireNonNull(str2, "str2");
+        return sensitive ? str1.compareTo(str2) : 
str1.compareToIgnoreCase(str2);
+    }
+
+    /**
+     * Checks if one string ends with another using the case-sensitivity rule.
+     * <p>
+     * This method mimics {@link String#endsWith} but takes case-sensitivity
+     * into account.
+     * </p>
+     *
+     * @param str  the string to check
+     * @param end  the end to compare against
+     * @return true if equal using the case rules, false if either input is 
null
+     */
+    public boolean checkEndsWith(final String str, final String end) {
+        if (str == null || end == null) {
+            return false;
+        }
+        final int endLen = end.length();
+        return str.regionMatches(!sensitive, str.length() - endLen, end, 0, 
endLen);
+    }
+
+    /**
+     * Compares two strings using the case-sensitivity rule.
+     * <p>
+     * This method mimics {@link String#equals} but takes case-sensitivity
+     * into account.
+     * </p>
+     *
+     * @param str1  the first string to compare, not null
+     * @param str2  the second string to compare, not null
+     * @return true if equal using the case rules
+     * @throws NullPointerException if either string is null
+     */
+    public boolean checkEquals(final String str1, final String str2) {
+        Objects.requireNonNull(str1, "str1");
+        Objects.requireNonNull(str2, "str2");
+        return sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2);
+    }
+
+    /**
+     * Checks if one string contains another starting at a specific index 
using the
+     * case-sensitivity rule.
+     * <p>
+     * This method mimics parts of {@link String#indexOf(String, int)}
+     * but takes case-sensitivity into account.
+     * </p>
+     *
+     * @param str  the string to check, not null
+     * @param strStartIndex  the index to start at in str
+     * @param search  the start to search for, not null
+     * @return the first index of the search String,
+     *  -1 if no match or {@code null} string input
+     * @throws NullPointerException if either string is null
+     * @since 2.0
+     */
+    public int checkIndexOf(final String str, final int strStartIndex, final 
String search) {
+        final int endIndex = str.length() - search.length();
+        if (endIndex >= strStartIndex) {
+            for (int i = strStartIndex; i <= endIndex; i++) {
+                if (checkRegionMatches(str, i, search)) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Checks if one string contains another at a specific index using the 
case-sensitivity rule.
+     * <p>
+     * This method mimics parts of {@link String#regionMatches(boolean, int, 
String, int, int)}
+     * but takes case-sensitivity into account.
+     * </p>
+     *
+     * @param str  the string to check, not null
+     * @param strStartIndex  the index to start at in str
+     * @param search  the start to search for, not null
+     * @return true if equal using the case rules
+     * @throws NullPointerException if either string is null
+     */
+    public boolean checkRegionMatches(final String str, final int 
strStartIndex, final String search) {
+        return str.regionMatches(!sensitive, strStartIndex, search, 0, 
search.length());
+    }
+
+    /**
+     * Checks if one string starts with another using the case-sensitivity 
rule.
+     * <p>
+     * This method mimics {@link String#startsWith(String)} but takes 
case-sensitivity
+     * into account.
+     * </p>
+     *
+     * @param str  the string to check
+     * @param start  the start to compare against
+     * @return true if equal using the case rules, false if either input is 
null
+     */
+    public boolean checkStartsWith(final String str, final String start) {
+        return str != null && start != null && str.regionMatches(!sensitive, 
0, start, 0, start.length());
+    }
+
+    /**
+     * Gets the name of the constant.
+     *
+     * @return the name of the constant
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Does the object represent case-sensitive comparison.
+     *
+     * @return true if case-sensitive
+     */
+    public boolean isCaseSensitive() {
+        return sensitive;
+    }
+
+    /**
+     * Replaces the enumeration from the stream with a real one.
+     * This ensures that the correct flag is set for SYSTEM.
+     *
+     * @return the resolved object
+     */
+    private Object readResolve() {
+        return forName(name);
+    }
+
+    /**
+     * Gets a string describing the sensitivity.
+     *
+     * @return a string describing the sensitivity
+     */
+    @Override
+    public String toString() {
+        return name;
+    }
+}
diff --git 
a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/RegexpClassNameMatcher.java
 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/RegexpClassNameMatcher.java
new file mode 100644
index 000000000..bb854245d
--- /dev/null
+++ 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/RegexpClassNameMatcher.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.mina.core.buffer.matcher;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link ClassNameMatcher} that uses regular expressions.
+ * <p>
+ * This object is immutable and thread-safe.
+ * </p>
+ */
+public final class RegexpClassNameMatcher implements ClassNameMatcher {
+    private final Pattern pattern; // Class is thread-safe
+
+    /**
+     * Constructs an object based on the specified pattern.
+     *
+     * @param pattern a pattern for evaluating acceptable class names
+     * @throws NullPointerException if {@code pattern} is null
+     */
+    public RegexpClassNameMatcher(Pattern pattern) {
+        this.pattern = Objects.requireNonNull(pattern, "pattern");
+    }
+
+    /**
+     * Constructs an object based on the specified regular expression.
+     *
+     * @param regex a regular expression for evaluating acceptable class names
+     */
+    public RegexpClassNameMatcher(String regex) {
+        this(Pattern.compile(regex));
+    }
+
+    @Override
+    public boolean matches(String className) {
+        return pattern.matcher(className).matches();
+    }
+}
\ No newline at end of file
diff --git 
a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/WildcardClassNameMatcher.java
 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/WildcardClassNameMatcher.java
new file mode 100644
index 000000000..36e607138
--- /dev/null
+++ 
b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/WildcardClassNameMatcher.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.mina.core.buffer.matcher;
+
+/**
+ * A {@link ClassNameMatcher} that uses simplified regular expressions
+ *  provided by {@link 
org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) 
FilenameUtils.wildcardMatch}
+ * <p>
+ * This object is immutable and thread-safe.
+ * </p>
+ */
+public final class WildcardClassNameMatcher implements ClassNameMatcher {
+
+    private final String pattern;
+
+    /**
+     * Constructs an object based on the specified simplified regular 
expression.
+     *
+     * @param pattern a {@link FilenameUtils#wildcardMatch} pattern.
+     */
+    public WildcardClassNameMatcher(String pattern) {
+        this.pattern = pattern;
+    }
+
+    @Override
+    public boolean matches(String className) {
+        return FilenameUtils.wildcardMatch(className, pattern, 
IOCase.SENSITIVE);
+    }
+}
\ No newline at end of file
diff --git 
a/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java 
b/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java
index cfc2d7104..bf8c46743 100644
--- a/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java
+++ b/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java
@@ -372,6 +372,7 @@ public class IoBufferTest {
         List<Object> o = new ArrayList<>();
         o.add(new Date());
         o.add(long.class);
+        buf.accept(ArrayList.class.getName(), Date.class.getName(), 
long.class.getName());
 
         // Test writing an object.
         buf.putObject(o);
@@ -387,11 +388,12 @@ public class IoBufferTest {
 
     @Test
     public void testNonserializableClass() throws Exception {
-        Class<?> c = NonserializableClass.class;
+        Class<?> c = String.class;
 
         IoBuffer buffer = IoBuffer.allocate(16);
         buffer.setAutoExpand(true);
         buffer.putObject(c);
+        buffer.accept(String.class.getName());
 
         buffer.flip();
         Object o = buffer.getObject();
@@ -407,6 +409,7 @@ public class IoBufferTest {
         IoBuffer buffer = IoBuffer.allocate(16);
         buffer.setAutoExpand(true);
         buffer.putObject(c);
+        buffer.accept(NonserializableInterface.class.getName());
 
         buffer.flip();
         Object o = buffer.getObject();
@@ -947,6 +950,7 @@ public class IoBufferTest {
 
         // Test writing an object.
         buf.putObject(expected);
+        buf.accept(Bar.class.getName());
 
         // Test reading an object.
         buf.clear();
diff --git a/pom.xml b/pom.xml
index bc7296306..413033d3f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -90,9 +90,6 @@
     <!-- Make Java 8 javadoc lint to shut the f*** up... -->
     <!-- additionalparam>-Xdoclint:none</additionalparam -->
 
-    <!-- SBOM generation versions -->
-    <version.cyclonedx>2.9.0</version.cyclonedx>
-
     <!-- Maven Plugins -->
     <version.apache.rat.plugin>0.16.1</version.apache.rat.plugin>
     <version.api.plugin>3.6.3</version.api.plugin>
@@ -105,6 +102,7 @@
     <version.clirr.plugin>2.8</version.clirr.plugin>
     <version.cobertura.plugin>2.7</version.cobertura.plugin>
     <version.compiler.plugin>3.13.0</version.compiler.plugin>
+    <version.cyclonedx.plugin>2.9.0</version.cyclonedx.plugin>
     <version.dashboard.plugin>1.0.0-beta-1</version.dashboard.plugin>
     <version.dependency.plugin>3.8.0</version.dependency.plugin>
     <version.deploy.plugin>3.1.3</version.deploy.plugin>
@@ -186,6 +184,13 @@
   <!-- =========================================== -->
   <dependencyManagement>
     <dependencies>
+      <!-- Commons dependencies -->
+      <dependency>
+        <groupId>commons-io</groupId>
+        <artifactId>commons-io</artifactId>
+        <version>${commons.io.version}</version>
+      </dependency>
+
       <!-- Submodules -->
       <dependency>
         <groupId>${project.groupId}</groupId>
@@ -794,7 +799,7 @@
         <plugin>
           <groupId>org.cyclonedx</groupId>
           <artifactId>cyclonedx-maven-plugin</artifactId>
-          <version>${version.cyclonedx}</version>
+          <version>${version.cyclonedx.plugin}</version>
           <executions>
             <execution>
               <id>make-bom</id>

Reply via email to