All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mozilla.javascript.tools.shell.Global 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.

The newest version!
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * 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.tools.shell;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextAction;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeConsole;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Synchronizer;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.Wrapper;
import org.mozilla.javascript.commonjs.module.Require;
import org.mozilla.javascript.commonjs.module.RequireBuilder;
import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider;
import org.mozilla.javascript.commonjs.module.provider.UrlModuleSourceProvider;
import org.mozilla.javascript.serialize.ScriptableInputStream;
import org.mozilla.javascript.serialize.ScriptableOutputStream;
import org.mozilla.javascript.tools.ToolErrorReporter;

/**
 * This class provides for sharing functions across multiple threads. This is of particular interest
 * to server applications.
 *
 * @author Norris Boyd
 */
public class Global extends ImporterTopLevel {
    static final long serialVersionUID = 4029130780977538005L;

    NativeArray history;
    boolean attemptedJLineLoad;
    private ShellConsole console;
    private InputStream inStream;
    private PrintStream outStream;
    private PrintStream errStream;
    private boolean sealedStdLib = false;
    boolean initialized;
    private QuitAction quitAction;
    private String[] prompts = {"js> ", "  > "};
    private HashMap doctestCanonicalizations;

    public Global() {}

    public Global(Context cx) {
        init(cx);
    }

    public boolean isInitialized() {
        return initialized;
    }

    /** Set the action to call from quit(). */
    public void initQuitAction(QuitAction quitAction) {
        if (quitAction == null) throw new IllegalArgumentException("quitAction is null");
        if (this.quitAction != null) throw new IllegalArgumentException("The method is once-call.");

        this.quitAction = quitAction;
    }

    public void init(ContextFactory factory) {
        factory.call(
                cx -> {
                    init(cx);
                    return null;
                });
    }

    public void init(Context cx) {
        // Define some global functions particular to the shell. Note
        // that these functions are not part of ECMA.
        initStandardObjects(cx, sealedStdLib);
        NativeConsole.init(this, sealedStdLib, new ShellConsolePrinter());
        String[] names = {
            "defineClass",
            "deserialize",
            "doctest",
            "gc",
            "help",
            "load",
            "loadClass",
            "print",
            "quit",
            "readline",
            "readFile",
            "readUrl",
            "runCommand",
            "seal",
            "serialize",
            "spawn",
            "sync",
            "toint32",
            "version",
            "write"
        };
        defineFunctionProperties(names, Global.class, ScriptableObject.DONTENUM);

        // Set up "environment" in the global scope to provide access to the
        // System environment variables.
        Environment.defineClass(this);
        Environment environment = new Environment(this);
        defineProperty("environment", environment, ScriptableObject.DONTENUM);

        history = (NativeArray) cx.newArray(this, 0);
        defineProperty("history", history, ScriptableObject.DONTENUM);

        initialized = true;
    }

    public Require installRequire(Context cx, List modulePath, boolean sandboxed) {
        RequireBuilder rb = new RequireBuilder();
        rb.setSandboxed(sandboxed);
        List uris = new ArrayList();
        if (modulePath != null) {
            for (String path : modulePath) {
                try {
                    URI uri = new URI(path);
                    if (!uri.isAbsolute()) {
                        // call resolve("") to canonify the path
                        uri = new File(path).toURI().resolve("");
                    }
                    if (!uri.toString().endsWith("/")) {
                        // make sure URI always terminates with slash to
                        // avoid loading from unintended locations
                        uri = new URI(uri + "/");
                    }
                    uris.add(uri);
                } catch (URISyntaxException usx) {
                    throw new RuntimeException(usx);
                }
            }
        }
        rb.setModuleScriptProvider(
                new SoftCachingModuleScriptProvider(new UrlModuleSourceProvider(uris, null)));
        Require require = rb.createRequire(cx, this);
        require.install(this);
        return require;
    }

