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

claude pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/creadur-rat.git


The following commit(s) were added to refs/heads/master by this push:
     new 3529867e RAT-98: Added FSInfo to handle file system differences (#421)
3529867e is described below

commit 3529867ef406cca7e384519ed8dd5a2480cb2271
Author: Claude Warren <[email protected]>
AuthorDate: Tue Jan 28 00:09:44 2025 +0100

    RAT-98: Added FSInfo to handle file system differences (#421)
    
    * Added FSInfo to handle file system differences
    
    * updated spotbugs
    
    * added more descriptive failure messages
    
    * modified test output
    
    * fixed FileListWalker
    
    * reduced logging noise
    
    * Minor javadoc fixes
    
    * Apply minor cleanups and javadoc fixes
    
    * cleaned up as per review
    
    * fixed rebase issue
    
    ---------
    
    Co-authored-by: P. Ottlinger <[email protected]>
---
 apache-rat-core/pom.xml                            |   5 +
 .../src/it/java/org/apache/rat/ReportTest.java     |   2 +-
 .../src/main/java/org/apache/rat/api/Document.java |  12 +-
 .../rat/config/exclusion/ExclusionUtils.java       |  26 +-
 .../rat/config/exclusion/plexus/MatchPattern.java  |   8 +-
 .../rat/config/exclusion/plexus/MatchPatterns.java |   5 +-
 .../org/apache/rat/document/ArchiveEntryName.java  |  74 +++
 .../java/org/apache/rat/document/DocumentName.java | 567 ++++++++++++++-------
 .../apache/rat/document/DocumentNameMatcher.java   |  26 +-
 .../java/org/apache/rat/document/FileDocument.java |  18 +-
 .../java/org/apache/rat/walker/ArchiveWalker.java  |  20 +-
 .../java/org/apache/rat/walker/FileListWalker.java |  26 +-
 .../java/org/apache/rat/OptionCollectionTest.java  |  12 +-
 .../apache/rat/analysis/AnalyserFactoryTest.java   |   4 +-
 .../config/exclusion/ExclusionProcessorTest.java   |   8 +-
 .../rat/config/exclusion/FileProcessorTest.java    |   3 +-
 .../org/apache/rat/document/DocumentNameTest.java  | 315 ++++++++----
 .../java/org/apache/rat/document/FSInfoTest.java   |  58 +++
 .../rat/document/guesser/NoteGuesserTest.java      |   7 +-
 .../apache/rat/test/AbstractOptionsProvider.java   |  14 +-
 .../apache/rat/testhelpers/TestingDocument.java    |   7 +-
 .../org/apache/rat/walker/FileListWalkerTest.java  |  12 +-
 pom.xml                                            |  12 +-
 23 files changed, 878 insertions(+), 363 deletions(-)

diff --git a/apache-rat-core/pom.xml b/apache-rat-core/pom.xml
index 8ac00d1e..844a7efc 100644
--- a/apache-rat-core/pom.xml
+++ b/apache-rat-core/pom.xml
@@ -281,5 +281,10 @@
       <artifactId>groovy-all</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.google.jimfs</groupId>
+      <artifactId>jimfs</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>
diff --git a/apache-rat-core/src/it/java/org/apache/rat/ReportTest.java 
b/apache-rat-core/src/it/java/org/apache/rat/ReportTest.java
index b58c6732..16bc908b 100644
--- a/apache-rat-core/src/it/java/org/apache/rat/ReportTest.java
+++ b/apache-rat-core/src/it/java/org/apache/rat/ReportTest.java
@@ -163,7 +163,7 @@ public class ReportTest {
             @Override
             public void report(Document document)  {
                 if (!document.isIgnored()) {
-                    String[] tokens = 
document.getName().tokenize(document.getName().localized());
+                    String[] tokens = 
DocumentName.FSInfo.getDefault().tokenize(document.getName().localized());
                     results.add(Arguments.of(tokens[1], document));
                 }
             }
diff --git a/apache-rat-core/src/main/java/org/apache/rat/api/Document.java 
b/apache-rat-core/src/main/java/org/apache/rat/api/Document.java
index 3e29eb35..0dbfa10c 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/api/Document.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/api/Document.java
@@ -52,7 +52,7 @@ public abstract class Document implements 
Comparable<Document> {
     }
 
     /** The path matcher used by this document */
-    protected final DocumentNameMatcher nameExcluder;
+    protected final DocumentNameMatcher nameMatcher;
     /** The metadata for this document */
     private final MetaData metaData;
     /** The fully qualified name of this document */
@@ -61,11 +61,11 @@ public abstract class Document implements 
Comparable<Document> {
     /**
      * Creates an instance.
      * @param name the native NameSet of the resource.
-     * @param nameExcluder the document name matcher to filter 
directories/files.
+     * @param nameMatcher the document name matcher to filter 
directories/files.
      */
-    protected Document(final DocumentName name, final DocumentNameMatcher 
nameExcluder) {
+    protected Document(final DocumentName name, final DocumentNameMatcher 
nameMatcher) {
         this.name = name;
-        this.nameExcluder = nameExcluder;
+        this.nameMatcher = nameMatcher;
         this.metaData = new MetaData();
     }
 
@@ -81,8 +81,8 @@ public abstract class Document implements 
Comparable<Document> {
      * Gets the file filter this document was created with.
      * @return the file filter this document was created with.
      */
-    public final DocumentNameMatcher getNameExcluder() {
-        return nameExcluder;
+    public final DocumentNameMatcher getNameMatcher() {
+        return nameMatcher;
     }
 
     @Override
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionUtils.java
 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionUtils.java
index 339ce097..7932db4f 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionUtils.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionUtils.java
@@ -62,7 +62,7 @@ public final class ExclusionUtils {
      * Creates predicate that filters out comment and blank lines. Leading 
spaces are removed and
      * if the line then starts with a commentPrefix string it is considered a 
comment and will be removed
      *
-     * @param commentPrefixes the list of comment prefixes
+     * @param commentPrefixes the list of comment prefixes.
      * @return the Predicate that returns false for lines that start with 
commentPrefixes or are empty.
      */
     public static Predicate<String> commentFilter(final Iterable<String> 
commentPrefixes) {
@@ -108,7 +108,7 @@ public final class ExclusionUtils {
     /**
      * Create a FileFilter from a PathMatcher.
      * @param parent the document name for the parent of the file to be 
filtered.
-     * @param nameMatcher the path matcher to convert
+     * @param nameMatcher the path matcher to convert.
      * @return a FileFilter.
      */
     public static FileFilter asFileFilter(final DocumentName parent, final 
DocumentNameMatcher nameMatcher) {
@@ -119,7 +119,7 @@ public final class ExclusionUtils {
      * Creates an iterator of Strings from a file of patterns.
      * Removes comment lines.
      * @param patternFile the file to read.
-     * @param commentFilters A predicate return true for non-comment lines
+     * @param commentFilters A predicate returning {@code true} for 
non-comment lines.
      * @return the iterable of Strings from the file.
      */
     public static ExtendedIterator<String> asIterator(final File patternFile, 
final Predicate<String> commentFilters) {
@@ -147,7 +147,7 @@ public final class ExclusionUtils {
      * Creates an iterable of Strings from a file of patterns.
      * Removes comment lines.
      * @param patternFile the file to read.
-     * @param commentFilters A predicate returning true for non-comment lines
+     * @param commentFilters A predicate returning {@code true} for 
non-comment lines.
      * @return the iterable of Strings from the file.
      */
     public static Iterable<String> asIterable(final File patternFile, final 
Predicate<String> commentFilters)  {
@@ -171,9 +171,9 @@ public final class ExclusionUtils {
     }
 
     /**
-     * Returns {@code true} if the file name represents a hidden file
+     * Returns {@code true} if the file name represents a hidden file.
      * @param f the file to check.
-     * @return true if it is the name of a hidden file.
+     * @return {@code true} if it is the name of a hidden file.
      */
     public static boolean isHidden(final File f) {
         String s = f.getName();
@@ -185,4 +185,18 @@ public final class ExclusionUtils {
             throw new ConfigurationException(format("%s is not a valid file.", 
file));
         }
     }
+
+    /**
+     * Tokenizes the string based on the directory separator.
+     * @param source the source to tokenize.
+     * @param from the directory separator for the source.
+     * @param to the directory separator for the result.
+     * @return the source string with the separators converted.
+     */
+    public static String convertSeparator(final String source, final String 
from, final String to) {
+        if (StringUtils.isEmpty(source) || from.equals(to)) {
+            return source;
+        }
+        return String.join(to, source.split("\\Q" + from + "\\E"));
+    }
 }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPattern.java
 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPattern.java
index b606136a..9932376f 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPattern.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPattern.java
@@ -84,8 +84,8 @@ public final class MatchPattern {
         } else {
             result = 
SelectorUtils.matchAntPathPattern(getTokenizedPathChars(), strDirs, 
isCaseSensitive);
         }
-        if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
-            DefaultLog.getInstance().debug(format("%s match %s -> %s", this, 
str, result));
+        if (result && DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
+            DefaultLog.getInstance().debug(format("%s match %s -> true", this, 
str));
         }
         return result;
     }
@@ -117,7 +117,8 @@ public final class MatchPattern {
 
     @Override
     public String toString() {
-        return Arrays.asList(tokenized).toString();
+        return Arrays.asList(tokenized)
+                .toString();
     }
 
     public String source() {
@@ -141,5 +142,4 @@ public final class MatchPattern {
         }
         return tokenizedNameChar;
     }
-
 }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPatterns.java
 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPatterns.java
index 06c4672d..0a670393 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPatterns.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPatterns.java
@@ -42,7 +42,10 @@ public final class MatchPatterns {
 
     @Override
     public String toString() {
-        return 
Arrays.stream(patterns).map(MatchPattern::toString).collect(Collectors.toList()).toString();
+        return Arrays.stream(patterns)
+                .map(MatchPattern::toString)
+                .collect(Collectors.toList())
+                .toString();
     }
 
     public String source() {
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java 
b/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java
new file mode 100644
index 00000000..99bdcc45
--- /dev/null
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java
@@ -0,0 +1,74 @@
+/*
+ * 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.rat.document;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+
+public class ArchiveEntryName extends DocumentName {
+    /** The name of the document that contains this entry. */
+    private final DocumentName archiveFileName;
+
+    private static DocumentName.Builder prepareBuilder(final DocumentName 
archiveFileName, final String archiveEntryName) {
+        String root = archiveFileName.getName() + "#";
+        FSInfo fsInfo = new FSInfo("archiveEntry", "/", true, 
Collections.singletonList(root));
+        return DocumentName.builder(fsInfo)
+                .setRoot(root)
+                .setBaseName(root + "/")
+                .setName(archiveEntryName);
+    }
+    public ArchiveEntryName(final DocumentName archiveFileName, final String 
archiveEntryName) {
+        super(prepareBuilder(archiveFileName, archiveEntryName));
+        this.archiveFileName = archiveFileName;
+    }
+
+    @Override
+    public File asFile() {
+        return archiveFileName.asFile();
+    }
+
+    @Override
+    public Path asPath() {
+        return Paths.get(archiveFileName.asPath().toString(), "#", 
super.asPath().toString());
+    }
+
+    @Override
+    public DocumentName resolve(final String child) {
+        return new ArchiveEntryName(this.archiveFileName, 
super.resolve(child).localized());
+    }
+
+    @Override
+    public String getBaseName() {
+        return archiveFileName.getName() + "#";
+    }
+
+    @Override
+    boolean startsWithRootOrSeparator(final String candidate, final String 
root, final String separator) {
+        return super.startsWithRootOrSeparator(candidate, root, separator);
+    }
+
+    @Override
+    public String localized(final String dirSeparator) {
+        String superLocal = super.localized(dirSeparator);
+        superLocal = superLocal.substring(superLocal.lastIndexOf("#") + 1);
+        return archiveFileName.localized(dirSeparator) + "#" + superLocal;
+    }
+}
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java 
b/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java
index e71bed85..d5cc1126 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java
@@ -20,29 +20,34 @@ package org.apache.rat.document;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
+import java.util.Optional;
+import java.util.stream.Collectors;
 
-import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.CompareToBuilder;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
-import org.apache.rat.utils.DefaultLog;
 
 /**
  * The name for a document.  The {@code DocumentName} is an immutable 
structure that handles all the intricacies of file
  * naming on various operating systems. DocumentNames have several components:
  * <ul>
- *     <li>{@code root} - Where in the file system the name starts (e.g C: on 
windows). May be empty but not null.</li>
+ *     <li>{@code root} - where in the file system the name starts (e.g C: on 
windows). May be empty but not null.</li>
  *     <li>{@code dirSeparator} - the separator between name segments (e.g. 
"\\" on windows, "/" on linux). May not be
  *     empty or null.</li>
- *     <li>{@code name} - The name of the file relative to the {@code root}. 
May not be null. Does NOT begin with a {@code dirSeparator}</li>
- *     <li>{@code baseName} - The name of a directory or file from which this 
file is reported. A DocumentName with a
+ *     <li>{@code name} - the name of the file relative to the {@code root}. 
May not be null. Does NOT begin with a {@code dirSeparator}</li>
+ *     <li>{@code baseName} - the name of a directory or file from which this 
file is reported. A DocumentName with a
  *     {@code name} of "foo/bar/baz.txt" and a {@code baseName} of "foo" will 
be reported as "bar/baz.txt". May not be null.</li>
  *     <li>{@code isCaseSensitive} - identifies if the underlying file system 
is case-sensitive.</li>
  * </ul>
@@ -51,66 +56,84 @@ import org.apache.rat.utils.DefaultLog;
  *     within an archive. When representing a file in an archive the baseName 
is the name of the enclosing archive document.
  * </p>
  */
-public final class DocumentName implements Comparable<DocumentName> {
-    /** The list of all roots on the file system. */
-    static final Set<String> ROOTS = new HashSet<>();
-    /** {@code True} if the file system on which we are operating is 
case-sensitive. */
-    public static final boolean FS_IS_CASE_SENSITIVE;
+public class DocumentName implements Comparable<DocumentName> {
     /** The full name for the document. */
     private final String name;
     /** The name of the base directory for the document. */
-    private final String baseName;
-    /** The directory separator for this document. */
-    private final String dirSeparator;
-    /** The case-sensitive flag */
-    private final boolean isCaseSensitive;
+    private final DocumentName baseName;
+    /** The file system info for this document. */
+    private final FSInfo fsInfo;
     /** The root for the DocumentName. May be empty but not null. */
     private final String root;
 
-    // determine the case sensitivity of the file system we are operating on.
-    static {
-        boolean fsSensitive = true;
-        File f = null;
+    /**
+     * Determines if the file system is case-sensitive.
+     * @param fileSystem the file system to check
+     * @return {@code true} if the file system is case-sensitive.
+     */
+    private static boolean isCaseSensitive(final FileSystem fileSystem) {
+        boolean isCaseSensitive = false;
+        Path nameSet = null;
+        Path filea = null;
+        Path fileA = null;
         try {
-            Path p = Files.createTempDirectory("NameSet");
-            f = p.toFile();
-            fsSensitive = !new File(f, "a").equals(new File(f, "A"));
-        } catch (IOException e) {
-            fsSensitive = true;
-        } finally {
-            if (f != null) {
-                try {
-                    FileUtils.deleteDirectory(f);
-                } catch (IOException e) {
-                    DefaultLog.getInstance().warn("Unable to delete temporary 
directory: " + f, e);
+            try {
+                Path root = fileSystem.getPath("");
+                nameSet = Files.createTempDirectory(root, "NameSet");
+                filea = nameSet.resolve("a");
+                fileA = nameSet.resolve("A");
+                Files.createFile(filea);
+                Files.createFile(fileA);
+                isCaseSensitive = true;
+            } catch (IOException e) {
+                // do nothing
+            } finally {
+                if (filea != null) {
+                    Files.deleteIfExists(filea);
+                }
+                if (fileA != null) {
+                    Files.deleteIfExists(fileA);
+                }
+                if (nameSet != null) {
+                    Files.deleteIfExists(nameSet);
                 }
             }
+        } catch (IOException e) {
+            // do nothing.
         }
-        FS_IS_CASE_SENSITIVE = fsSensitive;
-
-        // determine all the roots on the file system(s).
-        File[] roots = File.listRoots();
-        if (roots != null) {
-            for (File root : roots) {
-                String name = root.getPath();
-                ROOTS.add(name);
-            }
-        }
-
+        return isCaseSensitive;
     }
 
     /**
-     * Creates a Builder with directory separator and case sensitivity based 
on the local file system.
+     * Creates a Builder with the default File system info.
      * @return the Builder.
+     * @see FSInfo
      */
     public static Builder builder() {
-        return new Builder();
+        return new Builder(FSInfo.getDefault());
+    }
+
+    /**
+     * Creates a builder with the specified FSInfo instance.
+     * @param fsInfo the FSInfo to use for the builder.
+     * @return a new builder.
+     */
+    public static Builder builder(final FSInfo fsInfo) {
+        return new Builder(fsInfo);
+    }
+
+    /**
+     * Creates a builder for the specified file system.
+     * @param fileSystem the file system to create ethe builder on.
+     * @return a new builder.
+     */
+    public static Builder builder(final FileSystem fileSystem) {
+        return new Builder(fileSystem);
     }
 
     /**
-     * Creates a builder from a File.  The {@link #baseName} is set to the 
file name if it is a directory otherwise
-     * it is set to the directory containing the file. The {@link 
#dirSeparator} is set from the file and
-     * case sensitivity based on the local file system.
+     * Creates a builder from a File. The {@link #baseName} is set to the file 
name if it is a directory otherwise
+     * it is set to the directory containing the file.
      * @param file The file to set defaults from.
      * @return the Builder.
      */
@@ -131,26 +154,49 @@ public final class DocumentName implements 
Comparable<DocumentName> {
      * Builds the DocumentName from the builder.
      * @param builder the builder to provide the values.
      */
-    private DocumentName(final Builder builder) {
+    DocumentName(final Builder builder) {
         this.name = builder.name;
-        this.baseName = builder.baseName;
-        this.dirSeparator = builder.dirSeparator;
-        this.isCaseSensitive = builder.isCaseSensitive;
+        this.fsInfo = builder.fsInfo;
         this.root = builder.root;
+        this.baseName = builder.sameNameFlag ? this : builder.baseName;
+    }
+
+    /**
+     * Creates a file from the document name.
+     * @return a new File object.
+     */
+    public File asFile() {
+        return new File(getName());
+    }
+
+    /**
+     * Creates a path from the document name.
+     * @return a new Path object.
+     */
+    public Path asPath() {
+        return Paths.get(name);
     }
 
     /**
      * Creates a new DocumentName by adding the child to the current name.
+     * Resulting documentName will have the same base name.
      * @param child the child to add (must use directory separator from this 
document name).
-     * @return the new document name with the same {@link #baseName}, {@link 
#dirSeparator} and case sensitivity as
+     * @return the new document name with the same {@link #baseName}, 
directory sensitivity and case sensitivity as
      * this one.
      */
     public DocumentName resolve(final String child) {
-        List<String> parts = new ArrayList<>();
-        parts.addAll(Arrays.asList(tokenize(name)));
-        parts.addAll(Arrays.asList(tokenize(child)));
-        String newName = String.join(dirSeparator, parts);
-        return new Builder(this).setName(newName).build();
+        if (StringUtils.isBlank(child)) {
+            return this;
+        }
+        String separator = fsInfo.dirSeparator();
+        String pattern = separator.equals("/") ? child.replace('\\', '/') :
+                child.replace('/', '\\');
+
+        if (!pattern.startsWith(separator)) {
+             pattern = name + separator + pattern;
+        }
+
+        return new Builder(this).setName(pattern).build();
     }
 
     /**
@@ -158,7 +204,7 @@ public final class DocumentName implements 
Comparable<DocumentName> {
      * @return the fully qualified name of the document.
      */
     public String getName() {
-        return root + dirSeparator + name;
+        return root + fsInfo.dirSeparator() + name;
     }
 
     /**
@@ -166,7 +212,7 @@ public final class DocumentName implements 
Comparable<DocumentName> {
      * @return the fully qualified basename of the document.
      */
     public String getBaseName() {
-        return root + dirSeparator + baseName;
+        return baseName.getName();
     }
 
     /**
@@ -182,7 +228,7 @@ public final class DocumentName implements 
Comparable<DocumentName> {
      * @return the DocumentName for the basename of this document name.
      */
     public DocumentName getBaseDocumentName() {
-        return name.equals(baseName) ? this : 
builder(this).setName(baseName).build();
+        return baseName;
     }
 
     /**
@@ -190,7 +236,25 @@ public final class DocumentName implements 
Comparable<DocumentName> {
      * @return the directory separator.
      */
     public String getDirectorySeparator() {
-        return dirSeparator;
+        return fsInfo.dirSeparator();
+    }
+
+    /**
+     * Determines if the candidate starts with the root or separator strings.
+     * @param candidate the candidate ot check. If blank method will return 
{@code false}.
+     * @param root the root to check. If blank the root check is skipped.
+     * @param separator the separator to check. If blank the check is skipped.
+     * @return true if either the root or separator check returned {@code 
true}.
+     */
+    boolean startsWithRootOrSeparator(final String candidate, final String 
root, final String separator) {
+        if (StringUtils.isBlank(candidate)) {
+            return false;
+        }
+        boolean result = !StringUtils.isBlank(root) && 
candidate.startsWith(root);
+        if (!result) {
+            result = !StringUtils.isBlank(separator) && 
candidate.startsWith(separator);
+        }
+        return result;
     }
 
     /**
@@ -199,12 +263,13 @@ public final class DocumentName implements 
Comparable<DocumentName> {
      * @return the portion of the name that is not part of the base name.
      */
     public String localized() {
-        String result = name;
-        if (result.startsWith(baseName)) {
-            result = result.substring(baseName.length());
+        String result = getName();
+        String baseNameStr = baseName.getName();
+        if (result.startsWith(baseNameStr)) {
+            result = result.substring(baseNameStr.length());
         }
-        if (!result.startsWith(dirSeparator)) {
-            result = dirSeparator + result;
+        if (!startsWithRootOrSeparator(result, getRoot(), 
fsInfo.dirSeparator())) {
+            result = fsInfo.dirSeparator() + result;
         }
         return result;
     }
@@ -216,24 +281,26 @@ public final class DocumentName implements 
Comparable<DocumentName> {
      * @return the portion of the name that is not part of the base name.
      */
     public String localized(final String dirSeparator) {
-        return String.join(dirSeparator, tokenize(localized()));
-    }
+        String[] tokens = fsInfo.tokenize(localized());
+        if (tokens.length == 0) {
+            return dirSeparator;
+        }
+        if (tokens.length == 1) {
+            return dirSeparator + tokens[0];
+        }
 
-    /**
-     * Tokenizes the string based on the {@link #dirSeparator} of this 
DocumentName.
-     * @param source the source to tokenize
-     * @return the array of tokenized strings.
-     */
-    public String[] tokenize(final String source) {
-        return source.split("\\Q" + dirSeparator + "\\E");
+        String modifiedRoot =  dirSeparator.equals("/") ? root.replace('\\', 
'/') :
+                root.replace('/', '\\');
+        String result = String.join(dirSeparator, tokens);
+        return startsWithRootOrSeparator(result, modifiedRoot, dirSeparator) ? 
result : dirSeparator + result;
     }
 
     /**
-     * Gets the last segment of the name. This is the part after the last 
{@link #dirSeparator}..
+     * Gets the last segment of the name. This is the part after the last 
directory separator.
      * @return the last segment of the name.
      */
     public String getShortName() {
-        int pos = name.lastIndexOf(dirSeparator);
+        int pos = name.lastIndexOf(fsInfo.dirSeparator());
         return pos == -1 ? name : name.substring(pos + 1);
     }
 
@@ -242,7 +309,7 @@ public final class DocumentName implements 
Comparable<DocumentName> {
      * @return {@code true} if the name is case-sensitive.
      */
     public boolean isCaseSensitive() {
-        return isCaseSensitive;
+        return fsInfo.isCaseSensitive();
     }
 
     /**
@@ -255,88 +322,239 @@ public final class DocumentName implements 
Comparable<DocumentName> {
     }
 
     @Override
-    public int compareTo(final DocumentName o) {
-        return FS_IS_CASE_SENSITIVE ? name.compareTo(o.name) : 
name.compareToIgnoreCase(o.name);
+    public int compareTo(final DocumentName other) {
+        return CompareToBuilder.reflectionCompare(this, other);
     }
 
     @Override
-    public boolean equals(final Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        DocumentName that = (DocumentName) o;
-        if (isCaseSensitive() == that.isCaseSensitive() &&  
Objects.equals(dirSeparator, that.dirSeparator)) {
-            return isCaseSensitive ? name.equalsIgnoreCase(that.name) : 
name.equals(that.name);
-        }
-        return false;
+    public boolean equals(final Object other) {
+        return EqualsBuilder.reflectionEquals(this, other);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(name, dirSeparator, isCaseSensitive());
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    /**
+     * The file system information needed to process document names.
+     */
+    public static class FSInfo implements Comparable<FSInfo> {
+        /** The common name for the file system this Info represents. */
+        private final String name;
+        /** The separator between directory names. */
+        private final String separator;
+        /** The case-sensitivity flag. */
+        private final boolean isCaseSensitive;
+        /** The list of roots for the file system. */
+        private final List<String> roots;
+
+        public static FSInfo getDefault() {
+            FSInfo result = (FSInfo) System.getProperties().get("FSInfo");
+            return result == null ?
+                    new FSInfo("default", FileSystems.getDefault())
+                    : result;
+        }
+        /**
+         * Constructor. Extracts the necessary data from the file system.
+         * @param fileSystem the file system to extract data from.
+         */
+        public FSInfo(final FileSystem fileSystem) {
+            this("anon", fileSystem);
+        }
+
+        /**
+         * Constructor. Extracts the necessary data from the file system.
+         * @param fileSystem the file system to extract data from.
+         */
+        public FSInfo(final String name, final FileSystem fileSystem) {
+            this.name = name;
+            this.separator = fileSystem.getSeparator();
+            this.isCaseSensitive = DocumentName.isCaseSensitive(fileSystem);
+            roots = new ArrayList<>();
+            fileSystem.getRootDirectories().forEach(r -> 
roots.add(r.toString()));
+        }
+
+        /**
+         * Gets the common name for the underlying file system.
+         * @return the common file system name.
+         */
+        @Override
+        public String toString() {
+            return name;
+        }
+
+        /**
+         * Constructor for virtual/abstract file systems for example the entry 
names within an archive.
+         * @param separator the separator string to use.
+         * @param isCaseSensitive the case-sensitivity flag.
+         * @param roots the roots for the file system.
+         */
+        FSInfo(final String name, final String separator, final boolean 
isCaseSensitive, final List<String> roots) {
+            this.name = name;
+            this.separator = separator;
+            this.isCaseSensitive = isCaseSensitive;
+            this.roots = new ArrayList<>(roots);
+        }
+
+        /**
+         * Gets the directory separator.
+         * @return The directory separator.
+         */
+        public String dirSeparator() {
+            return separator;
+        }
+
+        /**
+         * Gets the case-sensitivity flag.
+         * @return the case-sensitivity flag.
+         */
+        public boolean isCaseSensitive() {
+            return isCaseSensitive;
+        }
+
+        /**
+         * Retrieves the root extracted from the name.
+         * @param name the name to extract the root from
+         * @return an optional containing the root or empty.
+         */
+        public Optional<String> rootFor(final String name) {
+            for (String sysRoot : roots) {
+                if (name.startsWith(sysRoot)) {
+                    return Optional.of(sysRoot);
+                }
+            }
+            return Optional.empty();
+        }
+
+        /**
+         * Tokenizes the string based on the directory separator of this 
DocumentName.
+         * @param source the source to tokenize.
+         * @return the array of tokenized strings.
+         */
+        public String[] tokenize(final String source) {
+            return source.split("\\Q" + dirSeparator() + "\\E");
+        }
+
+        /**
+         * Removes {@code .} and {@code ..} from filenames.
+         * @param pattern the file name pattern
+         * @return the normalized file name.
+         */
+        public String normalize(final String pattern) {
+            if (StringUtils.isBlank(pattern)) {
+                return "";
+            }
+            List<String> parts = new 
ArrayList<>(Arrays.asList(tokenize(pattern)));
+            for (int i = 0; i < parts.size(); i++) {
+                String part = parts.get(i);
+                if (part.equals("..")) {
+                    if (i == 0) {
+                        throw new IllegalStateException("Unable to create path 
before root");
+                    }
+                    parts.set(i - 1, null);
+                    parts.set(i, null);
+                } else if (part.equals(".")) {
+                    parts.set(i, null);
+                }
+            }
+            return 
parts.stream().filter(Objects::nonNull).collect(Collectors.joining(dirSeparator()));
+        }
+
+        @Override
+        public int compareTo(final FSInfo other) {
+            return CompareToBuilder.reflectionCompare(this, other);
+        }
+
+        @Override
+        public boolean equals(final Object other) {
+            return EqualsBuilder.reflectionEquals(this, other);
+        }
+
+        @Override
+        public int hashCode() {
+            return HashCodeBuilder.reflectionHashCode(this);
+        }
     }
 
     /**
      * The Builder for a DocumentName.
      */
     public static final class Builder {
-        /** The name for the document */
+        /** The name for the document. */
         private String name;
-        /** The base name for the document */
-        private String baseName;
-        /** The directory separator */
-        private String dirSeparator;
-        /** The case sensitivity flag */
-        private boolean isCaseSensitive;
-        /** The file system root */
+        /** The base name for the document. */
+        private DocumentName baseName;
+        /** The file system info. */
+        private final FSInfo fsInfo;
+        /** The file system root. */
         private String root;
+        /** A flag for baseName same as this. */
+        private boolean sameNameFlag;
 
         /**
          * Create with default settings.
          */
-        private Builder() {
-            isCaseSensitive = FS_IS_CASE_SENSITIVE;
-            dirSeparator = File.separator;
+        private Builder(final FSInfo fsInfo) {
+            this.fsInfo = fsInfo;
             root = "";
         }
 
+        /**
+         * Create with default settings.
+         */
+        private Builder(final FileSystem fileSystem) {
+            this(new FSInfo(fileSystem));
+        }
+
         /**
          * Create based on the file provided.
          * @param file the file to base the builder on.
          */
         private Builder(final File file) {
-            this();
+            this(FSInfo.getDefault());
+            setName(file);
+        }
+
+        /**
+         * Used in testing.
+         * @param fsInfo the FSInfo for the file.
+         * @param file the file to process.
+         */
+        Builder(final FSInfo fsInfo, final File file) {
+            this(fsInfo);
             setName(file);
-            isCaseSensitive = FS_IS_CASE_SENSITIVE;
-            dirSeparator = File.separator;
         }
 
         /**
          * Create a Builder that clones the specified DocumentName.
          * @param documentName the DocumentName to clone.
          */
-        private Builder(final DocumentName documentName) {
+        Builder(final DocumentName documentName) {
             this.root = documentName.root;
             this.name = documentName.name;
             this.baseName = documentName.baseName;
-            this.isCaseSensitive = documentName.isCaseSensitive;
-            this.dirSeparator = documentName.dirSeparator;
+            this.fsInfo = documentName.fsInfo;
+        }
+
+        /**
+         * Get the directory separator for this builder.
+         * @return the directory separator fo this builder.
+         */
+        public String directorySeparator() {
+            return fsInfo.dirSeparator();
         }
 
         /**
          * Verify that the builder will build a proper DocumentName.
          */
         private void verify() {
-            Objects.requireNonNull(name, "Name cannot be null");
-            Objects.requireNonNull(baseName, "Basename cannot be null");
-            if (name.startsWith(dirSeparator)) {
-                name = name.substring(dirSeparator.length());
+            Objects.requireNonNull(name, "Name must not be null");
+            if (name.startsWith(fsInfo.dirSeparator())) {
+                name = name.substring(fsInfo.dirSeparator().length());
             }
-            if (baseName.startsWith(dirSeparator)) {
-                baseName = baseName.substring(dirSeparator.length());
+            if (!sameNameFlag) {
+                Objects.requireNonNull(baseName, "Basename must not be null");
             }
         }
 
@@ -346,58 +564,54 @@ public final class DocumentName implements 
Comparable<DocumentName> {
          * @return this.
          */
         public Builder setRoot(final String root) {
-            this.root = root;
+            this.root = StringUtils.defaultIfBlank(root, "");
             return this;
         }
 
         /**
-         * Sets the name for this DocumentName. Will reset the root to the 
empty string.
+         * Sets the name for this DocumentName relative to the baseName.
+         * If the {@code name} is {@code null} an empty string is used.
          * <p>
-         *     To correctly parse the string it must either be the directory 
separator specified by
-         *     {@link File#separator} or must have been explicitly set by 
calling {@link #setDirSeparator(String)}
-         *     before making this call.
+         *     To correctly parse the string it must use the directory 
separator specified by
+         *     this Document.
          * </p>
-         * @param name the name for this Document name.
+         * @param name the name for this Document name. Will be made relative 
to the baseName.
          * @return this
          */
         public Builder setName(final String name) {
-            Pair<String, String> pair = splitRoot(name, dirSeparator);
+            Pair<String, String> pair = 
splitRoot(StringUtils.defaultIfBlank(name, ""));
             if (this.root.isEmpty()) {
                 this.root = pair.getLeft();
             }
-            this.name = pair.getRight();
+            this.name = fsInfo.normalize(pair.getRight());
+            if (this.baseName != null && !baseName.name.isEmpty()) {
+                if (!this.name.startsWith(baseName.name)) {
+                    this.name = this.name.isEmpty() ? baseName.name :
+                            baseName.name + fsInfo.dirSeparator() + this.name;
+                }
+            }
             return this;
         }
 
-        /**
-         * Extracts the root/name pair from a file.
-         * @param file the file to extract the root/name pair from.
-         * @return the root/name pair.
-         */
-        static Pair<String, String> splitRoot(final File file) {
-            return splitRoot(file.getAbsolutePath(), File.separator);
-        }
-
         /**
          * Extracts the root/name pair from a name string.
          * <p>
          *     Package private for testing.
          * </p>
          * @param name the name to extract the root/name pair from.
-         * @param dirSeparator the directory separator.
          * @return the root/name pair.
          */
-        static Pair<String, String> splitRoot(final String name, final String 
dirSeparator) {
+        Pair<String, String> splitRoot(final String name) {
             String workingName = name;
-            String root = "";
-            for (String sysRoot : ROOTS) {
-                if (workingName.startsWith(sysRoot)) {
-                    workingName = workingName.substring(sysRoot.length());
-                    if (!workingName.startsWith(dirSeparator)) {
-                        if (sysRoot.endsWith(dirSeparator)) {
-                            root = sysRoot.substring(0, sysRoot.length() - 
dirSeparator.length());
+            Optional<String> maybeRoot = fsInfo.rootFor(name);
+            String root = maybeRoot.orElse("");
+            if (!root.isEmpty()) {
+                if (workingName.startsWith(root)) {
+                    workingName = workingName.substring(root.length());
+                    if (!workingName.startsWith(fsInfo.dirSeparator())) {
+                        if (root.endsWith(fsInfo.dirSeparator())) {
+                            root = root.substring(0, root.length() - 
fsInfo.dirSeparator().length());
                         }
-                        return ImmutablePair.of(root, workingName);
                     }
                 }
             }
@@ -415,21 +629,24 @@ public final class DocumentName implements 
Comparable<DocumentName> {
         }
 
         /**
-         * Sets the properties from the file. This method sets the {@link 
#root} if it is empty, and resets {@link #name},
-         * {@link #dirSeparator} and {@link #baseName}.
+         * Sets the properties from the file. Will reset the baseName 
appropriately.
          * @param file the file to set the properties from.
          * @return this.
          */
         public Builder setName(final File file) {
-            Pair<String, String> pair = splitRoot(file);
+            Pair<String, String> pair = splitRoot(file.getAbsolutePath());
             setEmptyRoot(pair.getLeft());
-            this.name = pair.getRight();
-            this.dirSeparator = File.separator;
-            this.baseName = name;
-            if (!file.isDirectory()) {
+            this.name = fsInfo.normalize(pair.getRight());
+            if (file.isDirectory()) {
+                sameNameFlag = true;
+            } else {
                 File p = file.getParentFile();
                 if (p != null) {
                     setBaseName(p);
+                } else {
+                    Builder baseBuilder = new 
Builder(this.fsInfo).setName(this.directorySeparator());
+                    baseBuilder.sameNameFlag = true;
+                    setBaseName(baseBuilder.build());
                 }
             }
             return this;
@@ -439,17 +656,15 @@ public final class DocumentName implements 
Comparable<DocumentName> {
          * Sets the baseName.
          * Will set the root if it is not set.
          * <p>
-         *     To correctly parse the string it must either be the directory 
separator specified by
-         *     {@link File#separator} or must have been explicitly set by 
calling {@link #setDirSeparator(String)}
-         *     before making this call.
+         *     To correctly parse the string it must use the directory 
separator specified by this builder.
          * </p>
          * @param baseName the basename to use.
          * @return this.
          */
         public Builder setBaseName(final String baseName) {
-            Pair<String, String> pair = splitRoot(baseName, dirSeparator);
-            setEmptyRoot(pair.getLeft());
-            this.baseName = pair.getRight();
+            DocumentName.Builder builder = 
DocumentName.builder(fsInfo).setName(baseName);
+            builder.sameNameFlag = true;
+            setBaseName(builder);
             return this;
         }
 
@@ -460,7 +675,7 @@ public final class DocumentName implements 
Comparable<DocumentName> {
          * @return this.
          */
         public Builder setBaseName(final DocumentName baseName) {
-            this.baseName = baseName.getName();
+            this.baseName = baseName;
             if (!baseName.getRoot().isEmpty()) {
                 this.root = baseName.getRoot();
             }
@@ -468,36 +683,24 @@ public final class DocumentName implements 
Comparable<DocumentName> {
         }
 
         /**
-         * Sets the basename from a File. Sets {@link #root} and the {@link 
#baseName}
-         * Will set the root.
-         * @param file the file to set the base name from.
-         * @return this.
+         * Executes the builder, sets the base name and clears the sameName 
flag.
+         * @param builder the builder for the base name.
          */
-        public Builder setBaseName(final File file) {
-            Pair<String, String> pair = splitRoot(file);
-            this.root = pair.getLeft();
-            this.baseName = pair.getRight();
-            return this;
+        private void setBaseName(final DocumentName.Builder builder) {
+            this.baseName = builder.build();
+            this.sameNameFlag = false;
         }
 
         /**
-         * Sets the directory separator.
-         * @param dirSeparator the directory separator to use.
-         * @return this.
-         */
-        public Builder setDirSeparator(final String dirSeparator) {
-            Objects.requireNonNull(dirSeparator, "Directory separator cannot 
be null");
-            this.dirSeparator = dirSeparator;
-            return this;
-        }
-
-        /**
-         * Sets the {@link #isCaseSensitive} flag.
-         * @param isCaseSensitive the expected state of the flag.
+         * Sets the basename from a File. Sets {@link #root} and the {@link 
#baseName}
+         * Will set the root.
+         * @param file the file to set the base name from.
          * @return this.
          */
-        public Builder setCaseSensitive(final boolean isCaseSensitive) {
-            this.isCaseSensitive = isCaseSensitive;
+        public Builder setBaseName(final File file) {
+            DocumentName.Builder builder = 
DocumentName.builder(fsInfo).setName(file);
+            builder.sameNameFlag = true;
+            setBaseName(builder);
             return this;
         }
 
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/document/DocumentNameMatcher.java
 
b/apache-rat-core/src/main/java/org/apache/rat/document/DocumentNameMatcher.java
index 80fe275e..6b3c31bd 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/document/DocumentNameMatcher.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/document/DocumentNameMatcher.java
@@ -90,8 +90,8 @@ public final class DocumentNameMatcher {
 
     /**
      * Tokenizes name for faster Matcher processing.
-     * @param name the name to tokenize
-     * @param dirSeparator the directory separator
+     * @param name the name to tokenize.
+     * @param dirSeparator the directory separator.
      * @return the tokenized name.
      */
     private static char[][] tokenize(final String name, final String 
dirSeparator) {
@@ -316,12 +316,12 @@ public final class DocumentNameMatcher {
     }
 
     /**
-     * A DocumentName predicate that uses MatchPatterns.
+     * A DocumentName predicate that uses {@link MatchPatterns}.
      */
     public static final class MatchPatternsPredicate implements 
Predicate<DocumentName> {
-        /** The base directory for the pattern matches */
+        /** The base directory for the pattern matches. */
         private final DocumentName basedir;
-        /** The pattern matchers */
+        /** The pattern matchers. */
         private final MatchPatterns patterns;
 
         private MatchPatternsPredicate(final DocumentName basedir, final 
MatchPatterns patterns) {
@@ -365,10 +365,10 @@ public final class DocumentNameMatcher {
     }
 
     /**
-     * A DocumentName predicate that uses FileFilter.
+     * A DocumentName predicate that uses {@link FileFilter}.
      */
     public static final class FileFilterPredicate implements 
Predicate<DocumentName> {
-        /** The file filter */
+        /** The file filter. */
         private final FileFilter fileFilter;
 
         private FileFilterPredicate(final FileFilter fileFilter) {
@@ -386,11 +386,15 @@ public final class DocumentNameMatcher {
         }
     }
 
+    /**
+     * A marker interface to indicate this predicate contains a collection of 
matchers.
+     */
     interface CollectionPredicate extends Predicate<DocumentName> {
         Iterable<DocumentNameMatcher> getMatchers();
     }
+
     /**
-     * A marker interface to indicate this predicate contains a collection of 
matchers.
+     * A marker class to indicate this predicate contains a collection of 
matchers.
      */
     abstract static class CollectionPredicateImpl implements 
CollectionPredicate {
         /** The collection for matchers that make up this predicate */
@@ -487,13 +491,13 @@ public final class DocumentNameMatcher {
      * Data from a {@link DocumentNameMatcher#decompose(DocumentName)} call.
      */
     public static final class DecomposeData {
-        /** The level this data was generated at */
+        /** The level this data was generated at. */
         private final int level;
-        /** The name of the DocumentNameMatcher that created this result */
+        /** The name of the DocumentNameMatcher that created this result. */
         private final DocumentNameMatcher matcher;
         /** The result of the check. */
         private final boolean result;
-        /** The candidate */
+        /** The actual candidate. */
         private final DocumentName candidate;
 
         private DecomposeData(final int level, final DocumentNameMatcher 
matcher, final DocumentName candidate, final boolean result) {
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/document/FileDocument.java 
b/apache-rat-core/src/main/java/org/apache/rat/document/FileDocument.java
index 3e7dec89..d7596a3e 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/document/FileDocument.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/document/FileDocument.java
@@ -42,20 +42,20 @@ public class FileDocument extends Document {
      * Creates a File document.
      * @param basedir the base directory for this document.
      * @param file the file to wrap.
-     * @param nameExcluder the path matcher to filter files/directories with.
+     * @param nameMatcher the path matcher to filter files/directories with.
      */
-    public FileDocument(final DocumentName basedir, final File file, final 
DocumentNameMatcher nameExcluder) {
-        
super(DocumentName.builder(file).setBaseName(basedir.getBaseName()).build(), 
nameExcluder);
+    public FileDocument(final DocumentName basedir, final File file, final 
DocumentNameMatcher nameMatcher) {
+        
super(DocumentName.builder(file).setBaseName(basedir.getBaseName()).build(), 
nameMatcher);
         this.file = file;
     }
 
     /**
      * Creates a File document where the baseDir is the root directory.
      * @param file the file to wrap.
-     * @param nameExcluder the path matcher to filter files/directories with.
+     * @param nameMatcher the path matcher to filter files/directories with.
      */
-    public FileDocument(final File file, final DocumentNameMatcher 
nameExcluder) {
-        super(DocumentName.builder(file).setBaseName(File.separator).build(), 
nameExcluder);
+    public FileDocument(final File file, final DocumentNameMatcher 
nameMatcher) {
+        super(DocumentName.builder(file).setBaseName(File.separator).build(), 
nameMatcher);
         this.file = file;
     }
 
@@ -70,12 +70,12 @@ public class FileDocument extends Document {
             SortedSet<Document> result = new TreeSet<>();
             File[] files = file.listFiles();
             if (files != null) {
-                FileFilter fileFilter = ExclusionUtils.asFileFilter(name, 
nameExcluder);
+                FileFilter fileFilter = ExclusionUtils.asFileFilter(name, 
nameMatcher);
                 for (File child : files) {
                     if (fileFilter.accept(child)) {
-                        result.add(new FileDocument(name, child, 
nameExcluder));
+                        result.add(new FileDocument(name, child, nameMatcher));
                     } else {
-                        result.add(new IgnoredDocument(name, child, 
nameExcluder));
+                        result.add(new IgnoredDocument(name, child, 
nameMatcher));
                     }
                 }
             }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/walker/ArchiveWalker.java 
b/apache-rat-core/src/main/java/org/apache/rat/walker/ArchiveWalker.java
index d9148701..a50aa5ef 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/walker/ArchiveWalker.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/walker/ArchiveWalker.java
@@ -35,6 +35,7 @@ import org.apache.commons.io.IOUtils;
 import org.apache.rat.api.Document;
 import org.apache.rat.api.RatException;
 import org.apache.rat.document.ArchiveEntryDocument;
+import org.apache.rat.document.ArchiveEntryName;
 import org.apache.rat.document.DocumentName;
 import org.apache.rat.report.RatReport;
 import org.apache.rat.utils.DefaultLog;
@@ -42,7 +43,7 @@ import org.apache.rat.utils.DefaultLog;
 import static java.lang.String.format;
 
 /**
- * Walks various kinds of archives files
+ * Walks various kinds of archives files.
  */
 public class ArchiveWalker extends Walker {
 
@@ -68,7 +69,7 @@ public class ArchiveWalker extends Walker {
     }
 
     /**
-     * Creates an input stream from the Directory being walked.
+     * Creates an input stream from the directory being walked.
      * @return A buffered input stream reading the archive data.
      * @throws IOException on error
      */
@@ -83,19 +84,16 @@ public class ArchiveWalker extends Walker {
     public Collection<Document> getDocuments() throws RatException {
         List<Document> result = new ArrayList<>();
         try (ArchiveInputStream<? extends ArchiveEntry> input = new 
ArchiveStreamFactory().createArchiveInputStream(createInputStream())) {
-            ArchiveEntry entry = null;
+            ArchiveEntry entry;
             while ((entry = input.getNextEntry()) != null) {
                 if (!entry.isDirectory() && input.canReadEntryData(entry)) {
-                    DocumentName innerName = 
DocumentName.builder().setDirSeparator("/").setName(entry.getName())
-                            .setBaseName(".").setCaseSensitive(true).build();
-                    if 
(this.getDocument().getNameExcluder().matches(innerName)) {
+                    DocumentName innerName = 
DocumentName.builder().setName(entry.getName())
+                            .setBaseName(".").build();
+                    if 
(this.getDocument().getNameMatcher().matches(innerName)) {
                         ByteArrayOutputStream baos = new 
ByteArrayOutputStream();
                         IOUtils.copy(input, baos);
-                        DocumentName archiveName = getDocument().getName();
-                        String outerNameStr = format("%s#%s", 
archiveName.getName(), entry.getName());
-                        DocumentName outerName = 
DocumentName.builder(archiveName).setName(outerNameStr)
-                                .setCaseSensitive(true).build();
-                        result.add(new ArchiveEntryDocument(outerName, 
baos.toByteArray(), getDocument().getNameExcluder()));
+                        ArchiveEntryName entryName = new 
ArchiveEntryName(getDocument().getName(), entry.getName());
+                        result.add(new ArchiveEntryDocument(entryName, 
baos.toByteArray(), getDocument().getNameMatcher()));
                     }
                 }
             }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/walker/FileListWalker.java 
b/apache-rat-core/src/main/java/org/apache/rat/walker/FileListWalker.java
index 6e6c50e5..05609085 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/walker/FileListWalker.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/walker/FileListWalker.java
@@ -25,6 +25,7 @@ import java.io.Reader;
 import org.apache.commons.io.IOUtils;
 import org.apache.rat.api.RatException;
 import org.apache.rat.commandline.Arg;
+import org.apache.rat.config.exclusion.ExclusionUtils;
 import org.apache.rat.document.DocumentName;
 import org.apache.rat.document.DocumentNameMatcher;
 import org.apache.rat.document.FileDocument;
@@ -37,12 +38,12 @@ import org.apache.rat.utils.DefaultLog;
  * internally.
  */
 public class FileListWalker implements IReportable {
-    /** The source document name */
+    /** The source document name. */
     private final FileDocument source;
-    /** The root document name */
-    private final FileDocument rootDoc;
-    /** the base directory for the source document */
-    private final FileDocument baseDoc;
+    /** The root document name. */
+    private final DocumentName rootDoc;
+    /** The base directory for the source document. */
+    private final DocumentName baseDoc;
 
     /**
      * Constructor.
@@ -51,22 +52,21 @@ public class FileListWalker implements IReportable {
     public FileListWalker(final FileDocument source) {
         this.source = source;
         File baseDir = source.getFile().getParentFile().getAbsoluteFile();
-        this.baseDoc = new FileDocument(baseDir, 
DocumentNameMatcher.MATCHES_ALL);
+        this.baseDoc = DocumentName.builder(baseDir).build();
         File p = baseDir;
         while (p.getParentFile() != null) {
             p = p.getParentFile();
         }
-        File rootDir = p;
-        rootDoc = new FileDocument(rootDir, DocumentNameMatcher.MATCHES_ALL);
+        this.rootDoc = DocumentName.builder(p).build();
     }
 
     private FileDocument createDocument(final String unixFileName) {
         DocumentName sourceName = source.getName();
-        String finalName = "/".equals(sourceName.getDirectorySeparator()) ? 
unixFileName :
-            unixFileName.replace("/", sourceName.getDirectorySeparator());
-        FileDocument documentBase = unixFileName.startsWith("/") ? rootDoc : 
baseDoc;
-        File documentFile = new File(documentBase.getFile(), finalName);
-        return new FileDocument(rootDoc.getName(), documentFile, 
DocumentNameMatcher.MATCHES_ALL);
+        String finalName = ExclusionUtils.convertSeparator(unixFileName, "/", 
sourceName.getDirectorySeparator());
+        DocumentName documentBase = unixFileName.startsWith("/") ? rootDoc : 
baseDoc;
+        DocumentName documentName = documentBase.resolve(finalName);
+        File documentFile = documentName.asFile();
+        return new FileDocument(documentBase, documentFile, 
DocumentNameMatcher.MATCHES_ALL);
     }
 
     @Override
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java
index f723e5fd..c0d45c08 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java
@@ -25,6 +25,7 @@ import org.apache.commons.cli.Option;
 import org.apache.commons.cli.ParseException;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.rat.commandline.ArgumentContext;
+import org.apache.rat.document.DocumentName;
 import org.apache.rat.license.LicenseSetFactory;
 import org.apache.rat.report.IReportable;
 import org.apache.rat.test.AbstractOptionsProvider;
@@ -50,8 +51,8 @@ import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.lang.String.format;
+
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.fail;
 
 public class OptionCollectionTest {
@@ -158,11 +159,12 @@ public class OptionCollectionTest {
     @ParameterizedTest
     @ValueSource(strings = { ".", "./", "target", "./target" })
     public void getReportableTest(String fName) throws IOException {
-        File expected = new File(fName);
+        File base = new File(fName);
+        String expected = 
DocumentName.FSInfo.getDefault().normalize(base.getAbsolutePath());
         ReportConfiguration config = 
OptionCollection.parseCommands(testPath.toFile(), new String[]{fName}, o -> 
fail("Help called"), false);
-        IReportable reportable = OptionCollection.getReportable(expected, 
config);
-        assertNotNull(reportable, () -> format("'%s' returned null", fName));
-        
assertThat(reportable.getName().getName()).isEqualTo(expected.getAbsolutePath());
+        IReportable reportable = OptionCollection.getReportable(base, config);
+        assertThat(reportable).as(() -> format("'%s' returned null", 
fName)).isNotNull();
+        assertThat(reportable.getName().getName()).isEqualTo(expected);
     }
 
     /**
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/analysis/AnalyserFactoryTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/analysis/AnalyserFactoryTest.java
index 4e2461ce..72ebff4b 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/analysis/AnalyserFactoryTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/analysis/AnalyserFactoryTest.java
@@ -114,8 +114,8 @@ public class AnalyserFactoryTest {
     private static Stream<Arguments> archiveProcessingTestData() {
         List<Arguments> lst = new ArrayList<>();
         lst.add(Arguments.of(ReportConfiguration.Processing.NOTIFICATION, 0));
-        lst.add(Arguments.of(ReportConfiguration.Processing.PRESENCE, 2));
-        lst.add(Arguments.of(ReportConfiguration.Processing.ABSENCE, 3));
+        lst.add(Arguments.of(ReportConfiguration.Processing.PRESENCE, 1));
+        lst.add(Arguments.of(ReportConfiguration.Processing.ABSENCE, 2));
         return lst.stream();
     }
 
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java
index 580d1084..f8535a90 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java
@@ -51,7 +51,13 @@ public class ExclusionProcessorTest {
     }
 
     private void testParseExclusion(DocumentNameMatcher nameMatcher, 
DocumentName name, boolean expected) {
-        assertThat(nameMatcher.matches(name)).as(() -> format("Failed on [%s 
%s]", basedir, name)).isEqualTo(expected);
+        assertThat(nameMatcher.matches(name)).as(() -> format("Failed on [%s 
%s]%n%s", basedir, name, dump(nameMatcher, name))).isEqualTo(expected);
+    }
+
+    private String dump(DocumentNameMatcher nameMatcher, DocumentName name) {
+        StringBuilder sb = new StringBuilder();
+        nameMatcher.decompose(name).forEach(s -> sb.append(s).append("\n"));
+        return sb.toString();
     }
 
     private DocumentName mkName(String pth) {
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/FileProcessorTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/FileProcessorTest.java
index 0519cee8..92291267 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/FileProcessorTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/FileProcessorTest.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Stream;
 import org.apache.rat.document.DocumentName;
+import org.apache.rat.document.FSInfoTest;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
@@ -40,7 +41,7 @@ public class FileProcessorTest {
 
     public static Stream<Arguments> localizePatternData() {
         List<Arguments>  lst = new ArrayList<>();
-        DocumentName baseName = 
DocumentName.builder().setName("fileBeingRead").setBaseName("baseDir").setDirSeparator("/").build();
+        DocumentName baseName = 
DocumentName.builder(FSInfoTest.UNIX).setName("fileBeingRead").setBaseName("baseDir").build();
 
         lst.add(Arguments.of(baseName, "file", "/baseDir/file"));
         lst.add(Arguments.of(baseName, "!file", "!/baseDir/file"));
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java
index def6ec44..beeeeca9 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java
@@ -19,39 +19,207 @@
 package org.apache.rat.document;
 
 import java.io.File;
+import java.io.FileFilter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Stream;
+
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.rat.config.exclusion.ExclusionUtils;
+import org.apache.rat.document.DocumentName.FSInfo;
+
 import org.assertj.core.util.Files;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mockito;
 
-import static java.lang.String.format;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.apache.rat.document.FSInfoTest.OSX;
+import static org.apache.rat.document.FSInfoTest.UNIX;
+import static org.apache.rat.document.FSInfoTest.WINDOWS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.any;
 
 public class DocumentNameTest {
 
+    public static DocumentName mkName(Path tempDir, FSInfo fsInfo) {
+        File docFile = mkFile(tempDir.toFile(), fsInfo);
+        DocumentName result = 
DocumentName.builder(fsInfo).setName(docFile).build();
+        DocumentName mocked = Mockito.spy(result);
+
+        String fn = result.localized(FileSystems.getDefault().getSeparator());
+        File file = tempDir.resolve(fn.substring(1)).toFile();
+        File mockedFile = mkFile(file, fsInfo);
+        when(mocked.asFile()).thenReturn(mockedFile);
+
+        assertThat(mocked.asFile()).isEqualTo(mockedFile);
+        return mocked;
+    }
+
+    private static File[] listFiles(File file, FSInfo fsInfo) {
+        File[] fileList = file.listFiles();
+        if (fileList == null) {
+            return fileList;
+        }
+        return Arrays.stream(fileList).map(f -> mkFile(f, 
fsInfo)).toArray(File[]::new);
+    }
+
+    private static File[] listFiles(File file, FSInfo fsInfo, FileFilter 
filter) {
+        File[] fileList = file.listFiles();
+        if (fileList == null) {
+            return fileList;
+        }
+        return Arrays.stream(fileList).map(f -> mkFile(f, 
fsInfo)).filter(filter::accept).toArray(File[]::new);
+    }
+
+    private static File[] listFiles(File file, FSInfo fsInfo, FilenameFilter 
filter) {
+        File[] fileList = file.listFiles();
+        if (fileList == null) {
+            return fileList;
+        }
+        return Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).filter(x -> 
filter.accept(x, x.getName())).toArray(File[]::new);
+    }
+
+    public static File mkFile(final File file, final FSInfo fsInfo) {
+        File mockedFile = mock(File.class);
+        when(mockedFile.listFiles()).thenAnswer( env -> listFiles(file, 
fsInfo));
+        when(mockedFile.listFiles(any(FilenameFilter.class))).thenAnswer( env 
-> listFiles(file, fsInfo, env.getArgument(0, FilenameFilter.class)));
+        when(mockedFile.listFiles(any(FileFilter.class))).thenAnswer(env -> 
listFiles(file, fsInfo, env.getArgument(0, FileFilter.class)));
+        
when(mockedFile.getName()).thenReturn(ExclusionUtils.convertSeparator(file.getName(),
 FSInfoTest.DEFAULT.dirSeparator(), fsInfo.dirSeparator()));
+        
when(mockedFile.getAbsolutePath()).thenReturn(ExclusionUtils.convertSeparator(file.getAbsolutePath(),
 FSInfoTest.DEFAULT.dirSeparator(), fsInfo.dirSeparator()));
+        when(mockedFile.isFile()).thenAnswer(env -> file.isFile());
+        when(mockedFile.exists()).thenAnswer(emv -> file.exists());
+        when(mockedFile.isDirectory()).thenAnswer(env -> file.isDirectory());
+
+        return mockedFile;
+    }
+
+    public static DocumentName mkName(Path tempDir, DocumentName baseDir, 
String pth) throws IOException {
+        DocumentName result = 
baseDir.resolve(ExclusionUtils.convertSeparator(pth, "/", 
baseDir.getDirectorySeparator()));
+        DocumentName mocked = Mockito.spy(result);
+
+        String fn = result.localized(FileSystems.getDefault().getSeparator());
+        File file = tempDir.resolve(fn.substring(1)).toFile();
+        File parent = file.getParentFile();
+        if (parent.exists() && !parent.isDirectory()) {
+            parent.delete();
+        }
+        parent.mkdirs();
+        if (file.exists()) {
+            if (file.isDirectory()) {
+                FileUtils.deleteDirectory(file);
+            } else {
+                FileUtils.delete(file);
+            }
+        }
+        file.createNewFile();
+        when(mocked.asFile()).thenReturn(file);
+        return mocked;
+    }
+
+    @ParameterizedTest(name = "{index} {0} {2}")
+    @MethodSource("resolveTestData")
+    void resolveTest(String testName, DocumentName base, String toResolve, 
DocumentName expected) {
+       DocumentName actual = base.resolve(toResolve);
+       assertThat(actual).isEqualTo(expected);
+    }
+
+    private static Stream<Arguments> resolveTestData() {
+        List<Arguments> lst = new ArrayList<>();
+
+        DocumentName base = 
DocumentName.builder(UNIX).setName("/dir/unix").setBaseName("/").build();
+
+        DocumentName expected = 
DocumentName.builder(UNIX).setName("/dir/unix/relative").setBaseName("/").build();
+        lst.add(Arguments.of("unix", base, "relative", expected));
+
+        expected = 
DocumentName.builder(UNIX).setName("/from/root").setBaseName("/").build();
+        lst.add(Arguments.of("unix", base, "/from/root", expected));
+
+        expected = 
DocumentName.builder(UNIX).setName("dir/up/and/down").setBaseName("/").build();
+        lst.add(Arguments.of("unix", base, "../up/and/down", expected));
+
+        expected = 
DocumentName.builder(UNIX).setName("/from/root").setBaseName("/").build();
+        lst.add(Arguments.of("unix", base, "\\from\\root", expected));
+
+        expected = 
DocumentName.builder(UNIX).setName("dir/up/and/down").setBaseName("/").build();
+        lst.add(Arguments.of("unix", base, "..\\up\\and\\down", expected));
+
+        // WINDOWS
+        base = 
DocumentName.builder(WINDOWS).setName("\\dir\\windows").setBaseName("C:\\").build();
+
+        expected = 
DocumentName.builder(WINDOWS).setName("\\dir\\windows\\relative").setBaseName("C:\\").build();
+        lst.add(Arguments.of("windows", base, "relative", expected));
+
+        expected = 
DocumentName.builder(WINDOWS).setName("\\from\\root").setBaseName("C:\\").build();
+        lst.add(Arguments.of("windows", base, "/from/root", expected));
+
+        expected = 
DocumentName.builder(WINDOWS).setName("dir\\up\\and\\down").setBaseName("C:\\").build();
+        lst.add(Arguments.of("windows", base, "../up/and/down", expected));
+
+        expected = 
DocumentName.builder(WINDOWS).setName("\\from\\root").setBaseName("C:\\").build();
+        lst.add(Arguments.of("windows", base, "\\from\\root", expected));
+
+        expected = 
DocumentName.builder(WINDOWS).setName("dir\\up\\and\\down").setBaseName("C:\\").build();
+        lst.add(Arguments.of("windows", base, "..\\up\\and\\down", expected));
+
+        // OSX
+        base = 
DocumentName.builder(OSX).setName("/dir/osx").setBaseName("/").build();
+
+        expected = 
DocumentName.builder(OSX).setName("/dir/osx/relative").setBaseName("/").build();
+        lst.add(Arguments.of("osx", base, "relative", expected));
+
+        expected = 
DocumentName.builder(OSX).setName("/from/root").setBaseName("/").build();
+        lst.add(Arguments.of("osx", base, "/from/root", expected));
+
+        expected = 
DocumentName.builder(OSX).setName("dir/up/and/down").setBaseName("/").build();
+        lst.add(Arguments.of("osx", base, "../up/and/down", expected));
+
+        expected = 
DocumentName.builder(OSX).setName("/from/root").setBaseName("/").build();
+        lst.add(Arguments.of("osx", base, "\\from\\root", expected));
+
+        expected = 
DocumentName.builder(OSX).setName("dir/up/and/down").setBaseName("/").build();
+        lst.add(Arguments.of("osx", base, "..\\up\\and\\down", expected));
+
+        return lst.stream();
+    }
+
     @Test
-    public void localizeTest() {
-        DocumentName documentName = DocumentName.builder().setName("/a/b/c")
-                
.setBaseName("/a").setDirSeparator("/").setCaseSensitive(false).build();
+    void localizeTest() {
+        DocumentName documentName = 
DocumentName.builder(UNIX).setName("/a/b/c")
+                .setBaseName("/a").build();
+        assertThat(documentName.localized()).isEqualTo("/b/c");
+        assertThat(documentName.localized("-")).isEqualTo("-b-c");
+
+        documentName = DocumentName.builder(WINDOWS).setName("\\a\\b\\c")
+                .setBaseName("\\a").build();
+        assertThat(documentName.localized()).isEqualTo("\\b\\c");
+        assertThat(documentName.localized("-")).isEqualTo("-b-c");
+
+        documentName = DocumentName.builder(OSX).setName("/a/b/c")
+                .setBaseName("/a").build();
         assertThat(documentName.localized()).isEqualTo("/b/c");
         assertThat(documentName.localized("-")).isEqualTo("-b-c");
     }
 
-    @ParameterizedTest(name ="{index} {0}")
+    @ParameterizedTest(name = "{index} {0}")
     @MethodSource("validBuilderData")
     void validBuilderTest(String testName, DocumentName.Builder builder, 
String root, String name, String baseName, String dirSeparator) {
         DocumentName underTest = builder.build();
         assertThat(underTest.getRoot()).as(testName).isEqualTo(root);
         
assertThat(underTest.getDirectorySeparator()).as(testName).isEqualTo(dirSeparator);
-        
assertThat(underTest.getName()).as(testName).isEqualTo(root+dirSeparator+name);
-        
assertThat(underTest.getBaseName()).as(testName).isEqualTo(root+dirSeparator+baseName);
+        assertThat(underTest.getName()).as(testName).isEqualTo(root + 
dirSeparator + name);
+        assertThat(underTest.getBaseName()).as(testName).isEqualTo(root + 
dirSeparator + baseName);
     }
 
     private static Stream<Arguments> validBuilderData() {
@@ -94,98 +262,65 @@ public class DocumentNameTest {
         lst.add(Arguments.of("setName(root)", 
DocumentName.builder().setName(r), root, "", "", File.separator));
         lst.add(Arguments.of("Builder(root)", DocumentName.builder(r), root, 
"", "", File.separator));
 
-        lst.add(Arguments.of("foo/bar foo", 
DocumentName.builder().setDirSeparator("/")
+
+        lst.add(Arguments.of("foo/bar foo", DocumentName.builder(UNIX)
                 .setName("/foo/bar").setBaseName("foo"), "", "foo/bar", "foo", 
"/"));
-        DocumentName.Builder builder = 
DocumentName.builder().setDirSeparator("\\").setName("\\foo\\bar").setBaseName("foo")
+
+        DocumentName.Builder builder = 
DocumentName.builder(WINDOWS).setName("\\foo\\bar").setBaseName("C:\\foo")
                 .setRoot("C:");
-        lst.add(Arguments.of("C:\\foo\\bar foo", builder, "C:", "foo\\bar", 
"foo", "\\"));
+        lst.add(Arguments.of("\\foo\\bar foo", builder, "C:", "foo\\bar", 
"foo", "\\"));
+
+        lst.add(Arguments.of("foo/bar foo", DocumentName.builder(OSX)
+                .setName("/foo/bar").setBaseName("foo"), "", "foo/bar", "foo", 
"/"));
 
         return lst.stream();
     }
 
     @Test
-    public void splitRootsTest() {
-        Set<String> preserve = new HashSet<>(DocumentName.ROOTS);
-        try {
-            DocumentName.ROOTS.clear();
-            DocumentName.ROOTS.add("C:\\");
-            Pair<String, String> result = 
DocumentName.Builder.splitRoot("C:\\My\\path\\to\\a\\file.txt", "\\");
-            assertThat(result.getLeft()).isEqualTo("C:");
-            
assertThat(result.getRight()).isEqualTo("My\\path\\to\\a\\file.txt");
-
-
-            DocumentName.ROOTS.clear();
-            DocumentName.ROOTS.add("/");
-            result = DocumentName.Builder.splitRoot("/My/path/to/a/file.txt", 
"/");
-            assertThat(result.getLeft()).isEqualTo("");
-            assertThat(result.getRight()).isEqualTo("My/path/to/a/file.txt");
-
-        } finally {
-            DocumentName.ROOTS.clear();
-            DocumentName.ROOTS.addAll(preserve);
-        }
+    void splitRootsTest() {
+        Pair<String, String> result = 
DocumentName.builder(WINDOWS).splitRoot("C:\\My\\path\\to\\a\\file.txt");
+        assertThat(result.getLeft()).isEqualTo("C:");
+        assertThat(result.getRight()).isEqualTo("My\\path\\to\\a\\file.txt");
+
+        result = 
DocumentName.builder(UNIX).splitRoot("/My/path/to/a/file.txt");
+        assertThat(result.getLeft()).isEqualTo("");
+        assertThat(result.getRight()).isEqualTo("My/path/to/a/file.txt");
+
+        result = DocumentName.builder(OSX).splitRoot("/My/path/to/a/file.txt");
+        assertThat(result.getLeft()).isEqualTo("");
+        assertThat(result.getRight()).isEqualTo("My/path/to/a/file.txt");
     }
 
     @Test
-    public void archiveEntryNameTest() {
-        Set<String> preserve = new HashSet<>(DocumentName.ROOTS);
-        try {
-            DocumentName.ROOTS.clear();
-            DocumentName.ROOTS.add("C:\\");
-
-            DocumentName archiveName = 
DocumentName.builder().setDirSeparator("\\")
-                    
.setName("C:\\archives\\anArchive.zip").setBaseName("archives").build();
-            assertThat(archiveName.getRoot()).isEqualTo("C:");
-            assertThat(archiveName.getDirectorySeparator()).isEqualTo("\\");
-            assertThat(archiveName.getBaseName()).isEqualTo("C:\\archives");
-            
assertThat(archiveName.getName()).isEqualTo("C:\\archives\\anArchive.zip");
-            assertThat(archiveName.localized()).isEqualTo("\\anArchive.zip");
-
-            String entryName = "./anArchiveEntry.txt";
-            DocumentName innerName = DocumentName.builder()
-                    .setDirSeparator("/").setName(entryName)
-                    .setBaseName(".").setCaseSensitive(true).build();
-            assertThat(innerName.getRoot()).isEqualTo("");
-            assertThat(innerName.getDirectorySeparator()).isEqualTo("/");
-            assertThat(innerName.getBaseName()).isEqualTo("/.");
-            assertThat(innerName.getName()).isEqualTo("/" + entryName);
-            assertThat(innerName.localized()).isEqualTo("/anArchiveEntry.txt");
-
-            String outerNameStr = format("%s#%s", archiveName.getName(), 
entryName);
-            DocumentName outerName = 
DocumentName.builder(archiveName).setName(outerNameStr)
-                    .setCaseSensitive(innerName.isCaseSensitive()).build();
-
-            assertThat(outerName.getRoot()).isEqualTo("C:");
-            assertThat(outerName.getDirectorySeparator()).isEqualTo("\\");
-            assertThat(outerName.getBaseName()).isEqualTo("C:\\archives");
-            
assertThat(outerName.getName()).isEqualTo("C:\\archives\\anArchive.zip#./anArchiveEntry.txt");
-            
assertThat(outerName.localized()).isEqualTo("\\anArchive.zip#./anArchiveEntry.txt");
-            
assertThat(outerName.localized("/")).isEqualTo("/anArchive.zip#./anArchiveEntry.txt");
-
-            // test with directory
-            entryName = "./someDir/anArchiveEntry.txt";
-            innerName = DocumentName.builder()
-                    .setDirSeparator("/").setName(entryName)
-                    .setBaseName(".").setCaseSensitive(true).build();
-            assertThat(innerName.getRoot()).isEqualTo("");
-            assertThat(innerName.getDirectorySeparator()).isEqualTo("/");
-            assertThat(innerName.getBaseName()).isEqualTo("/.");
-            assertThat(innerName.getName()).isEqualTo("/" + entryName);
-            
assertThat(innerName.localized()).isEqualTo("/someDir/anArchiveEntry.txt");
-
-            outerNameStr = format("%s#%s", archiveName.getName(), entryName);
-            outerName = DocumentName.builder(archiveName).setName(outerNameStr)
-                    .setCaseSensitive(innerName.isCaseSensitive()).build();
-
-            assertThat(outerName.getRoot()).isEqualTo("C:");
-            assertThat(outerName.getDirectorySeparator()).isEqualTo("\\");
-            assertThat(outerName.getBaseName()).isEqualTo("C:\\archives");
-            
assertThat(outerName.getName()).isEqualTo("C:\\archives\\anArchive.zip#./someDir/anArchiveEntry.txt");
-            
assertThat(outerName.localized()).isEqualTo("\\anArchive.zip#./someDir/anArchiveEntry.txt");
-            
assertThat(outerName.localized("/")).isEqualTo("/anArchive.zip#./someDir/anArchiveEntry.txt");
-        } finally {
-            DocumentName.ROOTS.clear();
-            DocumentName.ROOTS.addAll(preserve);
-        }
+    void archiveEntryNameTest() {
+        String entryName = "./anArchiveEntry.txt";
+        DocumentName archiveName = DocumentName.builder(WINDOWS)
+                
.setName("C:\\archives\\anArchive.zip").setBaseName("C:\\archives").build();
+
+        assertThat(archiveName.getRoot()).isEqualTo("C:");
+        assertThat(archiveName.getDirectorySeparator()).isEqualTo("\\");
+        assertThat(archiveName.getBaseName()).isEqualTo("C:\\archives");
+        
assertThat(archiveName.getName()).isEqualTo("C:\\archives\\anArchive.zip");
+        assertThat(archiveName.localized()).isEqualTo("\\anArchive.zip");
+
+        ArchiveEntryName archiveEntryName = new ArchiveEntryName(archiveName, 
entryName);
+
+        
assertThat(archiveEntryName.getRoot()).isEqualTo(archiveName.getName()+"#");
+        assertThat(archiveEntryName.getDirectorySeparator()).isEqualTo("/");
+        
assertThat(archiveEntryName.getBaseName()).isEqualTo("C:\\archives\\anArchive.zip#");
+        
assertThat(archiveEntryName.getName()).isEqualTo("C:\\archives\\anArchive.zip#/anArchiveEntry.txt");
+        
assertThat(archiveEntryName.localized()).isEqualTo("/anArchiveEntry.txt");
+        
assertThat(archiveEntryName.localized("/")).isEqualTo("/anArchive.zip#/anArchiveEntry.txt");
+
+        // test with directory
+        entryName = "./someDir/anArchiveEntry.txt";
+        archiveEntryName = new ArchiveEntryName(archiveName, entryName);
+
+        
assertThat(archiveEntryName.getRoot()).isEqualTo(archiveName.getName()+"#");
+        assertThat(archiveEntryName.getDirectorySeparator()).isEqualTo("/");
+        
assertThat(archiveEntryName.getBaseName()).isEqualTo("C:\\archives\\anArchive.zip#");
+        
assertThat(archiveEntryName.getName()).isEqualTo("C:\\archives\\anArchive.zip#/someDir/anArchiveEntry.txt");
+        
assertThat(archiveEntryName.localized()).isEqualTo("/someDir/anArchiveEntry.txt");
+        
assertThat(archiveEntryName.localized("/")).isEqualTo("/anArchive.zip#/someDir/anArchiveEntry.txt");
     }
 }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/document/FSInfoTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/document/FSInfoTest.java
new file mode 100644
index 00000000..ced9d8c6
--- /dev/null
+++ b/apache-rat-core/src/test/java/org/apache/rat/document/FSInfoTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.rat.document;
+
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.provider.Arguments;
+
+public class FSInfoTest {
+    public static final DocumentName.FSInfo DEFAULT;
+    public static final DocumentName.FSInfo OSX;
+    public static final DocumentName.FSInfo UNIX;
+    public static final DocumentName.FSInfo WINDOWS;
+
+    static {
+        try (FileSystem osx = Jimfs.newFileSystem(Configuration.osX());
+             FileSystem unix = Jimfs.newFileSystem(Configuration.unix());
+             FileSystem windows = 
Jimfs.newFileSystem(Configuration.windows())) {
+            OSX = new DocumentName.FSInfo("osx", osx);
+            UNIX = new DocumentName.FSInfo("unix", unix);
+            WINDOWS = new DocumentName.FSInfo("windows", windows);
+            DEFAULT = new DocumentName.FSInfo("default", 
FileSystems.getDefault());
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to creat FSInfo objects: " + 
e.getMessage(), e);
+        }
+    }
+
+    public static final DocumentName.FSInfo[] TEST_SUITE = {UNIX, WINDOWS, 
OSX};
+
+    /**
+     * Provides arguments for parameterized tests that only require the fsInfo.
+     * @return a stream of TEST_SUITE based Arguments.
+     */
+    public static Stream<Arguments> fsInfoArgs() {
+        return Arrays.stream(TEST_SUITE).map(Arguments::of);
+    }
+}
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/document/guesser/NoteGuesserTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/document/guesser/NoteGuesserTest.java
index 2abf1934..87d157a0 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/document/guesser/NoteGuesserTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/document/guesser/NoteGuesserTest.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Stream;
 import org.apache.rat.document.DocumentName;
+import org.apache.rat.document.FSInfoTest;
 import org.apache.rat.testhelpers.TestingDocument;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
@@ -41,10 +42,8 @@ public class NoteGuesserTest {
     private static Stream<Arguments> nameData() {
         List<Arguments> lst = new ArrayList<>();
 
-        final DocumentName linuxBaseName = 
DocumentName.builder().setName("/").setBaseName("/").setDirSeparator("/")
-                .setCaseSensitive(true).build();
-        final DocumentName windowsBaseName = 
DocumentName.builder().setName("\\").setBaseName("\\")
-                .setDirSeparator("\\").setCaseSensitive(false).build();
+        final DocumentName linuxBaseName = 
DocumentName.builder(FSInfoTest.UNIX).setName("/").setBaseName("/").build();
+        final DocumentName windowsBaseName = 
DocumentName.builder(FSInfoTest.WINDOWS).setName("\\").setBaseName("\\").build();
 
         lst.add(Arguments.of(linuxBaseName.resolve("DEPENDENCIES"), true));
         lst.add(Arguments.of(linuxBaseName.resolve("LICENSE"), true));
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java
 
b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java
index 57c5a051..8a34245e 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java
@@ -244,7 +244,7 @@ public abstract class AbstractOptionsProvider implements 
ArgumentsProvider {
         return DocumentName.builder(new File(baseDir, name)).build();
     }
 
-    /* tests to be implemented */
+    /* Tests to be implemented */
     protected abstract void helpTest();
 
     /* Display the option and value under test */
@@ -344,6 +344,12 @@ public abstract class AbstractOptionsProvider implements 
ArgumentsProvider {
         try {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
             DocumentNameMatcher excluder = 
config.getDocumentExcluder(baseName());
+            for (String fname : notExcluded) {
+                final DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName))
+                        .as(() -> String.format("option: %s name: %s%n%s", 
option.getKey(), fname, excluder.decompose(docName)))
+                        .isTrue();
+            }
             for (String fname : excluded) {
                 DocumentName docName = mkDocName(fname);
                 assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isFalse();
@@ -392,6 +398,12 @@ public abstract class AbstractOptionsProvider implements 
ArgumentsProvider {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args),
                     ImmutablePair.of(excludeOption, EXCLUDE_ARGS));
             DocumentNameMatcher excluder = 
config.getDocumentExcluder(baseName());
+            for (String fname : notExcluded) {
+                final DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName))
+                        .as(() -> String.format("option: %s name: %s%n%s", 
option.getKey(), fname, excluder.decompose(docName)))
+                        .isTrue();
+            }
             for (String fname : excluded) {
                 DocumentName docName = mkDocName(fname);
                 assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isFalse();
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/TestingDocument.java 
b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/TestingDocument.java
index c1e75668..1bd66377 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/TestingDocument.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/TestingDocument.java
@@ -29,6 +29,7 @@ import org.apache.commons.io.function.IOSupplier;
 import org.apache.rat.api.Document;
 import org.apache.rat.document.DocumentNameMatcher;
 import org.apache.rat.document.DocumentName;
+import org.apache.rat.document.FSInfoTest;
 
 public class TestingDocument extends Document {
 
@@ -50,19 +51,19 @@ public class TestingDocument extends Document {
     }
 
     public TestingDocument(String name, DocumentNameMatcher matcher) {
-        
super(DocumentName.builder().setName(name).setBaseName("").setDirSeparator("/").setCaseSensitive(true).build(),
 matcher);
+        super(DocumentName.builder().setName(name).setBaseName("").build(), 
matcher);
         this.reader = null;
         this.input = null;
     }
 
     public TestingDocument(Reader reader, String name) {
-        
super(DocumentName.builder().setName(name).setBaseName("").setDirSeparator("/").setCaseSensitive(true).build(),
 DocumentNameMatcher.MATCHES_ALL);
+        super(DocumentName.builder().setName(name).setBaseName("").build(), 
DocumentNameMatcher.MATCHES_ALL);
         this.reader = reader;
         this.input = null;
     }
 
     public TestingDocument(IOSupplier<InputStream> inputStream, String name) {
-        
super(DocumentName.builder().setName(name).setBaseName("").setDirSeparator("/").setCaseSensitive(true).build(),
 DocumentNameMatcher.MATCHES_ALL);
+        
super(DocumentName.builder(FSInfoTest.UNIX).setName(name).setBaseName("").build(),
 DocumentNameMatcher.MATCHES_ALL);
         this.input = inputStream;
         this.reader = null;
     }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/walker/FileListWalkerTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/walker/FileListWalkerTest.java
index 14ac1478..ec739f64 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/walker/FileListWalkerTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/walker/FileListWalkerTest.java
@@ -33,8 +33,7 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
 
 public class FileListWalkerTest {
 
@@ -84,7 +83,7 @@ public class FileListWalkerTest {
 
 
         source = new File(working, "source.txt");
-        DocumentName sourceName = 
DocumentName.builder(source).setBaseName(working.getAbsolutePath()).build();
+        DocumentName sourceName = DocumentName.builder(source).build();
         File regular = new File(working, "regular");
         regular.mkdir();
         fileWriter(regular, "regularFile", "regular file");
@@ -111,8 +110,6 @@ public class FileListWalkerTest {
             writer.flush();
             System.out.flush();
         }
-
-        hiddenName = 
DocumentName.builder(hiddenFile).setBaseName(rootName.getBaseName()).build();
     }
     
     @Test
@@ -122,10 +119,7 @@ public class FileListWalkerTest {
         walker.run(new TestRatReport(scanned));
         String[] expected = {regularName.localized("/"), 
hiddenName.localized("/"),
         anotherName.localized("/")};
-        assertEquals(3, scanned.size());
-        for (String ex : expected) {
-            assertTrue(scanned.contains(ex), ()-> String.format("Missing %s 
from %s", ex, String.join(", ", scanned)));
-        }
+        assertThat(scanned).containsExactly(expected);
     }
 
     static class TestRatReport implements RatReport {
diff --git a/pom.xml b/pom.xml
index 7bfceb03..4d164ecb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -217,6 +217,12 @@ agnostic home for software distribution comprehension and 
audit tools.
         <version>2.4.21</version>
         <scope>test</scope>
       </dependency>
+      <dependency>
+        <groupId>com.google.jimfs</groupId>
+        <artifactId>jimfs</artifactId>
+        <version>1.3.0</version>
+        <scope>test</scope>
+      </dependency>
     </dependencies>
   </dependencyManagement>
   <reporting>
@@ -379,8 +385,8 @@ agnostic home for software distribution comprehension and 
audit tools.
           <artifactId>spotbugs-maven-plugin</artifactId>
           <version>4.8.6.6</version>
           <configuration>
-            <!-- RAT-369: JDK21 finds 94 errors, while older releases find 
fewer -->
-            <maxAllowedViolations>94</maxAllowedViolations>
+            <!-- RAT-369: JDK21 finds 98 errors, while older releases find 
fewer -->
+            <maxAllowedViolations>98</maxAllowedViolations>
             <failOnError>true</failOnError>
             <!-- we only want to see our own problems in all subpackages -->
             <onlyAnalyze>org.apache.rat.-</onlyAnalyze>
@@ -489,7 +495,7 @@ agnostic home for software distribution comprehension and 
audit tools.
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-remote-resources-plugin</artifactId>
-          <version>3.3.0</version>
+          <version>3.2.0</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>

Reply via email to