Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 3cfe2dadd089^
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: e0d8da1830ea
Choose a head ref
  • 2 commits
  • 4 files changed
  • 1 contributor

Commits on Nov 25, 2014

  1. remove extra whitespaces

    mkristian committed Nov 25, 2014
    Copy the full SHA
    3cfe2da View commit details
  2. clean up classloader type hierachy

    * only the classloader runtime.getJRubyClassLoader needs to have
      all the extra features. all other classloaders just needs to
      be able to define classes and do not need the extra overhead of
      loading and finding resources and adding jar files during runtime.
    * the OneShotClassloader is only used with runtime.getJRubyClassLoader
      so ensure this relation via types
    * the JRubyClassLoader is used at various places also as "flag via instanceof".
      keep this and all other uses of JRubyClassLoader as it was before
    mkristian committed Nov 25, 2014
    Copy the full SHA
    e0d8da1 View commit details
515 changes: 259 additions & 256 deletions core/src/main/java/org/jruby/Ruby.java

Large diffs are not rendered by default.

325 changes: 325 additions & 0 deletions core/src/main/java/org/jruby/util/DynamicJRubyClassLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
/*
**** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/

package org.jruby.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;

public class DynamicJRubyClassLoader extends JRubyClassLoader {

private static final Logger LOG = LoggerFactory.getLogger("JRubyClassLoader");

private final Map<URL,Set<String>> jarIndexes = new LinkedHashMap<URL,Set<String>>();

private Runnable unloader;

public DynamicJRubyClassLoader(ClassLoader parent) {
super(parent);
}

public void addURLNoIndex(URL url) {
// if we have such embedded jar within a jar, we copy it to temp file and use the
// the temp file with the super URLClassLoader
if ( url.toString().contains( "!/" )) {
InputStream in = null;
OutputStream out = null;
try
{
File f = File.createTempFile( "jruby", ".jar");
f.deleteOnExit();
out = new BufferedOutputStream( new FileOutputStream( f ) );
in = new BufferedInputStream( url.openStream() );
int i = in.read();
while( i != -1 ) {
out.write( i );
i = in.read();
}
out.close();
in.close();
url = f.toURI().toURL();
}
catch (IOException e)
{
throw new RuntimeException("BUG: we can not copy embedded jar to temp directory", e);
}
finally {
// make sure we close everything
if ( out != null ) {
try
{
out.close();
}
catch (IOException e)
{
}
}
if ( in != null ) {
try
{
in.close();
}
catch (IOException e)
{
}
}
}
}
super.addURL( url );
}

// Change visibility so others can see it
@Override
public void addURL(URL url) {
super.addURL(url);
indexJarContents(url);
}

@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
c = findClass(name);
} catch (ClassNotFoundException e) {
return super.loadClass(name, resolve);
}
}
return c;
}
}

/**
* Called when the parent runtime is torn down.
*/
public void tearDown(boolean debug) {
try {
// A hack to allow unloading all JDBC Drivers loaded by this classloader.
// See http://bugs.jruby.org/4226
getJDBCDriverUnloader().run();
} catch (Exception e) {
if (debug) {
LOG.debug(e);
}
}
}

