8134255: Implement tab-completion for java package prefixes and package names
Reviewed-by: attila, mhaupt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/samples/classes.js Sun Aug 23 10:02:14 2015 +0530
@@ -0,0 +1,47 @@
+// Usage: jjs classes.js [ -- <java_package_name > ]
+
+/*
+ * 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.
+ */
+
+// print all Java classes of the given package and its subpackages.
+var pkg = arguments.length > 0? arguments[0] : "java.lang";
+
+with (new JavaImporter(javax.tools, java.util)) {
+ var compiler = ToolProvider.systemJavaCompiler;
+ var fm = compiler.getStandardFileManager(null, null, null);
+ var kinds = EnumSet.of(JavaFileObject.Kind.CLASS);
+ var loc = StandardLocation.PLATFORM_CLASS_PATH;
+ var itr = fm.list(loc, pkg, kinds, true).iterator();
+ while(itr.hasNext()) {
+ print(fm.inferBinaryName(loc, itr.next()));
+ }
+ fm.close();
+}
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java Fri Aug 21 18:01:23 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java Sun Aug 23 10:02:14 2015 +0530
@@ -25,6 +25,9 @@
package jdk.nashorn.tools.jjs;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
import java.util.function.Consumer;
import jdk.nashorn.api.scripting.AbstractJSObject;
import jdk.nashorn.internal.runtime.JSType;
@@ -35,6 +38,13 @@
* for editing and evaluating scripts from it.
*/
final class EditObject extends AbstractJSObject {
+ private static final Set<String> props;
+ static {
+ final HashSet<String> s = new HashSet<>();
+ s.add("editor");
+ props = Collections.unmodifiableSet(s);
+ }
+
private final Consumer<String> errorHandler;
private final Consumer<String> evaluator;
private final Console console;
@@ -61,6 +71,11 @@
}
@Override
+ public Set<String> keySet() {
+ return props;
+ }
+
+ @Override
public Object getMember(final String name) {
if (name.equals("editor")) {
return editor;
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java Fri Aug 21 18:01:23 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java Sun Aug 23 10:02:14 2015 +0530
@@ -49,6 +49,8 @@
public final class Main extends Shell {
private Main() {}
+ static final boolean DEBUG = Boolean.getBoolean("nashorn.jjs.debug");
+
// file where history is persisted.
private static final File HIST_FILE = new File(new File(System.getProperty("user.home")), ".jjs.history");
@@ -100,7 +102,8 @@
final PrintWriter err = context.getErr();
final Global oldGlobal = Context.getGlobal();
final boolean globalChanged = (oldGlobal != global);
- final Completer completer = new NashornCompleter(context, global, this);
+ final PropertiesHelper propsHelper = new PropertiesHelper(env._classpath);
+ final Completer completer = new NashornCompleter(context, global, this, propsHelper);
try (final Console in = new Console(System.in, System.out, HIST_FILE, completer)) {
if (globalChanged) {
@@ -161,6 +164,13 @@
if (globalChanged) {
Context.setGlobal(oldGlobal);
}
+ try {
+ propsHelper.close();
+ } catch (final Exception exp) {
+ if (DEBUG) {
+ exp.printStackTrace();
+ }
+ }
}
return SUCCESS;
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java Fri Aug 21 18:01:23 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java Sun Aug 23 10:02:14 2015 +0530
@@ -55,12 +55,15 @@
private final Context context;
private final Global global;
private final PartialParser partialParser;
+ private final PropertiesHelper propsHelper;
private final Parser parser;
- NashornCompleter(final Context context, final Global global, final PartialParser partialParser) {
+ NashornCompleter(final Context context, final Global global,
+ final PartialParser partialParser, final PropertiesHelper propsHelper) {
this.context = context;
this.global = global;
this.partialParser = partialParser;
+ this.propsHelper = propsHelper;
this.parser = Parser.create();
}
@@ -122,19 +125,22 @@
Object obj = null;
try {
obj = context.eval(global, objExprCode, global, "<suggestions>");
- } catch (Exception ignored) {
- // throw the exception - this is during tab-completion
+ } catch (Exception exp) {
+ // throw away the exception - this is during tab-completion
+ if (Main.DEBUG) {
+ exp.printStackTrace();
+ }
}
if (obj != null && obj != ScriptRuntime.UNDEFINED) {
if (endsWithDot) {
// no user specified "prefix". List all properties of the object
- result.addAll(PropertiesHelper.getProperties(obj));
+ result.addAll(propsHelper.getProperties(obj));
return cursor;
} else {
// list of properties matching the user specified prefix
final String prefix = select.getIdentifier();
- result.addAll(PropertiesHelper.getProperties(obj, prefix));
+ result.addAll(propsHelper.getProperties(obj, prefix));
return cursor - prefix.length();
}
}
@@ -145,7 +151,7 @@
private int completeIdentifier(final String test, final int cursor, final List<CharSequence> result,
final IdentifierTree ident) {
final String name = ident.getName();
- result.addAll(PropertiesHelper.getProperties(global, name));
+ result.addAll(propsHelper.getProperties(global, name));
return cursor - name.length();
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PackagesHelper.java Sun Aug 23 10:02:14 2015 +0530
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.tools.jjs;
+
+import java.io.IOException;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager.Location;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+/**
+ * A helper class to compute properties of a Java package object. Properties of
+ * package object are (simple) top level class names in that java package and
+ * immediate subpackages of that package.
+ */
+final class PackagesHelper {
+ // JavaCompiler may be null on certain platforms (eg. JRE)
+ private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+
+ /**
+ * Is Java package properties helper available?
+ *
+ * @return true if package properties support is available
+ */
+ static boolean isAvailable() {
+ return compiler != null;
+ }
+
+ private final StandardJavaFileManager fm;
+ private final Set<JavaFileObject.Kind> fileKinds;
+
+ /**
+ * Construct a new PackagesHelper.
+ *
+ * @param classPath Class path to compute properties of java package objects
+ */
+ PackagesHelper(final String classPath) throws IOException {
+ assert isAvailable() : "no java compiler found!";
+
+ fm = compiler.getStandardFileManager(null, null, null);
+ fileKinds = EnumSet.of(JavaFileObject.Kind.CLASS);
+
+ if (classPath != null && !classPath.isEmpty()) {
+ fm.setLocation(StandardLocation.CLASS_PATH, getFiles(classPath));
+ } else {
+ // no classpath set. Make sure that it is empty and not any default like "."
+ fm.setLocation(StandardLocation.CLASS_PATH, Collections.<File>emptyList());
+ }
+ }
+
+ // LRU cache for java package properties lists
+ private final LinkedHashMap<String, List<String>> propsCache =
+ new LinkedHashMap<>(32, 0.75f, true) {
+ private static final int CACHE_SIZE = 100;
+ private static final long serialVersionUID = 1;
+
+ @Override
+ protected boolean removeEldestEntry(final Map.Entry<String, List<String>> eldest) {
+ return size() > CACHE_SIZE;
+ }
+ };
+
+ /**
+ * Return the list of properties of the given Java package or package prefix
+ *
+ * @param pkg Java package name or package prefix name
+ * @return the list of properties of the given Java package or package prefix
+ */
+ List<String> getPackageProperties(final String pkg) {
+ // check the cache first
+ if (propsCache.containsKey(pkg)) {
+ return propsCache.get(pkg);
+ }
+
+ try {
+ // make sorted list of properties
+ final List<String> props = new ArrayList<>(listPackage(pkg));
+ Collections.sort(props);
+ propsCache.put(pkg, props);
+ return props;
+ } catch (final IOException exp) {
+ if (Main.DEBUG) {
+ exp.printStackTrace();
+ }
+ return Collections.<String>emptyList();
+ }
+ }
+
+ public void close() throws IOException {
+ fm.close();
+ }
+
+ private Set<String> listPackage(final String pkg) throws IOException {
+ final Set<String> props = new HashSet<>();
+ listPackage(StandardLocation.PLATFORM_CLASS_PATH, pkg, props);
+ listPackage(StandardLocation.CLASS_PATH, pkg, props);
+ return props;
+ }
+
+ private void listPackage(final Location loc, final String pkg, final Set<String> props)
+ throws IOException {
+ for (JavaFileObject file : fm.list(loc, pkg, fileKinds, true)) {
+ final String binaryName = fm.inferBinaryName(loc, file);
+ // does not start with the given package prefix
+ if (!binaryName.startsWith(pkg + ".")) {
+ continue;
+ }
+
+ final int nextDot = binaryName.indexOf('.', pkg.length() + 1);
+ final int start = pkg.length() + 1;
+
+ if (nextDot != -1) {
+ // subpackage - eg. "regex" for "java.util"
+ props.add(binaryName.substring(start, nextDot));
+ } else {
+ // class - filter out nested, inner, anonymous, local classes.
+ // Dynalink supported public nested classes as properties of
+ // StaticClass object anyway. We don't want to expose those
+ // "$" internal names as properties of package object.
+
+ final String clsName = binaryName.substring(start);
+ if (clsName.indexOf('$') == -1) {
+ props.add(clsName);
+ }
+ }
+ }
+ }
+
+ // return list of File objects for the given class path
+ private static List<File> getFiles(final String classPath) {
+ return Stream.of(classPath.split(File.pathSeparator))
+ .map(File::new)
+ .collect(Collectors.toList());
+ }
+}
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java Fri Aug 21 18:01:23 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java Sun Aug 23 10:02:14 2015 +0530
@@ -25,6 +25,7 @@
package jdk.nashorn.tools.jjs;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -32,6 +33,7 @@
import java.util.WeakHashMap;
import java.util.stream.Collectors;
import jdk.nashorn.internal.runtime.JSType;
+import jdk.nashorn.internal.runtime.NativeJavaPackage;
import jdk.nashorn.internal.runtime.PropertyMap;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.ScriptRuntime;
@@ -41,19 +43,59 @@
* A helper class to get properties of a given object for source code completion.
*/
final class PropertiesHelper {
- private PropertiesHelper() {}
-
+ // Java package properties helper, may be null
+ private PackagesHelper pkgsHelper;
// cached properties list
- private static final WeakHashMap<Object, List<String>> propsCache = new WeakHashMap<>();
+ private final WeakHashMap<Object, List<String>> propsCache = new WeakHashMap<>();
- // returns the list of properties of the given object
- static List<String> getProperties(final Object obj) {
+ /**
+ * Construct a new PropertiesHelper.
+ *
+ * @param classPath Class path to compute properties of java package objects
+ */
+ PropertiesHelper(final String classPath) {
+ if (PackagesHelper.isAvailable()) {
+ try {
+ this.pkgsHelper = new PackagesHelper(classPath);
+ } catch (final IOException exp) {
+ if (Main.DEBUG) {
+ exp.printStackTrace();
+ }
+ this.pkgsHelper = null;
+ }
+ }
+ }
+
+ void close() throws Exception {
+ propsCache.clear();
+ pkgsHelper.close();
+ }
+
+ /**
+ * returns the list of properties of the given object.
+ *
+ * @param obj object whose property list is returned
+ * @return the list of properties of the given object
+ */
+ List<String> getProperties(final Object obj) {
assert obj != null && obj != ScriptRuntime.UNDEFINED;
+ // wrap JS primitives as objects before gettting properties
if (JSType.isPrimitive(obj)) {
return getProperties(JSType.toScriptObject(obj));
}
+ // Handle Java package prefix case first. Should do it before checking
+ // for its super class ScriptObject!
+ if (obj instanceof NativeJavaPackage) {
+ if (pkgsHelper != null) {
+ return pkgsHelper.getPackageProperties(((NativeJavaPackage)obj).getName());
+ } else {
+ return Collections.<String>emptyList();
+ }
+ }
+
+ // script object - all inherited and non-enumerable, non-index properties
if (obj instanceof ScriptObject) {
final ScriptObject sobj = (ScriptObject)obj;
final PropertyMap pmap = sobj.getMap();
@@ -71,6 +113,7 @@
return props;
}
+ // java class case - don't refer to StaticClass directly
if (NativeJava.isType(ScriptRuntime.UNDEFINED, obj)) {
if (propsCache.containsKey(obj)) {
return propsCache.get(obj);
@@ -82,6 +125,7 @@
return props;
}
+ // any other Java object
final Class<?> clazz = obj.getClass();
if (propsCache.containsKey(clazz)) {
return propsCache.get(clazz);
@@ -94,8 +138,14 @@
return props;
}
- // returns the list of properties of the given object that start with the given prefix
- static List<String> getProperties(final Object obj, final String prefix) {
+ /**
+ * Returns the list of properties of the given object that start with the given prefix.
+ *
+ * @param obj object whose property list is returned
+ * @param prefix property prefix to be matched
+ * @return the list of properties of the given object
+ */
+ List<String> getProperties(final Object obj, final String prefix) {
assert prefix != null && !prefix.isEmpty();
return getProperties(obj).stream()
.filter(s -> s.startsWith(prefix))
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptEnvironment.java Fri Aug 21 18:01:23 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptEnvironment.java Sun Aug 23 10:02:14 2015 +0530
@@ -61,6 +61,9 @@
/** Size of the per-global Class cache size */
public final int _class_cache_size;
+ /** -classpath value. */
+ public final String _classpath;
+
/** Only compile script, do not run it or generate other ScriptObjects */
public final boolean _compile_only;
@@ -220,6 +223,7 @@
this.options = options;
_class_cache_size = options.getInteger("class.cache.size");
+ _classpath = options.getString("classpath");
_compile_only = options.getBoolean("compile.only");
_const_as_var = options.getBoolean("const.as.var");
_debug_lines = options.getBoolean("debug.lines");