001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.core;
018
019import java.lang.reflect.Constructor;
020import java.lang.reflect.Method;
021import java.util.List;
022import java.util.stream.Collectors;
023
024import kotlin.reflect.KFunction;
025import kotlin.reflect.KParameter;
026import kotlin.reflect.jvm.ReflectJvmMapping;
027
028import org.springframework.lang.Nullable;
029
030/**
031 * {@link ParameterNameDiscoverer} implementation which uses Kotlin's reflection facilities
032 * for introspecting parameter names.
033 *
034 * Compared to {@link StandardReflectionParameterNameDiscoverer}, it allows in addition to
035 * determine interface parameter names without requiring Java 8 -parameters compiler flag.
036 *
037 * @author Sebastien Deleuze
038 * @since 5.0
039 */
040public class KotlinReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {
041
042        @Override
043        @Nullable
044        public String[] getParameterNames(Method method) {
045                if (!KotlinDetector.isKotlinType(method.getDeclaringClass())) {
046                        return null;
047                }
048
049                try {
050                        KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
051                        return (function != null ? getParameterNames(function.getParameters()) : null);
052                }
053                catch (UnsupportedOperationException ex) {
054                        return null;
055                }
056        }
057
058        @Override
059        @Nullable
060        public String[] getParameterNames(Constructor<?> ctor) {
061                if (ctor.getDeclaringClass().isEnum() || !KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
062                        return null;
063                }
064
065                try {
066                        KFunction<?> function = ReflectJvmMapping.getKotlinFunction(ctor);
067                        return (function != null ? getParameterNames(function.getParameters()) : null);
068                }
069                catch (UnsupportedOperationException ex) {
070                        return null;
071                }
072        }
073
074        @Nullable
075        private String[] getParameterNames(List<KParameter> parameters) {
076                List<KParameter> filteredParameters = parameters
077                                .stream()
078                                // Extension receivers of extension methods must be included as they appear as normal method parameters in Java
079                                .filter(p -> KParameter.Kind.VALUE.equals(p.getKind()) || KParameter.Kind.EXTENSION_RECEIVER.equals(p.getKind()))
080                                .collect(Collectors.toList());
081                String[] parameterNames = new String[filteredParameters.size()];
082                for (int i = 0; i < filteredParameters.size(); i++) {
083                        KParameter parameter = filteredParameters.get(i);
084                        // extension receivers are not explicitly named, but require a name for Java interoperability
085                        // $receiver is not a valid Kotlin identifier, but valid in Java, so it can be used here
086                        String name = KParameter.Kind.EXTENSION_RECEIVER.equals(parameter.getKind())  ? "$receiver" : parameter.getName();
087                        if (name == null) {
088                                return null;
089                        }
090                        parameterNames[i] = name;
091                }
092                return parameterNames;
093        }
094
095}