From f4d9d5ed6acf12429e1bfff39ba0b40a88ced0a4 Mon Sep 17 00:00:00 2001 From: Scott Leberknight <174812+sleberknight@users.noreply.github.com> Date: Sat, 12 Oct 2024 13:29:13 -0400 Subject: [PATCH] Add overloaded readAllMainAttributesFromJarManifest methods to KiwiJars * Add 3 overloaded methods named readAllMainAttributesFromJarManifest which provide an easier way to get all main attributes from a JAR manifest. Since these are new methods, I named them to make it clear what they are getting from the manifest, at the expense of inconsistency with the existing method names. * Changed a log message to make it clearer when a specific main attribute could not be found in a JAR manifest. * A few minor Javadoc changes to add a space in the generic type declaration. --- .../java/org/kiwiproject/jar/KiwiJars.java | 75 +++++++++++++++--- .../org/kiwiproject/jar/KiwiJarsTest.java | 79 ++++++++++++++++--- 2 files changed, 132 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/kiwiproject/jar/KiwiJars.java b/src/main/java/org/kiwiproject/jar/KiwiJars.java index 3ed9c2b9..d7798501 100644 --- a/src/main/java/org/kiwiproject/jar/KiwiJars.java +++ b/src/main/java/org/kiwiproject/jar/KiwiJars.java @@ -20,7 +20,6 @@ import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; @@ -150,7 +149,7 @@ public static Optional readSingleValueFromJarManifest(ClassLoader classL var value = manifest.getMainAttributes().getValue(mainAttributeName); return Optional.ofNullable(value); } catch (Exception e) { - LOG.warn("Unable to locate {} from JAR", mainAttributeName, e); + LOG.warn("Unable to get main attribute {} from JAR manifest", mainAttributeName, e); return Optional.empty(); } } @@ -159,7 +158,7 @@ public static Optional readSingleValueFromJarManifest(ClassLoader classL * Get the values of the given main attribute names from the manifest (if found) from the current class loader. * * @param mainAttributeNames an array of main attribute names to resolve from the manifest - * @return a {@code Map} of resolved main attributes + * @return a {@code Map} of resolved main attributes */ public static Map readValuesFromJarManifest(String... mainAttributeNames) { return readValuesFromJarManifest(KiwiJars.class.getClassLoader(), null, mainAttributeNames); @@ -170,7 +169,7 @@ public static Map readValuesFromJarManifest(String... mainAttrib * * @param classLoader the classloader to search for manifest files in * @param mainAttributeNames an array of names to resolve from the manifest - * @return a {@code Map} of resolved main attributes + * @return a {@code Map} of resolved main attributes */ public static Map readValuesFromJarManifest(ClassLoader classLoader, String... mainAttributeNames) { @@ -184,32 +183,79 @@ public static Map readValuesFromJarManifest(ClassLoader classLoa * @param classLoader the classloader to search for manifest files in * @param manifestFilter a predicate filter used to limit which jar files to search for a manifest file * @param mainAttributeNames an array of names to resolve from the manifest - * @return a {@code Map} of resolved main attributes + * @return a {@code Map} of resolved main attributes * @implNote If this code is called from a "fat-jar" with a single manifest file, then the filter predicate is unnecessary. * The predicate filter is really only necessary if there are multiple jars loaded in the classpath all containing manifest files. */ public static Map readValuesFromJarManifest(ClassLoader classLoader, @Nullable Predicate manifestFilter, String... mainAttributeNames) { + + var uniqueNames = Set.of(mainAttributeNames); + return readMainAttributesFromJarManifest(classLoader, + entry -> uniqueNames.contains(entry.getKey()), + manifestFilter); + } + + /** + * Get the values of all main attributes from the manifest (if found) from the current class loader. + * + * @return a {@code Map} of all main attributes + */ + public static Map readAllMainAttributesFromJarManifest() { + return readAllMainAttributesFromJarManifest(KiwiJars.class.getClassLoader(), null); + } + + /** + * Get the values of all main attributes from the manifest (if found) from the given class loader. + * + * @param classLoader the classloader to search for manifest files in + * @return a {@code Map} of all main attributes + */ + public static Map readAllMainAttributesFromJarManifest(ClassLoader classLoader) { + return readAllMainAttributesFromJarManifest(classLoader, null); + } + + /** + * Get the values of all main attributes from the manifest (if found) from the given class loader + * and filtering manifests using the given Predicate. + * + * @param classLoader the classloader to search for manifest files in + * @param manifestFilter a predicate filter used to limit which jar files to search for a manifest file + * @return a {@code Map} of all main attributes + */ + public static Map readAllMainAttributesFromJarManifest(ClassLoader classLoader, + @Nullable Predicate manifestFilter) { + return readMainAttributesFromJarManifest(classLoader, entry -> true, manifestFilter); + } + + private static Map readMainAttributesFromJarManifest(ClassLoader classLoader, + Predicate> entryPredicate, + @Nullable Predicate manifestFilter) { try { var manifest = findManifestOrNull(classLoader, manifestFilter); if (isNull(manifest)) { return Map.of(); } - var uniqueNames = Set.of(mainAttributeNames); return manifest.getMainAttributes() .entrySet() .stream() - .filter(e -> uniqueNames.contains(String.valueOf(e.getKey()))) - .collect(toUnmodifiableMap(e -> String.valueOf(e.getKey()), e -> String.valueOf(e.getValue()))); - + .map(KiwiJars::toEntryOfStringToString) + .filter(entryPredicate) + .collect(toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); + } catch (Exception e) { - LOG.warn("Unable to locate {} from JAR", Arrays.toString(mainAttributeNames), e); + LOG.warn("Unable to get main attributes from JAR manifest", e); return Map.of(); } } + private static Map.Entry toEntryOfStringToString(Map.Entry e) { + return Map.entry(String.valueOf(e.getKey()), String.valueOf(e.getValue())); + } + + @Nullable private static Manifest findManifestOrNull(ClassLoader classLoader, @Nullable Predicate manifestFilter) throws IOException { @@ -240,14 +286,21 @@ private static List findManifestUrls(ClassLoader classLoader, } @VisibleForTesting + @Nullable static Manifest readFirstManifestOrNull(List urls) { LOG.trace("Using manifest URL(s): {}", urls); - return urls.stream() + var manifest = urls.stream() .map(KiwiJars::readManifest) .flatMap(Optional::stream) .findFirst() .orElse(null); + + if (isNull(manifest)) { + LOG.warn("Unable to get a manifest using URLs: {}", urls); + } + + return manifest; } private static Optional readManifest(URL url) { diff --git a/src/test/java/org/kiwiproject/jar/KiwiJarsTest.java b/src/test/java/org/kiwiproject/jar/KiwiJarsTest.java index 87972ad5..459ba96f 100644 --- a/src/test/java/org/kiwiproject/jar/KiwiJarsTest.java +++ b/src/test/java/org/kiwiproject/jar/KiwiJarsTest.java @@ -16,7 +16,7 @@ import org.kiwiproject.junit.jupiter.ClearBoxTest; import java.io.File; -import java.io.IOException; +import java.io.UncheckedIOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; @@ -116,11 +116,8 @@ void shouldReturnItemsWithLeadingFileSeparator_WhenListHasEmptyFirstElement() { class ReadSingleValueFromJarManifest { @Test - void shouldReadAnActualValueFromTheManifest_WithGivenClassLoaderAndPredicate() throws IOException { - var classLoader = new URLClassLoader( - new URL[] {Fixtures.fixturePath("KiwiJars/KiwiTestSample.jar").toUri().toURL()}, - this.getClass().getClassLoader() - ); + void shouldReadAnActualValueFromTheManifest_WithGivenClassLoaderAndPredicate() { + var classLoader = urlClassLoaderForKiwiTestSampleJar(); var value = KiwiJars.readSingleValueFromJarManifest(classLoader, "Sample-Attribute", url -> url.getPath().contains("KiwiTestSample")); @@ -161,11 +158,8 @@ void shouldReturnOptionalEmptyIfValueCouldNotBeFoundInManifest_UsingDefaultClass class ReadValuesFromJarManifest { @Test - void shouldReadActualValuesFromTheManifest_WithGivenClassLoaderAndPredicate() throws IOException { - var classLoader = new URLClassLoader( - new URL[] {Fixtures.fixturePath("KiwiJars/KiwiTestSample.jar").toUri().toURL()}, - this.getClass().getClassLoader() - ); + void shouldReadActualValuesFromTheManifest_WithGivenClassLoaderAndPredicate() { + var classLoader = urlClassLoaderForKiwiTestSampleJar(); var values = KiwiJars.readValuesFromJarManifest(classLoader, url -> url.getPath().contains("KiwiTestSample"), "Sample-Attribute", "Main-Class"); @@ -216,6 +210,58 @@ void shouldReturnEmptyMap_WhenManifestCannotBeFound() { } } + @Nested + class ReadAllMainAttributesFromJarManifest { + + @Test + void shouldReturnAllValuesFromTheManifest_WithDefaultClassLoader() { + var values = KiwiJars.readAllMainAttributesFromJarManifest(); + + assertThat(values).isNotEmpty(); + } + + @Test + void shouldReturnAllValuesFromTheManifest_WithGivenClassLoader() { + var classLoader = urlClassLoaderForKiwiTestSampleJar(); + + var values = KiwiJars.readAllMainAttributesFromJarManifest(classLoader); + + assertThat(values).isNotEmpty(); + } + + @Test + void shouldReturnAllValuesFromTheManifest_WithGivenClassLoaderAndPredicate() { + var classLoader = urlClassLoaderForKiwiTestSampleJar(); + + var values = KiwiJars.readAllMainAttributesFromJarManifest(classLoader, + url -> url.getPath().contains("KiwiTestSample")); + + assertThat(values).contains( + entry("Manifest-Version", "1.0"), + entry("Main-Class", "KiwiTestClass"), + entry("Sample-Attribute", "the-value"), + entry("Created-By", "11.0.2 (Oracle Corporation)") + ); + } + + @SuppressWarnings("ConstantValue") + @Test + void shouldReturnEmptyMap_ForInvalidClassLoader() { + ClassLoader classLoader = null; + var values = KiwiJars.readAllMainAttributesFromJarManifest(classLoader); + + assertThat(values).isEmpty(); + } + + @Test + void shouldReturnEmptyMap_WhenManifestCannotBeFound() { + var values = KiwiJars.readAllMainAttributesFromJarManifest(this.getClass().getClassLoader(), + url -> false); // ensures manifest won't be found + + assertThat(values).isEmpty(); + } + } + @Nested class ReadFirstManifestOrNull { @@ -233,4 +279,15 @@ void shouldReturnNull_WhenUrlIsInvalid() throws MalformedURLException { assertThat(manifest).isNull(); } } + + private static URLClassLoader urlClassLoaderForKiwiTestSampleJar() { + try { + return new URLClassLoader( + new URL[] { Fixtures.fixturePath("KiwiJars/KiwiTestSample.jar").toUri().toURL() }, + KiwiJars.class.getClassLoader() + ); + } catch (MalformedURLException e) { + throw new UncheckedIOException(e); + } + } }