From 9f82d3d5db8fd1640cbbf991c32077c3f396c9a6 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Sun, 12 Jan 2025 16:01:43 +0000 Subject: [PATCH 01/17] Added FSInfo to handle file system differences --- apache-rat-core/pom.xml | 5 + .../it/java/org/apache/rat/ReportTest.java | 2 +- .../rat/config/exclusion/ExclusionUtils.java | 14 + .../apache/rat/document/ArchiveEntryName.java | 74 +++ .../org/apache/rat/document/DocumentName.java | 553 ++++++++++++------ .../org/apache/rat/walker/ArchiveWalker.java | 15 +- .../org/apache/rat/OptionCollectionTest.java | 12 +- .../rat/analysis/AnalyserFactoryTest.java | 4 +- .../config/exclusion/FileProcessorTest.java | 3 +- .../apache/rat/document/DocumentNameTest.java | 325 +++++++--- .../org/apache/rat/document/FSInfoTest.java | 60 ++ .../rat/document/guesser/NoteGuesserTest.java | 7 +- .../rat/testhelpers/TestingDocument.java | 4 +- pom.xml | 10 +- 14 files changed, 798 insertions(+), 290 deletions(-) create mode 100644 apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java create mode 100644 apache-rat-core/src/test/java/org/apache/rat/document/FSInfoTest.java diff --git a/apache-rat-core/pom.xml b/apache-rat-core/pom.xml index 8ac00d1e3..844a7efc5 100644 --- a/apache-rat-core/pom.xml +++ b/apache-rat-core/pom.xml @@ -281,5 +281,10 @@ groovy-all test + + com.google.jimfs + jimfs + test + 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 b58c6732b..16bc908bc 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 static Stream args() throws RatException { @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/config/exclusion/ExclusionUtils.java b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionUtils.java index 339ce0978..3f082336a 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 @@ -185,4 +185,18 @@ private static void verifyFile(final File file) { 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/document/ArchiveEntryName.java b/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java new file mode 100644 index 000000000..889a7b165 --- /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 { + /** Then 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 e71bed85b..9b0c6a0d9 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,19 +20,24 @@ 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 @@ -51,66 +56,85 @@ * within an archive. When representing a file in an archive the baseName is the name of the enclosing archive document. *

*/ -public final class DocumentName implements Comparable { - /** The list of all roots on the file system. */ - static final Set 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 { /** 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 String baseName; + 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. + * it is set to the directory containing the file. * @param file The file to set defaults from. * @return the Builder. */ @@ -131,26 +155,49 @@ public static Builder builder(final DocumentName 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 an new Path object. + */ + public Path asPath() { + return Paths.get(name); } /** - * Creates a new DocumentName by adding the child to the current 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 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 +205,7 @@ public DocumentName resolve(final String child) { * @return the fully qualified name of the document. */ public String getName() { - return root + dirSeparator + name; + return root + fsInfo.dirSeparator() + name; } /** @@ -166,7 +213,7 @@ public String getName() { * @return the fully qualified basename of the document. */ public String getBaseName() { - return root + dirSeparator + baseName; + return baseName.getName(); } /** @@ -182,7 +229,7 @@ public String getRoot() { * @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 +237,25 @@ public DocumentName getBaseDocumentName() { * @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 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 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 +264,13 @@ public String getDirectorySeparator() { * @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 +282,26 @@ public String localized() { * @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 +310,7 @@ public String getShortName() { * @return {@code true} if the name is case-sensitive. */ public boolean isCaseSensitive() { - return isCaseSensitive; + return fsInfo.isCaseSensitive(); } /** @@ -255,28 +323,158 @@ public String toString() { } @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 { + /** 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 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 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 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 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 "." and ".." from filenames names. + * @param pattern the file name pattern + * @return the normalized file name. + */ + public String normalize(final String pattern) { + if (StringUtils.isBlank(pattern)) { + return ""; + } + List 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("can not creat 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); + } } /** @@ -286,44 +484,65 @@ public static final class Builder { /** 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; + 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(); } /** @@ -331,12 +550,11 @@ private Builder(final DocumentName 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()); + 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 cannot be null"); } } @@ -346,58 +564,54 @@ private void verify() { * @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 null + * an empty string is used. *

- * 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. *

- * @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 pair = splitRoot(name, dirSeparator); + Pair 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 splitRoot(final File file) { - return splitRoot(file.getAbsolutePath(), File.separator); - } - /** * Extracts the root/name pair from a name string. *

* Package private for testing. *

* @param name the name to extract the root/name pair from. - * @param dirSeparator the directory separator. * @return the root/name pair. */ - static Pair splitRoot(final String name, final String dirSeparator) { + Pair 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 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 @@ private void setEmptyRoot(final String root) { } /** - * 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 appropraitly. * @param file the file to set the properties from. * @return this. */ public Builder setName(final File file) { - Pair pair = splitRoot(file); + Pair 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 Builder setName(final File file) { * Sets the baseName. * Will set the root if it is not set. *

- * 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. *

* @param baseName the basename to use. * @return this. */ public Builder setBaseName(final String baseName) { - Pair 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 Builder setBaseName(final String baseName) { * @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 Builder setBaseName(final DocumentName baseName) { } /** - * 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 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/walker/ArchiveWalker.java b/apache-rat-core/src/main/java/org/apache/rat/walker/ArchiveWalker.java index d9148701e..e2cc470e6 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.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; @@ -82,20 +83,18 @@ private InputStream createInputStream() throws IOException { */ public Collection getDocuments() throws RatException { List result = new ArrayList<>(); + //DocumentName.FSInfo archiveInfo = new DocumentName.FSInfo(true, Arrays.asList("/"), "/"); try (ArchiveInputStream 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(); + DocumentName innerName = DocumentName.builder().setName(entry.getName()) + .setBaseName(".").build(); if (this.getDocument().getNameExcluder().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().getNameExcluder())); } } } 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 ab1ce763a..38712c9bf 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 @@ -18,11 +18,13 @@ */ package org.apache.rat; +import java.nio.file.Path; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.tuple.Pair; +import org.apache.rat.document.DocumentName; import org.apache.rat.license.LicenseSetFactory; import org.apache.rat.report.IReportable; import org.apache.rat.test.AbstractOptionsProvider; @@ -46,6 +48,7 @@ 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.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -148,11 +151,12 @@ public void testDefaultConfiguration() throws ParseException { @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(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 4e2461ce6..72ebff4be 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 void archiveTypeAnalyserTest() throws Exception { private static Stream archiveProcessingTestData() { List 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/FileProcessorTest.java b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/FileProcessorTest.java index 0519cee82..922912674 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.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 @@ void localizePatternTest(DocumentName baseName, String pattern, String expectedS public static Stream localizePatternData() { List 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 def6ec447..31693e030 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 @@ -18,43 +18,217 @@ */ package org.apache.rat.document; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.nio.file.FileSystem; +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) throws IOException { + 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 resolveTestData() { + List 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 validBuilderData() { + private static Stream validBuilderData() throws IOException { List lst = new ArrayList<>(); File f = Files.newTemporaryFile(); @@ -94,98 +268,67 @@ private static Stream validBuilderData() { 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 preserve = new HashSet<>(DocumentName.ROOTS); - try { - DocumentName.ROOTS.clear(); - DocumentName.ROOTS.add("C:\\"); - Pair 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() throws IOException { + Pair 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 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() throws IOException { + 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 000000000..db2f91ea2 --- /dev/null +++ b/apache-rat-core/src/test/java/org/apache/rat/document/FSInfoTest.java @@ -0,0 +1,60 @@ +/* + * 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.ArrayList; +import java.util.Arrays; +import java.util.List; +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}; + + /** + * Provided arguments for parameterized tests that only require the fsInfo. + * @return a stream of TEST_SUITE based Arguments. + */ + public static Stream 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 2abf19340..87d157a0e 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.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 void testMatches(DocumentName testingName, boolean expected) { private static Stream nameData() { List 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/testhelpers/TestingDocument.java b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/TestingDocument.java index c1e756687..532182b8e 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 @@ -50,13 +50,13 @@ public TestingDocument(DocumentName documentName) { } 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; } diff --git a/pom.xml b/pom.xml index b181a2056..531a403e2 100644 --- a/pom.xml +++ b/pom.xml @@ -198,7 +198,7 @@ agnostic home for software distribution comprehension and audit tools. org.assertj assertj-core - 3.27.2 + 3.27.1 test @@ -217,6 +217,12 @@ agnostic home for software distribution comprehension and audit tools. 2.4.21 test + + com.google.jimfs + jimfs + 1.3.0 + test + @@ -489,7 +495,7 @@ agnostic home for software distribution comprehension and audit tools. org.apache.maven.plugins maven-remote-resources-plugin - 3.3.0 + 3.2.0 org.apache.maven.plugins From f9a46f61127c8cdfab0bdf5c46cb7500976ae2b2 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Sun, 12 Jan 2025 16:18:06 +0000 Subject: [PATCH 02/17] updated spotbugs --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 531a403e2..890ec493a 100644 --- a/pom.xml +++ b/pom.xml @@ -386,7 +386,7 @@ agnostic home for software distribution comprehension and audit tools. 4.8.6.6 - 94 + 98 true org.apache.rat.- From cefaff7e2329caee6e00029dda7bc98c10ba977f Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Sun, 12 Jan 2025 17:34:34 +0000 Subject: [PATCH 03/17] attempt to fix windows error --- .../org/apache/rat/walker/FileListWalkerTest.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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 14ac1478b..9bc554029 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.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 { @@ -117,15 +116,13 @@ public static void setUp() throws Exception { @Test public void readFilesTest() throws RatException { - FileListWalker walker = new FileListWalker(new FileDocument(source, DocumentNameMatcher.MATCHES_ALL)); + FileDocument fileDocument = new FileDocument(source, DocumentNameMatcher.MATCHES_ALL); + FileListWalker walker = new FileListWalker(fileDocument); List scanned = new ArrayList<>(); 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 { From a46a244676557e0a65a5dd9e4c00808c39b85233 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Sun, 12 Jan 2025 18:00:11 +0000 Subject: [PATCH 04/17] fixed merge error --- .../test/java/org/apache/rat/testhelpers/TestingDocument.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 532182b8e..1bd66377b 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.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 { @@ -62,7 +63,7 @@ public TestingDocument(Reader reader, String name) { } public TestingDocument(IOSupplier 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; } From 103d9dbbf48c26c3c22a665da6c9fd38a239e58d Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Mon, 13 Jan 2025 23:43:39 +0000 Subject: [PATCH 05/17] fixed pattern match --- .../config/exclusion/ExclusionProcessor.java | 4 +- .../config/exclusion/plexus/MatchPattern.java | 2 +- .../exclusion/plexus/MatchPatterns.java | 40 +- .../rat/document/DocumentNameMatcher.java | 390 ++++++++++++++++-- 4 files changed, 370 insertions(+), 66 deletions(-) diff --git a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java index e21432e03..105312930 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java +++ b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java @@ -255,10 +255,10 @@ public DocumentNameMatcher getNameMatcher(final DocumentName basedir) { .addTo(new ArrayList<>()); if (!incl.isEmpty()) { - inclMatchers.add(new DocumentNameMatcher("included patterns", MatchPatterns.from(incl), basedir)); + inclMatchers.add(new DocumentNameMatcher("included patterns", MatchPatterns.from("/", incl), basedir)); } if (!excl.isEmpty()) { - exclMatchers.add(new DocumentNameMatcher("excluded patterns", MatchPatterns.from(excl), basedir)); + exclMatchers.add(new DocumentNameMatcher("excluded patterns", MatchPatterns.from("/", excl), basedir)); } if (!includedPaths.isEmpty()) { 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 c43836ecd..c2c3ae7c9 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 @@ -49,7 +49,7 @@ public final class MatchPattern { private final char[][] tokenizedChar; - private MatchPattern(final String source, final String separator) { + MatchPattern(final String source, final String separator) { regexPattern = SelectorUtils.isRegexPrefixedPattern(source) ? source.substring( SelectorUtils.REGEX_HANDLER_PREFIX.length(), 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 454ba304e..9a6d443e4 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 @@ -22,8 +22,9 @@ import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.function.Predicate; +import java.util.stream.Collectors; @SuppressWarnings({"checkstyle:RegexpSingleLine", "checkstyle:JavadocVariable"}) /** @@ -41,15 +42,15 @@ private MatchPatterns(final MatchPattern[] patterns) { @Override public String toString() { - return source(); + return Arrays.stream(patterns).map(MatchPattern::toString).collect(Collectors.toList()).toString(); } public String source() { - List sources = new ArrayList<>(); - for (MatchPattern pattern : patterns) { - sources.add(pattern.source()); - } - return "[" + String.join(", ", sources) + "]"; + return Arrays.stream(patterns).map(MatchPattern::source).collect(Collectors.toList()).toString(); + } + + public Iterable patterns() { + return Arrays.asList(patterns); } /** @@ -83,36 +84,23 @@ public boolean matches(final String name, final char[][] tokenizedNameChar, fina return false; } - public Predicate asPredicate(final boolean isCaseSensitive) { - return name -> matches(name, isCaseSensitive); - } - - public boolean matchesPatternStart(final String name, final boolean isCaseSensitive) { - for (MatchPattern includesPattern : patterns) { - if (includesPattern.matchPatternStart(name, isCaseSensitive)) { - return true; - } - } - return false; - } - - public static MatchPatterns from(final String... sources) { + public static MatchPatterns from(final String separator, final String... sources) { final int length = sources.length; MatchPattern[] result = new MatchPattern[length]; for (int i = 0; i < length; i++) { - result[i] = MatchPattern.fromString(sources[i]); + result[i] = new MatchPattern(sources[i], separator); } return new MatchPatterns(result); } - public static MatchPatterns from(final Iterable strings) { - return new MatchPatterns(getMatchPatterns(strings)); + public static MatchPatterns from(final String separator, final Iterable strings) { + return new MatchPatterns(getMatchPatterns(separator, strings)); } - private static MatchPattern[] getMatchPatterns(final Iterable items) { + private static MatchPattern[] getMatchPatterns(final String separator, final Iterable items) { List result = new ArrayList<>(); for (String string : items) { - result.add(MatchPattern.fromString(string)); + result.add(new MatchPattern(string, separator)); } return result.toArray(new MatchPattern[0]); } 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 30bbbbede..0e66b19f7 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 @@ -23,9 +23,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; +import org.apache.rat.ConfigurationException; import org.apache.rat.config.exclusion.plexus.MatchPattern; import org.apache.rat.config.exclusion.plexus.MatchPatterns; @@ -40,6 +45,8 @@ public final class DocumentNameMatcher { private final Predicate predicate; /** The name of this matcher. */ private final String name; + /** {@code true} this this matcher is a collection of matchers */ + private final boolean isCollection; /** * A matcher that matches all documents. @@ -59,6 +66,7 @@ public final class DocumentNameMatcher { public DocumentNameMatcher(final String name, final Predicate predicate) { this.name = name; this.predicate = predicate; + this.isCollection = predicate instanceof CollectionPredicateImpl; } /** @@ -77,9 +85,22 @@ public DocumentNameMatcher(final String name, final DocumentNameMatcher delegate * @param basedir the base directory for the scanning. */ public DocumentNameMatcher(final String name, final MatchPatterns patterns, final DocumentName basedir) { - this(name, (Predicate) documentName -> patterns.matches(documentName.getName(), - MatchPattern.tokenizePathToString(documentName.getName(), basedir.getDirectorySeparator()), - basedir.isCaseSensitive())); + this(name, new MatchPatternsPredicate(basedir, patterns)); + } + + /** + * Tokenizes name for faster Matcher processing. + * @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) { + String[] tokenizedName = MatchPattern.tokenizePathToString(name, dirSeparator); + char[][] tokenizedNameChar = new char[tokenizedName.length][]; + for (int i = 0; i < tokenizedName.length; i++) { + tokenizedNameChar[i] = tokenizedName[i].toCharArray(); + } + return tokenizedNameChar; } /** @@ -88,7 +109,20 @@ public DocumentNameMatcher(final String name, final MatchPatterns patterns, fina * @param matchers fully specified matchers. */ public DocumentNameMatcher(final String name, final MatchPatterns matchers) { - this(name, (Predicate) documentName -> matchers.matches(documentName.getName(), documentName.isCaseSensitive())); + this(name, new CollectionPredicate() { + @Override + public Iterable getMatchers() { + final List result = new ArrayList<>(); + matchers.patterns().forEach(p -> result.add(new DocumentNameMatcher(p.source(), + (Predicate) x -> MatchPatterns.from("/", p.source()).matches(x.getName(), x.isCaseSensitive())))); + return result; + } + + @Override + public boolean test(final DocumentName documentName) { + return matchers.matches(documentName.getName(), documentName.isCaseSensitive()); + } + }); } /** @@ -97,7 +131,7 @@ public DocumentNameMatcher(final String name, final MatchPatterns matchers) { * @param fileFilter the file filter to execute. */ public DocumentNameMatcher(final String name, final FileFilter fileFilter) { - this(name, (Predicate) documentName -> fileFilter.accept(new File(documentName.getName()))); + this(name, new FileFilterPredicate(fileFilter)); } /** @@ -108,11 +142,39 @@ public DocumentNameMatcher(final FileFilter fileFilter) { this(fileFilter.toString(), fileFilter); } + public boolean isCollection() { + return isCollection; + } + + /** + * Returns the predicate that this DocumentNameMatcher is using. + * @return The predicate that this DocumentNameMatcher is using. + */ + public Predicate getPredicate() { + return predicate; + } + @Override public String toString() { return name; } + /** + * Decomposes the matcher execution against the candidate. + * @param candidate the candiate to check. + * @return a list of {@link DecomposeData} for each evaluation in the matcher. + */ + public List decompose(final DocumentName candidate) { + final List result = new ArrayList<>(); + decompose(0, this, candidate, result); + return result; + } + + private void decompose(final int level, final DocumentNameMatcher matcher, final DocumentName candidate, final List result) { + final Predicate pred = matcher.getPredicate(); + result.add(new DecomposeData(level, matcher, candidate, pred.test(candidate))); + } + /** * Performs the match against the DocumentName. * @param documentName the document name to check. @@ -135,8 +197,7 @@ public static DocumentNameMatcher not(final DocumentNameMatcher nameMatcher) { return MATCHES_ALL; } - return new DocumentNameMatcher(format("not(%s)", nameMatcher), - (Predicate) documentName -> !nameMatcher.matches(documentName)); + return new DocumentNameMatcher(format("not(%s)", nameMatcher), new NotPredicate(nameMatcher)); } /** @@ -150,30 +211,43 @@ private static String join(final Collection matchers) { return String.join(", ", children); } + private static Optional standardCollectionCheck(final Collection matchers, + final DocumentNameMatcher override) { + if (matchers.isEmpty()) { + throw new ConfigurationException("Empty matcher collection"); + } + if (matchers.size() == 1) { + return Optional.of(matchers.iterator().next()); + } + if (matchers.contains(override)) { + return Optional.of(override); + } + return Optional.empty(); + } + /** * Performs a logical {@code OR} across the collection of matchers. * @param matchers the matchers to check. * @return a matcher that returns {@code true} if any of the enclosed matchers returns {@code true}. */ public static DocumentNameMatcher or(final Collection matchers) { - if (matchers.isEmpty()) { - return MATCHES_NONE; - } - if (matchers.size() == 1) { - return matchers.iterator().next(); - } - if (matchers.contains(MATCHES_ALL)) { - return MATCHES_ALL; + Optional opt = standardCollectionCheck(matchers, MATCHES_ALL); + if (opt.isPresent()) { + return opt.get(); } - return new DocumentNameMatcher(format("or(%s)", join(matchers)), (Predicate) documentName -> { - for (DocumentNameMatcher matcher : matchers) { - if (matcher.matches(documentName)) { - return true; - } - } - return false; - }); + // preserve order + Set workingSet = new LinkedHashSet<>(); + for (DocumentNameMatcher matcher : matchers) { + // check for nested or + if (matcher.predicate instanceof Or) { + ((Or) matcher.predicate).getMatchers().forEach(workingSet::add); + } else { + workingSet.add(matcher); + } + } + return standardCollectionCheck(matchers, MATCHES_ALL) + .orElseGet(() -> new DocumentNameMatcher(format("or(%s)", join(workingSet)), new Or(workingSet))); } /** @@ -191,24 +265,45 @@ public static DocumentNameMatcher or(final DocumentNameMatcher... matchers) { * @return a matcher that returns {@code true} if all the enclosed matchers return {@code true}. */ public static DocumentNameMatcher and(final Collection matchers) { - if (matchers.isEmpty()) { - return MATCHES_NONE; - } - if (matchers.size() == 1) { - return matchers.iterator().next(); + Optional opt = standardCollectionCheck(matchers, MATCHES_NONE); + if (opt.isPresent()) { + return opt.get(); } - if (matchers.contains(MATCHES_NONE)) { - return MATCHES_NONE; + + // preserve order + Set workingSet = new LinkedHashSet<>(); + for (DocumentNameMatcher matcher : matchers) { + // check for nexted And + if (matcher.predicate instanceof And) { + ((And) matcher.predicate).getMatchers().forEach(workingSet::add); + } else { + workingSet.add(matcher); + } } + opt = standardCollectionCheck(matchers, MATCHES_NONE); + return opt.orElseGet(() -> new DocumentNameMatcher(format("and(%s)", join(workingSet)), new And(workingSet))); + } - return new DocumentNameMatcher(format("and(%s)", join(matchers)), (Predicate) documentName -> { - for (DocumentNameMatcher matcher : matchers) { - if (!matcher.matches(documentName)) { - return false; - } + /** + * A particular matcher that will not match any excluded unless they are listed in the includes. + * @param includes the DocumentNameMatcher to match the includes. + * @param excludes the DocumentNameMatcher to match the excludes. + * @return a DocumentNameMatcher with the specified logic. + */ + public static DocumentNameMatcher matcherSet(final DocumentNameMatcher includes, + final DocumentNameMatcher excludes) { + if (excludes == MATCHES_NONE) { + return MATCHES_ALL; + } else { + if (includes == MATCHES_NONE) { + return not(excludes); } - return true; - }); + } + if (includes == MATCHES_ALL) { + return MATCHES_ALL; + } + List workingSet = Arrays.asList(includes, excludes); + return new DocumentNameMatcher(format("matcherSet(%s)", join(workingSet)), new MatcherPredicate(workingSet)); } /** @@ -219,4 +314,225 @@ public static DocumentNameMatcher and(final Collection matc public static DocumentNameMatcher and(final DocumentNameMatcher... matchers) { return and(Arrays.asList(matchers)); } + + + + /** + * A DocumentName predicate that uses MatchPatterns. + */ + public static final class MatchPatternsPredicate implements Predicate { + /** The base diirectory for the pattern matches */ + private final DocumentName basedir; + /** The patter matchers */ + private final MatchPatterns patterns; + + private MatchPatternsPredicate(final DocumentName basedir, final MatchPatterns patterns) { + this.basedir = basedir; + this.patterns = patterns; + } + + @Override + public boolean test(final DocumentName documentName) { + return patterns.matches(documentName.getName(), + tokenize(documentName.getName(), basedir.getDirectorySeparator()), + basedir.isCaseSensitive()); + } + + @Override + public String toString() { + return patterns.toString(); + } + } + + /** + * A DocumentName predicate reverses another DocumentNameMatcher + */ + public static final class NotPredicate implements Predicate { + /** The document name matcher to reverse */ + private final DocumentNameMatcher nameMatcher; + + private NotPredicate(final DocumentNameMatcher nameMatcher) { + this.nameMatcher = nameMatcher; + } + + @Override + public boolean test(final DocumentName documentName) { + return !nameMatcher.matches(documentName); + } + + @Override + public String toString() { + return nameMatcher.predicate.toString(); + } + } + + /** + * A DocumentName predicate that uses FileFilter. + */ + public static final class FileFilterPredicate implements Predicate { + /** The file filter */ + private final FileFilter fileFilter; + + private FileFilterPredicate(final FileFilter fileFilter) { + this.fileFilter = fileFilter; + } + + @Override + public boolean test(final DocumentName documentName) { + return fileFilter.accept(new File(documentName.getName())); + } + + @Override + public String toString() { + return fileFilter.toString(); + } + } + + interface CollectionPredicate extends Predicate { + Iterable getMatchers(); + } + /** + * A marker interface to indicate this predicate contains a collection of matchers. + */ + abstract static class CollectionPredicateImpl implements CollectionPredicate { + /** The collection for matchers that make up this predicate */ + private final Iterable matchers; + + /** + * Constructs a collecton predicate from the collection of matchers + * @param matchers the colleciton of matchers to use. + */ + protected CollectionPredicateImpl(final Iterable matchers) { + this.matchers = matchers; + } + + /** + * Gets the internal matchers. + * @return an iterable over the internal matchers. + */ + public Iterable getMatchers() { + return matchers; + } + + public String toString() { + StringBuilder builder = new StringBuilder(this.getClass().getName()).append(": ").append(System.lineSeparator()); + for (DocumentNameMatcher matcher : matchers) { + builder.append(matcher.predicate.toString()).append(System.lineSeparator()); + } + return builder.toString(); + } + } + + /** + * An implementation of "and" logic across a collection of DocumentNameMatchers. + */ + // package private for testing access + static class And extends CollectionPredicateImpl { + And(final Iterable matchers) { + super(matchers); + } + + @Override + public boolean test(final DocumentName documentName) { + for (DocumentNameMatcher matcher : getMatchers()) { + if (!matcher.matches(documentName)) { + return false; + } + } + return true; + } + } + + /** + * An implementation of "or" logic across a collection of DocumentNameMatchers. + */ + // package private for testing access + static class Or extends CollectionPredicateImpl { + Or(final Iterable matchers) { + super(matchers); + } + + @Override + public boolean test(final DocumentName documentName) { + for (DocumentNameMatcher matcher : getMatchers()) { + if (matcher.matches(documentName)) { + return true; + } + } + return false; + } + } + + /** + * An implementation of "or" logic across a collection of DocumentNameMatchers. + */ + // package private for testing access + static class MatcherPredicate extends CollectionPredicateImpl { + MatcherPredicate(final Iterable matchers) { + super(matchers); + } + + @Override + public boolean test(final DocumentName documentName) { + Iterator iter = getMatchers().iterator(); + // included + if (iter.next().matches(documentName)) { + return true; + } + // excluded + if (iter.next().matches(documentName)) { + return false; + } + return true; + } + } + + /** + * Data from a {@link DocumentNameMatcher#decompose(DocumentName)} call. + */ + public static final class DecomposeData { + /** the level this data was generated at */ + private final int level; + /** The name of the DocumentNameMatcher that created this result */ + private final DocumentNameMatcher matcher; + /** The result of the check. */ + private final boolean result; + /** The candidate */ + private final DocumentName candidate; + + private DecomposeData(final int level, final DocumentNameMatcher matcher, final DocumentName candidate, final boolean result) { + this.level = level; + this.matcher = matcher; + this.result = result; + this.candidate = candidate; + } + + @Override + public String toString() { + final String fill = createFill(level); + return format("%s%s: >>%s<< %s%n%s", + fill, matcher.toString(), result, + level == 0 ? candidate.getName() : "", + matcher.predicate instanceof CollectionPredicate ? + decompose(level + 1, (CollectionPredicate) matcher.predicate, candidate) : + String.format("%s%s >>%s<<", createFill(level + 1), matcher.predicate.toString(), matcher.predicate.test(candidate))); + } + + private String createFill(final int level) { + final char[] chars = new char[level * 2]; + Arrays.fill(chars, ' '); + return new String(chars); + } + + private String decompose(final int level, final CollectionPredicate predicate, final DocumentName candidate) { + List result = new ArrayList<>(); + + for (DocumentNameMatcher nameMatcher : predicate.getMatchers()) { + nameMatcher.decompose(level, nameMatcher, candidate, result); + } + StringBuilder sb = new StringBuilder(); + result.forEach(x -> sb.append(x).append(System.lineSeparator())); + return sb.toString(); + } + } } From 818618f21d572fab83bb03cbc53acacc0a8798f5 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Tue, 14 Jan 2025 18:12:26 +0000 Subject: [PATCH 06/17] added more descriptive failure messages --- .../rat/config/exclusion/ExclusionProcessorTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 580d10849..f8535a90b 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 void setup() { } 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) { From c8a5f9d544c135395e7060004398dd67a8434076 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Tue, 14 Jan 2025 19:07:07 +0000 Subject: [PATCH 07/17] modified test output --- .../config/exclusion/plexus/MatchPattern.java | 7 +- .../rat/test/AbstractOptionsProvider.java | 80 ++++++++++++++----- 2 files changed, 60 insertions(+), 27 deletions(-) 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 c2c3ae7c9..ff6505986 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 @@ -19,6 +19,7 @@ import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.StringTokenizer; @@ -116,7 +117,7 @@ public boolean startsWith(final String string) { @Override public String toString() { - return source; + return Arrays.asList(tokenized).toString(); } public String source() { @@ -140,8 +141,4 @@ static char[][] tokenizePathToCharArray(final String path, final String separato } return tokenizedNameChar; } - - public static MatchPattern fromString(final String source) { - return new MatchPattern(source, File.separator); - } } 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 baf44eada..3bc776b58 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 @@ -213,10 +213,16 @@ private void execExcludeTest(Option option, String[] args) { ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); DocumentNameMatcher excluder = config.getDocumentExcluder(baseName()); for (String fname : notExcluded) { - assertTrue(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); + 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) { - assertFalse(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); + final DocumentName docName = mkDocName(fname); + assertThat(excluder.matches(docName)) + .as(() -> String.format("option: %s name: %s%n%s", option.getKey(), fname, excluder.decompose(docName))) + .isFalse(); } } catch (IOException e) { fail(e.getMessage()); @@ -252,11 +258,17 @@ protected void inputExcludeStdTest() { try { ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); DocumentNameMatcher excluder = config.getDocumentExcluder(baseName()); - for (String fname : excluded) { - assertFalse(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); - } for (String fname : notExcluded) { - assertTrue(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); + 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) { + final DocumentName docName = mkDocName(fname); + assertThat(excluder.matches(docName)) + .as(() -> String.format("option: %s name: %s%n%s", option.getKey(), fname, excluder.decompose(docName))) + .isFalse(); } } catch (IOException e) { fail(e.getMessage()); @@ -284,11 +296,17 @@ protected void inputExcludeParsedScmTest() { try { ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); DocumentNameMatcher excluder = config.getDocumentExcluder(baseName()); - for (String fname : excluded) { - assertFalse(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); - } for (String fname : notExcluded) { - assertTrue(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); + 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) { + final DocumentName docName = mkDocName(fname); + assertThat(excluder.matches(docName)) + .as(() -> String.format("option: %s name: %s%n%s", option.getKey(), fname, excluder.decompose(docName))) + .isFalse(); } } catch (IOException e) { fail(e.getMessage()); @@ -308,11 +326,17 @@ private void inputExcludeSizeTest() { try { ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); DocumentNameMatcher excluder = config.getDocumentExcluder(baseName()); - for (String fname : excluded) { - assertFalse(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); - } for (String fname : notExcluded) { - assertTrue(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); + 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) { + final DocumentName docName = mkDocName(fname); + assertThat(excluder.matches(docName)) + .as(() -> String.format("option: %s name: %s%n%s", option.getKey(), fname, excluder.decompose(docName))) + .isFalse(); } } catch (IOException e) { fail(e.getMessage()); @@ -328,11 +352,17 @@ private void execIncludeTest(Option option, String[] args) { ReportConfiguration config = generateConfig(ImmutablePair.of(option, args), ImmutablePair.of(excludeOption, EXCLUDE_ARGS)); DocumentNameMatcher excluder = config.getDocumentExcluder(baseName()); - for (String fname : excluded) { - assertFalse(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); - } for (String fname : notExcluded) { - assertTrue(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); + 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) { + final DocumentName docName = mkDocName(fname); + assertThat(excluder.matches(docName)) + .as(() -> String.format("option: %s name: %s%n%s", option.getKey(), fname, excluder.decompose(docName))) + .isFalse(); } } catch (IOException e) { fail(e.getMessage()); @@ -370,11 +400,17 @@ protected void inputIncludeStdTest() { try { ReportConfiguration config = generateConfig(excludes, ImmutablePair.of(option, args)); DocumentNameMatcher excluder = config.getDocumentExcluder(baseName()); - for (String fname : excluded) { - assertFalse(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); - } for (String fname : notExcluded) { - assertTrue(excluder.matches(mkDocName(fname)), () -> option.getKey() + " " + fname); + 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) { + final DocumentName docName = mkDocName(fname); + assertThat(excluder.matches(docName)) + .as(() -> String.format("option: %s name: %s%n%s", option.getKey(), fname, excluder.decompose(docName))) + .isFalse(); } } catch (IOException e) { fail(e.getMessage()); From 2a20800f751b19b2a546954831aa156805d41223 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Tue, 14 Jan 2025 19:24:49 +0000 Subject: [PATCH 08/17] added debug info --- .../org/apache/rat/config/exclusion/plexus/MatchPattern.java | 2 ++ .../org/apache/rat/config/exclusion/plexus/MatchPatterns.java | 1 + 2 files changed, 3 insertions(+) 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 ff6505986..f5df27f2d 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 @@ -51,6 +51,7 @@ public final class MatchPattern { private final char[][] tokenizedChar; MatchPattern(final String source, final String separator) { + DefaultLog.getInstance().warn(String.format("Creating MatchPattern('%s', '%s')...")); regexPattern = SelectorUtils.isRegexPrefixedPattern(source) ? source.substring( SelectorUtils.REGEX_HANDLER_PREFIX.length(), @@ -67,6 +68,7 @@ public final class MatchPattern { for (int i = 0; i < tokenized.length; i++) { tokenizedChar[i] = tokenized[i].toCharArray(); } + DefaultLog.getInstance().warn(String.format("... as %s", this.toString())); } public boolean matchPath(final String str, final boolean isCaseSensitive) { 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 9a6d443e4..f0d1cf0e3 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 @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.apache.rat.utils.DefaultLog; @SuppressWarnings({"checkstyle:RegexpSingleLine", "checkstyle:JavadocVariable"}) /** From 074172509175fe724a87aa2b8a10a5c3411f4e64 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Tue, 14 Jan 2025 19:27:27 +0000 Subject: [PATCH 09/17] added debug info --- .../org/apache/rat/config/exclusion/plexus/MatchPatterns.java | 1 - 1 file changed, 1 deletion(-) 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 f0d1cf0e3..9a6d443e4 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 @@ -25,7 +25,6 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import org.apache.rat.utils.DefaultLog; @SuppressWarnings({"checkstyle:RegexpSingleLine", "checkstyle:JavadocVariable"}) /** From be21fef7ce03d13abfb82ffed224d3d53d2c1991 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Wed, 15 Jan 2025 17:13:49 +0000 Subject: [PATCH 10/17] added debug info --- .../org/apache/rat/config/exclusion/plexus/MatchPattern.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f5df27f2d..ce180b331 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 @@ -51,7 +51,7 @@ public final class MatchPattern { private final char[][] tokenizedChar; MatchPattern(final String source, final String separator) { - DefaultLog.getInstance().warn(String.format("Creating MatchPattern('%s', '%s')...")); + DefaultLog.getInstance().warn(String.format("Creating MatchPattern('%s', '%s')...", source, separator)); regexPattern = SelectorUtils.isRegexPrefixedPattern(source) ? source.substring( SelectorUtils.REGEX_HANDLER_PREFIX.length(), From 6f8c7a02c9dfc788f3d4f7c4e7dea5e99f1dc5c2 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Wed, 15 Jan 2025 17:35:09 +0000 Subject: [PATCH 11/17] added debug info --- .../org/apache/rat/config/exclusion/plexus/MatchPattern.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 ce180b331..49c6cedf3 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 @@ -51,7 +51,6 @@ public final class MatchPattern { private final char[][] tokenizedChar; MatchPattern(final String source, final String separator) { - DefaultLog.getInstance().warn(String.format("Creating MatchPattern('%s', '%s')...", source, separator)); regexPattern = SelectorUtils.isRegexPrefixedPattern(source) ? source.substring( SelectorUtils.REGEX_HANDLER_PREFIX.length(), @@ -68,7 +67,9 @@ public final class MatchPattern { for (int i = 0; i < tokenized.length; i++) { tokenizedChar[i] = tokenized[i].toCharArray(); } - DefaultLog.getInstance().warn(String.format("... as %s", this.toString())); + if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) { + DefaultLog.getInstance().debug(String.format("Created MatchPattern('%s', '%s') as %s", source, separator, this.toString())); + } } public boolean matchPath(final String str, final boolean isCaseSensitive) { From 32cd2d163c440c3a60d4c90125a30e226ba83ac3 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Wed, 15 Jan 2025 17:48:40 +0000 Subject: [PATCH 12/17] Attempt to fix pattern match creation --- .../org/apache/rat/config/exclusion/ExclusionProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java index 105312930..c1de611ff 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java +++ b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java @@ -255,10 +255,10 @@ public DocumentNameMatcher getNameMatcher(final DocumentName basedir) { .addTo(new ArrayList<>()); if (!incl.isEmpty()) { - inclMatchers.add(new DocumentNameMatcher("included patterns", MatchPatterns.from("/", incl), basedir)); + inclMatchers.add(new DocumentNameMatcher("included patterns", MatchPatterns.from(basedir.getDirectorySeparator(), incl), basedir)); } if (!excl.isEmpty()) { - exclMatchers.add(new DocumentNameMatcher("excluded patterns", MatchPatterns.from("/", excl), basedir)); + exclMatchers.add(new DocumentNameMatcher("excluded patterns", MatchPatterns.from(basedir.getDirectorySeparator(), excl), basedir)); } if (!includedPaths.isEmpty()) { From c02f80222da1f716a91b1ee98e697c340b9a05b9 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Wed, 15 Jan 2025 22:52:36 +0000 Subject: [PATCH 13/17] added debug info --- .../java/org/apache/rat/document/FileDocument.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 3e7dec89b..891fd33b9 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 @@ -29,6 +29,7 @@ import org.apache.rat.api.Document; import org.apache.rat.config.exclusion.ExclusionUtils; +import org.apache.rat.utils.DefaultLog; /** * Document wrapping a File object. @@ -52,11 +53,14 @@ public FileDocument(final DocumentName basedir, final File file, final DocumentN /** * 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; + DefaultLog.getInstance().info("Created file document " + file.getAbsolutePath()); + DefaultLog.getInstance().info("... as " + this.getName().getName()); + DefaultLog.getInstance().info("... on root " + this.getName().getRoot()); } @Override From 3c32108f34ed9a659ee1ebdd015bf6c33d0611ea Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Wed, 15 Jan 2025 23:06:36 +0000 Subject: [PATCH 14/17] added debug info --- .../main/java/org/apache/rat/api/Document.java | 12 ++++++------ .../org/apache/rat/document/FileDocument.java | 16 ++++++---------- .../org/apache/rat/walker/ArchiveWalker.java | 4 ++-- .../org/apache/rat/walker/FileListWalker.java | 5 ++++- 4 files changed, 18 insertions(+), 19 deletions(-) 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 3e29eb350..0dbfa10cf 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 enum Type { } /** 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 enum Type { /** * 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 final DocumentName getName() { * 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/document/FileDocument.java b/apache-rat-core/src/main/java/org/apache/rat/document/FileDocument.java index 891fd33b9..d7596a3ef 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 @@ -29,7 +29,6 @@ import org.apache.rat.api.Document; import org.apache.rat.config.exclusion.ExclusionUtils; -import org.apache.rat.utils.DefaultLog; /** * Document wrapping a File object. @@ -43,10 +42,10 @@ 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; } @@ -58,9 +57,6 @@ public FileDocument(final DocumentName basedir, final File file, final DocumentN public FileDocument(final File file, final DocumentNameMatcher nameMatcher) { super(DocumentName.builder(file).setBaseName(File.separator).build(), nameMatcher); this.file = file; - DefaultLog.getInstance().info("Created file document " + file.getAbsolutePath()); - DefaultLog.getInstance().info("... as " + this.getName().getName()); - DefaultLog.getInstance().info("... on root " + this.getName().getRoot()); } @Override @@ -74,12 +70,12 @@ public SortedSet listChildren() { SortedSet 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 e2cc470e6..d1e9a3dad 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 @@ -90,11 +90,11 @@ public Collection getDocuments() throws RatException { if (!entry.isDirectory() && input.canReadEntryData(entry)) { DocumentName innerName = DocumentName.builder().setName(entry.getName()) .setBaseName(".").build(); - if (this.getDocument().getNameExcluder().matches(innerName)) { + if (this.getDocument().getNameMatcher().matches(innerName)) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); IOUtils.copy(input, baos); ArchiveEntryName entryName = new ArchiveEntryName(getDocument().getName(), entry.getName()); - result.add(new ArchiveEntryDocument(entryName, baos.toByteArray(), getDocument().getNameExcluder())); + 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 6e6c50e5a..9cad11462 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 @@ -49,6 +49,8 @@ public class FileListWalker implements IReportable { * @param source The file document that is the source from which this walker will read. */ public FileListWalker(final FileDocument source) { + DefaultLog.getInstance().info("Created file list document on " + source.getName().getName()); + DefaultLog.getInstance().info("... on root " + source.getName().getRoot()); this.source = source; File baseDir = source.getFile().getParentFile().getAbsoluteFile(); this.baseDoc = new FileDocument(baseDir, DocumentNameMatcher.MATCHES_ALL); @@ -65,7 +67,8 @@ private FileDocument createDocument(final String unixFileName) { String finalName = "/".equals(sourceName.getDirectorySeparator()) ? unixFileName : unixFileName.replace("/", sourceName.getDirectorySeparator()); FileDocument documentBase = unixFileName.startsWith("/") ? rootDoc : baseDoc; - File documentFile = new File(documentBase.getFile(), finalName); + File documentFile = new File(documentBase.getFile(), finalName).getAbsoluteFile(); + DefaultLog.getInstance().info("Created file document on " + documentFile); return new FileDocument(rootDoc.getName(), documentFile, DocumentNameMatcher.MATCHES_ALL); } From 093dee0ecd5ca3710a36f3138506c82874692c1a Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Wed, 15 Jan 2025 23:21:03 +0000 Subject: [PATCH 15/17] added debug info --- .../src/main/java/org/apache/rat/document/FileDocument.java | 4 ++++ .../src/main/java/org/apache/rat/walker/FileListWalker.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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 d7596a3ef..5b811b4e1 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 @@ -29,6 +29,7 @@ import org.apache.rat.api.Document; import org.apache.rat.config.exclusion.ExclusionUtils; +import org.apache.rat.utils.DefaultLog; /** * Document wrapping a File object. @@ -47,6 +48,9 @@ public class FileDocument extends Document { public FileDocument(final DocumentName basedir, final File file, final DocumentNameMatcher nameMatcher) { super(DocumentName.builder(file).setBaseName(basedir.getBaseName()).build(), nameMatcher); this.file = file; + DefaultLog.getInstance().info("Created file document on " + file); + DefaultLog.getInstance().info("... as " + getName().getName()); + DefaultLog.getInstance().info("... on " + getName().getRoot()); } /** 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 9cad11462..bdcd5aea8 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 @@ -68,7 +68,7 @@ private FileDocument createDocument(final String unixFileName) { unixFileName.replace("/", sourceName.getDirectorySeparator()); FileDocument documentBase = unixFileName.startsWith("/") ? rootDoc : baseDoc; File documentFile = new File(documentBase.getFile(), finalName).getAbsoluteFile(); - DefaultLog.getInstance().info("Created file document on " + documentFile); + DefaultLog.getInstance().info("Creating document from " + unixFileName); return new FileDocument(rootDoc.getName(), documentFile, DocumentNameMatcher.MATCHES_ALL); } From d96420b1dd6deab6b567f23131ac774ca229f693 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Thu, 16 Jan 2025 13:56:04 +0000 Subject: [PATCH 16/17] fixed FileListWalker --- .../org/apache/rat/document/DocumentName.java | 2 +- .../org/apache/rat/walker/FileListWalker.java | 23 ++++++++----------- .../apache/rat/walker/FileListWalkerTest.java | 7 ++---- 3 files changed, 13 insertions(+), 19 deletions(-) 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 9b0c6a0d9..5dad33e37 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 @@ -629,7 +629,7 @@ private void setEmptyRoot(final String root) { } /** - * Sets the properties from the file. Will reset the baseName appropraitly. + * Sets the properties from the file. Will reset the baseName appropriately. * @param file the file to set the properties from. * @return this. */ 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 bdcd5aea8..5aa48852d 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 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; @@ -40,36 +41,32 @@ public class FileListWalker implements IReportable { /** The source document name */ private final FileDocument source; /** The root document name */ - private final FileDocument rootDoc; + private final DocumentName rootDoc; /** the base directory for the source document */ - private final FileDocument baseDoc; + private final DocumentName baseDoc; /** * Constructor. * @param source The file document that is the source from which this walker will read. */ public FileListWalker(final FileDocument source) { - DefaultLog.getInstance().info("Created file list document on " + source.getName().getName()); - DefaultLog.getInstance().info("... on root " + source.getName().getRoot()); 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).getAbsoluteFile(); - DefaultLog.getInstance().info("Creating document from " + unixFileName); - 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/walker/FileListWalkerTest.java b/apache-rat-core/src/test/java/org/apache/rat/walker/FileListWalkerTest.java index 9bc554029..ec739f643 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 @@ -83,7 +83,7 @@ public static void setUp() throws Exception { 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"); @@ -110,14 +110,11 @@ public static void setUp() throws Exception { writer.flush(); System.out.flush(); } - - hiddenName = DocumentName.builder(hiddenFile).setBaseName(rootName.getBaseName()).build(); } @Test public void readFilesTest() throws RatException { - FileDocument fileDocument = new FileDocument(source, DocumentNameMatcher.MATCHES_ALL); - FileListWalker walker = new FileListWalker(fileDocument); + FileListWalker walker = new FileListWalker(new FileDocument(source, DocumentNameMatcher.MATCHES_ALL)); List scanned = new ArrayList<>(); walker.run(new TestRatReport(scanned)); String[] expected = {regularName.localized("/"), hiddenName.localized("/"), From 46cd644f620e2c863c4931b52c6e8789b068ae63 Mon Sep 17 00:00:00 2001 From: Claude Warren Date: Thu, 16 Jan 2025 14:12:27 +0000 Subject: [PATCH 17/17] reduced logging noise --- .../org/apache/rat/config/exclusion/plexus/MatchPattern.java | 3 --- .../src/main/java/org/apache/rat/document/FileDocument.java | 4 ---- 2 files changed, 7 deletions(-) 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 49c6cedf3..ff6505986 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 @@ -67,9 +67,6 @@ public final class MatchPattern { for (int i = 0; i < tokenized.length; i++) { tokenizedChar[i] = tokenized[i].toCharArray(); } - if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) { - DefaultLog.getInstance().debug(String.format("Created MatchPattern('%s', '%s') as %s", source, separator, this.toString())); - } } public boolean matchPath(final String str, final boolean isCaseSensitive) { 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 5b811b4e1..d7596a3ef 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 @@ -29,7 +29,6 @@ import org.apache.rat.api.Document; import org.apache.rat.config.exclusion.ExclusionUtils; -import org.apache.rat.utils.DefaultLog; /** * Document wrapping a File object. @@ -48,9 +47,6 @@ public class FileDocument extends Document { public FileDocument(final DocumentName basedir, final File file, final DocumentNameMatcher nameMatcher) { super(DocumentName.builder(file).setBaseName(basedir.getBaseName()).build(), nameMatcher); this.file = file; - DefaultLog.getInstance().info("Created file document on " + file); - DefaultLog.getInstance().info("... as " + getName().getName()); - DefaultLog.getInstance().info("... on " + getName().getRoot()); } /**