    /**
     * Print a help message.
     *
     * 

This method is defined as a JavaScript function. */ public static void help(Context cx, Scriptable thisObj, Object[] args, Function funObj) { PrintStream out = getInstance(funObj).getOut(); out.println(ToolErrorReporter.getMessage("msg.help")); } public static void gc(Context cx, Scriptable thisObj, Object[] args, Function funObj) { System.gc(); } /** * Print the string values of its arguments. * *

This method is defined as a JavaScript function. Note that its arguments are of the * "varargs" form, which allows it to handle an arbitrary number of arguments supplied to the * JavaScript function. */ public static Object print(Context cx, Scriptable thisObj, Object[] args, Function funObj) { return doPrint(args, funObj, true); } /** Print just as in "print," but without the trailing newline. */ public static Object write(Context cx, Scriptable thisObj, Object[] args, Function funObj) { return doPrint(args, funObj, false); } private static Object doPrint(Object[] args, Function funObj, boolean newline) { PrintStream out = getInstance(funObj).getOut(); for (int i = 0; i < args.length; i++) { if (i > 0) out.print(" "); // Convert the arbitrary JavaScript value into a string form. String s = Context.toString(args[i]); out.print(s); } if (newline) { out.println(); } return Context.getUndefinedValue(); } /** * Call embedding-specific quit action passing its argument as int32 exit code. * *

This method is defined as a JavaScript function. */ public static void quit(Context cx, Scriptable thisObj, Object[] args, Function funObj) { Global global = getInstance(funObj); if (global.quitAction != null) { int exitCode = (args.length == 0 ? 0 : ScriptRuntime.toInt32(args[0])); global.quitAction.quit(cx, exitCode); } } /** * Get and set the language version. * *

This method is defined as a JavaScript function. */ public static double version(Context cx, Scriptable thisObj, Object[] args, Function funObj) { if (args.length > 0) { double d = Context.toNumber(args[0]); cx.setLanguageVersion((int) d); } return cx.getLanguageVersion(); } /** * Load and execute a set of JavaScript source files. * *

This method is defined as a JavaScript function. */ public static void load(Context cx, Scriptable thisObj, Object[] args, Function funObj) { for (Object arg : args) { String file = Context.toString(arg); try { Main.processFile(cx, thisObj, file); } catch (IOException ioex) { String msg = ToolErrorReporter.getMessage( "msg.couldnt.read.source", file, ioex.getMessage()); throw Context.reportRuntimeError(msg); } catch (VirtualMachineError ex) { // Treat StackOverflow and OutOfMemory as runtime errors ex.printStackTrace(); String msg = ToolErrorReporter.getMessage("msg.uncaughtJSException", ex.toString()); throw Context.reportRuntimeError(msg); } } } /** * Load a Java class that defines a JavaScript object using the conventions outlined in * ScriptableObject.defineClass. * *

This method is defined as a JavaScript function. * * @exception IllegalAccessException if access is not available to a reflected class member * @exception InstantiationException if unable to instantiate the named class * @exception InvocationTargetException if an exception is thrown during execution of methods of * the named class * @see org.mozilla.javascript.ScriptableObject#defineClass(Scriptable,Class) */ @SuppressWarnings({"unchecked"}) public static void defineClass(Context cx, Scriptable thisObj, Object[] args, Function funObj) throws IllegalAccessException, InstantiationException, InvocationTargetException { Class clazz = getClass(args); if (!Scriptable.class.isAssignableFrom(clazz)) { throw reportRuntimeError("msg.must.implement.Scriptable"); } ScriptableObject.defineClass(thisObj, (Class) clazz); } /** * Load and execute a script compiled to a class file. * *

This method is defined as a JavaScript function. When called as a JavaScript function, a * single argument is expected. This argument should be the name of a class that implements the * Script interface, as will any script compiled by jsc. * * @exception IllegalAccessException if access is not available to the class * @exception InstantiationException if unable to instantiate the named class */ public static void loadClass(Context cx, Scriptable thisObj, Object[] args, Function funObj) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class clazz = getClass(args); if (!Script.class.isAssignableFrom(clazz)) { throw reportRuntimeError("msg.must.implement.Script"); } Script script = (Script) clazz.getDeclaredConstructor().newInstance(); script.exec(cx, thisObj); } private static Class getClass(Object[] args) { if (args.length == 0) { throw reportRuntimeError("msg.expected.string.arg"); } Object arg0 = args[0]; if (arg0 instanceof Wrapper) { Object wrapped = ((Wrapper) arg0).unwrap(); if (wrapped instanceof Class) return (Class) wrapped; } String className = Context.toString(args[0]); try { return Class.forName(className); } catch (ClassNotFoundException cnfe) { throw reportRuntimeError("msg.class.not.found", className); } } public static void serialize(Context cx, Scriptable thisObj, Object[] args, Function funObj) throws IOException { if (args.length < 2) { throw Context.reportRuntimeError( "Expected an object to serialize and a filename to write " + "the serialization to"); } Object obj = args[0]; String filename = Context.toString(args[1]); FileOutputStream fos = new FileOutputStream(filename); Scriptable scope = ScriptableObject.getTopLevelScope(thisObj); try (ScriptableOutputStream out = new ScriptableOutputStream(fos, scope)) { out.writeObject(obj); } } public static Object deserialize(Context cx, Scriptable thisObj, Object[] args, Function funObj) throws IOException, ClassNotFoundException { if (args.length < 1) { throw Context.reportRuntimeError("Expected a filename to read the serialization from"); } String filename = Context.toString(args[0]); try (FileInputStream fis = new FileInputStream(filename)) { Scriptable scope = ScriptableObject.getTopLevelScope(thisObj); try (ObjectInputStream in = new ScriptableInputStream(fis, scope)) { Object deserialized = in.readObject(); return Context.toObject(deserialized, scope); } } } public String[] getPrompts(Context cx) { if (ScriptableObject.hasProperty(this, "prompts")) { Object promptsJS = ScriptableObject.getProperty(this, "prompts"); if (promptsJS instanceof Scriptable) { Scriptable s = (Scriptable) promptsJS; if (ScriptableObject.hasProperty(s, 0) && ScriptableObject.hasProperty(s, 1)) { Object elem0 = ScriptableObject.getProperty(s, 0); if (elem0 instanceof Function) { elem0 = ((Function) elem0).call(cx, this, s, new Object[0]); } prompts[0] = Context.toString(elem0); Object elem1 = ScriptableObject.getProperty(s, 1); if (elem1 instanceof Function) { elem1 = ((Function) elem1).call(cx, this, s, new Object[0]); } prompts[1] = Context.toString(elem1); } } } return prompts; } /** * Example: doctest("js> function f() {\n > return 3;\n > }\njs> f();\n3\n"); * returns 2 (since 2 tests were executed). */ public static Object doctest(Context cx, Scriptable thisObj, Object[] args, Function funObj) { if (args.length == 0) { return Boolean.FALSE; } String session = Context.toString(args[0]); Global global = getInstance(funObj); return global.runDoctest(cx, global, session, null, 0); } public int runDoctest( Context cx, Scriptable scope, String session, String sourceName, int lineNumber) { doctestCanonicalizations = new HashMap(); String[] lines = session.split("\r\n?|\n"); String prompt0 = this.prompts[0].trim(); String prompt1 = this.prompts[1].trim(); int testCount = 0; int i = 0; while (i < lines.length && !lines[i].trim().startsWith(prompt0)) { i++; // skip lines that don't look like shell sessions } while (i < lines.length) { StringBuilder inputString = new StringBuilder(lines[i].trim().substring(prompt0.length())); inputString.append('\n'); i++; while (i < lines.length && lines[i].trim().startsWith(prompt1)) { inputString.append(lines[i].trim().substring(prompt1.length())); inputString.append('\n'); i++; } StringBuilder expectedString = new StringBuilder(); while (i < lines.length && !lines[i].trim().startsWith(prompt0)) { expectedString.append(lines[i]).append('\n'); i++; } PrintStream savedOut = this.getOut(); PrintStream savedErr = this.getErr(); ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream err = new ByteArrayOutputStream(); this.setOut(new PrintStream(out)); this.setErr(new PrintStream(err)); String resultString = ""; ErrorReporter savedErrorReporter = cx.getErrorReporter(); cx.setErrorReporter(new ToolErrorReporter(false, this.getErr())); try { testCount++; String finalInputString = inputString.toString(); Object result = cx.evaluateString(scope, finalInputString, "doctest input", 1, null); if (result != Context.getUndefinedValue() && !(result instanceof Function && finalInputString.trim().startsWith("function"))) { resultString = Context.toString(result); } } catch (RhinoException e) { ToolErrorReporter.reportException(cx.getErrorReporter(), e); } finally { this.setOut(savedOut); this.setErr(savedErr); cx.setErrorReporter(savedErrorReporter); resultString += err.toString() + out.toString(); } if (!doctestOutputMatches(expectedString.toString(), resultString)) { String message = "doctest failure running:\n" + inputString + "expected: " + expectedString + "actual: " + resultString + "\n"; if (sourceName != null) { throw Context.reportRuntimeError( message, sourceName, lineNumber + i - 1, null, 0); } else { throw Context.reportRuntimeError(message); } } } return testCount; } /** * Compare actual result of doctest to expected, modulo some acceptable differences. Currently * just trims the strings before comparing, but should ignore differences in line numbers for * error messages for example. * * @param expected the expected string * @param actual the actual string * @return true iff actual matches expected modulo some acceptable differences */ private boolean doctestOutputMatches(String expected, String actual) { expected = expected.trim(); actual = actual.trim().replace("\r\n", "\n"); if (expected.equals(actual)) return true; for (Map.Entry entry : doctestCanonicalizations.entrySet()) { expected = expected.replace(entry.getKey(), entry.getValue()); } if (expected.equals(actual)) return true; // java.lang.Object.toString() prints out a unique hex number associated // with each object. This number changes from run to run, so we want to // ignore differences between these numbers in the output. We search for a // regexp that matches the hex number preceded by '@', then enter mappings into // "doctestCanonicalizations" so that we ensure that the mappings are // consistent within a session. Pattern p = Pattern.compile("@[0-9a-fA-F]+"); Matcher expectedMatcher = p.matcher(expected); Matcher actualMatcher = p.matcher(actual); for (; ; ) { if (!expectedMatcher.find()) return false; if (!actualMatcher.find()) return false; if (actualMatcher.start() != expectedMatcher.start()) return false; int start = expectedMatcher.start(); if (!expected.substring(0, start).equals(actual.substring(0, start))) return false; String expectedGroup = expectedMatcher.group(); String actualGroup = actualMatcher.group(); String mapping = doctestCanonicalizations.get(expectedGroup); if (mapping == null) { doctestCanonicalizations.put(expectedGroup, actualGroup); expected = expected.replace(expectedGroup, actualGroup); } else if (!actualGroup.equals(mapping)) { return false; // wrong object! } if (expected.equals(actual)) return true; } } /** * The spawn function runs a given function or script in a different thread. * *

js> function g() { a = 7; } js> a = 3; 3 js> spawn(g) Thread[Thread-1,5,main] * js> a 3 */ public static Object spawn(Context cx, Scriptable thisObj, Object[] args, Function funObj) { Scriptable scope = funObj.getParentScope(); Runner runner; if (args.length != 0 && args[0] instanceof Function) { Object[] newArgs = null; if (args.length > 1 && args[1] instanceof Scriptable) { newArgs = cx.getElements((Scriptable) args[1]); } if (newArgs == null) { newArgs = ScriptRuntime.emptyArgs; } runner = new Runner(scope, (Function) args[0], newArgs); } else if (args.length != 0 && args[0] instanceof Script) { runner = new Runner(scope, (Script) args[0]); } else { throw reportRuntimeError("msg.spawn.args"); } runner.factory = cx.getFactory(); Thread thread = new Thread(runner); thread.start(); return thread; } /** * The sync function creates a synchronized function (in the sense of a Java synchronized * method) from an existing function. The new function synchronizes on the the second argument * if it is defined, or otherwise the this object of its invocation. js> var o = * { f : sync(function(x) { print("entry"); Packages.java.lang.Thread.sleep(x*1000); * print("exit"); })}; js> spawn(function() {o.f(5);}); Thread[Thread-0,5,main] entry js> * spawn(function() {o.f(5);}); Thread[Thread-1,5,main] js> exit entry exit */ public static Object sync(Context cx, Scriptable thisObj, Object[] args, Function funObj) { if (args.length >= 1 && args.length <= 2 && args[0] instanceof Function) { Object syncObject = null; if (args.length == 2 && args[1] != Undefined.instance) { syncObject = args[1]; } return new Synchronizer((Function) args[0], syncObject); } else { throw reportRuntimeError("msg.sync.args"); } } /** * Execute the specified command with the given argument and options as a separate process and * return the exit status of the process. * *

Usage: * *

     * runCommand(command)
     * runCommand(command, arg1, ..., argN)
     * runCommand(command, arg1, ..., argN, options)
     * 
* * All except the last arguments to runCommand are converted to strings and denote command name * and its arguments. If the last argument is a JavaScript object, it is an option object. * Otherwise it is converted to string denoting the last argument and options objects assumed to * be empty. The following properties of the option object are processed: * *
    *
  • args - provides an array of additional command arguments *
  • env - explicit environment object. All its enumerable properties define * the corresponding environment variable names. *
  • input - the process input. If it is not java.io.InputStream, it is * converted to string and sent to the process as its input. If not specified, no input is * provided to the process. *
  • output - the process output instead of java.lang.System.out. If it is not * instance of java.io.OutputStream, the process output is read, converted to a string, * appended to the output property value converted to string and put as the new value of * the output property. *
  • err - the process error output instead of java.lang.System.err. If it is * not instance of java.io.OutputStream, the process error output is read, converted to a * string, appended to the err property value converted to string and put as the new value * of the err property. *
  • dir - the working direcotry to run the commands. *
*/ public static Object runCommand(Context cx, Scriptable thisObj, Object[] args, Function funObj) throws IOException { int L = args.length; if (L == 0 || (L == 1 && args[0] instanceof Scriptable)) { throw reportRuntimeError("msg.runCommand.bad.args"); } File wd = null; InputStream in = null; OutputStream out = null, err = null; ByteArrayOutputStream outBytes = null, errBytes = null; Object outObj = null, errObj = null; String[] environment = null; Scriptable params = null; Object[] addArgs = null; if (args[L - 1] instanceof Scriptable) { params = (Scriptable) args[L - 1]; --L; Object envObj = ScriptableObject.getProperty(params, "env"); if (envObj != Scriptable.NOT_FOUND) { if (envObj == null) { environment = new String[0]; } else { if (!(envObj instanceof Scriptable)) { throw reportRuntimeError("msg.runCommand.bad.env"); } Scriptable envHash = (Scriptable) envObj; Object[] ids = ScriptableObject.getPropertyIds(envHash); environment = new String[ids.length]; for (int i = 0; i != ids.length; ++i) { Object keyObj = ids[i], val; String key; if (keyObj instanceof String) { key = (String) keyObj; val = ScriptableObject.getProperty(envHash, key); } else { int ikey = ((Number) keyObj).intValue(); key = Integer.toString(ikey); val = ScriptableObject.getProperty(envHash, ikey); } if (val == ScriptableObject.NOT_FOUND) { val = Undefined.instance; } environment[i] = key + '=' + ScriptRuntime.toString(val); } } } Object wdObj = ScriptableObject.getProperty(params, "dir"); if (wdObj != Scriptable.NOT_FOUND) { wd = new File(ScriptRuntime.toString(wdObj)); } Object inObj = ScriptableObject.getProperty(params, "input"); if (inObj != Scriptable.NOT_FOUND) { in = toInputStream(inObj); } outObj = ScriptableObject.getProperty(params, "output"); if (outObj != Scriptable.NOT_FOUND) { out = toOutputStream(outObj); if (out == null) { outBytes = new ByteArrayOutputStream(); out = outBytes; } } errObj = ScriptableObject.getProperty(params, "err"); if (errObj != Scriptable.NOT_FOUND) { err = toOutputStream(errObj); if (err == null) { errBytes = new ByteArrayOutputStream(); err = errBytes; } } Object addArgsObj = ScriptableObject.getProperty(params, "args"); if (addArgsObj != Scriptable.NOT_FOUND) { Scriptable s = Context.toObject(addArgsObj, getTopLevelScope(thisObj)); addArgs = cx.getElements(s); } } Global global = getInstance(funObj); if (out == null) { out = global.getOut(); } if (err == null) { err = global.getErr(); } // If no explicit input stream, do not send any input to process, // in particular, do not use System.in to avoid deadlocks // when waiting for user input to send to process which is already // terminated as it is not always possible to interrupt read method. String[] cmd = new String[(addArgs == null) ? L : L + addArgs.length]; for (int i = 0; i != L; ++i) { cmd[i] = ScriptRuntime.toString(args[i]); } if (addArgs != null) { for (int i = 0; i != addArgs.length; ++i) { cmd[L + i] = ScriptRuntime.toString(addArgs[i]); } } int exitCode = runProcess(cmd, environment, wd, in, out, err); if (outBytes != null) { String s = ScriptRuntime.toString(outObj) + outBytes.toString(); ScriptableObject.putProperty(params, "output", s); } if (errBytes != null) { String s = ScriptRuntime.toString(errObj) + errBytes.toString(); ScriptableObject.putProperty(params, "err", s); } return exitCode; } /** The seal function seals all supplied arguments. */ public static void seal(Context cx, Scriptable thisObj, Object[] args, Function funObj) { for (int i = 0; i != args.length; ++i) { Object arg = args[i]; if (!(arg instanceof ScriptableObject) || arg == Undefined.instance) { if (!(arg instanceof Scriptable) || arg == Undefined.instance) { throw reportRuntimeError("msg.shell.seal.not.object"); } else { throw reportRuntimeError("msg.shell.seal.not.scriptable"); } } } for (int i = 0; i != args.length; ++i) { Object arg = args[i]; ((ScriptableObject) arg).sealObject(); } } /** * The readFile reads the given file content and convert it to a string using the specified * character coding or default character coding if explicit coding argument is not given. * *

Usage: * *

     * readFile(filePath)
     * readFile(filePath, charCoding)
     * 
* * The first form converts file's context to string using the default character coding. */ public static Object readFile(Context cx, Scriptable thisObj, Object[] args, Function funObj) throws IOException { if (args.length == 0) { throw reportRuntimeError("msg.shell.readFile.bad.args"); } String path = ScriptRuntime.toString(args[0]); String charCoding = null; if (args.length >= 2) { charCoding = ScriptRuntime.toString(args[1]); } return readUrl(path, charCoding, true); } /** * The readUrl opens connection to the given URL, read all its data and converts them to a * string using the specified character coding or default character coding if explicit coding * argument is not given. * *

Usage: * *

     * readUrl(url)
     * readUrl(url, charCoding)
     * 
* * The first form converts file's context to string using the default charCoding. */ public static Object readUrl(Context cx, Scriptable thisObj, Object[] args, Function funObj) throws IOException { if (args.length == 0) { throw reportRuntimeError("msg.shell.readUrl.bad.args"); } String url = ScriptRuntime.toString(args[0]); String charCoding = null; if (args.length >= 2) { charCoding = ScriptRuntime.toString(args[1]); } return readUrl(url, charCoding, false); } /** Convert the argument to int32 number. */ public static Object toint32(Context cx, Scriptable thisObj, Object[] args, Function funObj) { Object arg = (args.length != 0 ? args[0] : Undefined.instance); if (arg instanceof Integer) return arg; return ScriptRuntime.wrapInt(ScriptRuntime.toInt32(arg)); } private boolean loadJLine(Charset cs) { if (!attemptedJLineLoad) { // Check if we can use JLine for better command line handling attemptedJLineLoad = true; console = ShellConsole.getConsole(this, cs); } return console != null; } public ShellConsole getConsole(Charset cs) { if (!loadJLine(cs)) { console = ShellConsole.getConsole(getIn(), getErr(), cs); } return console; } public InputStream getIn() { if (inStream == null && !attemptedJLineLoad) { if (loadJLine(Charset.defaultCharset())) { inStream = console.getIn(); } } return inStream == null ? System.in : inStream; } public void setIn(InputStream in) { inStream = in; } public PrintStream getOut() { return outStream == null ? System.out : outStream; } public void setOut(PrintStream out) { outStream = out; } public PrintStream getErr() { return errStream == null ? System.err : errStream; } public void setErr(PrintStream err) { errStream = err; } public void setSealedStdLib(boolean value) { sealedStdLib = value; } private static Global getInstance(Function function) { Scriptable scope = function.getParentScope(); if (!(scope instanceof Global)) throw reportRuntimeError("msg.bad.shell.function.scope", String.valueOf(scope)); return (Global) scope; } /** * Runs the given process using Runtime.exec(). If any of in, out, err is null, the * corresponding process stream will be closed immediately, otherwise it will be closed as soon * as all data will be read from/written to process * * @return Exit value of process. * @throws IOException If there was an error executing the process. */ private static int runProcess( String[] cmd, String[] environment, File wd, InputStream in, OutputStream out, OutputStream err) throws IOException { Process p; if (environment == null) { p = Runtime.getRuntime().exec(cmd, null, wd); } else { p = Runtime.getRuntime().exec(cmd, environment, wd); } try { PipeThread inThread = null; if (in != null) { inThread = new PipeThread(false, in, p.getOutputStream()); inThread.start(); } else { p.getOutputStream().close(); } PipeThread outThread = null; if (out != null) { outThread = new PipeThread(true, p.getInputStream(), out); outThread.start(); } else { p.getInputStream().close(); } PipeThread errThread = null; if (err != null) { errThread = new PipeThread(true, p.getErrorStream(), err); errThread.start(); } else { p.getErrorStream().close(); } // wait for process completion for (; ; ) { try { p.waitFor(); if (outThread != null) { outThread.join(); } if (inThread != null) { inThread.join(); } if (errThread != null) { errThread.join(); } break; } catch (InterruptedException ignore) { } } return p.exitValue(); } finally { p.destroy(); } } static void pipe(boolean fromProcess, InputStream from, OutputStream to) throws IOException { try { final int SIZE = 4096; byte[] buffer = new byte[SIZE]; for (; ; ) { int n; if (!fromProcess) { n = from.read(buffer, 0, SIZE); } else { try { n = from.read(buffer, 0, SIZE); } catch (IOException ex) { // Ignore exception as it can be cause by closed pipe break; } } if (n < 0) { break; } if (fromProcess) { to.write(buffer, 0, n); to.flush(); } else { try { to.write(buffer, 0, n); to.flush(); } catch (IOException ex) { // Ignore exception as it can be cause by closed pipe break; } } } } finally { try { if (fromProcess) { from.close(); } else { to.close(); } } catch (IOException ex) { // Ignore errors on close. On Windows JVM may throw invalid // refrence exception if process terminates too fast. } } } private static InputStream toInputStream(Object value) throws IOException { InputStream is = null; String s = null; if (value instanceof Wrapper) { Object unwrapped = ((Wrapper) value).unwrap(); if (unwrapped instanceof InputStream) { is = (InputStream) unwrapped; } else if (unwrapped instanceof byte[]) { is = new ByteArrayInputStream((byte[]) unwrapped); } else if (unwrapped instanceof Reader) { s = readReader((Reader) unwrapped); } else if (unwrapped instanceof char[]) { s = new String((char[]) unwrapped); } } if (is == null) { if (s == null) { s = ScriptRuntime.toString(value); } is = new ByteArrayInputStream(s.getBytes()); } return is; } private static OutputStream toOutputStream(Object value) { OutputStream os = null; if (value instanceof Wrapper) { Object unwrapped = ((Wrapper) value).unwrap(); if (unwrapped instanceof OutputStream) { os = (OutputStream) unwrapped; } } return os; } private static String readUrl(String filePath, String charCoding, boolean urlIsFile) throws IOException { int chunkLength; InputStream is = null; try { if (!urlIsFile) { URL urlObj = new URL(filePath); URLConnection uc = urlObj.openConnection(); is = uc.getInputStream(); chunkLength = uc.getContentLength(); if (chunkLength <= 0) chunkLength = 1024; if (charCoding == null) { String type = uc.getContentType(); if (type != null) { charCoding = getCharCodingFromType(type); } } } else { File f = new File(filePath); if (!f.exists()) { throw new FileNotFoundException("File not found: " + filePath); } else if (!f.canRead()) { throw new IOException("Cannot read file: " + filePath); } long length = f.length(); chunkLength = (int) length; if (chunkLength != length) throw new IOException("Too big file size: " + length); if (chunkLength == 0) { return ""; } is = new FileInputStream(f); } try (Reader r = new InputStreamReader( is, charCoding == null ? Charset.defaultCharset().name() : charCoding)) { return readReader(r, chunkLength); } } finally { if (is != null) is.close(); } } /** * The readline reads one line from the standard input. "Prompt" is optional. * *

Usage: * *

     * readline(prompt)
     * 
*/ public static Object readline(Context cx, Scriptable thisObj, Object[] args, Function funObj) throws IOException { Global self = getInstance(funObj); if (args.length > 0) { return self.console.readLine(Context.toString(args[0])); } return self.console.readLine(); } private static String getCharCodingFromType(String type) { int i = type.indexOf(';'); if (i >= 0) { int end = type.length(); ++i; while (i != end && type.charAt(i) <= ' ') { ++i; } String charset = "charset"; if (charset.regionMatches(true, 0, type, i, charset.length())) { i += charset.length(); while (i != end && type.charAt(i) <= ' ') { ++i; } if (i != end && type.charAt(i) == '=') { ++i; while (i != end && type.charAt(i) <= ' ') { ++i; } if (i != end) { // i is at the start of non-empty // charCoding spec while (type.charAt(end - 1) <= ' ') { --end; } return type.substring(i, end); } } } } return null; } private static String readReader(Reader reader) throws IOException { return readReader(reader, 4096); } private static String readReader(Reader reader, int initialBufferSize) throws IOException { char[] buffer = new char[initialBufferSize]; int offset = 0; for (; ; ) { int n = reader.read(buffer, offset, buffer.length - offset); if (n < 0) { break; } offset += n; if (offset == buffer.length) { char[] tmp = new char[buffer.length * 2]; System.arraycopy(buffer, 0, tmp, 0, offset); buffer = tmp; } } return new String(buffer, 0, offset); } static RuntimeException reportRuntimeError(String msgId) { String message = ToolErrorReporter.getMessage(msgId); return Context.reportRuntimeError(message); } static RuntimeException reportRuntimeError(String msgId, String msgArg) { String message = ToolErrorReporter.getMessage(msgId, msgArg); return Context.reportRuntimeError(message); } } class Runner implements Runnable, ContextAction { Runner(Scriptable scope, Function func, Object[] args) { this.scope = scope; f = func; this.args = args; } Runner(Scriptable scope, Script script) { this.scope = scope; s = script; } @Override public void run() { factory.call(this); } @Override public Object run(Context cx) { if (f != null) return f.call(cx, scope, scope, args); else return s.exec(cx, scope); } ContextFactory factory; private Scriptable scope; private Function f; private Script s; private Object[] args; } class PipeThread extends Thread { PipeThread(boolean fromProcess, InputStream from, OutputStream to) { setDaemon(true); this.fromProcess = fromProcess; this.from = from; this.to = to; } @Override public void run() { try { Global.pipe(fromProcess, from, to); } catch (IOException ex) { throw Context.throwAsScriptRuntimeEx(ex); } } private boolean fromProcess; private InputStream from; private OutputStream to; }