src/jdk/nashorn/internal/runtime/ScriptingFunctions.java

Tue, 09 Jun 2015 09:27:02 +0200

author
mhaupt
date
Tue, 09 Jun 2015 09:27:02 +0200
changeset 1403
b39a918a34a4
parent 1398
2f1b9f4daec1
child 1405
98b090e45df3
permissions
-rw-r--r--

8080490: add $EXECV command to Nashorn scripting mode
Summary: Additional arguments to the command line can be passed as a single array, or as a sequence of varargs.
Reviewed-by: attila, hannesw

jlaskey@3 1 /*
mhaupt@1370 2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
jlaskey@3 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
jlaskey@3 4 *
jlaskey@3 5 * This code is free software; you can redistribute it and/or modify it
jlaskey@3 6 * under the terms of the GNU General Public License version 2 only, as
jlaskey@3 7 * published by the Free Software Foundation. Oracle designates this
jlaskey@3 8 * particular file as subject to the "Classpath" exception as provided
jlaskey@3 9 * by Oracle in the LICENSE file that accompanied this code.
jlaskey@3 10 *
jlaskey@3 11 * This code is distributed in the hope that it will be useful, but WITHOUT
jlaskey@3 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
jlaskey@3 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
jlaskey@3 14 * version 2 for more details (a copy is included in the LICENSE file that
jlaskey@3 15 * accompanied this code).
jlaskey@3 16 *
jlaskey@3 17 * You should have received a copy of the GNU General Public License version
jlaskey@3 18 * 2 along with this work; if not, write to the Free Software Foundation,
jlaskey@3 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
jlaskey@3 20 *
jlaskey@3 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
jlaskey@3 22 * or visit www.oracle.com if you need additional information or have any
jlaskey@3 23 * questions.
jlaskey@3 24 */
jlaskey@3 25
jlaskey@3 26 package jdk.nashorn.internal.runtime;
jlaskey@3 27
attila@962 28 import static jdk.nashorn.internal.lookup.Lookup.MH;
jlaskey@3 29 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
jlaskey@3 30 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
jlaskey@3 31
jlaskey@3 32 import java.io.BufferedReader;
jlaskey@3 33 import java.io.File;
jlaskey@3 34 import java.io.IOException;
jlaskey@3 35 import java.io.InputStreamReader;
jlaskey@327 36 import java.io.OutputStreamWriter;
mhaupt@1370 37 import java.io.StreamTokenizer;
mhaupt@1370 38 import java.io.StringReader;
jlaskey@3 39 import java.lang.invoke.MethodHandle;
jlaskey@3 40 import java.lang.invoke.MethodHandles;
mhaupt@1370 41 import java.util.ArrayList;
mhaupt@1370 42 import java.util.List;
jlaskey@67 43 import java.util.Map;
mhaupt@1403 44 import jdk.nashorn.internal.objects.NativeArray;
jlaskey@3 45
jlaskey@3 46 /**
jlaskey@3 47 * Global functions supported only in scripting mode.
jlaskey@3 48 */
sundar@82 49 public final class ScriptingFunctions {
jlaskey@3 50
lagergren@57 51 /** Handle to implementation of {@link ScriptingFunctions#readLine} - Nashorn extension */
jlaskey@282 52 public static final MethodHandle READLINE = findOwnMH("readLine", Object.class, Object.class, Object.class);
jlaskey@3 53
sundar@52 54 /** Handle to implementation of {@link ScriptingFunctions#readFully} - Nashorn extension */
sundar@52 55 public static final MethodHandle READFULLY = findOwnMH("readFully", Object.class, Object.class, Object.class);
jlaskey@3 56
jlaskey@75 57 /** Handle to implementation of {@link ScriptingFunctions#exec} - Nashorn extension */
mhaupt@1403 58 public static final MethodHandle EXEC = findOwnMH("exec", Object.class, Object.class, Object.class, Object.class, Object[].class);
jlaskey@67 59
lagergren@89 60 /** EXEC name - special property used by $EXEC API. */
lagergren@89 61 public static final String EXEC_NAME = "$EXEC";
lagergren@89 62
lagergren@89 63 /** OUT name - special property used by $EXEC API. */
lagergren@89 64 public static final String OUT_NAME = "$OUT";
lagergren@89 65
lagergren@89 66 /** ERR name - special property used by $EXEC API. */
lagergren@89 67 public static final String ERR_NAME = "$ERR";
lagergren@89 68
lagergren@89 69 /** EXIT name - special property used by $EXEC API. */
lagergren@89 70 public static final String EXIT_NAME = "$EXIT";
jlaskey@67 71
jlaskey@67 72 /** Names of special properties used by $ENV API. */
jlaskey@67 73 public static final String ENV_NAME = "$ENV";
lagergren@89 74
mhaupt@1398 75 /** Name of the environment variable for the current working directory. */
mhaupt@1398 76 public static final String PWD_NAME = "PWD";
jlaskey@67 77
jlaskey@3 78 private ScriptingFunctions() {
jlaskey@3 79 }
jlaskey@3 80
jlaskey@3 81 /**
jlaskey@3 82 * Nashorn extension: global.readLine (scripting-mode-only)
jlaskey@3 83 * Read one line of input from the standard input.
jlaskey@3 84 *
jlaskey@282 85 * @param self self reference
jlaskey@282 86 * @param prompt String used as input prompt
jlaskey@3 87 *
jlaskey@3 88 * @return line that was read
jlaskey@3 89 *
jlaskey@3 90 * @throws IOException if an exception occurs
jlaskey@3 91 */
jlaskey@282 92 public static Object readLine(final Object self, final Object prompt) throws IOException {
jlaskey@282 93 if (prompt != UNDEFINED) {
jlaskey@282 94 System.out.print(JSType.toString(prompt));
jlaskey@282 95 }
jlaskey@3 96 final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
jlaskey@3 97 return reader.readLine();
jlaskey@3 98 }
jlaskey@3 99
jlaskey@3 100 /**
jlaskey@3 101 * Nashorn extension: Read the entire contents of a text file and return as String.
jlaskey@3 102 *
jlaskey@3 103 * @param self self reference
jlaskey@3 104 * @param file The input file whose content is read.
jlaskey@3 105 *
jlaskey@3 106 * @return String content of the input file.
jlaskey@3 107 *
jlaskey@3 108 * @throws IOException if an exception occurs
jlaskey@3 109 */
sundar@52 110 public static Object readFully(final Object self, final Object file) throws IOException {
jlaskey@3 111 File f = null;
jlaskey@3 112
jlaskey@3 113 if (file instanceof File) {
jlaskey@3 114 f = (File)file;
attila@1251 115 } else if (JSType.isString(file)) {
attila@963 116 f = new java.io.File(((CharSequence)file).toString());
jlaskey@3 117 }
jlaskey@3 118
jlaskey@3 119 if (f == null || !f.isFile()) {
lagergren@112 120 throw typeError("not.a.file", ScriptRuntime.safeToString(file));
jlaskey@3 121 }
jlaskey@3 122
jlaskey@3 123 return new String(Source.readFully(f));
jlaskey@3 124 }
jlaskey@3 125
jlaskey@3 126 /**
jlaskey@67 127 * Nashorn extension: exec a string in a separate process.
jlaskey@67 128 *
jlaskey@67 129 * @param self self reference
jlaskey@67 130 * @param string string to execute
lagergren@108 131 * @param input input
mhaupt@1403 132 * @param argv additional arguments, to be appended to {@code string}. Additional arguments can be passed as
mhaupt@1403 133 * either one JavaScript array, whose elements will be converted to strings; or as a sequence of
mhaupt@1403 134 * varargs, each of which will be converted to a string.
jlaskey@67 135 *
jlaskey@67 136 * @return output string from the request
mhaupt@1403 137 *
lagergren@108 138 * @throws IOException if any stream access fails
lagergren@108 139 * @throws InterruptedException if execution is interrupted
jlaskey@67 140 */
mhaupt@1403 141 public static Object exec(final Object self, final Object string, final Object input, final Object... argv) throws IOException, InterruptedException {
jlaskey@67 142 // Current global is need to fetch additional inputs and for additional results.
jlaskey@67 143 final ScriptObject global = Context.getGlobal();
jlaskey@67 144
mhaupt@1403 145 // Assemble command line, process additional arguments.
mhaupt@1403 146 final List<String> cmdLine = tokenizeString(JSType.toString(string));
mhaupt@1403 147 final Object[] additionalArgs = argv.length == 1 && argv[0] instanceof NativeArray ?
mhaupt@1403 148 ((NativeArray) argv[0]).asObjectArray() :
mhaupt@1403 149 argv;
mhaupt@1403 150 for (Object arg : additionalArgs) {
mhaupt@1403 151 cmdLine.add(JSType.toString(arg));
mhaupt@1403 152 }
mhaupt@1403 153
jlaskey@67 154 // Set up initial process.
mhaupt@1403 155 final ProcessBuilder processBuilder = new ProcessBuilder(cmdLine);
jlaskey@67 156
sundar@69 157 // Current ENV property state.
sundar@69 158 final Object env = global.get(ENV_NAME);
sundar@69 159 if (env instanceof ScriptObject) {
sundar@69 160 final ScriptObject envProperties = (ScriptObject)env;
jlaskey@67 161
sundar@69 162 // If a working directory is present, use it.
sundar@69 163 final Object pwd = envProperties.get(PWD_NAME);
sundar@69 164 if (pwd != UNDEFINED) {
sundar@69 165 processBuilder.directory(new File(JSType.toString(pwd)));
sundar@69 166 }
jlaskey@67 167
sundar@69 168 // Set up ENV variables.
sundar@69 169 final Map<String, String> environment = processBuilder.environment();
sundar@69 170 environment.clear();
attila@962 171 for (final Map.Entry<Object, Object> entry : envProperties.entrySet()) {
sundar@69 172 environment.put(JSType.toString(entry.getKey()), JSType.toString(entry.getValue()));
sundar@69 173 }
jlaskey@67 174 }
jlaskey@67 175
jlaskey@67 176 // Start the process.
jlaskey@67 177 final Process process = processBuilder.start();
jlaskey@327 178 final IOException exception[] = new IOException[2];
jlaskey@327 179
jlaskey@327 180 // Collect output.
jlaskey@327 181 final StringBuilder outBuffer = new StringBuilder();
attila@962 182 final Thread outThread = new Thread(new Runnable() {
jlaskey@327 183 @Override
jlaskey@327 184 public void run() {
attila@962 185 final char buffer[] = new char[1024];
jlaskey@327 186 try (final InputStreamReader inputStream = new InputStreamReader(process.getInputStream())) {
jlaskey@327 187 for (int length; (length = inputStream.read(buffer, 0, buffer.length)) != -1; ) {
jlaskey@327 188 outBuffer.append(buffer, 0, length);
jlaskey@327 189 }
attila@962 190 } catch (final IOException ex) {
jlaskey@327 191 exception[0] = ex;
jlaskey@327 192 }
jlaskey@327 193 }
jlaskey@327 194 }, "$EXEC output");
jlaskey@327 195
jlaskey@327 196 // Collect errors.
jlaskey@327 197 final StringBuilder errBuffer = new StringBuilder();
attila@962 198 final Thread errThread = new Thread(new Runnable() {
jlaskey@327 199 @Override
jlaskey@327 200 public void run() {
attila@962 201 final char buffer[] = new char[1024];
jlaskey@327 202 try (final InputStreamReader inputStream = new InputStreamReader(process.getErrorStream())) {
jlaskey@327 203 for (int length; (length = inputStream.read(buffer, 0, buffer.length)) != -1; ) {
jlaskey@655 204 errBuffer.append(buffer, 0, length);
jlaskey@327 205 }
attila@962 206 } catch (final IOException ex) {
jlaskey@327 207 exception[1] = ex;
jlaskey@327 208 }
jlaskey@327 209 }
jlaskey@327 210 }, "$EXEC error");
jlaskey@327 211
jlaskey@327 212 // Start gathering output.
jlaskey@327 213 outThread.start();
jlaskey@327 214 errThread.start();
jlaskey@67 215
jlaskey@67 216 // If input is present, pass on to process.
jlaskey@327 217 try (OutputStreamWriter outputStream = new OutputStreamWriter(process.getOutputStream())) {
jlaskey@67 218 if (input != UNDEFINED) {
attila@962 219 final String in = JSType.toString(input);
jlaskey@327 220 outputStream.write(in, 0, in.length());
jlaskey@67 221 }
attila@962 222 } catch (final IOException ex) {
jlaskey@327 223 // Process was not expecting input. May be normal state of affairs.
jlaskey@67 224 }
jlaskey@67 225
jlaskey@67 226 // Wait for the process to complete.
jlaskey@67 227 final int exit = process.waitFor();
jlaskey@327 228 outThread.join();
jlaskey@327 229 errThread.join();
jlaskey@67 230
jlaskey@327 231 final String out = outBuffer.toString();
jlaskey@327 232 final String err = errBuffer.toString();
jlaskey@67 233
jlaskey@67 234 // Set globals for secondary results.
hannesw@1020 235 global.set(OUT_NAME, out, 0);
hannesw@1020 236 global.set(ERR_NAME, err, 0);
hannesw@1020 237 global.set(EXIT_NAME, exit, 0);
jlaskey@67 238
jlaskey@327 239 // Propagate exception if present.
attila@963 240 for (final IOException element : exception) {
attila@963 241 if (element != null) {
attila@963 242 throw element;
jlaskey@327 243 }
jlaskey@327 244 }
jlaskey@327 245
jlaskey@67 246 // Return the result from stdout.
jlaskey@67 247 return out;
jlaskey@67 248 }
jlaskey@67 249
jlaskey@3 250 private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
jlaskey@3 251 return MH.findStatic(MethodHandles.lookup(), ScriptingFunctions.class, name, MH.type(rtype, types));
jlaskey@3 252 }
mhaupt@1370 253
mhaupt@1370 254 /**
mhaupt@1388 255 * Break a string into tokens, honoring quoted arguments and escaped spaces.
mhaupt@1370 256 *
mhaupt@1388 257 * @param str a {@link String} to tokenize.
mhaupt@1370 258 * @return a {@link List} of {@link String}s representing the tokens that
mhaupt@1388 259 * constitute the string.
mhaupt@1370 260 * @throws IOException in case {@link StreamTokenizer#nextToken()} raises it.
mhaupt@1370 261 */
mhaupt@1388 262 public static List<String> tokenizeString(final String str) throws IOException {
mhaupt@1388 263 final StreamTokenizer tokenizer = new StreamTokenizer(new StringReader(str));
mhaupt@1370 264 tokenizer.resetSyntax();
mhaupt@1370 265 tokenizer.wordChars(0, 255);
mhaupt@1370 266 tokenizer.whitespaceChars(0, ' ');
mhaupt@1370 267 tokenizer.commentChar('#');
mhaupt@1370 268 tokenizer.quoteChar('"');
mhaupt@1370 269 tokenizer.quoteChar('\'');
mhaupt@1388 270 final List<String> tokenList = new ArrayList<>();
mhaupt@1370 271 final StringBuilder toAppend = new StringBuilder();
mhaupt@1370 272 while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) {
mhaupt@1370 273 final String s = tokenizer.sval;
mhaupt@1370 274 // The tokenizer understands about honoring quoted strings and recognizes
mhaupt@1370 275 // them as one token that possibly contains multiple space-separated words.
mhaupt@1370 276 // It does not recognize quoted spaces, though, and will split after the
mhaupt@1370 277 // escaping \ character. This is handled here.
mhaupt@1370 278 if (s.endsWith("\\")) {
mhaupt@1370 279 // omit trailing \, append space instead
mhaupt@1370 280 toAppend.append(s.substring(0, s.length() - 1)).append(' ');
mhaupt@1370 281 } else {
mhaupt@1388 282 tokenList.add(toAppend.append(s).toString());
mhaupt@1370 283 toAppend.setLength(0);
mhaupt@1370 284 }
mhaupt@1370 285 }
mhaupt@1370 286 if (toAppend.length() != 0) {
mhaupt@1388 287 tokenList.add(toAppend.toString());
mhaupt@1370 288 }
mhaupt@1388 289 return tokenList;
mhaupt@1370 290 }
jlaskey@3 291 }

mercurial