Browse Source

Merge pull request #168 from cmelchior/cm/bug/classpath-close

[WIP] Fix leaking file handlers
tags/rel_3_23_0_ga
Shigeru Chiba 6 years ago
parent
commit
1513c3372b
No account linked to committer's email address

+ 1
- 0
.gitignore View File

.idea/
bin/ bin/
eclipse-output/ eclipse-output/
target/ target/

+ 8
- 0
Readme.html View File



<h2>Changes</h2> <h2>Changes</h2>


<p>-version 3.23 on MMM DD, YYYY

<ul>
<li>Fix leaking file handlers in ClassPool and removed ClassPath.close(). Github issue #165.
</ul>
</p>


<p>-version 3.22 on October 10, 2017 <p>-version 3.22 on October 10, 2017


<ul> <ul>

+ 10
- 2
pom.xml View File

<build> <build>
<sourceDirectory>src/main/</sourceDirectory> <sourceDirectory>src/main/</sourceDirectory>
<testSourceDirectory>src/test/</testSourceDirectory> <testSourceDirectory>src/test/</testSourceDirectory>
<testResources>
<testResource>
<directory>src/test/resources</directory>
</testResource>
</testResources>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<includes> <includes>
<include>javassist/JvstTest.java</include> <include>javassist/JvstTest.java</include>
</includes> </includes>
<forkMode>once</forkMode>
<workingDirectory>${project.build.directory}/runtest</workingDirectory>
<forkMode>once</forkMode>
<additionalClasspathElements>
<additionalClasspathElement>resources</additionalClasspathElement>
</additionalClasspathElements>
<workingDirectory>${project.build.directory}/runtest</workingDirectory>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>

+ 0
- 6
src/main/javassist/ByteArrayClassPath.java View File

this.classfile = classfile; this.classfile = classfile;
} }


/**
* Closes this class path.
*/
@Override
public void close() {}

@Override @Override
public String toString() { public String toString() {
return "byte[]:" + classname; return "byte[]:" + classname;

+ 0
- 7
src/main/javassist/ClassClassPath.java View File

return thisClass.getResource(filename); return thisClass.getResource(filename);
} }


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

@Override @Override
public String toString() { public String toString() {
return thisClass.getName() + ".class"; return thisClass.getName() + ".class";

+ 0
- 7
src/main/javassist/ClassPath.java View File

* @return null if the specified class file could not be found. * @return null if the specified class file could not be found.
*/ */
URL find(String classname); URL find(String classname);

/**
* This method is invoked when the <code>ClassPath</code> object is
* detached from the search path. It will be an empty method in most of
* classes.
*/
void close();
} }

+ 30
- 40
src/main/javassist/ClassPoolTail.java View File

import java.io.OutputStream; import java.io.OutputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;


return null; return null;
} }


@Override
public void close() {}

@Override @Override
public String toString() { public String toString() {
return directory; return directory;


return null; // not found return null; // not found
} }

@Override
public void close() {
if (jars != null)
for (int i = 0; i < jars.length; i++)
jars[i].close();
}
} }


final class JarClassPath implements ClassPath { final class JarClassPath implements ClassPath {
JarFile jarfile;
List<String> jarfileEntries;
String jarfileURL; String jarfileURL;


JarClassPath(String pathname) throws NotFoundException { JarClassPath(String pathname) throws NotFoundException {
JarFile jarfile = null;
try { try {
jarfile = new JarFile(pathname); jarfile = new JarFile(pathname);
jarfileEntries = new ArrayList<String>();
for (JarEntry je:Collections.list(jarfile.entries()))
if (je.getName().endsWith(".class"))
jarfileEntries.add(je.getName());
jarfileURL = new File(pathname).getCanonicalFile() jarfileURL = new File(pathname).getCanonicalFile()
.toURI().toURL().toString();
.toURI().toURL().toString();
return; return;
} catch (IOException e) {}
finally {
if (null != jarfile)
try {
jarfile.close();
} catch (IOException e) {}
} }
catch (IOException e) {}
throw new NotFoundException(pathname); throw new NotFoundException(pathname);
} }


@Override @Override
public InputStream openClassfile(String classname) public InputStream openClassfile(String classname)
throws NotFoundException
throws NotFoundException
{ {
try {
String jarname = classname.replace('.', '/') + ".class";
JarEntry je = jarfile.getJarEntry(jarname);
if (je != null)
return jarfile.getInputStream(je);
return null; // not found
}
catch (IOException e) {}
throw new NotFoundException("broken jar file?: "
+ jarfile.getName());
URL jarURL = find(classname);
if (null != jarURL)
try {
return jarURL.openConnection().getInputStream();
}
catch (IOException e) {
throw new NotFoundException("broken jar file?: "
+ classname);
}
return null;
} }


@Override @Override
public URL find(String classname) { public URL find(String classname) {
String jarname = classname.replace('.', '/') + ".class"; String jarname = classname.replace('.', '/') + ".class";
JarEntry je = jarfile.getJarEntry(jarname);
if (je != null)
if (jarfileEntries.contains(jarname))
try { try {
return new URL("jar:" + jarfileURL + "!/" + jarname);
return new URL(String.format("jar:%s!/%s", jarfileURL, jarname));
} }
catch (MalformedURLException e) {} catch (MalformedURLException e) {}

return null; // not found return null; // not found
} }


@Override
public void close() {
try {
jarfile.close();
jarfile = null;
}
catch (IOException e) {}
}

@Override @Override
public String toString() { public String toString() {
return jarfile == null ? "<null>" : jarfile.toString();
return jarfileURL == null ? "<null>" : jarfileURL.toString();
} }
} }


