package org.jruby.runtime.load;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import org.jruby.Ruby;
import org.jruby.RubyFile;
import org.jruby.RubyHash;
import org.jruby.RubyString;
import org.jruby.ir.IRScope;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.LoadService.SuffixType;
import org.jruby.util.FileResource;
import org.jruby.util.JRubyFile;
import org.jruby.util.URLResource;
class LibrarySearcher {
static class FoundLibrary implements Library {
private final Library delegate;
private final String loadName;
public FoundLibrary(Library delegate, String loadName) {
this.delegate = delegate;
this.loadName = loadName;
}
@Override
public void load(Ruby runtime, boolean wrap) throws IOException {
delegate.load(runtime, wrap);
}
public String getLoadName() {
return loadName;
}
}
private final LoadService loadService;
private final Ruby runtime;
public LibrarySearcher(LoadService loadService) {
this.loadService = loadService;
this.runtime = loadService.runtime;
}
public FoundLibrary findBySearchState(LoadService.SearchState state) {
FoundLibrary lib = findLibrary(state.searchFile, state.suffixType);
if (lib != null) {
state.library = lib;
state.setLoadName(lib.getLoadName());
}
return lib;
}
public FoundLibrary findLibrary(String baseName, SuffixType suffixType) {
for (String suffix : suffixType.getSuffixes()) {
FoundLibrary library = findResourceLibrary(baseName, suffix);
if (library != null) {
return library;
}
}
return findServiceLibrary(baseName);
}
private FoundLibrary findServiceLibrary(String name) {
DebugLog.JarExtension.logTry(name);
Library extensionLibrary = ClassExtensionLibrary.tryFind(runtime, name);
if (extensionLibrary != null) {
DebugLog.JarExtension.logFound(name);
return new FoundLibrary(extensionLibrary, name);
} else {
return null;
}
}
private FoundLibrary findResourceLibrary(String baseName, String suffix) {
if (baseName.startsWith("./")) {
return findFileResource(baseName, suffix);
}
if (baseName.startsWith("../")) {
return findFileResource(baseName, suffix);
}
if (baseName.startsWith("~/")) {
RubyHash env = (RubyHash) runtime.getObject().getConstant("ENV");
RubyString env_home = runtime.newString("HOME");
if (env.has_key_p(env_home).isFalse()) {
return null;
}
String home = env.op_aref(runtime.getCurrentContext(), env_home).toString();
String path = home + "/" + baseName.substring(2);
return findFileResource(path, suffix);
}
if (isAbsolute(baseName)) {
return findFileResource(baseName, suffix);
}
try {
for (IRubyObject loadPathEntry : loadService.loadPath.toJavaArray()) {
FoundLibrary library = findFileResourceWithLoadPath(baseName, suffix, getPath(loadPathEntry));
if (library != null) return library;
}
} catch (Throwable t) {
t.printStackTrace();
}
if (!runtime.getCurrentDirectory().startsWith(URLResource.URI_CLASSLOADER)) {
FoundLibrary library = findFileResourceWithLoadPath(baseName, suffix, ".");
if (library != null) return null;
}
return findFileResourceWithLoadPath(baseName, suffix, URLResource.URI_CLASSLOADER);
}
private String getPath(IRubyObject loadPathEntry) {
return RubyFile.get_path(runtime.getCurrentContext(), loadPathEntry).asJavaString();
}
private FoundLibrary findFileResource(String searchName, String suffix) {
return findFileResourceWithLoadPath(searchName, suffix, null);
}
private FoundLibrary findFileResourceWithLoadPath(String searchName, String suffix, String loadPath) {
String fullPath = loadPath != null ? loadPath + "/" + searchName : searchName;
String pathWithSuffix = fullPath + suffix;
DebugLog.Resource.logTry(pathWithSuffix);
FileResource resource = JRubyFile.createResourceAsFile(runtime, pathWithSuffix);
if (resource.exists()) {
if (resource.absolutePath() != resource.canonicalPath()) {
FileResource expandedResource = JRubyFile.createResourceAsFile(runtime, resource.canonicalPath());
if (expandedResource.exists()){
String scriptName = resolveScriptName(expandedResource, expandedResource.canonicalPath());
String loadName = resolveLoadName(expandedResource, searchName + suffix);
DebugLog.Resource.logFound(pathWithSuffix);
return new FoundLibrary(ResourceLibrary.create(searchName, scriptName, resource), loadName);
}
}
DebugLog.Resource.logFound(pathWithSuffix);
String scriptName = resolveScriptName(resource, pathWithSuffix);
String loadName = resolveLoadName(resource, searchName + suffix);
return new FoundLibrary(ResourceLibrary.create(searchName, scriptName, resource), loadName);
}
return null;
}
private static boolean isAbsolute(String path) {
if (path.startsWith("jar:")) {
path = path.substring(4);
}
if (path.startsWith("file:")) {
return true;
}
if (path.startsWith("uri:")) {
return true;
}
if (path.startsWith("classpath:")) {
return true;
}
return new File(path).isAbsolute();
}
protected String resolveLoadName(FileResource resource, String ruby18path) {
return resource.absolutePath();
}
protected String resolveScriptName(FileResource resource, String ruby18Path) {
return resource.absolutePath();
}
static class ResourceLibrary implements Library {
public static ResourceLibrary create(String searchName, String scriptName, FileResource resource) {
String location = resource.absolutePath();
if (location.endsWith(".class")) return new ClassResourceLibrary(searchName, scriptName, resource);
if (location.endsWith(".jar")) return new JarResourceLibrary(searchName, scriptName, resource);
return new ResourceLibrary(searchName, scriptName, resource);
}
protected final String searchName;
protected final String scriptName;
protected final FileResource resource;
protected final String location;
public ResourceLibrary(String searchName, String scriptName, FileResource resource) {
this.searchName = searchName;
this.scriptName = scriptName;
this.location = resource.absolutePath();
this.resource = resource;
}
@Override
public void load(Ruby runtime, boolean wrap) {
try (InputStream ris = resource.inputStream()) {
if (runtime.getInstanceConfig().getCompileMode().shouldPrecompileAll()) {
runtime.compileAndLoadFile(scriptName, ris, wrap);
} else {
runtime.loadFile(scriptName, new LoadServiceResourceInputStream(ris), wrap);
}
} catch(IOException e) {
throw runtime.newLoadError("no such file to load -- " + searchName, searchName);
}
}
}
static class ClassResourceLibrary extends ResourceLibrary {
public ClassResourceLibrary(String searchName, String scriptName, FileResource resource) {
super(searchName, scriptName, resource);
}
@Override
public void load(Ruby runtime, boolean wrap) {
try (InputStream ris = resource.inputStream()) {
InputStream is = new BufferedInputStream(ris, 32768);
IRScope script = CompiledScriptLoader.loadScriptFromFile(runtime, is, null, scriptName, false);
if (script == null) return;
script.setFileName(scriptName);
runtime.loadScope(script, wrap);
} catch(IOException e) {
throw runtime.newLoadError("no such file to load -- " + searchName, searchName);
}
}
}
static class JarResourceLibrary extends ResourceLibrary {
public JarResourceLibrary(String searchName, String scriptName, FileResource resource) {
super(searchName, scriptName, resource);
}
@Override
public void load(Ruby runtime, boolean wrap) {
try {
URL url;
if (location.startsWith(URLResource.URI)) {
url = URLResource.getResourceURL(runtime, location);
} else {
File f = new File(location);
if (f.exists() || location.contains( "!")){
url = f.toURI().toURL();
if (location.contains( "!")) {
url = new URL( "jar:" + url );
}
} else {
url = new URL(location);
}
}
runtime.getJRubyClassLoader().addURL(url);
}
catch (MalformedURLException badUrl) {
throw runtime.newIOErrorFromException(badUrl);
}
ClassExtensionLibrary serviceExtension = ClassExtensionLibrary.tryFind(runtime, searchName);
if (serviceExtension != null) {
serviceExtension.load(runtime, wrap);
}
}
}
}