Skip to content

Commit

Permalink
Add support for multi-release jars in folder-based class file locator.
Browse files Browse the repository at this point in the history
  • Loading branch information
raphw committed Sep 22, 2024
1 parent 4cd8d23 commit b6d7c21
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 16 deletions.
11 changes: 6 additions & 5 deletions byte-buddy-dep/src/main/java/net/bytebuddy/build/Plugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -2254,9 +2254,10 @@ interface Origin extends Iterable<Element>, Closeable {
* Returns a class file locator for the represented source. If the class file locator needs to be closed, it is the responsibility
* of this origin to close the locator or its underlying resources.
*
* @return A class file locator for locating class files of this instance..
* @return A class file locator for locating class files of this instance.
* @throws IOException If an I/O exception occurs.
*/
ClassFileLocator getClassFileLocator();
ClassFileLocator getClassFileLocator() throws IOException;

/**
* An origin implementation for a jar file.
Expand Down Expand Up @@ -2403,7 +2404,7 @@ public Manifest getManifest() throws IOException {
/**
* {@inheritDoc}
*/
public ClassFileLocator getClassFileLocator() {
public ClassFileLocator getClassFileLocator() throws IOException {
return delegate.getClassFileLocator();
}

Expand Down Expand Up @@ -2815,7 +2816,7 @@ public Manifest getManifest() throws IOException {
/**
* {@inheritDoc}
*/
public ClassFileLocator getClassFileLocator() {
public ClassFileLocator getClassFileLocator() throws IOException {
List<ClassFileLocator> classFileLocators = new ArrayList<ClassFileLocator>(origins.size());
for (Source.Origin origin : origins) {
classFileLocators.add(origin.getClassFileLocator());
Expand Down Expand Up @@ -3080,7 +3081,7 @@ public Origin read() {
/**
* {@inheritDoc}
*/
public ClassFileLocator getClassFileLocator() {
public ClassFileLocator getClassFileLocator() throws IOException {
return new ClassFileLocator.ForFolder(folder);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package net.bytebuddy.dynamic;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.build.AccessControllerPlugin;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
Expand Down Expand Up @@ -43,6 +44,7 @@
import java.security.ProtectionDomain;
import java.util.*;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
Expand Down Expand Up @@ -808,7 +810,7 @@ public static ClassFileLocator ofClassPath() throws IOException {
*/
public static ClassFileLocator ofClassPath(String classPath) throws IOException {
List<ClassFileLocator> classFileLocators = new ArrayList<ClassFileLocator>();
for (String element : Pattern.compile(System.getProperty("path.separator"), Pattern.LITERAL).split(classPath)) {
for (String element : Pattern.compile(File.pathSeparator, Pattern.LITERAL).split(classPath)) {
File file = new File(element);
if (file.isDirectory()) {
classFileLocators.add(new ForFolder(file));
Expand Down Expand Up @@ -1065,30 +1067,84 @@ class ForFolder implements ClassFileLocator {
*/
private final File folder;

/**
* Indicates the existing multi-release jar folders that are available for the current JVM.
*/
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.IGNORE)
private final int[] multiRelease;

/**
* Creates a new class file locator for a folder structure of class files.
*
* @param folder The base folder of the package structure.
* @throws IOException If an I/O exception occurs.
*/
public ForFolder(File folder) {
public ForFolder(File folder) throws IOException {
this.folder = folder;
int current = ClassFileVersion.ofThisVm().getMajorVersion();
if (current < 9) {
multiRelease = new int[0];
} else {
File manifest = new File(folder, "META-INF" + File.separatorChar + "MANIFEST.MF");
boolean mr;
if (manifest.exists()) {
InputStream inputStream = new FileInputStream(manifest);
try {
mr = Boolean.parseBoolean(new Manifest(inputStream).getMainAttributes().getValue("Multi-Release"));
} finally {
inputStream.close();
}
} else {
mr = false;
}
if (mr) {
File[] file = new File(folder, "META-INF" + File.separatorChar + "versions").listFiles();
if (file != null) {
SortedSet<Integer> versions = new TreeSet<Integer>();
for (int index = 0; index < file.length; index++) {
try {
int version = Integer.parseInt(file[index].getName());
if (version <= current) {
versions.add(version);
}
} catch (NumberFormatException ignored) {
/* do nothing */
}
}
multiRelease = new int[versions.size()];
Iterator<Integer> iterator = versions.iterator();
for (int index = 0; index < versions.size(); index++) {
multiRelease[versions.size() - index - 1] = iterator.next();
}
} else {
multiRelease = new int[0];
}
} else {
multiRelease = new int[0];
}
}
}

/**
* {@inheritDoc}
*/
public Resolution locate(String name) throws IOException {
File file = new File(folder, name.replace('.', File.separatorChar) + CLASS_FILE_EXTENSION);
if (file.exists()) {
InputStream inputStream = new FileInputStream(file);
try {
return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
} finally {
inputStream.close();
String path = name.replace('.', File.separatorChar) + CLASS_FILE_EXTENSION;
for (int index = 0; index < multiRelease.length + 1; index++) {
File file = new File(folder, index == multiRelease.length ? path : "META-INF"
+ File.separatorChar + "versions"
+ File.separatorChar + multiRelease[index]
+ File.separatorChar + path);
if (file.exists()) {
InputStream inputStream = new FileInputStream(file);
try {
return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
} finally {
inputStream.close();
}
}
} else {
return new Resolution.Illegal(name);
}
return new Resolution.Illegal(name);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package net.bytebuddy.dynamic;

import net.bytebuddy.test.utility.JavaVersionRule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Random;
import java.util.jar.Manifest;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
Expand All @@ -17,6 +21,9 @@ public class ClassFileLocatorForFolderTest {

private static final int VALUE = 42;

@Rule
public JavaVersionRule javaVersionRule = new JavaVersionRule();

private File folder;

@Before
Expand Down Expand Up @@ -64,4 +71,46 @@ public void testNonSuccessfulLocation() throws Exception {
public void testClose() throws Exception {
new ClassFileLocator.ForFolder(folder).close();
}

@Test
@JavaVersionRule.Enforce(9)
public void testSuccessfulVersionLocation() throws Exception {
File metaInf = new File(folder, "META-INF");
assertThat(metaInf.mkdir(), is(true));
File manifestMf = new File(metaInf, "MANIFEST.MF");
Manifest manifest = new Manifest();
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
manifest.getMainAttributes().putValue("Multi-Release", "true");
OutputStream outputStream = new FileOutputStream(manifestMf);
try {
manifest.write(outputStream);
} finally {
outputStream.close();
}
File versions = new File(metaInf, "versions");
assertThat(versions.mkdir(), is(true));
File version = new File(versions, "9");
assertThat(version.mkdir(), is(true));
File packageFolder = new File(version, FOO);
assertThat(packageFolder.mkdir(), is(true));
File file = new File(packageFolder, BAR + ".class");
assertThat(file.createNewFile(), is(true));
outputStream = new FileOutputStream(file);
try {
outputStream.write(VALUE);
outputStream.write(VALUE * 2);
} finally {
outputStream.close();
}
ClassFileLocator classFileLocator = new ClassFileLocator.ForFolder(folder);
ClassFileLocator.Resolution resolution = classFileLocator.locate(FOO + "." + BAR);
assertThat(resolution.isResolved(), is(true));
assertThat(resolution.resolve(), is(new byte[]{VALUE, VALUE * 2}));
assertThat(file.delete(), is(true));
assertThat(packageFolder.delete(), is(true));
assertThat(version.delete(), is(true));
assertThat(versions.delete(), is(true));
assertThat(manifestMf.delete(), is(true));
assertThat(metaInf.delete(), is(true));
}
}

0 comments on commit b6d7c21

Please sign in to comment.