else else
list = list.next; list = list.next;
} }

cp.close();
} }


public ClassPath appendSystemPath() { public ClassPath appendSystemPath() {

+ 0
- 8
src/main/javassist/LoaderClassPath.java View File

URL url = cl.getResource(cname); URL url = cl.getResource(cname);
return url; return url;
} }

/**
* Closes this class path.
*/
@Override
public void close() {
clref = null;
}
} }

+ 0
- 6
src/main/javassist/URLClassPath.java View File

return null; return null;
} }


/**
* Closes this class path.
*/
@Override
public void close() {}

/** /**
* Reads a class file on an http server. * Reads a class file on an http server.
* *

+ 0
- 1
src/main/javassist/scopedpool/ScopedClassPool.java View File

*/ */
public void close() { public void close() {
this.removeClassPath(classPath); this.removeClassPath(classPath);
classPath.close();
classes.clear(); classes.clear();
softcache.clear(); softcache.clear();
} }

+ 18
- 2
src/test/Readme.txt View File

check javassist.JvstTestRoot.PATH and .JAR_PATH and then
run javassist.JvstTest under ./runtest
# How to run tests

Requirements:
Java JDK 9
Maven

1) Build jar file and move it to the top level folder.

> mvn package
> mv ./target/javassist*-GA.jar ./javaassist.jar

2) Check that ./src/test/javassist/JvstTestRoot.PATH and .JAR_PATH point to the compiled jar file.
The default is "../../".

3) Run Tests

> mvn test
> mvn surefire:test

+ 32
- 0
src/test/javassist/JvstTest.java View File

package javassist; package javassist;


import junit.framework.*; import junit.framework.*;
import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import javassist.bytecode.*; import javassist.bytecode.*;
import javassist.expr.*; import javassist.expr.*;
assertTrue("[class path: ]".equals(pool.toString())); assertTrue("[class path: ]".equals(pool.toString()));
} }


public void testReleaseJarClassPathFileHandle() throws Exception {
String jarFileName = "./empty.jar";
ClassLoader classLoader = getClass().getClassLoader();
File jarFile = new File(classLoader.getResource(jarFileName).getFile());
assertTrue(jarFile.exists());

// Prepare class pool and force it to open the Jar file
ClassPool pool = ClassPool.getDefault();
ClassPath cp = pool.appendClassPath(jarFile.getAbsolutePath());
assertNull(cp.openClassfile("nothere.Dummy"));

// Assert that it is possible to delete the jar file.
// On Windows deleting an open file will fail, while on on Mac/Linux this is always possible.
// This check will thus only fail on Windos if the file is still open.
assertTrue(jarFile.delete());
}

public void testJarClassPath() throws Exception {
String jarFileName = "./simple.jar";
ClassLoader classLoader = getClass().getClassLoader();
File jarFile = new File(classLoader.getResource(jarFileName).getFile());
assertTrue(jarFile.exists());

ClassPool pool = ClassPool.getDefault();
ClassPath cp = pool.appendClassPath(jarFile.getAbsolutePath());
InputStream is = cp.openClassfile("com.test.Test");
assertNotNull(is);
is.close();
}

public void testSubtype() throws Exception { public void testSubtype() throws Exception {
CtClass cc = sloader.get("test1.Subtype"); CtClass cc = sloader.get("test1.Subtype");
assertTrue(cc.subtypeOf(cc)); assertTrue(cc.subtypeOf(cc));

+ 16
- 0
src/test/resources/Readme.txt View File

This directory contains files used by the unit tests.

empty.jar:
An empty, but valid, jar file.

simple.jar:
Contains a single Java class

```
package com.test;

public class Test {
public Test() {
}
}
```

BIN
src/test/resources/empty.jar View File


BIN
src/test/resources/simple.jar View File


Loading…
Cancel
Save