8217047: Provide a way to inject missing parameter names
Summary: Adding a way to provide parameter names that are missing in the classfiles.
Reviewed-by: darcy, jjg
--- a/src/jdk.compiler/share/classes/com/sun/source/util/JavacTask.java Thu Apr 11 15:36:09 2019 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/source/util/JavacTask.java Thu Apr 11 17:55:18 2019 +0200
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2019, 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
@@ -29,6 +29,7 @@
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
+import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
@@ -137,6 +138,27 @@
public abstract void removeTaskListener(TaskListener taskListener);
/**
+ * Sets the specified {@link ParameterNameProvider}. It may be used when
+ * {@link VariableElement#getSimpleName()} is called for a method parameter
+ * for which an authoritative name is not found. The given
+ * {@code ParameterNameProvider} may infer a user-friendly name
+ * for the method parameter.
+ *
+ * Setting a new {@code ParameterNameProvider} will clear any previously set
+ * {@code ParameterNameProvider}, which won't be queried any more.
+ *
+ * When no {@code ParameterNameProvider} is set, or when it returns null from
+ * {@link ParameterNameProvider#getParameterName(javax.lang.model.element.VariableElement)},
+ * an automatically synthesized name is returned from {@code VariableElement.getSimpleName()}.
+ *
+ * @implSpec The default implementation of this method does nothing.
+ *
+ * @param provider the provider.
+ * @since 13
+ */
+ public void setParameterNameProvider(ParameterNameProvider provider) {}
+
+ /**
* Returns a type mirror of the tree node determined by the specified path.
* This method has been superceded by methods on
* {@link com.sun.source.util.Trees Trees}.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/source/util/ParameterNameProvider.java Thu Apr 11 17:55:18 2019 +0200
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2019, 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 com.sun.source.util;
+
+import javax.lang.model.element.VariableElement;
+
+/**
+ * A provider for parameter names when the parameter names are not determined from
+ * a reliable source, like a classfile.
+ *
+ * @since 13
+ */
+public interface ParameterNameProvider {
+
+ /**
+ * Infer a parameter name for the given parameter. The implementations of this method
+ * should infer parameter names in such a way that the parameter names are distinct
+ * for any given owning method.
+ *
+ * If the implementation of this method returns null, an automatically synthesized name is used.
+ *
+ * @param parameter the parameter for which the name should be inferred.
+ * @return a user-friendly name for the parameter, or null if unknown
+ */
+ public CharSequence getParameterName(VariableElement parameter);
+
+}
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/BasicJavacTask.java Thu Apr 11 15:36:09 2019 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/BasicJavacTask.java Thu Apr 11 17:55:18 2019 +0200
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2019, 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
@@ -43,9 +43,11 @@
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.JavacTask;
+import com.sun.source.util.ParameterNameProvider;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskListener;
import com.sun.tools.doclint.DocLint;
+import com.sun.tools.javac.code.MissingInfoHandler;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.model.JavacTypes;
@@ -123,6 +125,11 @@
mtl.remove(taskListener);
}
+ @Override
+ public void setParameterNameProvider(ParameterNameProvider handler) {
+ MissingInfoHandler.instance(context).setDelegate(handler);
+ }
+
public Collection<TaskListener> getTaskListeners() {
MultiTaskListener mtl = MultiTaskListener.instance(context);
return mtl.getTaskListeners();
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java Thu Apr 11 15:36:09 2019 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java Thu Apr 11 17:55:18 2019 +0200
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2019, 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
@@ -319,6 +319,11 @@
*/
public static final long BODY_ONLY_FINALIZE = 1L<<17; //blocks only
+ /**
+ * Flag to indicate the given ParamSymbol has a user-friendly name filled.
+ */
+ public static final long NAME_FILLED = 1L<<58; //ParamSymbols only
+
/** Modifier masks.
*/
public static final int
@@ -435,7 +440,8 @@
DEPRECATED_REMOVAL(Flags.DEPRECATED_REMOVAL),
HAS_RESOURCE(Flags.HAS_RESOURCE),
POTENTIALLY_AMBIGUOUS(Flags.POTENTIALLY_AMBIGUOUS),
- ANONCONSTR_BASED(Flags.ANONCONSTR_BASED);
+ ANONCONSTR_BASED(Flags.ANONCONSTR_BASED),
+ NAME_FILLED(Flags.NAME_FILLED);
Flag(long flag) {
this.value = flag;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/MissingInfoHandler.java Thu Apr 11 17:55:18 2019 +0200
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019, 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 com.sun.tools.javac.code;
+
+import com.sun.source.util.ParameterNameProvider;
+import com.sun.tools.javac.code.Symbol.ParamSymbol;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.util.Names;
+
+/**
+ * A Context class, that can return additional useful information for Symbols, currently
+ * parameter names. It does so by calling user-supplied {@link ParameterNameProvider}.
+ *
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.</b>
+ */
+public class MissingInfoHandler {
+ protected static final Context.Key<MissingInfoHandler> missingInfoHandlerWrapperKey = new Context.Key<>();
+
+ public static MissingInfoHandler instance(Context context) {
+ MissingInfoHandler instance = context.get(missingInfoHandlerWrapperKey);
+ if (instance == null)
+ instance = new MissingInfoHandler(context);
+ return instance;
+ }
+
+ private final Names names;
+ private ParameterNameProvider parameterNameProvider;
+
+ protected MissingInfoHandler(Context context) {
+ context.put(missingInfoHandlerWrapperKey, this);
+ names = Names.instance(context);
+ }
+
+ public Name getParameterName(ParamSymbol parameter) {
+ if (parameterNameProvider != null) {
+ CharSequence name = parameterNameProvider.getParameterName(parameter);
+ if (name != null) {
+ return names.fromString(name.toString());
+ }
+ }
+
+ return null;
+ }
+
+ public void setDelegate(ParameterNameProvider delegate) {
+ this.parameterNameProvider = delegate;
+ }
+}
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java Thu Apr 11 15:36:09 2019 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java Thu Apr 11 17:55:18 2019 +0200
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2019, 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
@@ -65,9 +65,12 @@
import static com.sun.tools.javac.code.Flags.*;
import static com.sun.tools.javac.code.Kinds.*;
import static com.sun.tools.javac.code.Kinds.Kind.*;
+import com.sun.tools.javac.code.MissingInfoHandler;
import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
import com.sun.tools.javac.code.Scope.WriteableScope;
+import com.sun.tools.javac.code.Symbol;
import static com.sun.tools.javac.code.Symbol.OperatorSymbol.AccessCode.FIRSTASGOP;
+import com.sun.tools.javac.code.Type;
import static com.sun.tools.javac.code.TypeTag.CLASS;
import static com.sun.tools.javac.code.TypeTag.FORALL;
import static com.sun.tools.javac.code.TypeTag.TYPEVAR;
@@ -76,6 +79,7 @@
import static com.sun.tools.javac.jvm.ByteCodes.lushrl;
import static com.sun.tools.javac.jvm.ByteCodes.lxor;
import static com.sun.tools.javac.jvm.ByteCodes.string_add;
+import com.sun.tools.javac.util.Name;
/** Root class for Java symbols. It contains subclasses
* for specific sorts of symbols, such as variables, methods and operators,
@@ -1189,6 +1193,16 @@
}
+ public static class RootPackageSymbol extends PackageSymbol {
+ public final MissingInfoHandler missingInfoHandler;
+
+ public RootPackageSymbol(Name name, Symbol owner, MissingInfoHandler missingInfoHandler) {
+ super(name, owner);
+ this.missingInfoHandler = missingInfoHandler;
+ }
+
+ }
+
/** A class for class symbols
*/
public static class ClassSymbol extends TypeSymbol implements TypeElement {
@@ -1634,6 +1648,32 @@
}
}
+ public static class ParamSymbol extends VarSymbol {
+ public ParamSymbol(long flags, Name name, Type type, Symbol owner) {
+ super(flags, name, type, owner);
+ }
+
+ @Override
+ public Name getSimpleName() {
+ if ((flags_field & NAME_FILLED) == 0) {
+ flags_field |= NAME_FILLED;
+ Symbol rootPack = this;
+ while (rootPack != null && !(rootPack instanceof RootPackageSymbol)) {
+ rootPack = rootPack.owner;
+ }
+ if (rootPack != null) {
+ Name inferredName =
+ ((RootPackageSymbol) rootPack).missingInfoHandler.getParameterName(this);
+ if (inferredName != null) {
+ this.name = inferredName;
+ }
+ }
+ }
+ return super.getSimpleName();
+ }
+
+ }
+
/** A class for method symbols.
*/
public static class MethodSymbol extends Symbol implements ExecutableElement {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java Thu Apr 11 15:36:09 2019 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java Thu Apr 11 17:55:18 2019 +0200
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2019, 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
@@ -42,6 +42,7 @@
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.ModuleSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
+import com.sun.tools.javac.code.Symbol.RootPackageSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type.BottomType;
@@ -382,7 +383,9 @@
messages = JavacMessages.instance(context);
- rootPackage = new PackageSymbol(names.empty, null);
+ MissingInfoHandler missingInfoHandler = MissingInfoHandler.instance(context);
+
+ rootPackage = new RootPackageSymbol(names.empty, null, missingInfoHandler);
// create the basic builtin symbols
unnamedModule = new ModuleSymbol(names.empty, null) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java Thu Apr 11 15:36:09 2019 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java Thu Apr 11 17:55:18 2019 +0200
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2019, 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
@@ -2557,14 +2557,12 @@
firstParam += skip;
}
}
- List<Name> paramNames = List.nil();
+ Set<Name> paramNames = new HashSet<>();
ListBuffer<VarSymbol> params = new ListBuffer<>();
int nameIndex = firstParam;
int annotationIndex = 0;
for (Type t: sym.type.getParameterTypes()) {
- Name name = parameterName(nameIndex, paramNames);
- paramNames = paramNames.prepend(name);
- VarSymbol param = new VarSymbol(PARAMETER, name, t, sym);
+ VarSymbol param = parameter(nameIndex, t, sym, paramNames);
params.append(param);
if (parameterAnnotations != null) {
ParameterAnnotations annotations = parameterAnnotations[annotationIndex];
@@ -2589,18 +2587,24 @@
// Returns the name for the parameter at position 'index', either using
// names read from the MethodParameters, or by synthesizing a name that
// is not on the 'exclude' list.
- private Name parameterName(int index, List<Name> exclude) {
+ private VarSymbol parameter(int index, Type t, MethodSymbol owner, Set<Name> exclude) {
+ long flags = PARAMETER;
+ Name argName;
if (parameterNameIndices != null && index < parameterNameIndices.length
&& parameterNameIndices[index] != 0) {
- return readName(parameterNameIndices[index]);
+ argName = readName(parameterNameIndices[index]);
+ flags |= NAME_FILLED;
+ } else {
+ String prefix = "arg";
+ while (true) {
+ argName = names.fromString(prefix + exclude.size());
+ if (!exclude.contains(argName))
+ break;
+ prefix += "$";
+ }
}
- String prefix = "arg";
- while (true) {
- Name argName = names.fromString(prefix + exclude.size());
- if (!exclude.contains(argName))
- return argName;
- prefix += "$";
- }
+ exclude.add(argName);
+ return new ParamSymbol(flags, argName, t, owner);
}
/**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/api/lazy/LoadParameterNamesLazily.java Thu Apr 11 17:55:18 2019 +0200
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2019, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8217047
+ * @summary Verify the parameter names can be injected using ParameterNameProvider.
+ * @library /tools/lib
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ * jdk.compiler/com.sun.tools.javac.main
+ * @build toolbox.JavacTask toolbox.TestRunner toolbox.ToolBox
+ * @run main LoadParameterNamesLazily
+ */
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementFilter;
+
+import com.sun.source.util.*;
+
+import toolbox.JavacTask;
+import toolbox.Task;
+import toolbox.TestRunner;
+import toolbox.ToolBox;
+
+public class LoadParameterNamesLazily extends TestRunner {
+
+ public static void main(String... args) throws Exception {
+ LoadParameterNamesLazily t = new LoadParameterNamesLazily();
+ t.runTests();
+ }
+
+ private static final String libClass =
+ "package lib;" +
+ "/**Lib javadoc.*/" +
+ "public class Lib {" +
+ " /**Lib method javadoc.*/" +
+ " public static void m(int param, int other) {}" +
+ "}";
+ private final ToolBox tb = new ToolBox();
+
+ LoadParameterNamesLazily() throws IOException {
+ super(System.err);
+ }
+
+ @Test
+ public void testLoadTreesLazily() throws IOException {
+ Path libSrc = Paths.get("lib-src");
+ tb.writeJavaFiles(libSrc, libClass);
+ Path libClasses = Paths.get("lib-classes");
+ Files.createDirectories(libClasses);
+
+ new JavacTask(tb)
+ .outdir(libClasses)
+ .options()
+ .files(tb.findJavaFiles(libSrc))
+ .run(Task.Expect.SUCCESS)
+ .writeAll();
+
+ Path src = Paths.get("src");
+ tb.writeJavaFiles(src,
+ "class Use {" +
+ " lib.Lib lib;" +
+ "}");
+ Path classes = Paths.get("classes");
+ Files.createDirectories(classes);
+
+ new JavacTask(tb)
+ .outdir(classes)
+ .options("-classpath", libClasses.toString())
+ .files(tb.findJavaFiles(src))
+ .callback(task -> {
+ task.setParameterNameProvider(parameter -> {
+ ExecutableElement method = (ExecutableElement) parameter.getEnclosingElement();
+ TypeElement clazz =
+ (TypeElement) method.getEnclosingElement();
+ if (clazz.getQualifiedName().contentEquals("lib.Lib")) {
+ if (method.getParameters().indexOf(parameter) == 0) {
+ return "testName";
+ } else {
+ return null;
+ }
+ }
+ return null;
+ });
+ task.addTaskListener(new TaskListener() {
+ @Override
+ public void finished(TaskEvent e) {
+ if (e.getKind() == TaskEvent.Kind.ANALYZE) {
+ TypeElement lib = task.getElements().getTypeElement("lib.Lib");
+ lib.getClass(); //not null
+ ExecutableElement method =
+ ElementFilter.methodsIn(lib.getEnclosedElements()).get(0);
+ Name paramName0 = method.getParameters().get(0).getSimpleName();
+ if (!paramName0.contentEquals("testName")) {
+ throw new IllegalStateException("Unexpected parameter name: " +
+ paramName0);
+ }
+ Name paramName1 = method.getParameters().get(1).getSimpleName();
+ if (!paramName1.contentEquals("arg1")) {
+ throw new IllegalStateException("Unexpected parameter name: " +
+ paramName1);
+ }
+ }
+ }
+ });
+ })
+ .run(Task.Expect.SUCCESS)
+ .writeAll();
+ }
+
+}