[CLI] Implement CommonToolArguments.toStringList (to replace convertArgumentsToStringList`)

KTIJ-24976
This commit is contained in:
Sebastian Sellmair
2023-03-22 16:59:25 +01:00
committed by Space Team
parent 2b893365aa
commit d07b1b6502
6 changed files with 279 additions and 83 deletions
@@ -16,25 +16,10 @@
package org.jetbrains.kotlin.compilerRunner;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KClass;
import kotlin.reflect.KProperty1;
import kotlin.reflect.KVisibility;
import kotlin.reflect.full.KClasses;
import kotlin.reflect.jvm.ReflectJvmMapping;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.common.arguments.Argument;
import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments;
import org.jetbrains.kotlin.cli.common.arguments.InternalArgument;
import org.jetbrains.kotlin.cli.common.arguments.ParseCommandLineArgumentsKt;
import org.jetbrains.kotlin.idea.ExplicitDefaultSubstitutor;
import org.jetbrains.kotlin.idea.ExplicitDefaultSubstitutorsKt;
import org.jetbrains.kotlin.utils.StringsKt;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.*;
public class ArgumentUtils {
@@ -44,82 +29,18 @@ public class ArgumentUtils {
@NotNull
public static List<String> convertArgumentsToStringList(@NotNull CommonToolArguments arguments)
throws InstantiationException, IllegalAccessException, InvocationTargetException {
List<String> convertedArguments = convertArgumentsToStringListInternal(arguments);
Map<KClass<? extends CommonToolArguments>, Collection<ExplicitDefaultSubstitutor>> defaultSubstitutorsMap =
ExplicitDefaultSubstitutorsKt.getDefaultSubstitutors();
KClass<? extends CommonToolArguments> argumentsKClass = JvmClassMappingKt.getKotlinClass(arguments.getClass());
Collection<ExplicitDefaultSubstitutor> defaultSubstitutors = defaultSubstitutorsMap.get(argumentsKClass);
if (defaultSubstitutors != null) {
for (ExplicitDefaultSubstitutor substitutor : defaultSubstitutors) {
if (substitutor.isSubstitutable(convertedArguments)) convertedArguments.addAll(substitutor.getNewSubstitution());
}
}
return convertedArguments;
return convertArgumentsToStringListInternal(arguments);
}
@NotNull
@Deprecated()
public static List<String> convertArgumentsToStringListNoDefaults(@NotNull CommonToolArguments arguments)
throws InstantiationException, IllegalAccessException, InvocationTargetException {
return convertArgumentsToStringListInternal(arguments);
return convertArgumentsToStringList(arguments);
}
private static List<String> convertArgumentsToStringListInternal(@NotNull CommonToolArguments arguments)
throws InstantiationException, IllegalAccessException, InvocationTargetException {
List<String> result = new ArrayList<>();
Class<? extends CommonToolArguments> argumentsClass = arguments.getClass();
convertArgumentsToStringList(arguments, argumentsClass.newInstance(), JvmClassMappingKt.getKotlinClass(argumentsClass), result);
result.addAll(arguments.getFreeArgs());
result.addAll(CollectionsKt.map(arguments.getInternalArguments(), InternalArgument::getStringRepresentation));
return result;
}
@SuppressWarnings("unchecked")
private static void convertArgumentsToStringList(
@NotNull CommonToolArguments arguments,
@NotNull CommonToolArguments defaultArguments,
@NotNull KClass<?> clazz,
@NotNull List<String> result
) throws IllegalAccessException, InstantiationException, InvocationTargetException {
for (KProperty1 property : KClasses.getMemberProperties(clazz)) {
Argument argument = findInstance(property.getAnnotations(), Argument.class);
if (argument == null) continue;
if (property.getVisibility() != KVisibility.PUBLIC) continue;
Object value = property.get(arguments);
Object defaultValue = property.get(defaultArguments);
if (value == null || Objects.equals(value, defaultValue)) continue;
Type propertyJavaType = ReflectJvmMapping.getJavaType(property.getReturnType());
if (propertyJavaType instanceof Class && ((Class) propertyJavaType).isArray()) {
Object[] values = (Object[]) value;
if (values.length == 0) continue;
value = StringsKt.join(Arrays.asList(values), ",");
}
result.add(argument.value());
if (propertyJavaType == boolean.class || propertyJavaType == Boolean.class) continue;
if (ParseCommandLineArgumentsKt.isAdvanced(argument)) {
result.set(result.size() - 1, argument.value() + "=" + value.toString());
}
else {
result.add(value.toString());
}
}
}
@Nullable
private static <T> T findInstance(Iterable<? super T> iterable, Class<T> clazz) {
for (Object item : iterable) {
if (clazz.isInstance(item)) {
return clazz.cast(item);
}
}
return null;
return ArgumentsToStrings.toArgumentStrings(arguments, false);
}
}
@@ -0,0 +1,91 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:JvmName("ArgumentsToStrings")
package org.jetbrains.kotlin.compilerRunner
import org.jetbrains.kotlin.cli.common.arguments.Argument
import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments
import org.jetbrains.kotlin.cli.common.arguments.isAdvanced
import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
@Suppress("UNCHECKED_CAST")
@JvmOverloads
fun CommonToolArguments.toArgumentStrings(useShortNames: Boolean = false): List<String> {
return toArgumentStrings(
this, this::class as KClass<CommonToolArguments>, useShortNames = useShortNames
)
}
@PublishedApi
internal fun <T : CommonToolArguments> toArgumentStrings(thisArguments: T, type: KClass<T>, useShortNames: Boolean): List<String> {
val defaultArguments = type.newArgumentsInstance()
val result = mutableListOf<String>()
type.memberProperties.forEach { property ->
val argumentAnnotation = property.findAnnotation<Argument>() ?: return@forEach
val rawPropertyValue = property.get(thisArguments)
val rawDefaultValue = property.get(defaultArguments)
/* Default value can be omitted when not marked as 'isExplicit' */
if (rawPropertyValue == rawDefaultValue && !argumentAnnotation.isExplicit) {
return@forEach
}
val argumentStringValues = when {
property.returnType.classifier == Boolean::class -> listOf(rawPropertyValue?.toString() ?: false.toString())
(property.returnType.classifier as? KClass<*>)?.java?.isArray == true ->
getArgumentStringValue(argumentAnnotation, rawPropertyValue as Array<*>?)
property.returnType.classifier == List::class ->
getArgumentStringValue(argumentAnnotation, (rawPropertyValue as List<*>?)?.toTypedArray())
else -> listOf(rawPropertyValue.toString())
}
val argumentName = if (useShortNames && argumentAnnotation.shortName.isNotEmpty()) argumentAnnotation.shortName
else argumentAnnotation.value
argumentStringValues.forEach { argumentStringValue ->
when {
/* We can just enable the flag by passing the argument name like -myFlag: Value not required */
rawPropertyValue is Boolean && rawPropertyValue -> {
result.add(argumentName)
}
/* Advanced (e.g. -X arguments) or boolean properties need to be passed using the '=' */
argumentAnnotation.isAdvanced || property.returnType.classifier == Boolean::class -> {
result.add("$argumentName=$argumentStringValue")
}
else -> {
result.add(argumentName)
result.add(argumentStringValue)
}
}
}
}
result.addAll(thisArguments.freeArgs)
result.addAll(thisArguments.internalArguments.map { it.stringRepresentation })
return result
}
private fun getArgumentStringValue(argumentAnnotation: Argument, values: Array<*>?): List<String> {
if (values.isNullOrEmpty()) return emptyList()
val delimiter = argumentAnnotation.delimiter
return if (delimiter.isEmpty()) values.map { it.toString() }
else listOf(values.joinToString(delimiter))
}
private fun <T : CommonToolArguments> KClass<T>.newArgumentsInstance(): T {
val argumentConstructor = constructors.find { it.parameters.isEmpty() } ?: throw IllegalArgumentException(
"$qualifiedName has no empty constructor"
)
return argumentConstructor.call()
}