Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Introduce pluggable module loaders.

This adds a number of internal loaders and allows developers to add custom loaders at runtime.

Preconfigured internal loaders:

  * .js - loads from JavaScript, same as hardcoded loader in the past
  * .json - loads a JSON file and returns the parsed object
  * .class - loads a script compiled to a Java class using the Rhino jsc (JavaScript compiler) tool.
     Note that the class name and package must correspond to the module name.

 Plugging in custom module loaders:

   The require function now contains a "extension" property. To add a module loader add a property
   on `require.extensions` with the file extension you want to handle as property key and the
    function that compiles the module source as value. The function will be called with the resource
    as argument (an instance of org.ringojs.repository.Resource) and is expected to return either
    the exported module object or the module's JavaScript source code.

    For example, the following line shows how to load files with extension ".foo" as plain JavaScript modules:

         require.extensions[".foo"] = function(resource) {return resource.getContent()};

    Modules loaded through custom module loaders are subject to automatic reloading just like
     any other modules, i.e. they will be reloaded when the resource is modified unless running in
     production mode.
  • Loading branch information...
commit 20eee6739da7c69bf93c19d544abc2896df6b7a5 1 parent c978ee1
@hns hns authored
View
161 src/org/ringojs/engine/ModuleLoader.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2012 Hannes Wallnoefer <hannes@helma.at>
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.ringojs.engine;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.GeneratedClassLoader;
+import org.mozilla.javascript.Script;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.SecurityController;
+import org.mozilla.javascript.json.JsonParser;
+import org.ringojs.repository.Resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+public abstract class ModuleLoader {
+
+ private String extension;
+
+ public ModuleLoader(String extension) {
+ this.extension = extension;
+ }
+ public String getExtension() {
+ return extension;
+ }
+
+ public abstract Object load(Context cx, RhinoEngine engine,
+ Object securityDomain, String moduleName,
+ String charset, Resource resource) throws Exception;
+
+}
+
+class JsModuleLoader extends ModuleLoader {
+
+ public JsModuleLoader() {
+ super(".js");
+ }
+
+ @Override
+ public Object load(final Context cx, RhinoEngine engine, final Object securityDomain,
+ String moduleName, String charset, final Resource resource)
+ throws Exception {
+ return AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ try {
+ return cx.compileReader(resource.getReader(),
+ resource.getRelativePath(),
+ resource.getFirstLine(), securityDomain);
+ } catch (IOException iox) {
+ throw new RuntimeException(iox);
+ }
+ }
+ });
+ }
+}
+
+class JsonModuleLoader extends ModuleLoader {
+
+ public JsonModuleLoader() {
+ super(".json");
+ }
+
+ @Override
+ public Object load(Context cx, RhinoEngine engine, Object securityDomain,
+ String moduleName, String charset, Resource resource)
+ throws Exception {
+ JsonParser json = new JsonParser(cx, engine.getScope());
+ return json.parseValue(resource.getContent());
+ }
+}
+
+class ClassModuleLoader extends ModuleLoader {
+
+ public ClassModuleLoader() {
+ super(".class");
+ }
+
+ @Override
+ public Object load(Context cx, RhinoEngine engine, Object securityDomain,
+ String moduleName, String charset, Resource resource)
+ throws Exception {
+ long l = resource.getLength();
+ if (l > Integer.MAX_VALUE) {
+ throw new IOException("File too large: " + l);
+ }
+
+ int length = (int) l;
+ byte[] bytes = new byte[length];
+ InputStream input = resource.getInputStream();
+ int offset = 0, read;
+
+ while (offset < length) {
+ read = input.read(bytes, offset, length - offset);
+ if (read < 0) break;
+ offset += read;
+ }
+
+ if (offset < length) {
+ throw new IOException("Could not read file completely");
+ }
+ input.close();
+
+ String className = moduleName.replaceAll("/", ".");
+ ClassLoader rhinoLoader = getClass().getClassLoader();
+ GeneratedClassLoader loader;
+ loader = SecurityController.createLoader(rhinoLoader, securityDomain);
+
+ Class<?> clazz = loader.defineClass(className, bytes);
+ loader.linkClass(clazz);
+ if (!Script.class.isAssignableFrom(clazz)) {
+ throw new ClassCastException("Module must be a Rhino script class");
+ }
+
+ return clazz.newInstance();
+ }
+}
+
+class ScriptedModuleLoader extends ModuleLoader {
+
+ Function function;
+
+ public ScriptedModuleLoader(String extension, Function function) {
+ super(extension);
+ this.function = function;
+ }
+
+ @Override
+ public Object load(Context cx, RhinoEngine engine, Object securityDomain,
+ String moduleName, String charset, Resource resource)
+ throws Exception {
+ Scriptable scope = engine.getScope();
+ Object[] args = {cx.getWrapFactory().wrap(cx, scope, resource, null)};
+ Object source = function.call(cx, scope, scope, args);
+
+ if (source instanceof CharSequence) {
+ return cx.compileString(source.toString(), resource.getRelativePath(),
+ resource.getFirstLine(), securityDomain);
+ } else if (source instanceof Scriptable) {
+ return source;
+ } else {
+ throw new RuntimeException("Loader must return script or object");
+ }
+ }
+}
View
98 src/org/ringojs/engine/ReloadableScript.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2004 Hannes Wallnoefer <hannes@helma.at>
+ * Copyright 2004-2012 Hannes Wallnoefer <hannes@helma.at>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,12 +20,10 @@
import org.mozilla.javascript.*;
import org.mozilla.javascript.tools.ToolErrorReporter;
+import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.Reader;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.util.*;
import java.security.CodeSource;
import java.security.CodeSigner;
@@ -43,6 +41,8 @@
final Resource resource;
final RhinoEngine engine;
final String moduleName;
+ // the loader
+ ModuleLoader loader;
// true if we should reload modified source files
boolean reloading;
// the checksum of the underlying resource or repository when
@@ -72,6 +72,7 @@ public ReloadableScript(Resource source, RhinoEngine engine) {
source.setStripShebang(true);
this.resource = source;
this.engine = engine;
+ this.loader = engine.getModuleLoader(source);
reloading = engine.getConfig().isReloading();
moduleName = source.getModuleName();
}
@@ -92,14 +93,14 @@ public Resource getSource() {
* @throws IOException if an error occurred reading the script file
* @return the compiled and up-to-date script
*/
- public synchronized Script getScript(Context cx)
+ public synchronized Object getScript(Context cx)
throws JavaScriptException, IOException {
// only use shared code cache if optlevel >= 0
int optlevel = cx.getOptimizationLevel();
if (scriptref == null && optlevel > -1) {
scriptref = cache.get(resource);
}
- Script script = null;
+ Object script = null;
if (scriptref != null) {
script = scriptref.get();
checksum = scriptref.checksum;
@@ -110,7 +111,7 @@ public synchronized Script getScript(Context cx)
if ((script == null && exception == null)
|| (reloading && checksum != resource.getChecksum())) {
if (!resource.exists()) {
- throw new IOException(resource + " not found or not readable");
+ throw new FileNotFoundException(resource + " not found or not readable");
}
exception = null;
errors = null;
@@ -140,28 +141,17 @@ public synchronized Script getScript(Context cx)
* @throws IOException if an error occurred reading the script file
* @return the compiled and up-to-date script
*/
- protected synchronized Script compileScript(final Context cx)
+ protected synchronized Object compileScript(Context cx)
throws JavaScriptException, IOException {
- final String path = resource.getRelativePath();
- final ErrorReporter errorReporter = cx.getErrorReporter();
+ ErrorReporter errorReporter = cx.getErrorReporter();
cx.setErrorReporter(new ErrorCollector());
- Script script = null;
+ Object script = null;
String charset = engine.getCharset();
try {
- final CodeSource source = engine.isPolicyEnabled() ?
+ CodeSource securityDomain = engine.isPolicyEnabled() ?
new CodeSource(resource.getUrl(), (CodeSigner[]) null) : null;
- final Reader reader = resource.getReader(charset);
- script = AccessController.doPrivileged(new PrivilegedAction<Script>() {
- public Script run() {
- try {
- return cx.compileReader(reader, path,
- resource.getFirstLine(), source);
- } catch (Exception x) {
- exception = x;
- }
- return null;
- }
- });
+ script = loader.load(cx, engine, securityDomain, moduleName,
+ charset, resource);
} catch (Exception x) {
exception = x;
} finally {
@@ -181,11 +171,15 @@ public Script run() {
* @throws IOException if an error occurred reading the script file
*/
public Object evaluate(Scriptable scope, Context cx,
- Map<Resource,ModuleScope> modules)
+ Map<Resource, Scriptable> modules)
throws JavaScriptException, IOException {
- Script script = getScript(cx);
- ModuleScope module =
- scope instanceof ModuleScope ? (ModuleScope) scope : null;
+ Object obj = getScript(cx);
+ if (!(obj instanceof Script)) {
+ return obj;
+ }
+ Script script = (Script) obj;
+ ModuleScope module = scope instanceof ModuleScope ?
+ (ModuleScope) scope : null;
if (module != null) {
modules.put(resource, module);
}
@@ -202,37 +196,47 @@ public Object evaluate(Scriptable scope, Context cx,
*
* @param prototype the prototype for the module, usually the shared top level scope
* @param cx the rhino context
+ * @param module the preexisting module for this resource if available
* @param modules thread-local map for registering the module scope
* @return a new module scope
* @throws JavaScriptException if an error occurred evaluating the script file
* @throws IOException if an error occurred reading the script file
*/
- protected ModuleScope load(Scriptable prototype, Context cx,
- ModuleScope module,
- Map<Resource, ModuleScope> modules)
+ protected Scriptable load(Scriptable prototype, Context cx,
+ Scriptable module, Map<Resource, Scriptable> modules)
throws JavaScriptException, IOException {
- if (module != null && module.getChecksum() == getChecksum()) {
+ if (module instanceof ModuleScope &&
+ ((ModuleScope)module).getChecksum() == getChecksum()) {
// Module scope exists and is up to date
modules.put(resource, module);
return module;
}
- return exec(cx, getScript(cx), prototype, modules);
+ return exec(cx, prototype, modules);
}
- private synchronized ModuleScope exec(Context cx, Script script,
- Scriptable prototype,
- Map<Resource, ModuleScope> modules)
+ private synchronized Scriptable exec(Context cx, Scriptable prototype,
+ Map<Resource, Scriptable> modules)
throws IOException {
- ModuleScope module = new ModuleScope(moduleName, resource, prototype);
- // put module scope in map right away to make circular dependencies work
- modules.put(resource, module);
if (log.isLoggable(Level.FINE)) {
log.fine("Loading module: " + moduleName);
}
if (engine.getConfig().isVerbose()) {
System.err.println("Loading module: " + moduleName);
}
+ Object obj = getScript(cx);
+ if (!(obj instanceof Script)) {
+ if (!(obj instanceof Scriptable)) {
+ throw Context.reportRuntimeError("Module must be an object");
+ }
+ Scriptable scriptable = (Scriptable) obj;
+ modules.put(resource, scriptable);
+ return scriptable;
+ }
+ Script script = (Script) obj;
+ ModuleScope module = new ModuleScope(moduleName, resource, prototype);
+ // put module scope in map right away to make circular dependencies work
+ modules.put(resource, module);
// warnings are disabled in shell - enable warnings for module loading
ErrorReporter er = cx.getErrorReporter();
ToolErrorReporter reporter = er instanceof ToolErrorReporter ?
@@ -367,15 +371,15 @@ public EvaluatorException runtimeError(String message, String sourceName,
}
}
- static class ScriptReference extends SoftReference<Script> {
+ static class ScriptReference extends SoftReference<Object> {
Resource source;
long checksum;
List<ScriptError> errors;
Exception exception;
- ScriptReference(Resource source, Script script,
- ReloadableScript rescript, ReferenceQueue<Script> queue)
+ ScriptReference(Resource source, Object script,
+ ReloadableScript rescript, ReferenceQueue<Object> queue)
throws IOException {
super(script, queue);
this.source = source;
@@ -387,17 +391,17 @@ public EvaluatorException runtimeError(String message, String sourceName,
static class ScriptCache {
ConcurrentHashMap<Resource, ScriptReference> map;
- ReferenceQueue<Script> queue;
+ ReferenceQueue<Object> queue;
ScriptCache() {
map = new ConcurrentHashMap<Resource, ScriptReference>();
- queue = new ReferenceQueue<Script>();
+ queue = new ReferenceQueue<Object>();
}
- ScriptReference createReference(Resource source, Script script,
- ReloadableScript rescript)
+ ScriptReference createReference(Resource source, Object script,
+ ReloadableScript rlscript)
throws IOException {
- return new ScriptReference(source, script, rescript, queue);
+ return new ScriptReference(source, script, rlscript, queue);
}
ScriptReference get(Resource source) {
View
243 src/org/ringojs/engine/Require.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2010-2012 Hannes Wallnoefer <hannes@helma.at>
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.ringojs.engine;
+
+import org.mozilla.javascript.BaseFunction;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.NativeObject;
+import org.mozilla.javascript.ScriptRuntime;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.Undefined;
+import org.mozilla.javascript.WrappedException;
+import org.mozilla.javascript.Wrapper;
+import org.ringojs.repository.FileRepository;
+import org.ringojs.repository.Repository;
+import org.ringojs.repository.ZipRepository;
+import org.ringojs.util.ScriptUtils;
+import org.ringojs.util.StringUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Require extends BaseFunction {
+
+ RhinoEngine engine;
+ RingoGlobal scope;
+
+ static Method getMain;
+ static {
+ try {
+ getMain = Require.class.getDeclaredMethod("getMain", ScriptableObject.class);
+ } catch (NoSuchMethodException nsm) {
+ throw new NoSuchMethodError("getMain");
+ }
+ }
+
+ public Require(RhinoEngine engine, RingoGlobal scope) {
+ super(scope, ScriptableObject.getClassPrototype(scope, "Function"));
+ this.engine = engine;
+ this.scope = scope;
+ // Set up require.main property as setter - note that accessing this will cause
+ // the main module to be loaded, which may result in problems if engine setup
+ // isn't finished yet. Alas, the CommonJS Modules spec requires us to do this.
+ int attr = DONTENUM | PERMANENT | READONLY;
+ defineProperty("main", this, getMain, null, attr);
+ defineProperty("paths", new ModulePath(), attr);
+ defineProperty("extensions", new Extensions(), attr);
+ }
+
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+ if (args.length != 1 || !(args[0] instanceof CharSequence)) {
+ throw Context.reportRuntimeError(
+ "require() expects a single string argument");
+ }
+ ModuleScope moduleScope = thisObj instanceof ModuleScope ?
+ (ModuleScope) thisObj : null;
+ try {
+ RingoWorker worker = engine.getCurrentWorker();
+ String arg = args[0].toString();
+ Scriptable module = worker.loadModuleInternal(cx, arg, moduleScope);
+ return module instanceof ModuleScope ?
+ ((ModuleScope)module).getExports() : module;
+ } catch (FileNotFoundException notFound) {
+ throw Context.reportRuntimeError("Cannot find module '" + args[0] + "'");
+ } catch (IOException iox) {
+ throw Context.reportRuntimeError("Error loading module '" + args[0] + "': " + iox);
+ }
+ }
+
+ @Override
+ public int getArity() {
+ return 1;
+ }
+
+ public Object getMain(ScriptableObject thisObj) {
+ try {
+ ModuleScope main = engine.getMainModuleScope();
+ return main != null ? main.getModuleObject() : Undefined.instance;
+ } catch (Exception x) {
+ return Undefined.instance;
+ }
+ }
+
+ class Extensions extends NativeObject {
+
+ public Extensions() {
+ setParentScope(scope);
+ setPrototype(ScriptableObject.getClassPrototype(scope, "Object"));
+ }
+
+ @Override
+ public void put(String name, Scriptable start, Object value) {
+ engine.addModuleLoader(name, value);
+ super.put(name, start, value);
+ }
+
+ @Override
+ public void delete(String name) {
+ engine.removeModuleLoader(name);
+ super.delete(name);
+ }
+ }
+
+ class ModulePath extends ScriptableObject {
+
+ List<Repository> paths;
+ Map<String, SoftReference<Repository>> cache =
+ new HashMap<String, SoftReference<Repository>>();
+
+ public ModulePath() {
+ this.paths = engine.getRepositories();
+ for (Repository repo : paths) {
+ cache.put(repo.getPath(), new SoftReference<Repository>(repo));
+ }
+ setParentScope(scope);
+ setPrototype(ScriptableObject.getClassPrototype(scope, "Array"));
+ defineProperty("length", Integer.valueOf(this.paths.size()), DONTENUM);
+ }
+
+ @Override
+ public String getClassName() {
+ return "ModulePath";
+ }
+
+ @Override
+ public void put(int index, Scriptable start, Object value) {
+ if (paths != null) {
+ Repository repo;
+ try {
+ repo = toRepository(value);
+ } catch (IOException iox) {
+ throw new WrappedException(iox);
+ }
+ while (index >= paths.size()) {
+ paths.add(null);
+ }
+ paths.set(index, repo);
+ defineProperty("length", Integer.valueOf(paths.size()), DONTENUM);
+ } else {
+ super.put(index, start, value);
+ }
+ }
+
+ @Override
+ public void put(String id, Scriptable start, Object value) {
+ if (paths != null && "length".equals(id)) {
+ int length = ScriptUtils.toInt(value, -1);
+ if (length < 0) {
+ throw Context.reportRuntimeError("Invalid length value: " + value);
+ }
+ while (length > paths.size()) {
+ paths.add(null);
+ }
+ while (length < paths.size()) {
+ paths.remove(length);
+ }
+ }
+ super.put(id, start, value);
+ }
+
+ @Override
+ public Object get(int index, Scriptable start) {
+ if (paths != null) {
+ Repository value = index < paths.size() ? paths.get(index) : null;
+ return value == null ? NOT_FOUND : value.getPath();
+ }
+ return super.get(index, start);
+ }
+
+ @Override
+ public boolean has(int index, Scriptable start) {
+ if (paths != null) {
+ return index >= 0 && index < paths.size();
+ }
+ return super.has(index, start);
+ }
+
+ @Override
+ public Object[] getIds() {
+ if (paths != null) {
+ Object[] ids = new Object[paths.size()];
+ for (int i = 0; i < ids.length; i++) {
+ ids[i] = Integer.valueOf(i);
+ }
+ return ids;
+ }
+ return super.getIds();
+ }
+
+ private Repository toRepository(Object value) throws IOException {
+ if (value instanceof Wrapper) {
+ value = ((Wrapper) value).unwrap();
+ }
+ Repository repo = null;
+ if (value instanceof Repository) {
+ repo = (Repository) value;
+ // repositories in module search path must be configured as root repository
+ repo.setRoot();
+ cache.put(repo.getPath(), new SoftReference<Repository>(repo));
+ } else if (value != null && value != Undefined.instance) {
+ String str = ScriptRuntime.toString(value);
+ SoftReference<Repository> ref = cache.get(str);
+ repo = ref == null ? null : ref.get();
+ if (repo == null) {
+ File file = new File(str);
+ if (file.isFile() && StringUtils.isZipOrJarFile(str)) {
+ repo = new ZipRepository(str);
+ } else {
+ repo = new FileRepository(str);
+ }
+ cache.put(repo.getPath(), new SoftReference<Repository>(repo));
+ }
+ } else {
+ throw Context.reportRuntimeError("Invalid module path item: " + value);
+ }
+ return repo;
+ }
+ }
+
+}
+
+
View
96 src/org/ringojs/engine/RhinoEngine.java
@@ -50,6 +50,7 @@
private AppClassLoader loader = new AppClassLoader();
private WrapFactory wrapFactory;
private Set<Class> hostClasses;
+ private ModuleLoader[] loaders;
private RingoContextFactory contextFactory = null;
private ModuleScope mainScope = null;
@@ -82,6 +83,10 @@ public RhinoEngine(RingoConfiguration config, Map<String, Object> globals)
contextFactory = new RingoContextFactory(this, config);
repositories = config.getRepositories();
this.wrapFactory = config.getWrapFactory();
+
+ loaders = new ModuleLoader[] {
+ new JsModuleLoader(), new JsonModuleLoader(), new ClassModuleLoader()
+ };
RingoDebugger debugger = null;
if (config.getDebug()) {
@@ -164,7 +169,7 @@ public Object runScript(Object scriptResource, String... scriptArgs)
if (scriptResource instanceof Resource) {
resource = (Resource) scriptResource;
} else if (scriptResource instanceof String) {
- resource = findResource((String) scriptResource, null);
+ resource = findResource((String) scriptResource, null, null);
} else {
throw new IOException("Unsupported script resource: " + scriptResource);
}
@@ -395,11 +400,11 @@ public ReloadableScript getScript(String moduleName)
public ReloadableScript getScript(String moduleName, Repository localPath)
throws JavaScriptException, IOException {
ReloadableScript script;
- Resource source = findResource(moduleName + ".js", localPath);
+ Resource source = findResource(moduleName, loaders, localPath);
if (!source.exists()) {
source = loadPackage(moduleName, localPath);
if (!source.exists()) {
- source = findResource(moduleName, localPath);
+ source = findResource(moduleName, null, localPath);
}
}
Context cx = Context.getCurrentContext();
@@ -447,7 +452,7 @@ protected Resource loadPackage(String moduleName, Repository localPath)
remainingName = moduleName.substring(slash + 1);
}
- Resource json = findResource(packageName + "/package.json", localPath);
+ Resource json = findResource(packageName + "/package.json", null, localPath);
if (json != null && json.exists()) {
JsonParser parser = new JsonParser(
@@ -495,7 +500,7 @@ protected Resource loadPackage(String moduleName, Repository localPath)
}
} while (slash != -1);
- return findResource(moduleName + "/index.js", localPath);
+ return findResource(moduleName + "/index", loaders, localPath);
}
@@ -508,7 +513,7 @@ protected Resource loadPackage(String moduleName, Repository localPath)
* @return the loaded module's scope
* @throws IOException indicates that in input/output related error occurred
*/
- public ModuleScope loadModule(Context cx, String moduleName,
+ public Scriptable loadModule(Context cx, String moduleName,
Scriptable loadingScope)
throws IOException {
return mainWorker.loadModule(cx, moduleName, loadingScope);
@@ -688,7 +693,7 @@ public Repository getParentRepository(Scriptable scope) {
* @throws IOException if an I/O error occurred
*/
public Trackable resolve(String path, Repository localRoot) throws IOException {
- Trackable t = findResource(path, localRoot);
+ Trackable t = findResource(path, null, localRoot);
if (t == null || !t.exists()) {
t = findRepository(path, localRoot);
}
@@ -698,24 +703,40 @@ public Trackable resolve(String path, Repository localRoot) throws IOException {
/**
* Search for a resource in a local path, or the main repository path.
* @param path the resource name
+ * @param loaders optional list of module loaders
* @param localRoot a repository to look first
* @return the resource
* @throws IOException if an I/O error occurred
*/
- public Resource findResource(String path, Repository localRoot)
+ public Resource findResource(String path, ModuleLoader[] loaders,
+ Repository localRoot)
throws IOException {
- // Note: as an extension to the securable modules API
+ // Note: as an extension to the CommonJS modules API
// we allow absolute module paths for resources
File file = new File(path);
if (file.isAbsolute()) {
- Resource res = new FileResource(file);
+ Resource res;
+ outer: if (loaders != null) {
+ // loaders must contain at least one loader
+ assert loaders.length > 0 && loaders[0] != null;
+ for (ModuleLoader loader: loaders) {
+ res = new FileResource(path + loader.getExtension());
+ if (res.exists()) {
+ break outer;
+ }
+ }
+ res = new FileResource(path + loaders[0].getExtension());
+ } else {
+ res = new FileResource(file);
+ }
res.setAbsolute(true);
return res;
- } else if (localRoot != null
- && (path.startsWith("./") || path.startsWith("../"))) {
- return findResource(localRoot.getRelativePath() + path, null);
+ } else if (localRoot != null &&
+ (path.startsWith("./") || path.startsWith("../"))) {
+ String newpath = localRoot.getRelativePath() + path;
+ return findResource(newpath, loaders, null);
} else {
- return config.getResource(normalizePath(path));
+ return config.getResource(normalizePath(path), loaders);
}
}
@@ -742,7 +763,54 @@ public Repository findRepository(String path, Repository localPath) throws IOExc
}
return config.getRepository(normalizePath(path));
}
+
+ public ModuleLoader getModuleLoader(Resource resource) {
+ String name = resource.getName();
+ for (ModuleLoader loader : loaders) {
+ if (name.endsWith(loader.getExtension())) {
+ return loader;
+ }
+ }
+ return loaders[0];
+ }
+ public synchronized void addModuleLoader(String extension, Object value) {
+ if (value == null || value == Undefined.instance) {
+ removeModuleLoader(extension);
+ } else if (!(value instanceof Function)) {
+ throw Context.reportRuntimeError("Module loader must be a function");
+ }
+ Function function = (Function) value;
+ int length = loaders.length;
+ for (int i = 0; i < length; i++) {
+ if (extension.equals(loaders[i].getExtension())) {
+ // replace existing loader
+ loaders[i] = new ScriptedModuleLoader(extension, function);
+ return;
+ }
+ }
+ ModuleLoader[] newLoaders = new ModuleLoader[length + 1];
+ System.arraycopy(loaders, 0, newLoaders, 0, length);
+ newLoaders[length] = new ScriptedModuleLoader(extension, function);
+ loaders = newLoaders;
+ }
+
+ public synchronized void removeModuleLoader(String extension) {
+ int length = loaders.length;
+ for (int i = 0; i < length; i++) {
+ if (loaders[i] instanceof ScriptedModuleLoader &&
+ extension.equals(loaders[i].getExtension())) {
+ ModuleLoader[] newLoaders = new ModuleLoader[length - 1];
+ if (i > 0)
+ System.arraycopy(loaders, 0, newLoaders, 0, i);
+ if (i < length - 1)
+ System.arraycopy(loaders, i + 1, newLoaders, i, length - i - 1);
+ loaders = newLoaders;
+ return;
+ }
+ }
+ }
+
public static String normalizePath(String path) {
if (!path.contains("./")) {
return path;
View
42 src/org/ringojs/engine/RingoConfiguration.java
@@ -387,16 +387,47 @@ public void setParentProtoProperties(boolean flag) {
* @throws IOException an I/O error occurred
*/
public Resource getResource(String path) throws IOException {
+ return getResource(path, null);
+ }
+ /**
+ * Get a resource from our script repository
+ * @param path the resource path
+ * @param loaders optional list of module loaders
+ * @return the resource
+ * @throws IOException an I/O error occurred
+ */
+ public Resource getResource(String path, ModuleLoader[] loaders)
+ throws IOException {
Resource res;
for (Repository repo: repositories) {
- res = repo.getResource(path);
+ if (loaders != null) {
+ assert loaders.length > 0 && loaders[0] != null;
+ for (ModuleLoader loader : loaders) {
+ res = repo.getResource(path + loader.getExtension());
+ if (res != null && res.exists()) {
+ return res;
+ }
+ }
+ } else {
+ res = repo.getResource(path);
+ if (res != null && res.exists()) {
+ return res;
+ }
+ }
+ }
+ if (loaders == null) {
+ res = resourceFromClasspath(path, null);
if (res != null && res.exists()) {
return res;
}
- }
- res = resourceFromClasspath(path, null);
- if (res != null && res.exists()) {
- return res;
+ } else {
+ for (ModuleLoader loader : loaders) {
+ String p = path + loader.getExtension();
+ res = resourceFromClasspath(p, null);
+ if (res != null && res.exists()) {
+ return res;
+ }
+ }
}
return new NotFound(path);
}
@@ -546,7 +577,6 @@ private static Resource resourceFromClasspath(String path, ClassLoader loader)
}
}
return null;
-
}
private static Repository repositoryFromClasspath(String path, ClassLoader loader)
View
45 src/org/ringojs/engine/RingoGlobal.java
@@ -26,7 +26,6 @@
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
-import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.WrappedException;
import org.mozilla.javascript.Wrapper;
import org.mozilla.javascript.tools.shell.Environment;
@@ -36,11 +35,9 @@
import org.ringojs.security.RingoSecurityManager;
import org.ringojs.util.ScriptUtils;
import org.mozilla.javascript.tools.shell.Global;
-import org.ringojs.wrappers.ModulePath;
import org.ringojs.repository.Resource;
import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.io.IOException;
@@ -90,7 +87,6 @@ public void quit(Context cx, int exitCode) {
ScriptableObject.DONTENUM);
names = new String[] {
"defineClass",
- "require",
"getResource",
"getRepository",
"addToClasspath",
@@ -102,15 +98,7 @@ public void quit(Context cx, int exitCode) {
};
defineFunctionProperties(names, RingoGlobal.class,
ScriptableObject.DONTENUM);
-
- ScriptableObject require = (ScriptableObject) get("require", this);
- // Set up require.main property as setter - note that accessing this will cause
- // the main module to be loaded, which may result in problems if engine setup
- // isn't finished yet. Alas, the Securable Modules spec requires us to do this.
- require.defineProperty("main", RingoGlobal.class,
- DONTENUM | PERMANENT | READONLY);
- require.defineProperty("paths", new ModulePath(engine.getRepositories(), this),
- DONTENUM | PERMANENT | READONLY);
+ defineProperty("require", new Require(engine, this), DONTENUM);
defineProperty("arguments", cx.newArray(this, engine.getArguments()), DONTENUM);
// Set up "environment" in the global scope to provide access to the
// System environment variables. http://github.com/ringo/ringojs/issues/#issue/88
@@ -136,25 +124,6 @@ public static void defineClass(final Context cx, Scriptable thisObj,
engine.defineHostClass((Class) arg);
}
- public static Object require(final Context cx, Scriptable thisObj,
- Object[] args, Function funObj) {
- if (args.length != 1 || !(args[0] instanceof CharSequence)) {
- throw Context.reportRuntimeError(
- "require() expects a single string argument");
- }
- RhinoEngine engine = ((RingoGlobal)funObj.getParentScope()).engine;
- ModuleScope moduleScope = thisObj instanceof ModuleScope ?
- (ModuleScope) thisObj : null;
- try {
- RingoWorker worker = engine.getCurrentWorker();
- String arg = args[0].toString();
- ModuleScope module = worker.loadModuleInternal(cx, arg, moduleScope);
- return module.getExports();
- } catch (IOException iox) {
- throw Context.reportRuntimeError("Cannot find module '" + args[0] + "'");
- }
- }
-
public static Object getResource(final Context cx, Scriptable thisObj,
Object[] args, Function funObj) {
if (args.length != 1 || !(args[0] instanceof String)) {
@@ -163,7 +132,7 @@ public static Object getResource(final Context cx, Scriptable thisObj,
}
RhinoEngine engine = ((RingoGlobal) funObj.getParentScope()).engine;
try {
- Resource res = engine.findResource((String) args[0],
+ Resource res = engine.findResource((String) args[0], null,
engine.getParentRepository(thisObj));
return cx.getWrapFactory().wrapAsJavaObject(cx, engine.getScope(),
res, null);
@@ -291,16 +260,6 @@ public void exitAsyncTask() {
engine.exitAsyncTask();
}
- public static Object getMain(Scriptable thisObj) {
- try {
- RhinoEngine engine = ((RingoGlobal) thisObj.getParentScope()).engine;
- ModuleScope main = engine.getMainModuleScope();
- return main != null ? main.getModuleObject() : Undefined.instance;
- } catch (Exception x) {
- return Undefined.instance;
- }
- }
-
static ExecutorService getThreadPool() {
if (threadPool != null) {
return threadPool;
View
18 src/org/ringojs/engine/RingoWorker.java
@@ -32,7 +32,7 @@
private ReloadableScript currentScript;
private List<ScriptError> errors;
- private Map<Resource, ModuleScope> modules, checkedModules;
+ private Map<Resource, Scriptable> modules, checkedModules;
private boolean reload;
private static AtomicInteger workerId = new AtomicInteger(1);
@@ -44,9 +44,9 @@
*/
public RingoWorker(RhinoEngine engine) {
this.engine = engine;
- modules = new HashMap<Resource, ModuleScope>();
+ modules = new HashMap<Resource, Scriptable>();
reload = engine.getConfig().isReloading();
- checkedModules = reload ? new HashMap<Resource, ModuleScope>() : modules;
+ checkedModules = reload ? new HashMap<Resource, Scriptable>() : modules;
id = workerId.getAndIncrement();
}
@@ -244,7 +244,7 @@ public void cancel(Future<?> future) {
* @return the loaded module's scope
* @throws IOException indicates that in input/output related error occurred
*/
- public ModuleScope loadModule(Context cx, String moduleName,
+ public Scriptable loadModule(Context cx, String moduleName,
Scriptable loadingScope) throws IOException {
engine.setCurrentWorker(this);
try {
@@ -263,7 +263,7 @@ public ModuleScope loadModule(Context cx, String moduleName,
* @return the loaded module's scope
* @throws java.io.IOException indicates that in input/output related error occurred
*/
- protected ModuleScope loadModuleInternal(Context cx, String moduleName,
+ protected Scriptable loadModuleInternal(Context cx, String moduleName,
Scriptable loadingScope)
throws IOException {
Repository local = engine.getParentRepository(loadingScope);
@@ -275,7 +275,7 @@ protected ModuleScope loadModuleInternal(Context cx, String moduleName,
}
// check if module has been loaded before
- ModuleScope module = modules.get(script.resource);
+ Scriptable module = modules.get(script.resource);
ReloadableScript parent = currentScript;
runlock.lock();
try {
@@ -313,9 +313,7 @@ public Object evaluateScript(Context cx,
try {
currentScript = script;
result = script.evaluate(scope, cx, checkedModules);
- if (scope instanceof ModuleScope) {
- modules.put(script.resource, (ModuleScope)scope);
- }
+ modules.put(script.resource, scope);
} finally {
currentScript = parent;
engine.setCurrentWorker(null);
@@ -342,7 +340,7 @@ public boolean isReloading() {
*/
public void setReloading(boolean reload) {
if (reload != this.reload) {
- checkedModules = reload ? new HashMap<Resource, ModuleScope>() : modules;
+ checkedModules = reload ? new HashMap<Resource, Scriptable>() : modules;
}
this.reload = reload;
}
View
6 src/org/ringojs/tools/RingoRunner.java
@@ -18,6 +18,7 @@
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.WrappedException;
+import org.ringojs.engine.ModuleScope;
import org.ringojs.engine.RhinoEngine;
import org.ringojs.engine.RingoConfiguration;
import org.ringojs.engine.RingoWrapFactory;
@@ -182,7 +183,10 @@ public void init(String[] args) {
}
cx = engine.getContextFactory().enterContext(null);
String moduleId = config.getMainResource().getModuleName();
- module = engine.loadModule(cx, moduleId, null).getExports();
+ module = engine.loadModule(cx, moduleId, null);
+ if (module instanceof ModuleScope) {
+ module = ((ModuleScope)module).getExports();
+ }
engine.invoke(module, "init");
} catch (NoSuchMethodException nsm) {
// daemon life-cycle method not implemented
View
131 src/org/ringojs/wrappers/ModulePath.java
@@ -1,131 +0,0 @@
-package org.ringojs.wrappers;
-
-import org.mozilla.javascript.*;
-import org.ringojs.repository.Repository;
-import org.ringojs.repository.ZipRepository;
-import org.ringojs.repository.FileRepository;
-import org.ringojs.util.ScriptUtils;
-import org.ringojs.util.StringUtils;
-
-import java.io.File;
-import java.lang.ref.SoftReference;
-import java.util.HashMap;
-import java.util.List;
-import java.io.IOException;
-import java.util.Map;
-
-public class ModulePath extends ScriptableObject {
-
- List<Repository> paths;
- Map<String, SoftReference<Repository>> cache =
- new HashMap<String, SoftReference<Repository>>();
-
- public ModulePath(List<Repository> paths, Scriptable scope) {
- this.paths = paths;
- for (Repository repo : paths) {
- cache.put(repo.getPath(), new SoftReference<Repository>(repo));
- }
- setParentScope(scope);
- setPrototype(ScriptableObject.getClassPrototype(scope, "Array"));
- defineProperty("length", Integer.valueOf(this.paths.size()), DONTENUM);
- }
-
- @Override
- public String getClassName() {
- return "ModulePath";
- }
-
- @Override
- public void put(int index, Scriptable start, Object value) {
- if (paths != null) {
- Repository repo;
- try {
- repo = toRepository(value);
- } catch (IOException iox) {
- throw new WrappedException(iox);
- }
- while (index >= paths.size()) {
- paths.add(null);
- }
- paths.set(index, repo);
- defineProperty("length", Integer.valueOf(paths.size()), DONTENUM);
- } else {
- super.put(index, start, value);
- }
- }
-
- @Override
- public void put(String id, Scriptable start, Object value) {
- if (paths != null && "length".equals(id)) {
- int length = ScriptUtils.toInt(value, -1);
- if (length < 0) {
- throw Context.reportRuntimeError("Invalid length value: " + value);
- }
- while (length > paths.size()) {
- paths.add(null);
- }
- while (length < paths.size()) {
- paths.remove(length);
- }
- }
- super.put(id, start, value);
- }
-
- @Override
- public Object get(int index, Scriptable start) {
- if (paths != null) {
- Repository value = index < paths.size() ? paths.get(index) : null;
- return value == null ? NOT_FOUND : value.getPath();
- }
- return super.get(index, start);
- }
-
- @Override
- public boolean has(int index, Scriptable start) {
- if (paths != null) {
- return index >= 0 && index < paths.size();
- }
- return super.has(index, start);
- }
-
- @Override
- public Object[] getIds() {
- if (paths != null) {
- Object[] ids = new Object[paths.size()];
- for (int i = 0; i < ids.length; i++) {
- ids[i] = Integer.valueOf(i);
- }
- return ids;
- }
- return super.getIds();
- }
-
- private Repository toRepository(Object value) throws IOException {
- if (value instanceof Wrapper) {
- value = ((Wrapper) value).unwrap();
- }
- Repository repo = null;
- if (value instanceof Repository) {
- repo = (Repository) value;
- // repositories in module search path must be configured as root repository
- repo.setRoot();
- cache.put(repo.getPath(), new SoftReference<Repository>(repo));
- } else if (value != null && value != Undefined.instance) {
- String str = ScriptRuntime.toString(value);
- SoftReference<Repository> ref = cache.get(str);
- repo = ref == null ? null : ref.get();
- if (repo == null) {
- File file = new File(str);
- if (file.isFile() && StringUtils.isZipOrJarFile(str)) {
- repo = new ZipRepository(str);
- } else {
- repo = new FileRepository(str);
- }
- cache.put(repo.getPath(), new SoftReference<Repository>(repo));
- }
- } else {
- throw Context.reportRuntimeError("Invalid module path item: " + value);
- }
- return repo;
- }
-}
Please sign in to comment.
Something went wrong with that request. Please try again.