Skip to content

Commit

Permalink
Added a class file locator for Java 9 module files. Made class file l…
Browse files Browse the repository at this point in the history
…ocator closable.
  • Loading branch information
raphw committed Jul 30, 2016
1 parent 5107965 commit 522dd19
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 19 deletions.
Expand Up @@ -16,11 +16,12 @@
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
* Locates a class file or its byte array representation when it is given its type description.
*/
public interface ClassFileLocator {
public interface ClassFileLocator extends Closeable {

/**
* The file extension for a Java class file.
Expand Down Expand Up @@ -172,6 +173,11 @@ public Resolution locate(String typeName) {
return new Resolution.Illegal(typeName);
}

@Override
public void close() throws IOException {
/* do nothing */
}

@Override
public String toString() {
return "ClassFileLocator.NoOp." + name();
Expand Down Expand Up @@ -228,6 +234,11 @@ public Resolution locate(String typeName) {
: new Resolution.Explicit(binaryRepresentation);
}

@Override
public void close() {
/* do nothing */
}

@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
Expand Down Expand Up @@ -312,6 +323,13 @@ public Resolution locate(String typeName) throws IOException {
return locate(classLoader, typeName);
}

@Override
public void close() throws IOException {
if (classLoader instanceof Closeable) {
((Closeable) classLoader).close();
}
}

/**
* Locates the class file for the supplied type by requesting a resource from the class loader.
*
Expand Down Expand Up @@ -396,6 +414,14 @@ public Resolution locate(String typeName) throws IOException {
: ForClassLoader.locate(classLoader, typeName);
}

@Override
public void close() throws IOException {
ClassLoader classLoader = get();
if (classLoader instanceof Closeable) {
((Closeable) classLoader).close();
}
}

@Override
public int hashCode() {
return hashCode;
Expand Down Expand Up @@ -423,7 +449,7 @@ public String toString() {
/**
* A class file locator that locates classes within a Java <i>jar</i> file.
*/
class ForJarFile implements ClassFileLocator, Closeable {
class ForJarFile implements ClassFileLocator {

/**
* A list of potential locations of the runtime jar for different platforms.
Expand Down Expand Up @@ -451,7 +477,7 @@ public ForJarFile(JarFile jarFile) {
* @return A class file locator for the jar file.
* @throws IOException If an I/O exception is thrown.
*/
public static ForJarFile of(File file) throws IOException {
public static ClassFileLocator of(File file) throws IOException {
return new ForJarFile(new JarFile(file));
}

Expand Down Expand Up @@ -485,7 +511,7 @@ public static ClassFileLocator ofRuntimeJar() throws IOException {
String javaHome = System.getProperty("java.home").replace('\\', '/');
File runtimeJar = null;
for (String location : RUNTIME_LOCATIONS) {
File candidate = new File(javaHome + "/" + location);
File candidate = new File(javaHome, location);
if (candidate.isFile()) {
runtimeJar = candidate;
break;
Expand Down Expand Up @@ -536,6 +562,114 @@ public String toString() {
}
}

/**
* A class file locator that locates classes within a Java <i>jmod</i> file.
*/
class ForModuleFile implements ClassFileLocator {

/**
* A list of potential locations of the boot path for different platforms.
*/
private static final List<String> BOOT_LOCATIONS = Arrays.asList("../jmods", "jmods");

/**
* The represented jmod file.
*/
private final ZipFile zipFile;

/**
* Creates a new class file locator for a jmod file.
*
* @param zipFile The represented jmod file.
*/
public ForModuleFile(ZipFile zipFile) {
this.zipFile = zipFile;
}

/**
* Creates a new class file locator for this VMs boot module path.
*
* @return A class file locator for this VMs boot module path.
* @throws IOException If an I/O error occurs.
*/
public static ClassFileLocator ofBootPath() throws IOException {
String javaHome = System.getProperty("java.home").replace('\\', '/');
File bootModules = null;
for (String location : BOOT_LOCATIONS) {
File candidate = new File(javaHome, location);
if (candidate.isDirectory()) {
bootModules = candidate;
break;
}
}
if (bootModules == null) {
throw new IllegalStateException("Boot modules do not exist in " + javaHome + " for any of " + BOOT_LOCATIONS);
}
File[] module = bootModules.listFiles();
if (module == null) {
return NoOp.INSTANCE;
}
List<ClassFileLocator> classFileLocators = new ArrayList<ClassFileLocator>(module.length);
for (File aModule : module) {
if (aModule.isFile()) {
classFileLocators.add(of(aModule));
}
}
return new Compound(classFileLocators);
}

/**
* Returns a class file locator for the given module file.
*
* @param file The module file.
* @return A class file locator for the given module
* @throws IOException If an I/O error occurs.
*/
public static ClassFileLocator of(File file) throws IOException {
return new ForModuleFile(new ZipFile(file));
}

@Override
public Resolution locate(String typeName) throws IOException {
ZipEntry zipEntry = zipFile.getEntry("classes/" + typeName.replace('.', '/') + CLASS_FILE_EXTENSION);
if (zipEntry == null) {
return new Resolution.Illegal(typeName);
} else {
InputStream inputStream = zipFile.getInputStream(zipEntry);
try {
return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
} finally {
inputStream.close();
}
}
}

@Override
public void close() throws IOException {
zipFile.close();
}

@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
ForModuleFile that = (ForModuleFile) object;
return zipFile.equals(that.zipFile);
}

@Override
public int hashCode() {
return zipFile.hashCode();
}

@Override
public String toString() {
return "ClassFileLocator.ForModuleFile{" +
"zipFile=" + zipFile +
'}';
}
}

/**
* A class file locator that finds files from a standardized Java folder structure with
* folders donating packages and class files being saved as {@code <classname>.class} files
Expand Down Expand Up @@ -572,6 +706,11 @@ public Resolution locate(String typeName) throws IOException {
}
}

@Override
public void close() throws IOException {
/* do nothing */
}

@Override
public boolean equals(Object other) {
return this == other || other instanceof ForFolder
Expand Down Expand Up @@ -703,6 +842,11 @@ public Resolution locate(String typeName) {
}
}

@Override
public void close() throws IOException {
/* do nothing */
}

@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
Expand Down Expand Up @@ -1245,9 +1389,7 @@ public Resolution locate(String typeName) throws IOException {
@Override
public void close() throws IOException {
for (ClassFileLocator classFileLocator : classFileLocators) {
if (classFileLocator instanceof Closeable) {
((Closeable) classFileLocator).close();
}
classFileLocator.close();
}
}

Expand Down
Expand Up @@ -22,7 +22,7 @@ public class ClassFileLocatorCompoundTest {
public TestRule mockitoRule = new MockitoRule(this);

@Mock
private ClosableLocator classFileLocator;
private ClassFileLocator classFileLocator;

@Mock
private ClassFileLocator otherClassFileLocator;
Expand Down Expand Up @@ -61,15 +61,12 @@ public void testClosable() throws Exception {
new ClassFileLocator.Compound(classFileLocator, otherClassFileLocator).close();
verify(classFileLocator).close();
verifyNoMoreInteractions(classFileLocator);
verifyZeroInteractions(otherClassFileLocator);
verify(otherClassFileLocator).close();
verifyNoMoreInteractions(otherClassFileLocator);
}

@Test
public void testObjectProperties() throws Exception {
ObjectPropertyAssertion.of(ClassFileLocator.Compound.class).apply();
}

private static abstract class ClosableLocator implements ClassFileLocator, Closeable {
/* empty */
}
}
Expand Up @@ -2,6 +2,7 @@

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.test.utility.JavaVersionRule;
import net.bytebuddy.test.utility.MockitoRule;
import net.bytebuddy.test.utility.ObjectPropertyAssertion;
import net.bytebuddy.utility.StreamDrainer;
Expand All @@ -11,6 +12,7 @@
import org.mockito.Mock;

import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.InputStream;

import static org.hamcrest.CoreMatchers.is;
Expand All @@ -26,7 +28,7 @@ public class ClassFileLocatorForClassLoaderTest {
public TestRule mockitoRule = new MockitoRule(this);

@Mock
private ClassLoader classLoader;
private ClosableClassLoader classLoader;

@Test
public void testCreation() throws Exception {
Expand Down Expand Up @@ -93,6 +95,22 @@ public void testReadTypeIllegal() throws Exception {
resolution.resolve();
}

@Test
public void testClose() throws Exception {
ClassFileLocator.ForClassLoader.of(classLoader).close();
verify(classLoader).close();
}

@Test
public void testSystemClassLoader() throws Exception {
assertThat(ClassFileLocator.ForClassLoader.ofClassPath(), is(ClassFileLocator.ForClassLoader.of(ClassLoader.getSystemClassLoader())));
}

@Test
public void testBootLoader() throws Exception {
assertThat(ClassFileLocator.ForClassLoader.of(null), is(ClassFileLocator.ForClassLoader.of(ClassLoader.getSystemClassLoader())));
}

@Test
public void testObjectProperties() throws Exception {
ObjectPropertyAssertion.of(ClassFileLocator.ForClassLoader.class).apply();
Expand All @@ -101,4 +119,8 @@ public void testObjectProperties() throws Exception {
private static class Foo {
/* empty */
}

private static abstract class ClosableClassLoader extends ClassLoader implements Closeable {
/* empty */
}
}
Expand Up @@ -8,6 +8,7 @@
import org.mockito.Mock;

import java.io.ByteArrayInputStream;
import java.io.Closeable;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
Expand All @@ -22,7 +23,7 @@ public class ClassFileLocatorForClassLoaderWeaklyReferencedTest {
public TestRule mockitoRule = new MockitoRule(this);

@Mock
private ClassLoader classLoader;
private ClosableClassLoader classLoader;

@Test
public void testCreation() throws Exception {
Expand Down Expand Up @@ -58,8 +59,28 @@ public void testNonLocatable() throws Exception {
fail();
}

@Test
public void testClose() throws Exception {
ClassFileLocator.ForClassLoader.WeaklyReferenced.of(classLoader).close();
verify(classLoader).close();
}

@Test
public void testSystemClassLoader() throws Exception {
assertThat(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(ClassLoader.getSystemClassLoader()), is(ClassFileLocator.ForClassLoader.of(ClassLoader.getSystemClassLoader())));
}

@Test
public void testBootLoader() throws Exception {
assertThat(ClassFileLocator.ForClassLoader.WeaklyReferenced.of(null), is(ClassFileLocator.ForClassLoader.of(ClassLoader.getSystemClassLoader())));
}

@Test
public void testObjectProperties() throws Exception {
ObjectPropertyAssertion.of(ClassFileLocator.ForClassLoader.WeaklyReferenced.class).apply();
}

private static abstract class ClosableClassLoader extends ClassLoader implements Closeable {
/* empty */
}
}
Expand Up @@ -61,6 +61,11 @@ public void testNonSuccessfulLocation() throws Exception {
assertThat(resolution.isResolved(), is(false));
}

@Test
public void testClose() throws Exception {
new ClassFileLocator.ForFolder(folder).close();
}

@Test
public void testObjectProperties() throws Exception {
ObjectPropertyAssertion.of(ClassFileLocator.ForFolder.class).apply();
Expand Down

0 comments on commit 522dd19

Please sign in to comment.