1/*
2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.runtime;
27
28import static jdk.nashorn.internal.lookup.Lookup.MH;
29import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
30import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
31
32import java.io.BufferedReader;
33import java.io.File;
34import java.io.IOException;
35import java.io.InputStream;
36import java.io.InputStreamReader;
37import java.io.OutputStream;
38import java.lang.invoke.MethodHandle;
39import java.lang.invoke.MethodHandles;
40import java.util.ArrayList;
41import java.util.Arrays;
42import java.util.HashMap;
43import java.util.List;
44import java.util.Map;
45import java.util.Objects;
46import java.util.function.Function;
47import jdk.nashorn.internal.objects.NativeArray;
48import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
49
50/**
51 * Global functions supported only in scripting mode.
52 */
53public final class ScriptingFunctions {
54
55    /** Handle to implementation of {@link ScriptingFunctions#readLine} - Nashorn extension */
56    public static final MethodHandle READLINE = findOwnMH("readLine", Object.class, Object.class, Object.class);
57
58    /** Handle to implementation of {@link ScriptingFunctions#readFully} - Nashorn extension */
59    public static final MethodHandle READFULLY = findOwnMH("readFully",     Object.class, Object.class, Object.class);
60
61    /** Handle to implementation of {@link ScriptingFunctions#exec} - Nashorn extension */
62    public static final MethodHandle EXEC = findOwnMH("exec",     Object.class, Object.class, Object[].class);
63
64    /** EXEC name - special property used by $EXEC API. */
65    public static final String EXEC_NAME = "$EXEC";
66
67    /** OUT name - special property used by $EXEC API. */
68    public static final String OUT_NAME  = "$OUT";
69
70    /** ERR name - special property used by $EXEC API. */
71    public static final String ERR_NAME  = "$ERR";
72
73    /** EXIT name - special property used by $EXEC API. */
74    public static final String EXIT_NAME = "$EXIT";
75
76    /** Names of special properties used by $ENV API. */
77    public static final String ENV_NAME  = "$ENV";
78
79    /** Name of the environment variable for the current working directory. */
80    public static final String PWD_NAME  = "PWD";
81
82    private ScriptingFunctions() {
83    }
84
85    /**
86     * Nashorn extension: global.readLine (scripting-mode-only)
87     * Read one line of input from the standard input.
88     *
89     * @param self   self reference
90     * @param prompt String used as input prompt
91     *
92     * @return line that was read
93     *
94     * @throws IOException if an exception occurs
95     */
96    public static Object readLine(final Object self, final Object prompt) throws IOException {
97        return readLine(prompt);
98    }
99
100    /**
101     * Nashorn extension: Read the entire contents of a text file and return as String.
102     *
103     * @param self self reference
104     * @param file The input file whose content is read.
105     *
106     * @return String content of the input file.
107     *
108     * @throws IOException if an exception occurs
109     */
110    public static Object readFully(final Object self, final Object file) throws IOException {
111        File f = null;
112
113        if (file instanceof File) {
114            f = (File)file;
115        } else if (JSType.isString(file)) {
116            f = new java.io.File(((CharSequence)file).toString());
117        }
118
119        if (f == null || !f.isFile()) {
120            throw typeError("not.a.file", ScriptRuntime.safeToString(file));
121        }
122
123        return new String(Source.readFully(f));
124    }
125
126    /**
127     * Nashorn extension: exec a string in a separate process.
128     *
129     * @param self   self reference
130     * @param args   In one of four forms
131     *               1. String script, String input
132     *               2. String script, InputStream input, OutputStream output, OutputStream error
133     *               3. Array scriptTokens, String input
134     *               4. Array scriptTokens, InputStream input, OutputStream output, OutputStream error
135     *
136     * @return output string from the request if in form of 1. or 3., empty string otherwise
137     */
138    public static Object exec(final Object self, final Object... args) {
139        final Object arg0 = args.length > 0 ? args[0] : UNDEFINED;
140        final Object arg1 = args.length > 1 ? args[1] : UNDEFINED;
141        final Object arg2 = args.length > 2 ? args[2] : UNDEFINED;
142        final Object arg3 = args.length > 3 ? args[3] : UNDEFINED;
143
144        InputStream inputStream = null;
145        OutputStream outputStream = null;
146        OutputStream errorStream = null;
147        String script = null;
148        List<String> tokens = null;
149        String inputString = null;
150
151        if (arg0 instanceof NativeArray) {
152            final String[] array = (String[])JSType.toJavaArray(arg0, String.class);
153            tokens = new ArrayList<>();
154            tokens.addAll(Arrays.asList(array));
155        } else {
156            script = JSType.toString(arg0);
157        }
158
159        if (arg1 instanceof InputStream) {
160            inputStream = (InputStream)arg1;
161        } else {
162            inputString = JSType.toString(arg1);
163        }
164
165        if (arg2 instanceof OutputStream) {
166            outputStream = (OutputStream)arg2;
167        }
168
169        if (arg3 instanceof OutputStream) {
170            errorStream = (OutputStream)arg3;
171        }
172
173        // Current global is need to fetch additional inputs and for additional results.
174        final ScriptObject global = Context.getGlobal();
175
176        // Capture ENV property state.
177        final Map<String, String> environment = new HashMap<>();
178        final Object env = global.get(ENV_NAME);
179
180        if (env instanceof ScriptObject) {
181            final ScriptObject envProperties = (ScriptObject)env;
182
183            // Copy ENV variables.
184            envProperties.entrySet().stream().forEach((entry) -> {
185                environment.put(JSType.toString(entry.getKey()), JSType.toString(entry.getValue()));
186            });
187        }
188
189        // get the $EXEC function object from the global object
190        final Object exec = global.get(EXEC_NAME);
191        assert exec instanceof ScriptObject : EXEC_NAME + " is not a script object!";
192
193        // Execute the commands
194        final CommandExecutor executor = new CommandExecutor();
195        executor.setInputString(inputString);
196        executor.setInputStream(inputStream);
197        executor.setOutputStream(outputStream);
198        executor.setErrorStream(errorStream);
199        executor.setEnvironment(environment);
200
201        if (tokens != null) {
202            executor.process(tokens);
203        } else {
204            executor.process(script);
205        }
206
207        final String outString = executor.getOutputString();
208        final String errString = executor.getErrorString();
209        final int exitCode = executor.getExitCode();
210
211        // Set globals for secondary results.
212        global.set(OUT_NAME, outString, 0);
213        global.set(ERR_NAME, errString, 0);
214        global.set(EXIT_NAME, exitCode, 0);
215
216        // Return the result from stdout.
217        return outString;
218    }
219
220    // Implementation for pluggable "readLine" functionality
221    // Used by jjs interactive mode
222
223    private static Function<String, String> readLineHelper;
224
225    public static void setReadLineHelper(final Function<String, String> func) {
226        readLineHelper = Objects.requireNonNull(func);
227    }
228
229    public static Function<String, String> getReadLineHelper() {
230        return readLineHelper;
231    }
232
233    public static String readLine(final Object prompt) throws IOException {
234        final String p = (prompt != UNDEFINED)? JSType.toString(prompt) : "";
235        if (readLineHelper != null) {
236            return readLineHelper.apply(p);
237        } else {
238            System.out.print(p);
239            final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
240            return reader.readLine();
241        }
242    }
243
244    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
245        return MH.findStatic(MethodHandles.lookup(), ScriptingFunctions.class, name, MH.type(rtype, types));
246    }
247}
248