Leverage kotlin-reflect to determine parameter names

This is especially useful to determine interface parameter names
without requiring Java 8 -parameters compiler flag.

Issue: SPR-15541
This commit is contained in:
Sebastien Deleuze
2017-09-06 14:39:56 +02:00
parent 02a2c400c7
commit 5ae35f606c
4 changed files with 176 additions and 3 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,22 +16,35 @@
package org.springframework.core;
import org.springframework.util.ClassUtils;
/**
* Default implementation of the {@link ParameterNameDiscoverer} strategy interface,
* using the Java 8 standard reflection mechanism (if available), and falling back
* to the ASM-based {@link LocalVariableTableParameterNameDiscoverer} for checking
* debug information in the class file.
*
* <p>If Kotlin is present, {@link KotlinReflectionParameterNameDiscoverer} is added first
* in the list and used for Kotlin classes and interfaces.
*
* <p>Further discoverers may be added through {@link #addDiscoverer(ParameterNameDiscoverer)}.
*
* @author Juergen Hoeller
* @author Sebastien Deleuze
* @since 4.0
* @see StandardReflectionParameterNameDiscoverer
* @see LocalVariableTableParameterNameDiscoverer
* @see KotlinReflectionParameterNameDiscoverer
*/
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
private static final boolean kotlinPresent =
ClassUtils.isPresent("kotlin.Unit", DefaultParameterNameDiscoverer.class.getClassLoader());
public DefaultParameterNameDiscoverer() {
if (kotlinPresent) {
addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
}
addDiscoverer(new StandardReflectionParameterNameDiscoverer());
addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.stream.Collectors;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.jvm.ReflectJvmMapping;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* {@link ParameterNameDiscoverer} implementation which uses Kotlin's reflection facilities
* for introspecting parameter names.
*
* Compared to {@link StandardReflectionParameterNameDiscoverer}, it allows in addition to
* determine interface parameter names without requiring Java 8 -parameters compiler flag.
*
* @author Sebastien Deleuze
* @since 5.0
*/
public class KotlinReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {
@Nullable
private static final Class<?> kotlinMetadata;
static {
Class<?> metadata;
try {
metadata = ClassUtils.forName("kotlin.Metadata", KotlinReflectionParameterNameDiscoverer.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// Kotlin API not available - no special support for Kotlin class instantiation
metadata = null;
}
kotlinMetadata = metadata;
}
@Override
@Nullable
public String[] getParameterNames(Method method) {
if (!useKotlinSupport(method.getDeclaringClass())) {
return null;
}
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
return (function != null ? getParameterNames(function.getParameters()) : null);
}
@Override
@Nullable
public String[] getParameterNames(Constructor<?> ctor) {
if (!useKotlinSupport(ctor.getDeclaringClass())) {
return null;
}
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(ctor);
return (function != null ? getParameterNames(function.getParameters()) : null);
}
@Nullable
private String[] getParameterNames(List<KParameter> parameters) {
List<KParameter> filteredParameters = parameters
.stream()
.filter(p -> KParameter.Kind.VALUE.equals(p.getKind()))
.collect(Collectors.toList());
String[] parameterNames = new String[filteredParameters.size()];
for (int i = 0; i < filteredParameters.size(); i++) {
String name = filteredParameters.get(i).getName();
if (name == null) {
return null;
}
parameterNames[i] = name;
}
return parameterNames;
}
/**
* Return true if Kotlin is present and if the specified class is a Kotlin one.
*/
@SuppressWarnings("unchecked")
private static boolean useKotlinSupport(Class<?> clazz) {
return (kotlinMetadata != null &&
clazz.getDeclaredAnnotation((Class<? extends Annotation>) kotlinMetadata) != null);
}
}