Skip to content

Commit

Permalink
Prototype of load/require hook for #5206.
Browse files Browse the repository at this point in the history
  • Loading branch information
headius committed Jun 12, 2018
1 parent 459b812 commit 814d3c1
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 20 deletions.
Expand Up @@ -103,7 +103,7 @@ protected LoadServiceResource findFileInClasspath(String name) {
if (bundle != null) {
URL url = bundle.getEntry(sb.toString());
if (url != null) {
return new LoadServiceResource(
return new LoadServiceResource(runtime.getLoadService(),
OSGiFileLocator.getLocalURL(url), name);
}
}
Expand Down
Expand Up @@ -245,7 +245,10 @@ public void load(Ruby runtime, boolean wrap) {
try {
ris = resource.inputStream();

if (runtime.getInstanceConfig().getCompileMode().shouldPrecompileAll()) {
RubyString processed = runtime.getLoadService().runLoadHooks(scriptName, ris);
if (processed != null) {
runtime.loadFile(scriptName, new LoadServiceResourceInputStream(processed.getBytes()), wrap);
} else if (runtime.getInstanceConfig().getCompileMode().shouldPrecompileAll()) {
runtime.compileAndLoadFile(scriptName, ris, wrap);
} else {
runtime.loadFile(scriptName, new LoadServiceResourceInputStream(ris), wrap);
Expand Down
79 changes: 71 additions & 8 deletions core/src/main/java/org/jruby/runtime/load/LoadService.java
Expand Up @@ -34,17 +34,21 @@

package org.jruby.runtime.load;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URI;
import java.net.URL;
import java.security.AccessControlException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
Expand Down Expand Up @@ -192,6 +196,8 @@ public String[] getSuffixes() {
protected final Ruby runtime;
protected final LibrarySearcher librarySearcher;

protected final Set<LoadHook> loadHooks = new HashSet<>();

public LoadService(Ruby runtime) {
this.runtime = runtime;
if (RubyInstanceConfig.DEBUG_LOAD_TIMINGS) {
Expand Down Expand Up @@ -1084,7 +1090,7 @@ protected LoadServiceResource tryResourceFromCWD(SearchState state, String baseN
debugLogTry("resourceFromCWD", file.toString());
if (file.isFile() && file.isAbsolute() && file.canRead()) {
boolean absolute = true;
foundResource = new LoadServiceResource(file, getFileName(file, namePlusSuffix), absolute);
foundResource = new LoadServiceResource(this, file, getFileName(file, namePlusSuffix), absolute);
debugLogFound(foundResource);
state.setLoadName(resolveLoadName(foundResource, namePlusSuffix));
break;
Expand Down Expand Up @@ -1138,7 +1144,7 @@ protected LoadServiceResource tryResourceFromHome(SearchState state, String base
boolean absolute = true;

state.setLoadName(file.getPath());
foundResource = new LoadServiceResource(file, state.loadName, absolute);
foundResource = new LoadServiceResource(this, file, state.loadName, absolute);
debugLogFound(foundResource);
break;
}
Expand All @@ -1164,7 +1170,7 @@ protected LoadServiceResource tryResourceFromJarURL(SearchState state, String ba
URL url = resourceUri.toURL();
debugLogTry("resourceFromJarURL", url.toString());
if (url.openStream() != null) {
foundResource = new LoadServiceResource(url, namePlusSuffix);
foundResource = new LoadServiceResource(this, url, namePlusSuffix);
debugLogFound(foundResource);
}
} catch (FileNotFoundException e) {
Expand All @@ -1191,7 +1197,7 @@ protected LoadServiceResource tryResourceFromJarURL(SearchState state, String ba
debugLogTry("resourceFromJarURL", expandedFilename);
if(file.getJarEntry(expandedFilename) != null) {
URI resourceUri = new URI("jar", "file:" + jarFile + "!/" + expandedFilename, null);
foundResource = new LoadServiceResource(resourceUri.toURL(), namePlusSuffix);
foundResource = new LoadServiceResource(this, resourceUri.toURL(), namePlusSuffix);
debugLogFound(foundResource);
}
} catch (URISyntaxException e) {
Expand Down Expand Up @@ -1316,7 +1322,7 @@ protected LoadServiceResource tryResourceFromJarURLWithLoadPath(String namePlusS
if (current.getJarEntry(canonicalEntry) != null) {
try {
URI resourceUri = new URI("jar", "file:" + jarFileName + "!/" + canonicalEntry, null);
foundResource = new LoadServiceResource(resourceUri.toURL(), resourceUri.getSchemeSpecificPart());
foundResource = new LoadServiceResource(this, resourceUri.toURL(), resourceUri.getSchemeSpecificPart());
debugLogFound(foundResource);
} catch (URISyntaxException e) {
throw runtime.newIOError(e.getMessage());
Expand Down Expand Up @@ -1405,7 +1411,7 @@ protected LoadServiceResource tryResourceFromLoadPath( String namePlusSuffix,Str
debugLogTry("resourceFromLoadPath", "'" + actualPath.toString() + "' " + actualPath.isFile() + " " + actualPath.canRead());
}
if (actualPath.canRead()) {
foundResource = new LoadServiceResource(actualPath, reportedPath, absolute);
foundResource = new LoadServiceResource(this, actualPath, reportedPath, absolute);
debugLogFound(foundResource);
}
}
Expand Down Expand Up @@ -1452,7 +1458,7 @@ protected LoadServiceResource tryResourceAsIs(String namePlusSuffix, String debu
}

if (actualPath.isFile() && actualPath.canRead()) {
foundResource = new LoadServiceResource(actualPath, reportedPath);
foundResource = new LoadServiceResource(this, actualPath, reportedPath);
debugLogFound(foundResource);
}
}
Expand Down Expand Up @@ -1597,7 +1603,7 @@ public LoadServiceResource getClassPathResource(ClassLoader classLoader, String

if (loc != null) { // got it
String path = classpathFilenameFromURL(name, loc, isClasspathScheme);
LoadServiceResource foundResource = new LoadServiceResource(loc, path);
LoadServiceResource foundResource = new LoadServiceResource(this, loc, path);
debugLogFound(foundResource);
return foundResource;
}
Expand Down Expand Up @@ -1655,4 +1661,61 @@ protected String resolveLoadName(LoadServiceResource foundResource, String previ
protected String getFileName(JRubyFile file, String namePlusSuffix) {
return file.getAbsolutePath();
}

public interface LoadHook {
RubyString callback(String file, RubyString src);
}

public void addLoadHook(LoadHook loadHook) {
synchronized (loadHooks) {
loadHooks.add(loadHook);
}
}

public void removeLoadHook(LoadHook loadHook) {
synchronized (loadHooks) {
loadHooks.remove(loadHook);
}
}

public RubyString runLoadHooks(String file, byte[] src) {
if (loadHooks.size() > 0) {
synchronized (loadHooks) {
RubyString string = RubyString.newString(runtime, src);
for (LoadHook loadHook : loadHooks) {
string = loadHook.callback(file, string);
}
return string;
}
}

return null;
}

public RubyString runLoadHooks(String file, InputStream src) throws IOException {
if (loadHooks.size() > 0) {
synchronized (loadHooks) {

byte[] bytes = new byte[8192];

ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);

int len;
while ((len = src.read(bytes)) != -1) {
baos.write(bytes, 0, len);
}
bytes = baos.toByteArray();

RubyString string = RubyString.newString(runtime, bytes);

for (LoadHook loadHook : loadHooks) {
string = loadHook.callback(file, string);
}

return string;
}
}

return null;
}
}
37 changes: 27 additions & 10 deletions core/src/main/java/org/jruby/runtime/load/LoadServiceResource.java
Expand Up @@ -28,6 +28,7 @@

package org.jruby.runtime.load;

import org.jruby.RubyString;
import org.jruby.util.URLUtil;

import java.io.File;
Expand All @@ -50,29 +51,26 @@ public class LoadServiceResource {
private final String name;
private final boolean absolute;
private String absolutePath;
private LoadService loadService;

public LoadServiceResource(URL resource, String name) {
public LoadServiceResource(LoadService loadService, URL resource, String name) {
this.loadService = loadService;
this.resource = resource;
this.path = null;
this.name = name;
this.absolute = false;
}

public LoadServiceResource(URL resource, String name, boolean absolute) {
this.resource = resource;
this.path = null;
this.name = name;
this.absolute = absolute;
}

public LoadServiceResource(File path, String name) {
public LoadServiceResource(LoadService loadService, File path, String name) {
this.loadService = loadService;
this.resource = null;
this.path = path;
this.name = name;
this.absolute = false;
}

public LoadServiceResource(File path, String name, boolean absolute) {
public LoadServiceResource(LoadService loadService, File path, String name, boolean absolute) {
this.loadService = loadService;
this.resource = null;
this.path = path;
this.name = name;
Expand All @@ -83,17 +81,28 @@ public InputStream getInputStream() throws IOException {
if (resource != null) {
InputStream is = resource.openStream();
try {
RubyString processed = loadService.runLoadHooks(resource.getPath(), is);
if (processed != null) {
return new LoadServiceResourceInputStream(processed.getBytes());
}
return new LoadServiceResourceInputStream(is);
} finally {
is.close();
}
}

byte[] bytes = new byte[(int)path.length()];
ByteBuffer buffer = ByteBuffer.wrap(bytes);
FileInputStream fis = new FileInputStream(path);
FileChannel fc = fis.getChannel();
fc.read(buffer);
fis.close();

RubyString processed = loadService.runLoadHooks(path.getAbsolutePath(), bytes);
if (processed != null) {
bytes = processed.getBytes();
}

return new LoadServiceResourceInputStream(bytes);
}

Expand Down Expand Up @@ -148,4 +157,12 @@ public String getAbsolutePath() {
public boolean isAbsolute() {
return absolute;
}

@Deprecated
public LoadServiceResource(URL resource, String name, boolean absolute) {
this.resource = resource;
this.path = null;
this.name = name;
this.absolute = absolute;
}
}
11 changes: 11 additions & 0 deletions core/src/main/ruby/jruby/jruby.rb
Expand Up @@ -112,4 +112,15 @@ def inspect_bytecode

autoload :ASM, 'jruby/asm.rb'

module VM
def self.add_load_hook(&block)
# kinda gross; interface wants RubyString but RubyNil does not coerce to null due to looking like an IRubyObject

This comment has been minimized.

Copy link
@kares

kares Jun 20, 2018

Member

could go away is the signature avoids org.jruby internals
e.g. LoadHook#callback(String file, CharSequence src) (RubyString implements CharSequence in 9.2)

new_block = ->(file, src) do
new_src = block.call(file, src)
new_src.nil? ? nil.to_java : new_src
end
JRuby.runtime.load_service.add_load_hook(new_block)
end
end

end

0 comments on commit 814d3c1

Please sign in to comment.