# autoimports script requires -scripting mode

/*
 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * It is tedious to import Java classes used in a script. Sometimes it is easier
 * use simple names of java classes and have a script auto import Java classes. 
 * You can load this script at the start of an interactive jjs session or at the
 * start of your script. This script defines a __noSuchProperty__ hook to auto 
 * import Java classes as needed and when they are referred to for the first time
 * in your script. You can also call the "autoimports" function to print script 
 * statements that you need to use in your script, i.e., have the function generate
 * a script to import Java classes used by your script so far. After running your
 * script, you can call autoimports to get the exact Java imports you need and replace
 * the autoimports load with the generated import statements (to avoid costly init of
 * the autoimports script).
 */

(function() {
    var ArrayList = Java.type("java.util.ArrayList");
    var HashMap = Java.type("java.util.HashMap");
    var JarFile = Java.type("java.util.jar.JarFile");
    var File = Java.type("java.io.File");
    var Files = Java.type("java.nio.file.Files");
    var FileSystems = Java.type("java.nio.file.FileSystems");
    var System = Java.type("java.lang.System");
    var URI = Java.type("java.net.URI");

    // initialize a class to package map by iterating all
    // classes available in the system by walking through "jrt fs"

    var clsToPkg = new HashMap();

    // locate rt.jar from sun.boot.class.path
    function findRtJar() {
        var paths = System.getProperty("sun.boot.class.path").split(File.pathSeparator);
        for each (var p in paths) {
            if (p.endsWith("rt.jar") && new File(p).exists()) {
                return p;
            }
        }
    }


    function addToClsToPkg(c, p) {
        if (clsToPkg.containsKey(c)) {
            var val = clsToPkg.get(c);
            if (val instanceof ArrayList) {
                val.add(p);
            } else {
                var al = new ArrayList();
                al.add(val);
                al.add(p);
                clsToPkg.put(c, al);
            }
        } else {
            clsToPkg.put(c, p);
        }
    }

    // handle collision and allow user to choose package
    function getPkgOfCls(c) {
        var val = clsToPkg.get(c);
        if (val instanceof ArrayList) {
            var count = 1;
            print("Multiple matches for " + c + ", choose package:");
            for each (var v in val) {
                print(count + ". " + v);
                count++;
            }
            var choice = parseInt(readLine());
            if (isNaN(choice) || choice < 1 || choice > val.size()) {
                print("invalid choice: " + choice);
                return undefined;
            }
            return val.get(choice - 1);
        } else {
            return val;
        }
    }

    var rtJar = findRtJar();
    var stream = new JarFile(rtJar).stream();
    try {
        stream.forEach(
            function(entry) {
                var str = entry.name;
                if (str.endsWith(".class")) {
                    if (str.startsWith("java") ||
                        str.startsWith("javax") ||
                        str.startsWith("org")) {
                        var lastIdx = str.lastIndexOf('/');
                        if (lastIdx != -1) {
                            var pkg = str.substring(0, lastIdx).replaceAll('/', '.');
                            var cls = str.substring(lastIdx + 1, str.lastIndexOf(".class"));
                            addToClsToPkg(cls, pkg);
                        }
                    }
                }
            });
    } finally {
        stream.close();
    }

    var imports = new ArrayList();
    var global = this;
    var oldNoSuchProp = global.__noSuchProperty__;
    this.__noSuchProperty__ = function(name) {
        'use strict';

        if (clsToPkg.containsKey(name)) {
            var pkg = getPkgOfCls(name);
            if (pkg) {
                var clsName = pkg + "." + name;
                imports.add("var " + name + " = Java.type('" + clsName + "');");
                return global[name] = Java.type(clsName);
            }
        } else if (typeof oldNoSuchProp == 'function') {
            return oldNoSuchProp.call(this, name);
        }

        if (typeof this == 'undefined') {
            throw new ReferenceError(name);
        } else {
            return undefined;
        }
    }

    this.autoimports = function() {
        for each (var im in imports) {
            print(im);
        }
    }
})();
