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: 62fa282cd3fa
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: d6c5cd8d1f85
Choose a head ref
  • 3 commits
  • 1 file changed
  • 2 contributors

Commits on May 26, 2015

  1. Revert "find gem extension classes for uri-like filenames"

    This reverts commit 62fa282.
    mkristian committed May 26, 2015
    Copy the full SHA
    19e82ee View commit details
  2. Rework jar service loading to properly build path.

    The logic here will walk from end to beginning of the loading jar
    filename, building service class names and attempting to load them.
    This is an O(n) operation for the number of path elements, but I
    do not know another way to deal with RubyGems canonicalizing jar
    file path names.
    
    Conflicts:
    	core/src/main/java/org/jruby/runtime/load/ClassExtensionLibrary.java
    headius authored and mkristian committed May 26, 2015
    Copy the full SHA
    86887d3 View commit details
  3. add tests for #2986

    Sponsored By Lookout Inc.
    mkristian committed May 26, 2015
    Copy the full SHA
    d6c5cd8 View commit details
Showing with 41 additions and 96 deletions.
  1. +41 −96 core/src/main/java/org/jruby/runtime/load/ClassExtensionLibrary.java
137 changes: 41 additions & 96 deletions core/src/main/java/org/jruby/runtime/load/ClassExtensionLibrary.java
Original file line number Diff line number Diff line change
@@ -27,13 +27,9 @@
***** END LICENSE BLOCK *****/
package org.jruby.runtime.load;

import jnr.posix.JavaSecuredFile;

import org.jruby.Ruby;

