1 /*
2 * YuiCompressor Maven plugin
3 *
4 * Copyright 2012-2025 Hazendaz.
5 *
6 * Licensed under the GNU Lesser General Public License (LGPL),
7 * version 2.1 or later (the "License").
8 * You may not use this file except in compliance with the License.
9 * You may read the licence in the 'lgpl.txt' file in the root folder of
10 * project or obtain a copy at
11 *
12 * https://www.gnu.org/licenses/lgpl-2.1.html
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" basis,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20 /* ***** BEGIN LICENSE BLOCK *****
21 * Version: MPL 1.1/GPL 2.0
22 *
23 * The contents of this file are subject to the Mozilla Public License Version
24 * 1.1 (the "License"); you may not use this file except in compliance with
25 * the License. You may obtain a copy of the License at
26 * http://www.mozilla.org/MPL/
27 *
28 * Software distributed under the License is distributed on an "AS IS" basis,
29 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
30 * for the specific language governing rights and limitations under the
31 * License.
32 *
33 * The Original Code is Rhino code, released
34 * May 6, 1998.
35 *
36 * The Initial Developer of the Original Code is
37 * Netscape Communications Corporation.
38 * Portions created by the Initial Developer are Copyright (C) 1997-1999
39 * the Initial Developer. All Rights Reserved.
40 *
41 * Contributor(s):
42 *
43 * Alternatively, the contents of this file may be used under the terms of
44 * the GNU General Public License Version 2 or later (the "GPL"), in which
45 * case the provisions of the GPL are applicable instead of those above. If
46 * you wish to allow use of your version of this file only under the terms of
47 * the GPL and not to allow others to use your version of this file under the
48 * MPL, indicate your decision by deleting the provisions above and replacing
49 * them with the notice and other provisions required by the GPL. If you do
50 * not delete the provisions above, a recipient may use your version of this
51 * file under either the MPL or the GPL.
52 *
53 * ***** END LICENSE BLOCK ***** */
54
55 package net.alchim31.maven.yuicompressor;
56
57 import java.io.BufferedReader;
58 import java.io.IOException;
59 import java.io.InputStreamReader;
60 import java.nio.charset.StandardCharsets;
61 import java.nio.file.Files;
62 import java.nio.file.Path;
63
64 import org.mozilla.javascript.Context;
65 import org.mozilla.javascript.ErrorReporter;
66 import org.mozilla.javascript.EvaluatorException;
67 import org.mozilla.javascript.Function;
68 import org.mozilla.javascript.JavaScriptException;
69 import org.mozilla.javascript.Scriptable;
70 import org.mozilla.javascript.ScriptableObject;
71 import org.mozilla.javascript.WrappedException;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75 /**
76 * The BasicRhinoShell program.
77 * <p>
78 * Can execute scripts interactively or in batch mode at the command line. An example of controlling the JavaScript
79 * engine.
80 * <p>
81 * Based on Rhino.
82 *
83 * @see <a href="http://lxr.mozilla.org/mozilla/source/js/rhino/examples/BasicRhinoShell.java">Basic Rhino Shell</a>
84 */
85 public class BasicRhinoShell extends ScriptableObject {
86
87 /** The Constant serial version uid. */
88 private static final long serialVersionUID = 1L;
89
90 /** The Constant logger. */
91 private static final Logger logger = LoggerFactory.getLogger(BasicRhinoShell.class);
92
93 /** The quitting. */
94 private boolean quitting;
95
96 @Override
97 public String getClassName() {
98 return "global";
99 }
100
101 /**
102 * Main entry point.
103 * <p>
104 * Process arguments as would a normal Java program. Also create a new Context and associate it with the current
105 * thread. Then set up the execution environment and begin to execute scripts.
106 *
107 * @param args
108 * the args
109 * @param reporter
110 * the reporter
111 */
112 public static void exec(String[] args, ErrorReporter reporter) {
113 // Associate a new Context with this thread
114 Context cx = Context.enter();
115 cx.setErrorReporter(reporter);
116 try {
117 // Initialize the standard objects (Object, Function, etc.)
118 // This must be done before scripts can be executed.
119 BasicRhinoShell basicRhinoShell = new BasicRhinoShell();
120 cx.initStandardObjects(basicRhinoShell);
121
122 // Define some global functions particular to the BasicRhinoShell.
123 // Note
124 // that these functions are not part of ECMA.
125 String[] names = { "print", "quit", "version", "load", "help", "readFile", "warn" };
126 basicRhinoShell.defineFunctionProperties(names, BasicRhinoShell.class, ScriptableObject.DONTENUM);
127
128 args = processOptions(cx, args);
129
130 // Set up "arguments" in the global scope to contain the command
131 // line arguments after the name of the script to execute
132 Object[] array;
133 if (args.length == 0) {
134 array = new Object[0];
135 } else {
136 int length = args.length - 1;
137 array = new Object[length];
138 System.arraycopy(args, 1, array, 0, length);
139 }
140 Scriptable argsObj = cx.newArray(basicRhinoShell, array);
141 basicRhinoShell.defineProperty("arguments", argsObj, ScriptableObject.DONTENUM);
142
143 basicRhinoShell.processSource(cx, args.length == 0 ? null : args[0]);
144 } finally {
145 Context.exit();
146 }
147 }
148
149 /**
150 * Parse arguments.
151 *
152 * @param cx
153 * the cx
154 * @param args
155 * the args
156 *
157 * @return the string[]
158 */
159 public static String[] processOptions(Context cx, String[] args) {
160 for (int i = 0; i < args.length; i++) {
161 String arg = args[i];
162 if (!arg.startsWith("-")) {
163 String[] result = new String[args.length - i];
164 for (int j = i; j < args.length; j++) {
165 result[j - i] = args[j];
166 }
167 return result;
168 }
169 if (arg.equals("-version")) {
170 i++;
171 if (i == args.length) {
172 usage(arg);
173 }
174 double d = Context.toNumber(args[i]);
175 if (Double.isNaN(d)) {
176 usage(arg);
177 }
178 cx.setLanguageVersion((int) d);
179 continue;
180 }
181 usage(arg);
182 }
183 return new String[0];
184 }
185
186 /**
187 * Print a usage message.
188 *
189 * @param s
190 * the s
191 */
192 private static void usage(String s) {
193 p("Didn't understand \"" + s + "\".");
194 p("Valid arguments are:");
195 p("-version 100|110|120|130|140|150|160|170");
196 System.exit(1);
197 }
198
199 /**
200 * Print a help message.
201 * <p>
202 * This method is defined as a JavaScript function.
203 */
204 public void help() {
205 p("");
206 p("Command Description");
207 p("======= ===========");
208 p("help() Display usage and help messages. ");
209 p("defineClass(className) Define an extension using the Java class");
210 p(" named with the string argument. ");
211 p(" Uses ScriptableObject.defineClass(). ");
212 p("load(['foo.js', ...]) Load JavaScript source files named by ");
213 p(" string arguments. ");
214 p("loadClass(className) Load a class named by a string argument.");
215 p(" The class must be a script compiled to a");
216 p(" class file. ");
217 p("print([expr ...]) Evaluate and print expressions. ");
218 p("quit() Quit the BasicRhinoShell. ");
219 p("version([number]) Get or set the JavaScript version number.");
220 p("");
221 }
222
223 /**
224 * Print the string values of its arguments.
225 * <p>
226 * This method is defined as a JavaScript function. Note that its arguments are of the "varargs" form, which allows
227 * it to handle an arbitrary number of arguments supplied to the JavaScript function.
228 *
229 * @param cx
230 * the cx
231 * @param thisObj
232 * the this obj
233 * @param args
234 * the args
235 * @param funObj
236 * the fun obj
237 */
238 public static void print(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
239 for (int i = 0; i < args.length; i++) {
240 if (i > 0) {
241 logger.info("");
242 }
243
244 // Convert the arbitrary JavaScript value into a string form.
245 String s = Context.toString(args[i]);
246
247 logger.info(s);
248 }
249 logger.info("");
250 }
251
252 /**
253 * Quit the BasicRhinoShell.
254 * <p>
255 * This only affects the interactive mode.
256 * <p>
257 * This method is defined as a JavaScript function.
258 */
259 public void quit() {
260 quitting = true;
261 }
262
263 /**
264 * Warn.
265 *
266 * @param cx
267 * the cx
268 * @param thisObj
269 * the this obj
270 * @param args
271 * the args
272 * @param funObj
273 * the fun obj
274 */
275 public static void warn(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
276 String message = Context.toString(args[0]);
277 int line = (int) Context.toNumber(args[1]);
278 String source = Context.toString(args[2]);
279 int column = (int) Context.toNumber(args[3]);
280 cx.getErrorReporter().warning(message, null, line, source, column);
281 }
282
283 /**
284 * This method is defined as a JavaScript function.
285 *
286 * @param path
287 * the path
288 *
289 * @return the string
290 */
291 public String readFile(String path) {
292 try {
293 return new String(Files.readAllBytes(Path.of(path)), StandardCharsets.UTF_8);
294 } catch (RuntimeException exc) {
295 throw exc;
296 } catch (IOException exc) {
297 throw new RuntimeException("wrap: " + exc.getMessage(), exc);
298 }
299 }
300
301 /**
302 * Get and set the language version.
303 * <p>
304 * This method is defined as a JavaScript function.
305 *
306 * @param cx
307 * the cx
308 * @param thisObj
309 * the this obj
310 * @param args
311 * the args
312 * @param funObj
313 * the fun obj
314 *
315 * @return the double
316 */
317 public static double version(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
318 double result = cx.getLanguageVersion();
319 if (args.length > 0) {
320 double d = Context.toNumber(args[0]);
321 cx.setLanguageVersion((int) d);
322 }
323 return result;
324 }
325
326 /**
327 * Load and execute a set of JavaScript source files.
328 * <p>
329 * This method is defined as a JavaScript function.
330 *
331 * @param cx
332 * the cx
333 * @param thisObj
334 * the this obj
335 * @param args
336 * the args
337 * @param funObj
338 * the fun obj
339 */
340 public static void load(Context cx, Scriptable thisObj, Object[] args, Function funObj) {
341 BasicRhinoShell basicRhinoShell = (BasicRhinoShell) getTopLevelScope(thisObj);
342 for (Object element : args) {
343 basicRhinoShell.processSource(cx, Context.toString(element));
344 }
345 }
346
347 /**
348 * Evaluate JavaScript source.
349 *
350 * @param cx
351 * the current context
352 * @param filename
353 * the name of the file to compile, or null for interactive mode.
354 */
355 private void processSource(Context cx, String filename) {
356 if (filename == null) {
357 BufferedReader in = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
358 String sourceName = "<stdin>";
359 int lineno = 1;
360 boolean hitEOF = false;
361 do {
362 int startline = lineno;
363 logger.info("js> ");
364 try {
365 StringBuilder source = new StringBuilder();
366 // Collect lines of source to compile.
367 while (true) {
368 String newline = in.readLine();
369 if (newline == null) {
370 hitEOF = true;
371 break;
372 }
373 source.append(newline).append("\n");
374 lineno++;
375 // Continue collecting as long as more lines are needed to complete the current statement.
376 // stringIsCompilableUnit is also true if the source statement will result in any error other
377 // than one that might be resolved by appending more source.
378 if (cx.stringIsCompilableUnit(source.toString())) {
379 break;
380 }
381 }
382 Object result = cx.evaluateString(this, source.toString(), sourceName, startline, null);
383 if (result != Context.getUndefinedValue() && logger.isInfoEnabled()) {
384 logger.info("{}", Context.toString(result));
385 }
386 } catch (WrappedException e) {
387 // Some form of exception was caught by JavaScript and propagated up.
388 logger.info(e.getWrappedException().toString());
389 logger.error("", e);
390 } catch (EvaluatorException | JavaScriptException e) {
391 // Some form of JavaScript error.
392 logger.info("js: {}", e.getMessage());
393 } catch (IOException e) {
394 logger.info(e.toString());
395 }
396 if (quitting) {
397 // The user executed the quit() function.
398 break;
399 }
400 } while (!hitEOF);
401 logger.info("");
402 } else {
403 try (BufferedReader in = Files.newBufferedReader(Path.of(filename), StandardCharsets.UTF_8)) {
404 // Here we evaluate the entire contents of the file as a script. Text is printed only if the
405 // print() function is called.
406 cx.evaluateReader(this, in, filename, 1, null);
407 } catch (WrappedException e) {
408 logger.info(e.getWrappedException().toString());
409 logger.error("", e);
410 } catch (EvaluatorException | JavaScriptException e) {
411 logger.info("js: {}", e.getMessage());
412 } catch (IOException e) {
413 logger.error("", e);
414 }
415 }
416 }
417
418 /**
419 * P.
420 *
421 * @param s
422 * the s
423 */
424 private static void p(String s) {
425 logger.info(s);
426 }
427
428 }