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.

There is a newer version: 1.7.15
Show 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.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);
        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 { Class clazz = getClass(args); if (!Script.class.isAssignableFrom(clazz)) { throw reportRuntimeError("msg.must.implement.Script"); } Script script = (Script) clazz.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); ScriptableOutputStream out = new ScriptableOutputStream(fos, scope); out.writeObject(obj); out.close(); } 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]); FileInputStream fis = new FileInputStream(filename); Scriptable scope = ScriptableObject.getTopLevelScope(thisObj); ObjectInputStream in = new ScriptableInputStream(fis, scope); Object deserialized = in.readObject(); in.close(); 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); } Reader r; if (charCoding == null) { r = new InputStreamReader(is); } else { r = new InputStreamReader(is, 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; } public void run() { factory.call(this); } 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; }