Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add overloaded readAllMainAttributesFromJarManifest methods to KiwiJars #1204

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 64 additions & 11 deletions src/main/java/org/kiwiproject/jar/KiwiJars.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -150,7 +149,7 @@ public static Optional<String> 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();
}
}
Expand All @@ -159,7 +158,7 @@ public static Optional<String> 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<String,String>} of resolved main attributes
* @return a {@code Map<String, String>} of resolved main attributes
*/
public static Map<String, String> readValuesFromJarManifest(String... mainAttributeNames) {
return readValuesFromJarManifest(KiwiJars.class.getClassLoader(), null, mainAttributeNames);
Expand All @@ -170,7 +169,7 @@ public static Map<String, String> 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<String,String>} of resolved main attributes
* @return a {@code Map<String, String>} of resolved main attributes
*/
public static Map<String, String> readValuesFromJarManifest(ClassLoader classLoader,
String... mainAttributeNames) {
Expand All @@ -184,32 +183,79 @@ public static Map<String, String> 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<String,String>} of resolved main attributes
* @return a {@code Map<String, String>} 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<String, String> readValuesFromJarManifest(ClassLoader classLoader,
@Nullable Predicate<URL> 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<String, String>} of all main attributes
*/
public static Map<String, String> 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<String, String>} of all main attributes
*/
public static Map<String, String> 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<String, String>} of all main attributes
*/
public static Map<String, String> readAllMainAttributesFromJarManifest(ClassLoader classLoader,
@Nullable Predicate<URL> manifestFilter) {
return readMainAttributesFromJarManifest(classLoader, entry -> true, manifestFilter);
}

private static Map<String, String> readMainAttributesFromJarManifest(ClassLoader classLoader,
Predicate<Map.Entry<String, String>> entryPredicate,
@Nullable Predicate<URL> 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<String, String> toEntryOfStringToString(Map.Entry<Object, Object> e) {
return Map.entry(String.valueOf(e.getKey()), String.valueOf(e.getValue()));
}

@Nullable
private static Manifest findManifestOrNull(ClassLoader classLoader,
@Nullable Predicate<URL> manifestFilter) throws IOException {

Expand Down Expand Up @@ -240,14 +286,21 @@ private static List<URL> findManifestUrls(ClassLoader classLoader,
}

@VisibleForTesting
@Nullable
static Manifest readFirstManifestOrNull(List<URL> 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<Manifest> readManifest(URL url) {
Expand Down
79 changes: 68 additions & 11 deletions src/test/java/org/kiwiproject/jar/KiwiJarsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"));

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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 {

Expand All @@ -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);
}
}
}