From 89f3cfc704a48a66bebea6120cf0c202eeb1354e Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Tue, 25 Aug 2015 13:34:20 +0300 Subject: [PATCH] Support parameter names for Java 8 classes in reflection Also support specifying additional options to javac in codegen tests, which was needed to compile Java sources with the "-parameters" option --- ...BlackBoxWithJava8CodegenTestGenerated.java | 22 +++++++++ .../reflection/realParameterNames/J.java | 5 +++ .../reflection/realParameterNames/K.kt | 15 +++++++ .../synthesizedParameterNames/J.java | 5 +++ .../reflection/synthesizedParameterNames/K.kt | 13 ++++++ .../kotlin/codegen/CodegenTestUtil.java | 18 +++++--- .../AbstractBlackBoxCodegenTest.java | 7 ++- .../structure/reflect/ReflectJavaMember.kt | 45 ++++++++++++++++++- .../reflect/ReflectJavaValueParameter.kt | 5 ++- 9 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 compiler/testData/codegen/java8/boxWithJava/reflection/realParameterNames/J.java create mode 100644 compiler/testData/codegen/java8/boxWithJava/reflection/realParameterNames/K.kt create mode 100644 compiler/testData/codegen/java8/boxWithJava/reflection/synthesizedParameterNames/J.java create mode 100644 compiler/testData/codegen/java8/boxWithJava/reflection/synthesizedParameterNames/K.kt diff --git a/compiler/java8-tests/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJava8CodegenTestGenerated.java b/compiler/java8-tests/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJava8CodegenTestGenerated.java index 7b21cb4d729..6d1f5798e5b 100644 --- a/compiler/java8-tests/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJava8CodegenTestGenerated.java +++ b/compiler/java8-tests/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJava8CodegenTestGenerated.java @@ -89,4 +89,26 @@ public class BlackBoxWithJava8CodegenTestGenerated extends AbstractBlackBoxCodeg doTestWithJava(fileName); } + @TestMetadata("compiler/testData/codegen/java8/boxWithJava/reflection") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Reflection extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInReflection() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/java8/boxWithJava/reflection"), Pattern.compile("^([^\\.]+)$"), true); + } + + @TestMetadata("realParameterNames") + public void testRealParameterNames() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/java8/boxWithJava/reflection/realParameterNames/"); + doTestWithJava(fileName); + } + + @TestMetadata("synthesizedParameterNames") + public void testSynthesizedParameterNames() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/java8/boxWithJava/reflection/synthesizedParameterNames/"); + doTestWithJava(fileName); + } + + } + } diff --git a/compiler/testData/codegen/java8/boxWithJava/reflection/realParameterNames/J.java b/compiler/testData/codegen/java8/boxWithJava/reflection/realParameterNames/J.java new file mode 100644 index 00000000000..b5244858eaa --- /dev/null +++ b/compiler/testData/codegen/java8/boxWithJava/reflection/realParameterNames/J.java @@ -0,0 +1,5 @@ +public class J { + public J(String constructorParam) {} + + public static void foo(int methodParam) {} +} diff --git a/compiler/testData/codegen/java8/boxWithJava/reflection/realParameterNames/K.kt b/compiler/testData/codegen/java8/boxWithJava/reflection/realParameterNames/K.kt new file mode 100644 index 00000000000..653f4042dbb --- /dev/null +++ b/compiler/testData/codegen/java8/boxWithJava/reflection/realParameterNames/K.kt @@ -0,0 +1,15 @@ +// JAVAC_OPTIONS: -parameters + +import kotlin.test.assertEquals + +fun box(): String { + val methodParam = J::foo.parameters.single() + if (methodParam.name == null) return "Fail: method parameter has no name" + assertEquals("methodParam", methodParam.name) + + val constructorParam = J::class.constructors.single().parameters.single() + if (constructorParam.name == null) return "Fail: constructor parameter has no name" + assertEquals("constructorParam", constructorParam.name) + + return "OK" +} diff --git a/compiler/testData/codegen/java8/boxWithJava/reflection/synthesizedParameterNames/J.java b/compiler/testData/codegen/java8/boxWithJava/reflection/synthesizedParameterNames/J.java new file mode 100644 index 00000000000..b5244858eaa --- /dev/null +++ b/compiler/testData/codegen/java8/boxWithJava/reflection/synthesizedParameterNames/J.java @@ -0,0 +1,5 @@ +public class J { + public J(String constructorParam) {} + + public static void foo(int methodParam) {} +} diff --git a/compiler/testData/codegen/java8/boxWithJava/reflection/synthesizedParameterNames/K.kt b/compiler/testData/codegen/java8/boxWithJava/reflection/synthesizedParameterNames/K.kt new file mode 100644 index 00000000000..b322f675287 --- /dev/null +++ b/compiler/testData/codegen/java8/boxWithJava/reflection/synthesizedParameterNames/K.kt @@ -0,0 +1,13 @@ +import kotlin.test.assertEquals + +fun box(): String { + val methodParam = J::foo.parameters.single() + if (methodParam.name == null) return "Fail: method parameter has no name" + assertEquals("arg0", methodParam.name) + + val constructorParam = J::class.constructors.single().parameters.single() + if (constructorParam.name == null) return "Fail: constructor parameter has no name" + assertEquals("arg0", constructorParam.name) + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestUtil.java b/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestUtil.java index e3b273cb34a..0b7bfec0851 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestUtil.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestUtil.java @@ -111,24 +111,30 @@ public class CodegenTestUtil { @NotNull public static File compileJava(@NotNull String filename, @NotNull String... additionalClasspath) { - return compileJava(Collections.singletonList(filename), additionalClasspath); + return compileJava(Collections.singletonList(filename), Arrays.asList(additionalClasspath), Collections.emptyList()); } @NotNull - public static File compileJava(@NotNull List filenames, @NotNull String... additionalClasspath) { + public static File compileJava( + @NotNull List fileNames, + @NotNull List additionalClasspath, + @NotNull List additionalOptions + ) { try { File javaClassesTempDirectory = JetTestUtils.tmpDir("java-classes"); List classpath = new ArrayList(); classpath.add(ForTestCompileRuntime.runtimeJarForTests().getPath()); classpath.add(ForTestCompileRuntime.reflectJarForTests().getPath()); classpath.add(JetTestUtils.getAnnotationsJar().getPath()); - classpath.addAll(Arrays.asList(additionalClasspath)); - List options = Arrays.asList( + classpath.addAll(additionalClasspath); + + List options = new ArrayList(Arrays.asList( "-classpath", KotlinPackage.join(classpath, File.pathSeparator, "", "", -1, ""), "-d", javaClassesTempDirectory.getPath() - ); + )); + options.addAll(additionalOptions); - List fileList = Lists.transform(filenames, new Function() { + List fileList = Lists.transform(fileNames, new Function() { @Override public File apply(@Nullable String input) { return new File(JetTestUtils.getTestDataPathBase() + "/codegen/" + input); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/AbstractBlackBoxCodegenTest.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/AbstractBlackBoxCodegenTest.java index fe3bef52010..07cf9f4261a 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/AbstractBlackBoxCodegenTest.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/AbstractBlackBoxCodegenTest.java @@ -158,7 +158,12 @@ public abstract class AbstractBlackBoxCodegenTest extends CodegenTestCase { File kotlinOut = JetTestUtils.tmpDir(toString()); OutputUtilsPackage.writeAllTo(classFileFactory, kotlinOut); - File javaOut = compileJava(javaFilePaths, kotlinOut.getPath()); + List javacOptions = new ArrayList(0); + for (JetFile jetFile : myFiles.getPsiFiles()) { + javacOptions.addAll(InTextDirectivesUtils.findListWithPrefixes(jetFile.getText(), "// JAVAC_OPTIONS:")); + } + + File javaOut = compileJava(javaFilePaths, Collections.singletonList(kotlinOut.getPath()), javacOptions); // Add javac output to classpath so that the created class loader can find generated Java classes addJvmClasspathRoot(configuration, javaOut); diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMember.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMember.kt index c92a454c8e7..787fb3874f2 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMember.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMember.kt @@ -22,8 +22,9 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.SpecialNames import java.lang.reflect.AnnotatedElement import java.lang.reflect.Member +import java.lang.reflect.Method import java.lang.reflect.Type -import java.util.ArrayList +import java.util.* public abstract class ReflectJavaMember : ReflectJavaElement(), ReflectJavaAnnotationOwner, ReflectJavaModifierListOwner, JavaMember { internal abstract val member: Member @@ -42,9 +43,12 @@ public abstract class ReflectJavaMember : ReflectJavaElement(), ReflectJavaAnnot isVararg: Boolean ): List { val result = ArrayList(parameterTypes.size()) + val names = Java8ParameterNamesLoader.loadParameterNames(member) for (i in parameterTypes.indices) { + val type = ReflectJavaType.create(parameterTypes[i]) + val name = names?.run { get(i) } val isParamVararg = isVararg && i == parameterTypes.lastIndex - result.add(ReflectJavaValueParameter(ReflectJavaType.create(parameterTypes[i]), parameterAnnotations[i], isParamVararg)) + result.add(ReflectJavaValueParameter(type, parameterAnnotations[i], name, isParamVararg)) } return result } @@ -55,3 +59,40 @@ public abstract class ReflectJavaMember : ReflectJavaElement(), ReflectJavaAnnot override fun toString() = javaClass.getName() + ": " + member } + +private object Java8ParameterNamesLoader { + class Cache(val getParameters: Method?, val getName: Method?) + + var cache: Cache? = null + + fun buildCache(member: Member): Cache { + // This should be either j.l.reflect.Method or j.l.reflect.Constructor + val methodOrConstructorClass = member.javaClass + + val getParameters = try { + methodOrConstructorClass.getMethod("getParameters") + } + catch (e: NoSuchMethodException) { + return Cache(null, null) + } + + val parameterClass = methodOrConstructorClass.safeClassLoader.loadClass("java.lang.reflect.Parameter") + + return Cache(getParameters, parameterClass.getMethod("getName")) + } + + fun loadParameterNames(member: Member): List? { + var cache = cache + if (cache == null) { + cache = buildCache(member) + this.cache = cache + } + + val getParameters = cache.getParameters ?: return null + val getName = cache.getName ?: return null + + return (getParameters(member) as Array<*>).map { param -> + getName(param) as String + } + } +} diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaValueParameter.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaValueParameter.kt index 33d079608c9..0e82734392c 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaValueParameter.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaValueParameter.kt @@ -18,10 +18,13 @@ package org.jetbrains.kotlin.load.java.structure.reflect import org.jetbrains.kotlin.load.java.structure.JavaValueParameter import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.Name.guess public class ReflectJavaValueParameter( private val returnType: ReflectJavaType, private val annotations: Array, + private val name: String?, private val isVararg: Boolean ) : ReflectJavaElement(), JavaValueParameter { override fun getAnnotations() = getAnnotations(annotations) @@ -30,7 +33,7 @@ public class ReflectJavaValueParameter( override fun isDeprecatedInJavaDoc() = false - override fun getName() = null // TODO: use ParameterNames on JDK 8 + override fun getName() = name?.let(Name::guess) override fun getType() = returnType override fun isVararg() = isVararg