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
This commit is contained in:
+22
@@ -89,4 +89,26 @@ public class BlackBoxWithJava8CodegenTestGenerated extends AbstractBlackBoxCodeg
|
|||||||
doTestWithJava(fileName);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
public class J {
|
||||||
|
public J(String constructorParam) {}
|
||||||
|
|
||||||
|
public static void foo(int methodParam) {}
|
||||||
|
}
|
||||||
+15
@@ -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"
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
public class J {
|
||||||
|
public J(String constructorParam) {}
|
||||||
|
|
||||||
|
public static void foo(int methodParam) {}
|
||||||
|
}
|
||||||
+13
@@ -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"
|
||||||
|
}
|
||||||
@@ -111,24 +111,30 @@ public class CodegenTestUtil {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static File compileJava(@NotNull String filename, @NotNull String... additionalClasspath) {
|
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.<String>emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static File compileJava(@NotNull List<String> filenames, @NotNull String... additionalClasspath) {
|
public static File compileJava(
|
||||||
|
@NotNull List<String> fileNames,
|
||||||
|
@NotNull List<String> additionalClasspath,
|
||||||
|
@NotNull List<String> additionalOptions
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
File javaClassesTempDirectory = JetTestUtils.tmpDir("java-classes");
|
File javaClassesTempDirectory = JetTestUtils.tmpDir("java-classes");
|
||||||
List<String> classpath = new ArrayList<String>();
|
List<String> classpath = new ArrayList<String>();
|
||||||
classpath.add(ForTestCompileRuntime.runtimeJarForTests().getPath());
|
classpath.add(ForTestCompileRuntime.runtimeJarForTests().getPath());
|
||||||
classpath.add(ForTestCompileRuntime.reflectJarForTests().getPath());
|
classpath.add(ForTestCompileRuntime.reflectJarForTests().getPath());
|
||||||
classpath.add(JetTestUtils.getAnnotationsJar().getPath());
|
classpath.add(JetTestUtils.getAnnotationsJar().getPath());
|
||||||
classpath.addAll(Arrays.asList(additionalClasspath));
|
classpath.addAll(additionalClasspath);
|
||||||
List<String> options = Arrays.asList(
|
|
||||||
|
List<String> options = new ArrayList<String>(Arrays.asList(
|
||||||
"-classpath", KotlinPackage.join(classpath, File.pathSeparator, "", "", -1, ""),
|
"-classpath", KotlinPackage.join(classpath, File.pathSeparator, "", "", -1, ""),
|
||||||
"-d", javaClassesTempDirectory.getPath()
|
"-d", javaClassesTempDirectory.getPath()
|
||||||
);
|
));
|
||||||
|
options.addAll(additionalOptions);
|
||||||
|
|
||||||
List<File> fileList = Lists.transform(filenames, new Function<String, File>() {
|
List<File> fileList = Lists.transform(fileNames, new Function<String, File>() {
|
||||||
@Override
|
@Override
|
||||||
public File apply(@Nullable String input) {
|
public File apply(@Nullable String input) {
|
||||||
return new File(JetTestUtils.getTestDataPathBase() + "/codegen/" + input);
|
return new File(JetTestUtils.getTestDataPathBase() + "/codegen/" + input);
|
||||||
|
|||||||
+6
-1
@@ -158,7 +158,12 @@ public abstract class AbstractBlackBoxCodegenTest extends CodegenTestCase {
|
|||||||
File kotlinOut = JetTestUtils.tmpDir(toString());
|
File kotlinOut = JetTestUtils.tmpDir(toString());
|
||||||
OutputUtilsPackage.writeAllTo(classFileFactory, kotlinOut);
|
OutputUtilsPackage.writeAllTo(classFileFactory, kotlinOut);
|
||||||
|
|
||||||
File javaOut = compileJava(javaFilePaths, kotlinOut.getPath());
|
List<String> javacOptions = new ArrayList<String>(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
|
// Add javac output to classpath so that the created class loader can find generated Java classes
|
||||||
addJvmClasspathRoot(configuration, javaOut);
|
addJvmClasspathRoot(configuration, javaOut);
|
||||||
|
|
||||||
|
|||||||
+43
-2
@@ -22,8 +22,9 @@ import org.jetbrains.kotlin.name.Name
|
|||||||
import org.jetbrains.kotlin.name.SpecialNames
|
import org.jetbrains.kotlin.name.SpecialNames
|
||||||
import java.lang.reflect.AnnotatedElement
|
import java.lang.reflect.AnnotatedElement
|
||||||
import java.lang.reflect.Member
|
import java.lang.reflect.Member
|
||||||
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.util.ArrayList
|
import java.util.*
|
||||||
|
|
||||||
public abstract class ReflectJavaMember : ReflectJavaElement(), ReflectJavaAnnotationOwner, ReflectJavaModifierListOwner, JavaMember {
|
public abstract class ReflectJavaMember : ReflectJavaElement(), ReflectJavaAnnotationOwner, ReflectJavaModifierListOwner, JavaMember {
|
||||||
internal abstract val member: Member
|
internal abstract val member: Member
|
||||||
@@ -42,9 +43,12 @@ public abstract class ReflectJavaMember : ReflectJavaElement(), ReflectJavaAnnot
|
|||||||
isVararg: Boolean
|
isVararg: Boolean
|
||||||
): List<JavaValueParameter> {
|
): List<JavaValueParameter> {
|
||||||
val result = ArrayList<JavaValueParameter>(parameterTypes.size())
|
val result = ArrayList<JavaValueParameter>(parameterTypes.size())
|
||||||
|
val names = Java8ParameterNamesLoader.loadParameterNames(member)
|
||||||
for (i in parameterTypes.indices) {
|
for (i in parameterTypes.indices) {
|
||||||
|
val type = ReflectJavaType.create(parameterTypes[i])
|
||||||
|
val name = names?.run { get(i) }
|
||||||
val isParamVararg = isVararg && i == parameterTypes.lastIndex
|
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
|
return result
|
||||||
}
|
}
|
||||||
@@ -55,3 +59,40 @@ public abstract class ReflectJavaMember : ReflectJavaElement(), ReflectJavaAnnot
|
|||||||
|
|
||||||
override fun toString() = javaClass.getName() + ": " + member
|
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<String>? {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+4
-1
@@ -18,10 +18,13 @@ package org.jetbrains.kotlin.load.java.structure.reflect
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.load.java.structure.JavaValueParameter
|
import org.jetbrains.kotlin.load.java.structure.JavaValueParameter
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.name.Name.guess
|
||||||
|
|
||||||
public class ReflectJavaValueParameter(
|
public class ReflectJavaValueParameter(
|
||||||
private val returnType: ReflectJavaType,
|
private val returnType: ReflectJavaType,
|
||||||
private val annotations: Array<Annotation>,
|
private val annotations: Array<Annotation>,
|
||||||
|
private val name: String?,
|
||||||
private val isVararg: Boolean
|
private val isVararg: Boolean
|
||||||
) : ReflectJavaElement(), JavaValueParameter {
|
) : ReflectJavaElement(), JavaValueParameter {
|
||||||
override fun getAnnotations() = getAnnotations(annotations)
|
override fun getAnnotations() = getAnnotations(annotations)
|
||||||
@@ -30,7 +33,7 @@ public class ReflectJavaValueParameter(
|
|||||||
|
|
||||||
override fun isDeprecatedInJavaDoc() = false
|
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 getType() = returnType
|
||||||
override fun isVararg() = isVararg
|
override fun isVararg() = isVararg
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user