import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* The ClassExtensionLibrary wraps a class which implements BasicLibraryService,
@@ -47,8 +43,6 @@ public class ClassExtensionLibrary implements Library {
private final Class theClass;
private final String name;

private static Pattern URI_LIKE = Pattern.compile("(uri:classloader|file|jar|jar:file:uri):/?/?");

/**
* Try to locate an extension service in the current classloader resources. This happens
* after the jar has been added to JRuby's URLClassLoader (JRubyClassLoader) and is how
@@ -68,110 +62,61 @@ public class ClassExtensionLibrary implements Library {
* @return a ClassExtensionLibrary that will boot the ext, or null if none was found
*/
static ClassExtensionLibrary tryFind(Ruby runtime, String searchName) {
final boolean isAbsolute;
Matcher matcher = URI_LIKE.matcher(searchName);
if (matcher.matches()) {
searchName = matcher.replaceFirst("");
isAbsolute = true;
}
else {
isAbsolute = new JavaSecuredFile(searchName).isAbsolute();
}
// Create package name, by splitting on / and joining all but the last elements with a ".", and downcasing them.
String[] all = searchName.split("/");

// Create package name, by splitting on / and successively accumulating elements to form a class name
String[] elts = searchName.split("/");
// make service name out of last element
String serviceName = buildServiceName(all[all.length - 1]);

String simpleName = buildSimpleName(elts[elts.length - 1]);
// allocate once with plenty of space, to reduce object churn
StringBuilder classNameBuilder = new StringBuilder(searchName.length() * 2);
StringBuilder classFileBuilder = new StringBuilder(searchName.length() * 2);

int firstElement = isAbsolute ? elts.length - 1 : 0;
for (; firstElement >= 0; firstElement--) {
String className = buildClassName(elts, firstElement, elts.length - 1, simpleName);
ClassExtensionLibrary library = tryServiceLoad(runtime, className);

if (library != null) return library;
}

return null;
}
for (int i = all.length - 1; i >= 0; i--) {
buildClassName(classNameBuilder, classFileBuilder, all, i, serviceName);

/**
* Build the "simple" part of a service class name from the given require path element
* by splitting on "_" and concatenating as CamelCase, plus "Service" suffix.
*
* @param element the element from which to build a simple service class name
* @return the resulting simple service class name
*/
private static String buildSimpleName(String element) {
StringBuilder nameBuilder = new StringBuilder(element.length() + "Service".length());
// look for the filename in classloader resources
URL resource = runtime.getJRubyClassLoader().getResource(classFileBuilder.toString());
if (resource == null) continue;

String[] last = element.split("_");
for (String part : last) {
if (part.isEmpty()) break;
String className = classNameBuilder.toString();

nameBuilder
.append(Character.toUpperCase(part.charAt(0)))
.append(part, 1, part.length());
try {
Class theClass = runtime.getJavaSupport().loadJavaClass(className);
return new ClassExtensionLibrary(className + ".java", theClass);
} catch (ClassNotFoundException cnfe) {
// file was found but class couldn't load; continue to next package segment
continue;
} catch (UnsupportedClassVersionError ucve) {
if (runtime.isDebug()) ucve.printStackTrace();
throw runtime.newLoadError("JRuby ext built for wrong Java version in `" + className + "': " + ucve, className.toString());
}
}
nameBuilder.append("Service");

return nameBuilder.toString();
// not found
return null;
}

/**
* Build the full class name for a service by joining the specified package elements
* with '.' and appending the given simple class name.
*
* @param elts the array from which to retrieve the package elements
* @param firstElement the first element to use
* @param end the end index at which to stop building the package name
* @param simpleName the simple class name for the service
* @return the full class name for the service
*/
private static String buildClassName(String[] elts, int firstElement, int end, String simpleName) {
StringBuilder nameBuilder = new StringBuilder();
for (int offset = firstElement; offset < end; offset++) {
// avoid blank elements from leading or double slashes
if (elts[offset].isEmpty()) continue;

nameBuilder
.append(elts[offset].toLowerCase())
.append('.');
private static void buildClassName(StringBuilder nameBuilder, StringBuilder fileBuilder, String[] all, int i, String serviceName) {
nameBuilder.setLength(0);
fileBuilder.setLength(0);
for (int j = i; j < all.length - 1; j++) {
nameBuilder.append(all[j]).append(".");
fileBuilder.append(all[j]).append("/");
}

nameBuilder.append(simpleName);

return nameBuilder.toString();
nameBuilder.append(serviceName);
fileBuilder.append(serviceName).append(".class");
}

/**
* Try loading the given service class. Rather than raise ClassNotFoundException for
* jars that do not contain any service class, we require that the service class be a
* "normal" .class file accessible as a classloader resource. If it can be found
* using ClassLoader.getResource, we proceed to attempt to load it as a class.
*
* @param runtime the Ruby runtime into which the extension service will load
* @param className the class name of the service class
* @return a ClassExtensionLibrary if the service class was found; null otherwise.
*/
private static ClassExtensionLibrary tryServiceLoad(Ruby runtime, String className) {
String classFile = className.replaceAll("\\.", "/") + ".class";

try {
// quietly try to load the class, which must be reachable as a .class resource
URL resource = runtime.getJRubyClassLoader().getResource(classFile);
if (resource != null) {
Class theClass = runtime.getJavaSupport().loadJavaClass(className);
return new ClassExtensionLibrary(className + ".java", theClass);
}
} catch (ClassNotFoundException cnfe) {
if (runtime.isDebug()) cnfe.printStackTrace();
} catch (UnsupportedClassVersionError ucve) {
if (runtime.isDebug()) ucve.printStackTrace();
throw runtime.newLoadError("JRuby ext built for wrong Java version in `" + className + "': " + ucve, className);
private static String buildServiceName(String jarName) {
String[] last = jarName.split("_");
StringBuilder serviceName = new StringBuilder();
for (int i = 0, j = last.length; i < j; i++) {
if ("".equals(last[i])) break;
serviceName.append(Character.toUpperCase(last[i].charAt(0))).append(last[i].substring(1));
}

// The class doesn't exist
return null;
serviceName.append("Service");
return serviceName.toString();
}

public ClassExtensionLibrary(String name, Class extension) {