Java tutorial
/** * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.tools.rhinodo.rhino; import org.apache.commons.io.FilenameUtils; import org.mozilla.javascript.*; import org.mozilla.javascript.commonjs.module.ModuleScope; import org.mozilla.javascript.commonjs.module.ModuleScriptProvider; import org.mozilla.javascript.commonjs.module.Require; import org.mule.tools.rhinodo.api.NativeModule; import org.mule.tools.rhinodo.impl.ExitCallbackExecutor; import org.mule.tools.rhinodo.impl.NodeModuleImplBuilder; import org.mule.tools.rhinodo.node.child_process.ChildProcessNativeModule; import org.mule.tools.rhinodo.node.fs.FsNativeModule; import org.mule.tools.rhinodo.node.process.ProcessNativeModule; import org.mule.tools.rhinodo.node.timer.ClearInterval; import org.mule.tools.rhinodo.node.timer.ClearTimeout; import org.mule.tools.rhinodo.node.timer.SetInterval; import org.mule.tools.rhinodo.node.timer.SetTimeout; import org.mule.tools.rhinodo.node.vm.VmNativeModule; import sun.reflect.generics.reflectiveObjects.NotImplementedException; import java.io.File; import java.io.FilenameFilter; import java.net.URI; import java.util.*; public class NodeRequire extends Require { private HashMap<String, Scriptable> nativeModuleMap; private Script preExec; private Script postExec; private final ExitCallbackExecutor exitCallbackExecutor; private static void executeOptionalScript(Script script, Context cx, Scriptable executionScope) { if (script != null) { script.exec(cx, executionScope); } } private BaseFunction compile = new BaseFunction() { @Override public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { final ScriptableObject moduleObject = (ScriptableObject) cx.newObject(thisObj); defineReadOnlyProperty(moduleObject, "id", args[0]); String path = Context.toString(args[1]); File file = new File(path); URI uri = file.toURI(); URI base; if (file.exists() && file.isFile()) { base = file.getParentFile().toURI(); } else { base = file.toURI(); } final Scriptable executionScope = new ModuleScope(thisObj, uri, base); defineReadOnlyProperty(moduleObject, "uri", uri.toString()); Scriptable exports = cx.newObject(scope); executionScope.put("exports", executionScope, exports); executionScope.put("module", executionScope, moduleObject); moduleObject.put("exports", moduleObject, exports); install(executionScope); executeOptionalScript(preExec, cx, executionScope); cx.compileString(Context.toString(args[0]), path, 0, null).exec(cx, executionScope); executeOptionalScript(postExec, cx, executionScope); return ScriptRuntime.toObject(scope, ScriptableObject.getProperty(moduleObject, "exports")); } }; private static void defineReadOnlyProperty(ScriptableObject obj, String name, Object value) { ScriptableObject.putProperty(obj, name, value); obj.setAttributes(name, ScriptableObject.READONLY | ScriptableObject.PERMANENT); } public NodeRequire(final Queue<Function> asyncCallbacksQueue, final Scriptable env, final Context cx, final Scriptable globalScope, final ModuleScriptProvider moduleScriptProvider, final Script preExec, final Script postExec, boolean sandboxed, final ExitCallbackExecutor exitCallbackExecutor) { super(cx, globalScope, moduleScriptProvider, preExec, postExec, sandboxed); this.exitCallbackExecutor = exitCallbackExecutor; loadNativeModules(env, cx, globalScope, asyncCallbacksQueue); ScriptableObject.putProperty(this, "cache", new NativeObject()); //Adding extensions property NativeObject extensions = new NativeObject(); BaseFunction notImplemented = new BaseFunction() { @Override public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { if (ScriptableObject.getProperty(env, "RHINODO_IGNORE_NOT_IMPLEMENTED_EXTENSIONS") != null) { return cx.newObject(scope); } throw new NotImplementedException(); } }; ScriptableObject.putProperty(extensions, ".js", new BaseFunction() { @Override public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { return NodeRequire.this.callSuperWrapped(cx, scope, thisObj, new Object[] { args[1] }); } }); ScriptableObject.putProperty(extensions, ".json", notImplemented); ScriptableObject.putProperty(extensions, ".node", notImplemented); ScriptableObject.putProperty(this, "extensions", extensions); Scriptable process = nativeModuleMap.get("process"); ScriptableObject.putProperty(globalScope, "process", process); ScriptableObject.putProperty(globalScope, "clearTimeout", new ClearTimeout()); ScriptableObject.putProperty(globalScope, "setTimeout", new SetTimeout(asyncCallbacksQueue)); ScriptableObject.putProperty(globalScope, "clearInterval", new ClearInterval()); ScriptableObject.putProperty(globalScope, "setInterval", new SetInterval(asyncCallbacksQueue)); this.preExec = preExec; this.postExec = postExec; } private void loadNativeModules(Scriptable env, Context cx, Scriptable globalScope, Queue<Function> asyncCallbacksQueue) { NativeModule[] nativeModules = { new FsNativeModule(asyncCallbacksQueue), new ProcessNativeModule(env, asyncCallbacksQueue, exitCallbackExecutor), new ChildProcessNativeModule(asyncCallbacksQueue), new VmNativeModule(asyncCallbacksQueue) }; nativeModuleMap = new HashMap<String, Scriptable>(); for (NativeModule nativeModule : nativeModules) { nativeModuleMap.put(nativeModule.getId(), nativeModule.getModule(cx, globalScope)); } } public Object callSuperWrapped(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { try { ScriptableObject.putProperty(thisObj, "_compile", compile); return super.call(cx, scope, thisObj, args); } catch (JavaScriptException e) { if (thisObj instanceof ModuleScope) { String id = (String) Context.jsToJava(args[0], String.class); ModuleScope moduleScope = (ModuleScope) thisObj; URI base = moduleScope.getBase(); URI current = moduleScope.getUri(); URI uri = current.resolve(id + ".js/"); if (!id.startsWith("./") && !id.startsWith("../") && base != null && new File(uri.getPath()).exists()) { // try to convert to a relative URI rooted on base return super.call(cx, scope, thisObj, new Object[] { uri.getPath() }); } } throw e; } } public static class TryExtensionsResult { private final String extensionAsString; private final Function callback; private final File file; public TryExtensionsResult(String extensionAsString, Function value, File file) { this.extensionAsString = extensionAsString; this.callback = value; this.file = file; } public String getExtensionAsString() { return extensionAsString; } public Function getCallback() { return callback; } public File getFile() { return file; } } public TryExtensionsResult tryExtensions(String id, ModuleScope thisObj) { File cwdFile = getBasePathForModule(thisObj); NativeObject extensions = ScriptableObject.getTypedProperty(this, "extensions", NativeObject.class); Object[] propertyIds = ScriptableObject.getPropertyIds(extensions); if (!(id.startsWith("./") || id.startsWith("../") || id.startsWith("/"))) { return null; } for (Object extension : propertyIds) { String extensionAsString = (String) extension; File file; if (cwdFile != null) { file = new File(FilenameUtils.concat(cwdFile.getAbsolutePath(), id + extensionAsString)); /* Case of ./ only where we need to add index.js */ if (!file.exists()) { file = new File( FilenameUtils.concat(cwdFile.getAbsolutePath(), id + "index" + extensionAsString)); } } else { /* Case of /a/b/c.js */ file = new File(id + extensionAsString); } if (file.exists()) { Function value = ScriptableObject.getTypedProperty(extensions, extensionAsString, Function.class); return new TryExtensionsResult(extensionAsString, value, file); } } return null; } @Override public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { String id = Context.toString(args[0]); Scriptable moduleToLoad; if ((moduleToLoad = nativeModuleMap.get(id)) != null) { return moduleToLoad; } Scriptable extensions = ScriptableObject.getTypedProperty(this, "extensions", Scriptable.class); File file = new File(id); File packageJson = new File(id, "package.json"); /* Case when a path like a/b/c is required and a file a/b/c.js exists */ TryExtensionsResult extensionsResult; /* Absolute path */ if (id.startsWith("/") && (extensionsResult = tryExtensions(id, null)) != null) { return extensionsResult.getCallback().call(cx, scope, thisObj, new Object[] { thisObj, extensionsResult.getFile().getAbsolutePath() }); /* Relative Path */ } else if (thisObj instanceof ModuleScope && (extensionsResult = tryExtensions(id, (ModuleScope) thisObj)) != null) { return extensionsResult.getCallback().call(cx, scope, thisObj, new Object[] { thisObj, extensionsResult.getFile().getAbsolutePath() }); } else if (file.exists() && !file.isDirectory() && ScriptableObject.hasProperty(extensions, "." + FilenameUtils.getExtension(id))) { return ScriptableObject .getTypedProperty(extensions, "." + FilenameUtils.getExtension(id), Function.class) .call(cx, scope, thisObj, new Object[] { thisObj, id }); /* Case when a path like a/b/c and a file named a/b/c/package.json exists */ } else if (file.isDirectory() && packageJson.exists() && packageJson.isFile()) { Map<String, String> map = NodeModuleImplBuilder.getPackageJSONMap(packageJson); String main; if (map != null && (main = map.get("main")) != null) { /* Fetch entry point */ File mainFile = new File(FilenameUtils.concat(file.getPath(), main)); return callSuperWrapped(cx, scope, thisObj, new Object[] { mainFile.getAbsolutePath() }); } } else if (thisObj instanceof ModuleScope) { File cwdFile = getBasePathForModule((ModuleScope) thisObj); cwdFile = getModuleRootDirectory(cwdFile); if (cwdFile == null) { return callSuperWrapped(cx, scope, thisObj, args); } File modulePath = new File(new File(cwdFile, "node_modules"), id); File modulePackageJSON = new File(modulePath, "package.json"); if (modulePackageJSON.exists()) { Map<String, String> map = NodeModuleImplBuilder.getPackageJSONMap(modulePackageJSON); String main = map.get("main"); if (main == null) { main = "index.js"; } /* Fetch entry point */ File mainFile = new File(FilenameUtils.concat(modulePath.getPath(), main)); if (!mainFile.exists()) { mainFile = new File(FilenameUtils.concat(modulePath.getPath(), main + ".js")); } return callSuperWrapped(cx, scope, thisObj, new Object[] { mainFile.getAbsolutePath() }); } cwdFile = getModuleRootDirectory(cwdFile.getParentFile()); if (cwdFile == null) { return callSuperWrapped(cx, scope, thisObj, args); } modulePath = new File(new File(cwdFile, "node_modules"), id); modulePackageJSON = new File(modulePath, "package.json"); if (modulePackageJSON.exists()) { Map<String, String> map = NodeModuleImplBuilder.getPackageJSONMap(modulePackageJSON); /* Fetch entry point */ File mainFile = new File(FilenameUtils.concat(modulePath.getPath(), map.get("main"))); if (!mainFile.exists()) { mainFile = new File(FilenameUtils.concat(modulePath.getPath(), map.get("main") + ".js")); } return callSuperWrapped(cx, scope, thisObj, new Object[] { mainFile.getAbsolutePath() }); } } return callSuperWrapped(cx, scope, thisObj, args); } private File getModuleRootDirectory(File cwdFile) { String[] list; FilenameFilter filenameFilter = new FilenameFilter() { @Override public boolean accept(File file, String s) { return s.equals("package.json"); } }; list = cwdFile.list(filenameFilter); while (list.length != 1) { cwdFile = cwdFile.getParentFile(); if (cwdFile == null) { return null; } list = cwdFile.list(filenameFilter); } return cwdFile; } private File getBasePathForModule(ModuleScope thisObj) { ModuleScope moduleScope = thisObj; if (moduleScope == null) { return null; } URI base = moduleScope.getUri(); File cwdFile = new File(base.getPath()); if (!cwdFile.isDirectory()) { cwdFile = cwdFile.getParentFile(); } return cwdFile; } }