Skip to content

Commit

Permalink
Revise FilePatternResourceHintsRegistrar API and improve documentation
Browse files Browse the repository at this point in the history
This commit revises the FilePatternResourceHintsRegistrar API and
introduces List<String> overrides of various var-args methods used with
the new builder API.

Closes gh-29161
  • Loading branch information
sbrannen committed Sep 7, 2023
1 parent 453c0e5 commit 1cdbd68
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 73 deletions.
Expand Up @@ -27,60 +27,52 @@

/**
* Register the necessary resource hints for loading files from the classpath,
* based on a file name prefix and an extension with convenience to support
* based on file name prefixes and file extensions with convenience to support
* multiple classpath locations.
*
* <p>Only registers hints for matching classpath locations, which allows for
* several locations to be provided without contributing unnecessary hints.
*
* @author Stephane Nicoll
* @author Sam Brannen
* @since 6.0
*/
public class FilePatternResourceHintsRegistrar {

private final List<String> names;
private final List<String> classpathLocations;

private final List<String> locations;
private final List<String> filePrefixes;

private final List<String> fileExtensions;

private final List<String> extensions;

/**
* Create a new instance for the specified file names, locations, and file
* extensions.
* @param names the file names
* @param locations the classpath locations
* @param extensions the file extensions (starting with a dot)
* Create a new instance for the specified file prefixes, classpath locations,
* and file extensions.
* @param filePrefixes the file prefixes
* @param classpathLocations the classpath locations
* @param fileExtensions the file extensions (starting with a dot)
* @deprecated as of 6.0.12 in favor of {@linkplain #forClassPathLocations(String...) the builder}
*/
@Deprecated(since = "6.0.12", forRemoval = true)
public FilePatternResourceHintsRegistrar(List<String> names, List<String> locations,
List<String> extensions) {
this.names = Builder.validateFilePrefixes(names.toArray(String[]::new));
this.locations = Builder.validateClasspathLocations(locations.toArray(String[]::new));
this.extensions = Builder.validateFileExtensions(extensions.toArray(String[]::new));
}
public FilePatternResourceHintsRegistrar(List<String> filePrefixes, List<String> classpathLocations,
List<String> fileExtensions) {

/**
* Configure the registrar with the specified
* {@linkplain Builder#withClasspathLocations(String...) classpath locations}.
* @param locations the classpath locations
* @return a {@link Builder} to further configure the registrar
* @since 6.0.12
*/
public static Builder forClassPathLocations(String... locations) {
Assert.notEmpty(locations, "At least one classpath location should be specified");
return new Builder().withClasspathLocations(locations);
this.classpathLocations = validateClasspathLocations(classpathLocations);
this.filePrefixes = validateFilePrefixes(filePrefixes);
this.fileExtensions = validateFileExtensions(fileExtensions);
}


@Deprecated(since = "6.0.12", forRemoval = true)
public void registerHints(ResourceHints hints, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = (classLoader != null ? classLoader : getClass().getClassLoader());
List<String> includes = new ArrayList<>();
for (String location : this.locations) {
for (String location : this.classpathLocations) {
if (classLoaderToUse.getResource(location) != null) {
for (String extension : this.extensions) {
for (String name : this.names) {
includes.add(location + name + "*" + extension);
for (String filePrefix : this.filePrefixes) {
for (String fileExtension : this.fileExtensions) {
includes.add(location + filePrefix + "*" + fileExtension);
}
}
}
Expand All @@ -90,6 +82,68 @@ public void registerHints(ResourceHints hints, @Nullable ClassLoader classLoader
}
}


/**
* Configure the registrar with the specified
* {@linkplain Builder#withClasspathLocations(String...) classpath locations}.
* @param classpathLocations the classpath locations
* @return a {@link Builder} to further configure the registrar
* @since 6.0.12
* @see #forClassPathLocations(List)
*/
public static Builder forClassPathLocations(String... classpathLocations) {
return forClassPathLocations(Arrays.asList(classpathLocations));
}

/**
* Configure the registrar with the specified
* {@linkplain Builder#withClasspathLocations(List) classpath locations}.
* @param classpathLocations the classpath locations
* @return a {@link Builder} to further configure the registrar
* @since 6.0.12
* @see #forClassPathLocations(String...)
*/
public static Builder forClassPathLocations(List<String> classpathLocations) {
return new Builder().withClasspathLocations(classpathLocations);
}

private static List<String> validateClasspathLocations(List<String> classpathLocations) {
Assert.notEmpty(classpathLocations, "At least one classpath location must be specified");
List<String> parsedLocations = new ArrayList<>();
for (String location : classpathLocations) {
if (location.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
location = location.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length());
}
if (location.startsWith("/")) {
location = location.substring(1);
}
if (!location.isEmpty() && !location.endsWith("/")) {
location = location + "/";
}
parsedLocations.add(location);
}
return parsedLocations;
}

private static List<String> validateFilePrefixes(List<String> filePrefixes) {
for (String filePrefix : filePrefixes) {
if (filePrefix.contains("*")) {
throw new IllegalArgumentException("File prefix '" + filePrefix + "' cannot contain '*'");
}
}
return filePrefixes;
}

private static List<String> validateFileExtensions(List<String> fileExtensions) {
for (String fileExtension : fileExtensions) {
if (!fileExtension.startsWith(".")) {
throw new IllegalArgumentException("Extension '" + fileExtension + "' must start with '.'");
}
}
return fileExtensions;
}


/**
* Builder for {@link FilePatternResourceHintsRegistrar}.
* @since 6.0.12
Expand All @@ -103,15 +157,34 @@ public static final class Builder {
private final List<String> fileExtensions = new ArrayList<>();


private Builder() {
// no-op
}


/**
* Consider the specified classpath locations. A location can either be
* a special "classpath" pseudo location or a standard location, such as
* {@code com/example/resources}. An empty String represents the root of
* the classpath.
* Consider the specified classpath locations.
* <p>A location can either be a special {@value ResourceUtils#CLASSPATH_URL_PREFIX}
* pseudo location or a standard location, such as {@code com/example/resources}.
* An empty String represents the root of the classpath.
* @param classpathLocations the classpath locations to consider
* @return this builder
* @see #withClasspathLocations(List)
*/
public Builder withClasspathLocations(String... classpathLocations) {
return withClasspathLocations(Arrays.asList(classpathLocations));
}

/**
* Consider the specified classpath locations.
* <p>A location can either be a special {@value ResourceUtils#CLASSPATH_URL_PREFIX}
* pseudo location or a standard location, such as {@code com/example/resources}.
* An empty String represents the root of the classpath.
* @param classpathLocations the classpath locations to consider
* @return this builder
* @see #withClasspathLocations(String...)
*/
public Builder withClasspathLocations(List<String> classpathLocations) {
this.classpathLocations.addAll(validateClasspathLocations(classpathLocations));
return this;
}
Expand All @@ -122,8 +195,21 @@ public Builder withClasspathLocations(String... classpathLocations) {
* character.
* @param filePrefixes the file prefixes to consider
* @return this builder
* @see #withFilePrefixes(List)
*/
public Builder withFilePrefixes(String... filePrefixes) {
return withFilePrefixes(Arrays.asList(filePrefixes));
}

/**
* Consider the specified file prefixes. Any file whose name starts with one
* of the specified prefixes is considered. A prefix cannot contain the {@code *}
* character.
* @param filePrefixes the file prefixes to consider
* @return this builder
* @see #withFilePrefixes(String...)
*/
public Builder withFilePrefixes(List<String> filePrefixes) {
this.filePrefixes.addAll(validateFilePrefixes(filePrefixes));
return this;
}
Expand All @@ -133,14 +219,26 @@ public Builder withFilePrefixes(String... filePrefixes) {
* {@code .} character.
* @param fileExtensions the file extensions to consider
* @return this builder
* @see #withFileExtensions(List)
*/
public Builder withFileExtensions(String... fileExtensions) {
return withFileExtensions(Arrays.asList(fileExtensions));
}

/**
* Consider the specified file extensions. A file extension must start with a
* {@code .} character.
* @param fileExtensions the file extensions to consider
* @return this builder
* @see #withFileExtensions(String...)
*/
public Builder withFileExtensions(List<String> fileExtensions) {
this.fileExtensions.addAll(validateFileExtensions(fileExtensions));
return this;
}

FilePatternResourceHintsRegistrar build() {
Assert.notEmpty(this.classpathLocations, "At least one location should be specified");

private FilePatternResourceHintsRegistrar build() {
return new FilePatternResourceHintsRegistrar(this.filePrefixes,
this.classpathLocations, this.fileExtensions);
}
Expand All @@ -150,47 +248,13 @@ FilePatternResourceHintsRegistrar build() {
* classpath location that resolves against the {@code ClassLoader}, files
* with the configured file prefixes and extensions are registered.
* @param hints the hints contributed so far for the deployment unit
* @param classLoader the classloader, or {@code null} if even the system ClassLoader isn't accessible
* @param classLoader the classloader, or {@code null} if even the system
* ClassLoader isn't accessible
*/
public void registerHints(ResourceHints hints, @Nullable ClassLoader classLoader) {
build().registerHints(hints, classLoader);
}

private static List<String> validateClasspathLocations(String... locations) {
List<String> parsedLocations = new ArrayList<>();
for (String location : locations) {
if (location.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
location = location.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length());
}
if (location.startsWith("/")) {
location = location.substring(1);
}
if (!location.isEmpty() && !location.endsWith("/")) {
location = location + "/";
}
parsedLocations.add(location);
}
return parsedLocations;
}

private static List<String> validateFilePrefixes(String... filePrefixes) {
for (String filePrefix : filePrefixes) {
if (filePrefix.contains("*")) {
throw new IllegalArgumentException("File prefix '" + filePrefix + "' cannot contain '*'");
}
}
return Arrays.asList(filePrefixes);
}

private static List<String> validateFileExtensions(String... fileExtensions) {
for (String fileExtension : fileExtensions) {
if (!fileExtension.startsWith(".")) {
throw new IllegalArgumentException("Extension '" + fileExtension + "' should start with '.'");
}
}
return Arrays.asList(fileExtensions);
}

}

}
Expand Up @@ -40,7 +40,7 @@ class FilePatternResourceHintsRegistrarTests {
@Test
void configureWithNoClasspathLocation() {
assertThatIllegalArgumentException().isThrownBy(FilePatternResourceHintsRegistrar::forClassPathLocations)
.withMessageContaining("At least one classpath location should be specified");
.withMessageContaining("At least one classpath location must be specified");
}

@Test
Expand All @@ -54,7 +54,7 @@ void configureWithInvalidFilePrefix() {
void configureWithInvalidFileExtension() {
Builder builder = FilePatternResourceHintsRegistrar.forClassPathLocations("");
assertThatIllegalArgumentException().isThrownBy(() -> builder.withFileExtensions("txt"))
.withMessageContaining("should start with '.'");
.withMessageContaining("must start with '.'");
}

@Test
Expand Down

0 comments on commit 1cdbd68

Please sign in to comment.