FeatureManager.java
/*
* Copyright (c) 2013, 2014 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.runtime.subsystems;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.runtime.LexicalScope;
import org.jruby.truffle.runtime.ModuleOperations;
import org.jruby.truffle.runtime.RubyConstant;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.RubyFile;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
/**
* Manages the features loaded into Ruby. This basically means which library files are loaded, but
* Ruby often talks about requiring features, not files.
*
*/
public class FeatureManager {
private RubyContext context;
public FeatureManager(RubyContext context) {
this.context = context;
}
public boolean require(String feature, RubyNode currentNode) throws IOException {
final RubyConstant dataConstantBefore = ModuleOperations.lookupConstant(context, LexicalScope.NONE, context.getCoreLibrary().getObjectClass(), "DATA");
try {
// Some features are handled specially
if (feature.equals("zlib")) {
context.getWarnings().warn("zlib not yet implemented");
return true;
}
if (feature.equals("enumerator")) {
context.getWarnings().warn("enumerator not yet implemented");
return true;
}
if (feature.equals("rbconfig")) {
// Kernel#rbconfig is always there
return true;
}
if (feature.equals("pp")) {
// Kernel#pretty_inspect is always there
return true;
}
if (feature.equals("thread")) {
return true;
}
// Get the load path
// Try as a full path
if (requireInPath("", feature, currentNode)) {
return true;
}
// Try as a path relative to the current director
if (requireInPath(context.getRuntime().getCurrentDirectory(), feature, currentNode)) {
return true;
}
// Try each load path in turn
for (Object pathObject : context.getCoreLibrary().getLoadPath().slowToArray()) {
final String path = pathObject.toString();
if (requireInPath(path, feature, currentNode)) {
return true;
}
}
// Didn't find the feature
throw new RaiseException(context.getCoreLibrary().loadErrorCannotLoad(feature, currentNode));
} finally {
if (dataConstantBefore == null) {
context.getCoreLibrary().getObjectClass().removeConstant(currentNode, "DATA");
} else {
context.getCoreLibrary().getObjectClass().setConstant(currentNode, "DATA", dataConstantBefore.getValue());
}
}
}
public boolean requireInPath(String path, String feature, RubyNode currentNode) throws IOException {
if (requireFile(feature, currentNode)) {
return true;
}
if (requireFile(feature + ".rb", currentNode)) {
return true;
}
if (requireFile(new File(path, feature).getPath(), currentNode)) {
return true;
}
if (requireFile(new File(path, feature).getPath() + ".rb", currentNode)) {
return true;
}
return false;
}
private boolean requireFile(String fileName, RubyNode currentNode) throws IOException {
// We expect '/' in various classpath URLs, so normalize Windows file paths to use '/'.
fileName = fileName.replace('\\', '/');
// Loading from core:/ always just goes ahead without a proper check
if (fileName.startsWith("core:/")) {
try {
context.getCoreLibrary().loadRubyCore(fileName.substring("core:/".length()));
return true;
} catch (Exception e) {
// TODO(CS): obviously not the best way to do this
return false;
}
}
final File file = new File(fileName);
if (!file.isFile()) {
return false;
}
final String expandedPath = RubyFile.expandPath(fileName);
for (Object loaded : Arrays.asList(context.getCoreLibrary().getLoadedFeatures().slowToArray())) {
if (loaded.toString().equals(expandedPath)) {
return true;
}
}
context.loadFile(fileName, currentNode);
context.getCoreLibrary().getLoadedFeatures().slowPush(context.makeString(expandedPath));
return true;
}
}