1/*
2 * Copyright (c) 2005, 2013, 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
26/**
27 * jrunscript JavaScript built-in functions and objects.
28 */
29
30/**
31 * Creates an object that delegates all method calls on
32 * it to the 'invoke' method on the given delegate object.<br>
33 *
34 * Example:
35 * <pre>
36 * <code>
37 *     var x  = { invoke: function(name, args) { //code...}
38 *     var y = new JSInvoker(x);
39 *     y.func(3, 3); // calls x.invoke('func', args); where args is array of arguments
40 * </code>
41 * </pre>
42 * @param obj object to be wrapped by JSInvoker
43 * @constructor
44 */
45function JSInvoker(obj) {
46    return new JSAdapter({
47        __get__ : function(name) {
48            return function() {
49                return obj.invoke(name, arguments);
50            }
51        }
52    });
53}
54
55/**
56 * This variable represents OS environment. Environment
57 * variables can be accessed as fields of this object. For
58 * example, env.PATH will return PATH value configured.
59 */
60var env = new JSAdapter({
61    __get__ : function (name) {
62        return java.lang.System.getenv(name);
63    },
64    __has__ : function (name) {
65        return java.lang.System.getenv().containsKey(name);
66    },
67    __getIds__ : function() {
68        return java.lang.System.getenv().keySet().toArray();
69    },
70    __delete__ : function(name) {
71        println("can't delete env item");
72    },
73    __put__ : function (name, value) {
74        println("can't change env item");
75    },
76    toString: function() {
77        return java.lang.System.getenv().toString();
78    }
79});
80
81/**
82 * Creates a convenient script object to deal with java.util.Map instances.
83 * The result script object's field names are keys of the Map. For example,
84 * scriptObj.keyName can be used to access value associated with given key.<br>
85 * Example:
86 * <pre>
87 * <code>
88 *     var x = java.lang.SystemProperties();
89 *     var y = jmap(x);
90 *     println(y['java.class.path']); // prints java.class.path System property
91 *     delete y['java.class.path']; // remove java.class.path System property
92 * </code>
93 * </pre>
94 *
95 * @param map java.util.Map instance that will be wrapped
96 * @constructor
97 */
98function jmap(map) {
99    return new JSAdapter({
100        __get__ : function(name) {
101            if (map.containsKey(name)) {
102                return map.get(name);
103            } else {
104                return undefined;
105            }
106        },
107        __has__ :  function(name) {
108            return map.containsKey(name);
109        },
110
111        __delete__ : function (name) {
112            return map.remove(name);
113        },
114        __put__ : function(name, value) {
115            map.put(name, value);
116        },
117        __getIds__ : function() {
118            return map.keySet().toArray();
119        },
120        toString: function() {
121            return map.toString();
122        }
123    });
124}
125
126/**
127 * Creates a convenient script object to deal with java.util.List instances.
128 * The result script object behaves like an array. For example,
129 * scriptObj[index] syntax can be used to access values in the List instance.
130 * 'length' field gives size of the List. <br>
131 *
132 * Example:
133 * <pre>
134 * <code>
135 *    var x = new java.util.ArrayList(4);
136 *    x.add('Java');
137 *    x.add('JavaScript');
138 *    x.add('SQL');
139 *    x.add('XML');
140 *
141 *    var y = jlist(x);
142 *    println(y[2]); // prints third element of list
143 *    println(y.length); // prints size of the list
144 *
145 * @param map java.util.List instance that will be wrapped
146 * @constructor
147 */
148function jlist(list) {
149    function isValid(index) {
150        return typeof(index) == 'number' &&
151            index > -1 && index < list.size();
152    }
153    return new JSAdapter({
154        __get__ :  function(name) {
155            if (isValid(name)) {
156                return list.get(name);
157            } else if (name == 'length') {
158                return list.size();
159            } else {
160                return undefined;
161            }
162        },
163        __has__ : function (name) {
164            return isValid(name) || name == 'length';
165        },
166        __delete__ : function(name) {
167            if (isValid(name)) {
168                list.remove(name);
169            }
170        },
171        __put__ : function(name, value) {
172            if (isValid(name)) {
173                list.set(name, value);
174            }
175        },
176        __getIds__: function() {
177            var res = new Array(list.size());
178            for (var i = 0; i < res.length; i++) {
179                res[i] = i;
180            }
181            return res;
182        },
183        toString: function() {
184            return list.toString();
185        }
186    });
187}
188
189/**
190 * This is java.lang.System properties wrapped by JSAdapter.
191 * For eg. to access java.class.path property, you can use
192 * the syntax sysProps["java.class.path"]
193 */
194var sysProps = new JSAdapter({
195    __get__ : function (name) {
196        return java.lang.System.getProperty(name);
197    },
198    __has__ : function (name) {
199        return java.lang.System.getProperty(name) != null;
200    },
201    __getIds__ : function() {
202        return java.lang.System.getProperties().keySet().toArray();
203    },
204    __delete__ : function(name) {
205        java.lang.System.clearProperty(name);
206        return true;
207    },
208    __put__ : function (name, value) {
209        java.lang.System.setProperty(name, value);
210    },
211    toString: function() {
212        return "<system properties>";
213    }
214});
215
216// stdout, stderr & stdin
217var out = java.lang.System.out;
218var err = java.lang.System.err;
219// can't use 'in' because it is a JavaScript keyword :-(
220var inp = java.lang.System["in"];
221
222var BufferedInputStream = java.io.BufferedInputStream;
223var BufferedOutputStream = java.io.BufferedOutputStream;
224var BufferedReader = java.io.BufferedReader;
225var DataInputStream = java.io.DataInputStream;
226var File = java.io.File;
227var FileInputStream = java.io.FileInputStream;
228var FileOutputStream = java.io.FileOutputStream;
229var InputStream = java.io.InputStream;
230var InputStreamReader = java.io.InputStreamReader;
231var OutputStream = java.io.OutputStream;
232var Reader = java.io.Reader;
233var URL = java.net.URL;
234
235/**
236 * Generic any object to input stream mapper
237 * @param str input file name, URL or InputStream
238 * @return InputStream object
239 * @private
240 */
241function inStream(str) {
242    if (typeof(str) == "string") {
243        // '-' means standard input
244        if (str == '-') {
245            return java.lang.System["in"];
246        }
247        // try file first
248        var file = null;
249        try {
250            file = pathToFile(str);
251        } catch (e) {
252        }
253        if (file && file.exists()) {
254            return new FileInputStream(file);
255        } else {
256            try {
257                // treat the string as URL
258                return new URL(str).openStream();
259            } catch (e) {
260                throw 'file or URL ' + str + ' not found';
261            }
262        }
263    } else {
264        if (str instanceof InputStream) {
265            return str;
266        } else if (str instanceof URL) {
267            return str.openStream();
268        } else if (str instanceof File) {
269            return new FileInputStream(str);
270        }
271    }
272    // everything failed, just give input stream
273    return java.lang.System["in"];
274}
275
276/**
277 * Generic any object to output stream mapper
278 *
279 * @param out output file name or stream
280 * @return OutputStream object
281 * @private
282 */
283function outStream(out) {
284    if (typeof(out) == "string") {
285        if (out == '>') {
286            return java.lang.System.out;
287        } else {
288            // treat it as file
289            return new FileOutputStream(pathToFile(out));
290        }
291    } else {
292        if (out instanceof OutputStream) {
293            return out;
294        } else if (out instanceof File) {
295            return new FileOutputStream(out);
296        }
297    }
298
299    // everything failed, just return System.out
300    return java.lang.System.out;
301}
302
303/**
304 * stream close takes care not to close stdin, out & err.
305 * @private
306 */
307function streamClose(stream) {
308    if (stream) {
309        if (stream != java.lang.System["in"] &&
310            stream != java.lang.System.out &&
311            stream != java.lang.System.err) {
312            try {
313                stream.close();
314            } catch (e) {
315                println(e);
316            }
317        }
318    }
319}
320
321/**
322 * Loads and evaluates JavaScript code from a stream or file or URL<br>
323 *
324 * Examples:
325 * <pre>
326 * <code>
327 *    load('test.js'); // load script file 'test.js'
328 *    load('http://java.sun.com/foo.js'); // load from a URL
329 * </code>
330 * </pre>
331 *
332 * @param str input from which script is loaded and evaluated
333 */
334if (typeof(load) == 'undefined') {
335    this.load = function(str) {
336        var stream = inStream(str);
337        var bstream = new BufferedInputStream(stream);
338        var reader = new BufferedReader(new InputStreamReader(bstream));
339        var oldFilename = engine.get(engine.FILENAME);
340        engine.put(engine.FILENAME, str);
341        try {
342            engine.eval(reader);
343        } finally {
344            engine.put(engine.FILENAME, oldFilename);
345            streamClose(stream);
346        }
347    }
348}
349
350// file system utilities
351
352/**
353 * Creates a Java byte[] of given length
354 * @param len size of the array to create
355 * @private
356 */
357function javaByteArray(len) {
358    return java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, len);
359}
360
361var curDir = new File('.');
362
363/**
364 * Print present working directory
365 */
366function pwd() {
367    println(curDir.getAbsolutePath());
368}
369
370/**
371 * Changes present working directory to given directory
372 * @param target directory to change to. optional, defaults to user's HOME
373 */
374function cd(target) {
375    if (target == undefined) {
376        target = sysProps["user.home"];
377    }
378    if (!(target instanceof File)) {
379        target = pathToFile(target);
380    }
381    if (target.exists() && target.isDirectory()) {
382        curDir = target;
383    } else {
384        println(target + " is not a directory");
385    }
386}
387
388/**
389 * Converts path to java.io.File taking care of shell present working dir
390 *
391 * @param pathname file path to be converted
392 * @private
393 */
394function pathToFile(pathname) {
395    var tmp = pathname;
396    if (!(tmp instanceof File)) {
397        tmp = new File(tmp);
398    }
399    if (!tmp.isAbsolute()) {
400        return new File(curDir, pathname);
401    } else {
402        return tmp;
403    }
404}
405
406/**
407 * Copies a file or URL or stream to another file or stream
408 *
409 * @param from input file or URL or stream
410 * @param to output stream or file
411 */
412function cp(from, to) {
413    if (from == to) {
414        println("file " + from + " cannot be copied onto itself!");
415        return;
416    }
417    var inp = inStream(from);
418    var out = outStream(to);
419    var binp = new BufferedInputStream(inp);
420    var bout = new BufferedOutputStream(out);
421    var buff = javaByteArray(1024);
422    var len;
423    while ((len = binp.read(buff)) > 0 )
424        bout.write(buff, 0, len);
425
426    bout.flush();
427    streamClose(inp);
428    streamClose(out);
429}
430
431/**
432 * Shows the content of a file or URL or any InputStream<br>
433 * Examples:
434 * <pre>
435 * <code>
436 *    cat('test.txt'); // show test.txt file contents
437 *    cat('http://java.net'); // show the contents from the URL http://java.net
438 * </code>
439 * </pre>
440 * @param obj input to show
441 * @param pattern optional. show only the lines matching the pattern
442 */
443function cat(obj, pattern) {
444    if (obj instanceof File && obj.isDirectory()) {
445        ls(obj);
446        return;
447    }
448
449    var inp = null;
450    if (!(obj instanceof Reader)) {
451        inp = inStream(obj);
452        obj = new BufferedReader(new InputStreamReader(inp));
453    }
454    var line;
455    if (pattern) {
456        var count = 1;
457        while ((line=obj.readLine()) != null) {
458            if (line.match(pattern)) {
459                println(count + "\t: " + line);
460            }
461            count++;
462        }
463    } else {
464        while ((line=obj.readLine()) != null) {
465            println(line);
466        }
467    }
468}
469
470/**
471 * Returns directory part of a filename
472 *
473 * @param pathname input path name
474 * @return directory part of the given file name
475 */
476function dirname(pathname) {
477    var dirName = ".";
478    // Normalize '/' to local file separator before work.
479    var i = pathname.replace('/', File.separatorChar ).lastIndexOf(
480        File.separator );
481    if ( i != -1 )
482        dirName = pathname.substring(0, i);
483    return dirName;
484}
485
486/**
487 * Creates a new dir of given name
488 *
489 * @param dir name of the new directory
490 */
491function mkdir(dir) {
492    dir = pathToFile(dir);
493    println(dir.mkdir()? "created" : "can not create dir");
494}
495
496/**
497 * Creates the directory named by given pathname, including
498 * any necessary but nonexistent parent directories.
499 *
500 * @param dir input path name
501 */
502function mkdirs(dir) {
503    dir = pathToFile(dir);
504    println(dir.mkdirs()? "created" : "can not create dirs");
505}
506
507/**
508 * Removes a given file
509 *
510 * @param pathname name of the file
511 */
512function rm(pathname) {
513    var file = pathToFile(pathname);
514    if (!file.exists()) {
515        println("file not found: " + pathname);
516        return false;
517    }
518    // note that delete is a keyword in JavaScript!
519    println(file["delete"]()? "deleted" : "can not delete");
520}
521
522/**
523 * Removes a given directory
524 *
525 * @param pathname name of the directory
526 */
527function rmdir(pathname) {
528    rm(pathname);
529}
530
531/**
532 * Synonym for 'rm'
533 */
534function del(pathname) {
535    rm(pathname);
536}
537
538/**
539 * Moves a file to another
540 *
541 * @param from original name of the file
542 * @param to new name for the file
543 */
544function mv(from, to) {
545    println(pathToFile(from).renameTo(pathToFile(to))?
546        "moved" : "can not move");
547}
548
549/**
550 * Synonym for 'mv'.
551 */
552function ren(from, to) {
553    mv(from, to);
554}
555
556var months = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
557        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
558
559/**
560 * Helper function called by ls
561 * @private
562 */
563function printFile(f) {
564    var sb = new java.lang.StringBuffer();
565    sb.append(f.isDirectory()? "d" : "-");
566    sb.append(f.canRead() ? "r": "-" );
567    sb.append(f.canWrite() ? "w": "-" );
568    sb.append(" ");
569
570    var d = new java.util.Date(f.lastModified());
571    var c = new java.util.GregorianCalendar();
572    c.setTime(d);
573    var day    = c.get(java.util.Calendar.DAY_OF_MONTH);
574    sb.append(months[c.get(java.util.Calendar.MONTH)]
575         + " " + day );
576    if (day < 10) {
577        sb.append(" ");
578    }
579
580    // to get fixed length 'length' field
581    var fieldlen = 8;
582    var len = new java.lang.StringBuffer();
583    for(var j=0; j<fieldlen; j++)
584        len.append(" ");
585    len.insert(0, java.lang.Long.toString(f.length()));
586    len.setLength(fieldlen);
587    // move the spaces to the front
588    var si = len.toString().indexOf(" ");
589    if ( si != -1 ) {
590        var pad = len.toString().substring(si);
591        len.setLength(si);
592        len.insert(0, pad);
593    }
594    sb.append(len.toString());
595    sb.append(" ");
596    sb.append(f.getName());
597    if (f.isDirectory()) {
598        sb.append('/');
599    }
600    println(sb.toString());
601}
602
603/**
604 * Lists the files in a directory
605 *
606 * @param dir directory from which to list the files. optional, default to pwd
607 * @param filter pattern to filter the files listed. optional, default is '.'.
608 */
609function ls(dir, filter) {
610    if (dir) {
611        dir = pathToFile(dir);
612    } else {
613        dir = curDir;
614    }
615    if (dir.isDirectory()) {
616        var files = dir.listFiles();
617        for (var i in files) {
618            var f = files[i];
619            if (filter) {
620                if(!f.getName().match(filter)) {
621                    continue;
622                }
623            }
624            printFile(f);
625        }
626    } else {
627        printFile(dir);
628    }
629}
630
631/**
632 * Synonym for 'ls'.
633 */
634function dir(d, filter) {
635    ls(d, filter);
636}
637
638/**
639 * Unix-like grep, but accepts JavaScript regex patterns
640 *
641 * @param pattern to search in files
642 * @param files one or more files
643 */
644function grep(pattern, files /*, one or more files */) {
645    if (arguments.length < 2) return;
646    for (var i = 1; i < arguments.length; i++) {
647        println(arguments[i] + ":");
648        cat(arguments[i], pattern);
649    }
650}
651
652/**
653 * Find in files. Calls arbitrary callback function
654 * for each matching file.<br>
655 *
656 * Examples:
657 * <pre>
658 * <code>
659 *    find('.')
660 *    find('.', '.*\.class', rm);  // remove all .class files
661 *    find('.', '.*\.java');       // print fullpath of each .java file
662 *    find('.', '.*\.java', cat);  // print all .java files
663 * </code>
664 * </pre>
665 *
666 * @param dir directory to search files
667 * @param pattern to search in the files
668 * @param callback function to call for matching files
669 */
670function find(dir, pattern, callback) {
671    dir = pathToFile(dir);
672    if (!callback) callback = print;
673    var files = dir.listFiles();
674    for (var f in files) {
675        var file = files[f];
676        if (file.isDirectory()) {
677            find(file, pattern, callback);
678        } else {
679            if (pattern) {
680                if (file.getName().match(pattern)) {
681                    callback(file);
682                }
683            } else {
684                callback(file);
685            }
686        }
687    }
688}
689
690// process utilities
691
692/**
693 * Exec's a child process, waits for completion &amp; returns exit code
694 *
695 * @param cmd command to execute in child process
696 */
697function exec(cmd) {
698    var process = java.lang.Runtime.getRuntime().exec(cmd);
699    var inp = new DataInputStream(process.getInputStream());
700    var line = null;
701    while ((line = inp.readLine()) != null) {
702        println(line);
703    }
704    process.waitFor();
705    $exit = process.exitValue();
706}
707
708if (typeof(exit) == 'undefined') {
709    /**
710     * Exit the shell program.
711     *
712     * @param exitCode integer code returned to OS shell.
713     * optional, defaults to 0
714     */
715    this.exit = function (code) {
716        if (code) {
717            java.lang.System.exit(code + 0);
718        } else {
719            java.lang.System.exit(0);
720        }
721    }
722}
723
724if (typeof(quit) == 'undefined') {
725    /**
726     * synonym for exit
727     */
728    this.quit = function (code) {
729        exit(code);
730    }
731}
732
733// XML utilities
734
735/**
736 * Converts input to DOM Document object
737 *
738 * @param inp file or reader. optional, without this param,
739 * this function returns a new DOM Document.
740 * @return returns a DOM Document object
741 */
742function XMLDocument(inp) {
743    var factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
744    var builder = factory.newDocumentBuilder();
745    if (inp) {
746        if (typeof(inp) == "string") {
747            return builder.parse(pathToFile(inp));
748        } else {
749            return builder.parse(inp);
750        }
751    } else {
752        return builder.newDocument();
753    }
754}
755
756/**
757 * Converts arbitrary stream, file, URL to XMLSource
758 *
759 * @param inp input stream or file or URL
760 * @return XMLSource object
761 */
762function XMLSource(inp) {
763    if (inp instanceof javax.xml.transform.Source) {
764        return inp;
765    } else if (inp instanceof Packages.org.w3c.dom.Document) {
766        return new javax.xml.transform.dom.DOMSource(inp);
767    } else {
768        inp = new BufferedInputStream(inStream(inp));
769        return new javax.xml.transform.stream.StreamSource(inp);
770    }
771}
772
773/**
774 * Converts arbitrary stream, file to XMLResult
775 *
776 * @param inp output stream or file
777 * @return XMLResult object
778 */
779function XMLResult(out) {
780    if (out instanceof javax.xml.transform.Result) {
781        return out;
782    } else if (out instanceof Packages.org.w3c.dom.Document) {
783        return new javax.xml.transform.dom.DOMResult(out);
784    } else {
785        out = new BufferedOutputStream(outStream(out));
786        return new javax.xml.transform.stream.StreamResult(out);
787    }
788}
789
790/**
791 * Perform XSLT transform
792 *
793 * @param inp Input XML to transform (URL, File or InputStream)
794 * @param style XSL Stylesheet to be used (URL, File or InputStream). optional.
795 * @param out Output XML (File or OutputStream
796 */
797function XSLTransform(inp, style, out) {
798    switch (arguments.length) {
799    case 2:
800        inp = arguments[0];
801        out = arguments[1];
802        break;
803    case 3:
804        inp = arguments[0];
805        style = arguments[1];
806        out = arguments[2];
807        break;
808    default:
809        println("XSL transform requires 2 or 3 arguments");
810        return;
811    }
812
813    var factory = javax.xml.transform.TransformerFactory.newInstance();
814    var transformer;
815    if (style) {
816        transformer = factory.newTransformer(XMLSource(style));
817    } else {
818        transformer = factory.newTransformer();
819    }
820    var source = XMLSource(inp);
821    var result = XMLResult(out);
822    transformer.transform(source, result);
823    if (source.getInputStream) {
824        streamClose(source.getInputStream());
825    }
826    if (result.getOutputStream) {
827        streamClose(result.getOutputStream());
828    }
829}
830
831// miscellaneous utilities
832
833/**
834 * Prints which command is selected from PATH
835 *
836 * @param cmd name of the command searched from PATH
837 */
838function which(cmd) {
839    var st = new java.util.StringTokenizer(env.PATH, File.pathSeparator);
840    while (st.hasMoreTokens()) {
841        var file = new File(st.nextToken(), cmd);
842        if (file.exists()) {
843            println(file.getAbsolutePath());
844            return;
845        }
846    }
847}
848
849/**
850 * Prints IP addresses of given domain name
851 *
852 * @param name domain name
853 */
854function ip(name) {
855    var addrs = InetAddress.getAllByName(name);
856    for (var i in addrs) {
857        println(addrs[i]);
858    }
859}
860
861/**
862 * Prints current date in current locale
863 */
864function date() {
865    println(new Date().toLocaleString());
866}
867
868/**
869 * Echoes the given string arguments
870 */
871function echo(x) {
872    for (var i = 0; i < arguments.length; i++) {
873        println(arguments[i]);
874    }
875}
876
877if (typeof(printf) == 'undefined') {
878    /**
879     * This is C-like printf
880     *
881     * @param format string to format the rest of the print items
882     * @param args variadic argument list
883     */
884    this.printf = function (format, args/*, more args*/) {
885        var array = java.lang.reflect.Array.newInstance(java.lang.Object,
886                    arguments.length - 1);
887        for (var i = 0; i < array.length; i++) {
888            array[i] = arguments[i+1];
889        }
890        java.lang.System.out.printf(format, array);
891    }
892}
893
894/**
895 * Reads one or more lines from stdin after printing prompt
896 *
897 * @param prompt optional, default is '>'
898 * @param multiline to tell whether to read single line or multiple lines
899 */
900function read(prompt, multiline) {
901    if (!prompt) {
902        prompt = '>';
903    }
904    var inp = java.lang.System["in"];
905    var reader = new BufferedReader(new InputStreamReader(inp));
906    if (multiline) {
907        var line = '';
908        while (true) {
909            java.lang.System.err.print(prompt);
910            java.lang.System.err.flush();
911            var tmp = reader.readLine();
912            if (tmp == '' || tmp == null) break;
913            line += tmp + '\n';
914        }
915        return line;
916    } else {
917        java.lang.System.err.print(prompt);
918        java.lang.System.err.flush();
919        return reader.readLine();
920    }
921}
922
923if (typeof(println) == 'undefined') {
924    // just synonym to print
925    this.println = print;
926}
927
928