KotlinNullabilityDetector.java

package org.mozilla.kotlin;

import static kotlin.metadata.Attributes.isNullable;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.stream.Collectors;
import kotlin.Metadata;
import kotlin.metadata.KmClass;
import kotlin.metadata.KmConstructor;
import kotlin.metadata.KmFunction;
import kotlin.metadata.KmValueParameter;
import kotlin.metadata.jvm.KotlinClassMetadata;
import org.mozilla.javascript.NullabilityDetector;

public class KotlinNullabilityDetector implements NullabilityDetector {
    @Override
    public boolean[] getParameterNullability(Method method) {
        int paramCount = method.getParameterTypes().length;
        KmClass kmClass = getKmClassForJavaClass(method.getDeclaringClass());
        return getMethodParameterNullabilityFromKotlinMetadata(
                kmClass, method.getName(), paramCount);
    }

    @Override
    public boolean[] getParameterNullability(Constructor<?> constructor) {
        int paramCount = constructor.getParameterTypes().length;
        KmClass kmClass = getKmClassForJavaClass(constructor.getDeclaringClass());
        return getConstructorParameterNullabilityFromKotlinMetadata(kmClass, paramCount);
    }

    private KmClass getKmClassForJavaClass(Class<?> javaClass) {
        Metadata metadata = javaClass.getAnnotation(Metadata.class);
        if (metadata != null) {
            KotlinClassMetadata.Class kMetadata =
                    (KotlinClassMetadata.Class) KotlinClassMetadata.readLenient(metadata);
            return kMetadata.getKmClass();
        } else {
            return null;
        }
    }

    private boolean[] getMethodParameterNullabilityFromKotlinMetadata(
            KmClass clazz, String methodName, int paramCount) {
        boolean[] fallback = createFallbackNullabilityArray(paramCount);
        if (clazz == null) {
            return fallback;
        }
        List<KmFunction> candidates =
                clazz.getFunctions().stream()
                        .filter(
                                f ->
                                        f.getName().equals(methodName)
                                                && f.getValueParameters().size() == paramCount)
                        .collect(Collectors.toList());
        return candidates.size() == 1
                ? createNullabilityArray(candidates.get(0).getValueParameters())
                : fallback;
    }

    private boolean[] getConstructorParameterNullabilityFromKotlinMetadata(
            KmClass clazz, int paramCount) {
        boolean[] fallback = createFallbackNullabilityArray(paramCount);
        if (clazz == null) {
            return fallback;
        }
        List<KmConstructor> candidates =
                clazz.getConstructors().stream()
                        .filter(c -> c.getValueParameters().size() == paramCount)
                        .collect(Collectors.toList());
        return candidates.size() == 1
                ? createNullabilityArray(candidates.get(0).getValueParameters())
                : fallback;
    }

    private boolean[] createNullabilityArray(List<KmValueParameter> params) {
        boolean[] result = new boolean[params.size()];
        int index = 0;
        for (KmValueParameter parameter : params) {
            result[index++] = isNullable(parameter.getType());
        }
        return result;
    }

    private boolean[] createFallbackNullabilityArray(int paramCount) {
        return new boolean[paramCount];
    }
}