public synchronized Runnable getJDBCDriverUnloader() {
if (unloader == null) {
try {
InputStream unloaderStream = getClass().getResourceAsStream("/org/jruby/util/JDBCDriverUnloader.class");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int bytesRead;
while ((bytesRead = unloaderStream.read(buf)) != -1) {
baos.write(buf, 0, bytesRead);
}

Class<?> unloaderClass = defineClass("org.jruby.util.JDBCDriverUnloader", baos.toByteArray());
unloader = (Runnable) unloaderClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return unloader;
}

@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
try {
return super.findClass(className);
} catch (ClassNotFoundException ex) {
String resourceName = className.replace('.', '/').concat(".class");

URL classUrl = null;
synchronized (jarIndexes) {
for (URL jarUrl : jarIndexes.keySet()) {
if (jarIndexes.get(jarUrl).contains(resourceName)) {
try {
classUrl = CompoundJarURLStreamHandler.createUrl(jarUrl, resourceName);
break;
} catch (IOException e) {
// keep going to next URL
}
}
}
}

if (classUrl != null) {
try {
InputStream input = classUrl.openStream();
try {
byte[] buffer = new byte[4096];
ByteArrayOutputStream output = new ByteArrayOutputStream();

for (int count = input.read(buffer); count > 0; count = input.read(buffer)) {
output.write(buffer, 0, count);
}

byte[] data = output.toByteArray();
return defineClass(className, data, 0, data.length);
} finally {
close(input);
}
} catch (IOException e) {
// just fall-through to the re-throw below
}
}

throw ex;
}
}

@Override
public URL findResource(String resourceName) {
URL result = super.findResource(resourceName);

if (result == null) {
synchronized (jarIndexes) {
for (URL jarUrl : jarIndexes.keySet()) {
if (jarIndexes.get(jarUrl).contains(resourceName)) {
try {
return CompoundJarURLStreamHandler.createUrl(jarUrl, resourceName);
} catch (IOException e) {
// keep going
}
}
}
}
}

return result;
}

@Override
public Enumeration<URL> findResources(String resourceName) throws IOException {
final List<URL> embeddedUrls = new ArrayList<URL>();

synchronized (jarIndexes) {
for (URL jarUrl : jarIndexes.keySet()) {
if (jarIndexes.get(jarUrl).contains(resourceName)) {
try {
embeddedUrls.add(CompoundJarURLStreamHandler.createUrl(jarUrl, resourceName));
} catch (IOException e) {
// keep going
}
}
}
}

if (embeddedUrls.isEmpty()) {
return super.findResources(resourceName);
} else {
final Enumeration<URL> originalResult = super.findResources(resourceName);

return new Enumeration<URL>() {
private Iterator<URL> extendedResult;

public URL nextElement() {
if (extendedResult == null) {
return originalResult.nextElement();
} else {
return extendedResult.next();
}
}

public boolean hasMoreElements() {
if (extendedResult == null) {
boolean result = originalResult.hasMoreElements();

if (!result) {
// original result is consumed, switching to result
// from embedded jars processing.
extendedResult = embeddedUrls.iterator();
result = extendedResult.hasNext();
}
return result;
} else {
return extendedResult.hasNext();
}
}
};
}
}

private void indexJarContents(URL jarUrl) {
String proto = jarUrl.getProtocol();
// we only need to index jar: and compoundjar: URLs
// 1st-level jar files with file: URLs are handled by the JDK
if (proto.equals("jar") || proto.equals(CompoundJarURLStreamHandler.PROTOCOL)) {
synchronized (jarIndexes) {
Set<String> entries = new HashSet<String>();
jarIndexes.put(jarUrl, entries);

try {
InputStream baseInputStream = jarUrl.openStream();
try {
JarInputStream baseJar = new JarInputStream(baseInputStream);
for (JarEntry entry = baseJar.getNextJarEntry(); entry != null; entry = baseJar.getNextJarEntry()) {
entries.add(entry.getName());
}
} finally {
close(baseInputStream);
}
} catch (IOException ex) {
// can't read the stream, keep going
}
}
}
}

private static void close(Closeable resource) {
if (resource != null) {
try {
resource.close();
} catch (IOException ignore) {
}
}
}
}
291 changes: 1 addition & 290 deletions core/src/main/java/org/jruby/util/JRubyClassLoader.java
Original file line number Diff line number Diff line change
@@ -27,38 +27,17 @@

package org.jruby.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;

public class JRubyClassLoader extends URLClassLoader implements ClassDefiningClassLoader {

private static final Logger LOG = LoggerFactory.getLogger("JRubyClassLoader");
final static ProtectionDomain DEFAULT_DOMAIN;

private final static ProtectionDomain DEFAULT_DOMAIN;

static {
ProtectionDomain defaultDomain = null;
try {
@@ -69,279 +48,11 @@ public class JRubyClassLoader extends URLClassLoader implements ClassDefiningCla
DEFAULT_DOMAIN = defaultDomain;
}

private final Map<URL,Set<String>> jarIndexes = new LinkedHashMap<URL,Set<String>>();

private Runnable unloader;

public JRubyClassLoader(ClassLoader parent) {
super(new URL[0], parent);
}

public void addURLNoIndex(URL url) {
// if we have such embedded jar within a jar, we copy it to temp file and use the
// the temp file with the super URLClassLoader
if ( url.toString().contains( "!/" )) {
InputStream in = null;
OutputStream out = null;
try
{
File f = File.createTempFile( "jruby", ".jar");
f.deleteOnExit();
out = new BufferedOutputStream( new FileOutputStream( f ) );
in = new BufferedInputStream( url.openStream() );
int i = in.read();
while( i != -1 ) {
out.write( i );
i = in.read();
}
out.close();
in.close();
url = f.toURI().toURL();
}
catch (IOException e)
{
throw new RuntimeException("BUG: we can not copy embedded jar to temp directory", e);
}
finally {
// make sure we close everything
if ( out != null ) {
try
{
out.close();
}
catch (IOException e)
{
}
}
if ( in != null ) {
try
{
in.close();
}
catch (IOException e)
{
}
}
}
}
super.addURL( url );
}

// Change visibility so others can see it
@Override
public void addURL(URL url) {
super.addURL(url);
indexJarContents(url);
}

@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
c = findClass(name);
} catch (ClassNotFoundException e) {
return super.loadClass(name, resolve);
}
}
return c;
}
}

/**
* Called when the parent runtime is torn down.
*/
public void tearDown(boolean debug) {
try {
// A hack to allow unloading all JDBC Drivers loaded by this classloader.
// See http://bugs.jruby.org/4226
getJDBCDriverUnloader().run();
} catch (Exception e) {
if (debug) {
LOG.debug(e);
}
}
}

