Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.mozilla.javascript.commonjs.module.Require Maven / Gradle / Ivy
Go to download
Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically
embedded into Java applications to provide scripting to end users.
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript.commonjs.module;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.mozilla.javascript.BaseFunction;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
/**
* Implements the require() function as defined by Common JS modules .
*
* Thread safety
*
* You will ordinarily create one instance of require() for every top-level scope. This ordinarily
* means one instance per program execution, except if you use shared top-level scopes and
* installing most objects into them. Module loading is thread safe, so using a single require() in
* a shared top-level scope is also safe.
*
* Creation
*
* If you need to create many otherwise identical require() functions for different scopes, you
* might want to use {@link RequireBuilder} for convenience.
*
* Making it available
*
* In order to make the require() function available to your JavaScript program, you need to invoke
* either {@link #install(Scriptable)} or {@link #requireMain(Context, String)}.
*
* @author Attila Szegedi
* @version $Id: Require.java,v 1.4 2011/04/07 20:26:11 hannes%helma.at Exp $
*/
public class Require extends BaseFunction {
private static final long serialVersionUID = 1L;
private final ModuleScriptProvider moduleScriptProvider;
private final Scriptable nativeScope;
private final Scriptable paths;
private final boolean sandboxed;
private final Script preExec;
private final Script postExec;
private String mainModuleId = null;
private Scriptable mainExports;
// Modules that completed loading; visible to all threads
private final Map exportedModuleInterfaces = new ConcurrentHashMap<>();
private final Object loadLock = new Object();
// Modules currently being loaded on the thread. Used to resolve circular
// dependencies while loading.
private static final ThreadLocal> loadingModuleInterfaces =
new ThreadLocal<>();
/**
* Creates a new instance of the require() function. Upon constructing it, you will either want
* to install it in the global (or some other) scope using {@link #install(Scriptable)}, or
* alternatively, you can load the program's main module using {@link #requireMain(Context,
* String)} and then act on the main module's exports.
*
* @param cx the current context
* @param nativeScope a scope that provides the standard native JavaScript objects.
* @param moduleScriptProvider a provider for module scripts
* @param preExec an optional script that is executed in every module's scope before its module
* script is run.
* @param postExec an optional script that is executed in every module's scope after its module
* script is run.
* @param sandboxed if set to true, the require function will be sandboxed. This means that it
* doesn't have the "paths" property, and also that the modules it loads don't export the
* "module.uri" property.
*/
public Require(
Context cx,
Scriptable nativeScope,
ModuleScriptProvider moduleScriptProvider,
Script preExec,
Script postExec,
boolean sandboxed) {
this.moduleScriptProvider = moduleScriptProvider;
this.nativeScope = nativeScope;
this.sandboxed = sandboxed;
this.preExec = preExec;
this.postExec = postExec;
setPrototype(ScriptableObject.getFunctionPrototype(nativeScope));
if (!sandboxed) {
paths = cx.newArray(nativeScope, 0);
defineReadOnlyProperty(this, "paths", paths);
} else {
paths = null;
}
}
/**
* Calling this method establishes a module as being the main module of the program to which
* this require() instance belongs. The module will be loaded as if require()'d and its "module"
* property will be set as the "main" property of this require() instance. You have to call this
* method before the module has been loaded (that is, the call to this method must be the first
* to require the module and thus trigger its loading). Note that the main module will execute
* in its own scope and not in the global scope. Since all other modules see the global scope,
* executing the main module in the global scope would open it for tampering by other modules.
*
* @param cx the current context
* @param mainModuleId the ID of the main module
* @return the "exports" property of the main module
* @throws IllegalStateException if the main module is already loaded when required, or if this
* require() instance already has a different main module set.
*/
public Scriptable requireMain(Context cx, String mainModuleId) {
if (this.mainModuleId != null) {
if (!this.mainModuleId.equals(mainModuleId)) {
throw new IllegalStateException("Main module already set to " + this.mainModuleId);
}
return mainExports;
}
ModuleScript moduleScript;
try {
// try to get the module script to see if it is on the module path
moduleScript =
moduleScriptProvider.getModuleScript(cx, mainModuleId, null, null, paths);
} catch (RuntimeException x) {
throw x;
} catch (Exception x) {
throw new RuntimeException(x);
}
if (moduleScript != null) {
mainExports = getExportedModuleInterface(cx, mainModuleId, null, null, true);
} else if (!sandboxed) {
URI mainUri = null;
// try to resolve to an absolute URI or file path
try {
mainUri = new URI(mainModuleId);
} catch (URISyntaxException usx) {
// fall through
}
// if not an absolute uri resolve to a file path
if (mainUri == null || !mainUri.isAbsolute()) {
File file = new File(mainModuleId);
if (!file.isFile()) {
throw ScriptRuntime.throwError(
cx, nativeScope, "Module \"" + mainModuleId + "\" not found.");
}
mainUri = file.toURI();
}
mainExports = getExportedModuleInterface(cx, mainUri.toString(), mainUri, null, true);
}
this.mainModuleId = mainModuleId;
return mainExports;
}
/**
* Binds this instance of require() into the specified scope under the property name "require".
*
* @param scope the scope where the require() function is to be installed.
*/
public void install(Scriptable scope) {
ScriptableObject.putProperty(scope, "require", this);
}
@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (args == null || args.length < 1) {
throw ScriptRuntime.throwError(cx, scope, "require() needs one argument");
}
String id = (String) Context.jsToJava(args[0], String.class);
URI uri = null;
URI base = null;
if (id.startsWith("./") || id.startsWith("../")) {
if (!(thisObj instanceof ModuleScope)) {
throw ScriptRuntime.throwError(
cx,
scope,
"Can't resolve relative module ID \""
+ id
+ "\" when require() is used outside of a module");
}
ModuleScope moduleScope = (ModuleScope) thisObj;
base = moduleScope.getBase();
URI current = moduleScope.getUri();
uri = current.resolve(id);
if (base == null) {
// calling module is absolute, resolve to absolute URI
// (but without file extension)
id = uri.toString();
} else {
// try to convert to a relative URI rooted on base
id = base.relativize(current).resolve(id).toString();
if (id.charAt(0) == '.') {
// resulting URI is not contained in base,
// throw error or make absolute depending on sandbox flag.
if (sandboxed) {
throw ScriptRuntime.throwError(
cx, scope, "Module \"" + id + "\" is not contained in sandbox.");
}
id = uri.toString();
}
}
}
return getExportedModuleInterface(cx, id, uri, base, false);
}
@Override
public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
throw ScriptRuntime.throwError(cx, scope, "require() can not be invoked as a constructor");
}
private Scriptable getExportedModuleInterface(
Context cx, String id, URI uri, URI base, boolean isMain) {
// Check if the requested module is already completely loaded
Scriptable exports = exportedModuleInterfaces.get(id);
if (exports != null) {
if (isMain) {
throw new IllegalStateException("Attempt to set main module after it was loaded");
}
return exports;
}
// Check if it is currently being loaded on the current thread
// (supporting circular dependencies).
Map threadLoadingModules = loadingModuleInterfaces.get();
if (threadLoadingModules != null) {
exports = threadLoadingModules.get(id);
if (exports != null) {
return exports;
}
}
// The requested module is neither already loaded, nor is it being
// loaded on the current thread. End of fast path. We must synchronize
// now, as we have to guarantee that at most one thread can load
// modules at any one time. Otherwise, two threads could end up
// attempting to load two circularly dependent modules in opposite
// order, which would lead to either unacceptable non-determinism or
// deadlock, depending on whether we underprotected or overprotected it
// with locks.
synchronized (loadLock) {
// Recheck if it is already loaded - other thread might've
// completed loading it just as we entered the synchronized block.
exports = exportedModuleInterfaces.get(id);
if (exports != null) {
return exports;
}
// Nope, still not loaded; we're loading it then.
final ModuleScript moduleScript = getModule(cx, id, uri, base);
if (sandboxed && !moduleScript.isSandboxed()) {
throw ScriptRuntime.throwError(
cx, nativeScope, "Module \"" + id + "\" is not contained in sandbox.");
}
exports = cx.newObject(nativeScope);
// Are we the outermost locked invocation on this thread?
final boolean outermostLocked = threadLoadingModules == null;
if (outermostLocked) {
threadLoadingModules = new HashMap<>();
loadingModuleInterfaces.set(threadLoadingModules);
}
// Must make the module exports available immediately on the
// current thread, to satisfy the CommonJS Modules/1.1 requirement
// that "If there is a dependency cycle, the foreign module may not
// have finished executing at the time it is required by one of its
// transitive dependencies; in this case, the object returned by
// "require" must contain at least the exports that the foreign
// module has prepared before the call to require that led to the
// current module's execution."
threadLoadingModules.put(id, exports);
try {
// Support non-standard Node.js feature to allow modules to
// replace the exports object by setting module.exports.
Scriptable newExports = executeModuleScript(cx, id, exports, moduleScript, isMain);
if (exports != newExports) {
threadLoadingModules.put(id, newExports);
exports = newExports;
}
} catch (RuntimeException e) {
// Throw loaded module away if there was an exception
threadLoadingModules.remove(id);
throw e;
} finally {
if (outermostLocked) {
// Make loaded modules visible to other threads only after
// the topmost triggering load has completed. This strategy
// (compared to the one where we'd make each module
// globally available as soon as it loads) prevents other
// threads from observing a partially loaded circular
// dependency of a module that completed loading.
exportedModuleInterfaces.putAll(threadLoadingModules);
loadingModuleInterfaces.set(null);
}
}
}
return exports;
}
private Scriptable executeModuleScript(
Context cx, String id, Scriptable exports, ModuleScript moduleScript, boolean isMain) {
final ScriptableObject moduleObject = (ScriptableObject) cx.newObject(nativeScope);
URI uri = moduleScript.getUri();
URI base = moduleScript.getBase();
defineReadOnlyProperty(moduleObject, "id", id);
if (!sandboxed) {
defineReadOnlyProperty(moduleObject, "uri", uri.toString());
}
final Scriptable executionScope = new ModuleScope(nativeScope, uri, base);
// Set this so it can access the global JS environment objects.
// This means we're currently using the "MGN" approach (ModuleScript
// with Global Natives) as specified here:
//
executionScope.put("exports", executionScope, exports);
executionScope.put("module", executionScope, moduleObject);
moduleObject.put("exports", moduleObject, exports);
install(executionScope);
if (isMain) {
defineReadOnlyProperty(this, "main", moduleObject);
}
executeOptionalScript(preExec, cx, executionScope);
moduleScript.getScript().exec(cx, executionScope);
executeOptionalScript(postExec, cx, executionScope);
return ScriptRuntime.toObject(
cx, nativeScope, ScriptableObject.getProperty(moduleObject, "exports"));
}
private static void executeOptionalScript(
Script script, Context cx, Scriptable executionScope) {
if (script != null) {
script.exec(cx, executionScope);
}
}
private static void defineReadOnlyProperty(ScriptableObject obj, String name, Object value) {
ScriptableObject.putProperty(obj, name, value);
obj.setAttributes(name, ScriptableObject.READONLY | ScriptableObject.PERMANENT);
}
private ModuleScript getModule(Context cx, String id, URI uri, URI base) {
try {
final ModuleScript moduleScript =
moduleScriptProvider.getModuleScript(cx, id, uri, base, paths);
if (moduleScript == null) {
throw ScriptRuntime.throwError(cx, nativeScope, "Module \"" + id + "\" not found.");
}
return moduleScript;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw Context.throwAsScriptRuntimeEx(e);
}
}
@Override
public String getFunctionName() {
return "require";
}
@Override
public int getArity() {
return 1;
}
@Override
public int getLength() {
return 1;
}
}