diff --git a/.gitignore b/.gitignore index 7c759d8..97be1d3 100755 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ share latest_source_cache src_gen docs/api +test/test_nested_jars_outer.jar +test/test_nested_jars_support/*.class +test/test_nested_jars_support/*.jar diff --git a/src/org/jruby/util/JRubyClassLoader.java b/src/org/jruby/util/JRubyClassLoader.java index 1fa5dd5..52d8759 100644 --- a/src/org/jruby/util/JRubyClassLoader.java +++ b/src/org/jruby/util/JRubyClassLoader.java @@ -1,23 +1,73 @@ package org.jruby.util; +import static java.util.logging.Logger.getLogger; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; import java.net.URL; import java.net.URLClassLoader; import java.security.ProtectionDomain; +import java.util.HashMap; public class JRubyClassLoader extends URLClassLoader { private final static ProtectionDomain DEFAULT_DOMAIN = JRubyClassLoader.class.getProtectionDomain(); + public static final boolean DEBUG = false; + private HashMap embeddedJarToExpandedJar; + public JRubyClassLoader(ClassLoader parent) { super(new URL[0], parent); + embeddedJarToExpandedJar = new HashMap(); } // Change visibility so others can see it @Override public void addURL(URL url) { + if(url.getProtocol().equals("jar")) { + try { + synchronized(embeddedJarToExpandedJar) { + if(isEmbeddedJarAlreadyExpanded(url)) return; + URL tempFileURL = storeInTempFile(url); + embeddedJarToExpandedJar.put(url, tempFileURL); + url = tempFileURL; + } + } catch(Exception e) { + getLogger("JRubyClassLoader").severe("Unable to store " + url + " in a temp file: " + e); + } + } super.addURL(url); } + private boolean isEmbeddedJarAlreadyExpanded(URL url) throws Exception { + return embeddedJarToExpandedJar.containsKey(url) && + new File(embeddedJarToExpandedJar.get(url).toURI()).exists(); + } + + private URL storeInTempFile(URL embeddedJarUrl) throws Exception { + File tempFile = File.createTempFile("jruby_inner_jar_", ".jar"); + if(DEBUG) getLogger("JRubyClassLoader").info("Created temp file " + tempFile + " to store the jar " + embeddedJarUrl); + copy(embeddedJarUrl, tempFile); + return tempFile.toURL(); + } + + private void copy(URL source, File destination) throws Exception { + InputStream sourceStream = source.openStream(); + OutputStream destinationStream = new FileOutputStream(destination); + copy(sourceStream, destinationStream); + sourceStream.close(); + destinationStream.close(); + } + + private void copy(InputStream source, OutputStream destination) throws Exception { + byte [] buffer = new byte[8192]; + int bytesRead; + while(0 < (bytesRead = source.read(buffer))) { + destination.write(buffer, 0, bytesRead); + } + } + public Class defineClass(String name, byte[] bytes) { return super.defineClass(name, bytes, 0, bytes.length, DEFAULT_DOMAIN); } diff --git a/test/test_nested_jars.rb b/test/test_nested_jars.rb new file mode 100644 index 0000000..9e9ad0f --- /dev/null +++ b/test/test_nested_jars.rb @@ -0,0 +1,32 @@ +require 'test/unit' + +class TestNestedJars < Test::Unit::TestCase + def setup + $: << File.dirname(__FILE__) + begin + require 'test_nested_jars_outer.jar' + rescue LoadError + build_jars + require 'test_nested_jars_outer.jar' + end + require 'inner.jar' + end + + def build_jars + Dir.chdir(File.dirname(__FILE__) + '/test_nested_jars_support') do + require 'rake' + sh 'javac', FileList['*.java'] + sh 'jar', 'cf', 'inner.jar', FileList['*.class'], FileList['*.rb'] + sh 'jar', 'cf', '../test_nested_jars_outer.jar', 'inner.jar' + end + end + + def test_should_find_nested_java_class + assert_equal 'hello from java', Java::TestNestedJavaClass.new.hello + end + + def test_should_find_nested_ruby_class + require 'test_nested_ruby_class' + assert_equal 'hello from ruby', TestNestedRubyClass.new.hello + end +end diff --git a/test/test_nested_jars_support/TestNestedJavaClass.java b/test/test_nested_jars_support/TestNestedJavaClass.java new file mode 100644 index 0000000..d17ecb8 --- /dev/null +++ b/test/test_nested_jars_support/TestNestedJavaClass.java @@ -0,0 +1,4 @@ +public class TestNestedJavaClass +{ + public String hello() { return "hello from java"; } +} diff --git a/test/test_nested_jars_support/test_nested_ruby_class.rb b/test/test_nested_jars_support/test_nested_ruby_class.rb new file mode 100644 index 0000000..b683360 --- /dev/null +++ b/test/test_nested_jars_support/test_nested_ruby_class.rb @@ -0,0 +1,5 @@ +class TestNestedRubyClass + def hello + 'hello from ruby' + end +end