public synchronized Runnable getJDBCDriverUnloader() {
if (unloader == null) {
try {
InputStream unloaderStream = getClass().getResourceAsStream("/org/jruby/util/JDBCDriverUnloader.class");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int bytesRead;
while ((bytesRead = unloaderStream.read(buf)) != -1) {
baos.write(buf, 0, bytesRead);
}

Class unloaderClass = defineClass("org.jruby.util.JDBCDriverUnloader", baos.toByteArray());
unloader = (Runnable) unloaderClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return unloader;
}

public Class<?> defineClass(String name, byte[] bytes) {
return super.defineClass(name, bytes, 0, bytes.length, DEFAULT_DOMAIN);
}

public Class<?> defineClass(String name, byte[] bytes, ProtectionDomain domain) {
return super.defineClass(name, bytes, 0, bytes.length, domain);
}

@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
try {
return super.findClass(className);
} catch (ClassNotFoundException ex) {
String resourceName = className.replace('.', '/').concat(".class");

URL classUrl = null;
synchronized (jarIndexes) {
for (URL jarUrl : jarIndexes.keySet()) {
if (jarIndexes.get(jarUrl).contains(resourceName)) {
try {
classUrl = CompoundJarURLStreamHandler.createUrl(jarUrl, resourceName);
break;
} catch (IOException e) {
// keep going to next URL
}
}
}
}

if (classUrl != null) {
try {
InputStream input = classUrl.openStream();
try {
byte[] buffer = new byte[4096];
ByteArrayOutputStream output = new ByteArrayOutputStream();

for (int count = input.read(buffer); count > 0; count = input.read(buffer)) {
output.write(buffer, 0, count);
}

byte[] data = output.toByteArray();
return defineClass(className, data, 0, data.length);
} finally {
close(input);
}
} catch (IOException e) {
// just fall-through to the re-throw below
}
}

throw ex;
}
}

@Override
public URL findResource(String resourceName) {
URL result = super.findResource(resourceName);

if (result == null) {
synchronized (jarIndexes) {
for (URL jarUrl : jarIndexes.keySet()) {
if (jarIndexes.get(jarUrl).contains(resourceName)) {
try {
return CompoundJarURLStreamHandler.createUrl(jarUrl, resourceName);
} catch (IOException e) {
// keep going
}
}
}
}
}

return result;
}

@Override
public Enumeration<URL> findResources(String resourceName) throws IOException {
final List<URL> embeddedUrls = new ArrayList<URL>();

synchronized (jarIndexes) {
for (URL jarUrl : jarIndexes.keySet()) {
if (jarIndexes.get(jarUrl).contains(resourceName)) {
try {
embeddedUrls.add(CompoundJarURLStreamHandler.createUrl(jarUrl, resourceName));
} catch (IOException e) {
// keep going
}
}
}
}

if (embeddedUrls.isEmpty()) {
return super.findResources(resourceName);
} else {
final Enumeration<URL> originalResult = super.findResources(resourceName);

return new Enumeration<URL>() {
private Iterator<URL> extendedResult;

public URL nextElement() {
if (extendedResult == null) {
return originalResult.nextElement();
} else {
return extendedResult.next();
}
}

public boolean hasMoreElements() {
if (extendedResult == null) {
boolean result = originalResult.hasMoreElements();

if (!result) {
// original result is consumed, switching to result
// from embedded jars processing.
extendedResult = embeddedUrls.iterator();
result = extendedResult.hasNext();
}
return result;
} else {
return extendedResult.hasNext();
}
}
};
}
}

private void indexJarContents(URL jarUrl) {
String proto = jarUrl.getProtocol();
// we only need to index jar: and compoundjar: URLs
// 1st-level jar files with file: URLs are handled by the JDK
if (proto.equals("jar") || proto.equals(CompoundJarURLStreamHandler.PROTOCOL)) {
synchronized (jarIndexes) {
Set<String> entries = new HashSet<String>();
jarIndexes.put(jarUrl, entries);

try {
InputStream baseInputStream = jarUrl.openStream();
try {
JarInputStream baseJar = new JarInputStream(baseInputStream);
for (JarEntry entry = baseJar.getNextJarEntry(); entry != null; entry = baseJar.getNextJarEntry()) {
entries.add(entry.getName());
}
} finally {
close(baseInputStream);
}
} catch (IOException ex) {
// can't read the stream, keep going
}
}
}
}

private static void close(Closeable resource) {
if (resource != null) {
try {
resource.close();
} catch (IOException ignore) {
}
}
}
}
6 changes: 2 additions & 4 deletions core/src/main/java/org/jruby/util/OneShotClassLoader.java
Original file line number Diff line number Diff line change
@@ -6,14 +6,12 @@
* Represents a class loader designed to load exactly one class.
*/
public class OneShotClassLoader extends ClassLoader implements ClassDefiningClassLoader {
private final static ProtectionDomain DEFAULT_DOMAIN =
JRubyClassLoader.class.getProtectionDomain();

public OneShotClassLoader(ClassLoader parent) {
public OneShotClassLoader(JRubyClassLoader parent) {
super(parent);
}

public Class<?> defineClass(String name, byte[] bytes) {
return super.defineClass(name, bytes, 0, bytes.length, DEFAULT_DOMAIN);
return super.defineClass(name, bytes, 0, bytes.length, JRubyClassLoader.DEFAULT_DOMAIN);
}
}