Fix reflection-based operations on compiler arguments after conversion
This commit is contained in:
+38
-27
@@ -16,29 +16,47 @@
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.arguments
|
||||
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
|
||||
fun <T : Any> copyBean(bean: T) = copyFields(bean, bean::class.java.newInstance(), true, collectFieldsToCopy(bean::class.java, false))
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Any> copyBean(bean: T) =
|
||||
copyProperties(bean, bean::class.java.newInstance()!!, true, collectProperties(bean::class as KClass<T>, false))
|
||||
|
||||
fun <From : Any, To : From> mergeBeans(from: From, to: To): To {
|
||||
// TODO: rewrite when updated version of com.intellij.util.xmlb is available on TeamCity
|
||||
return copyFields(from, to, false, collectFieldsToCopy(from::class.java, false))
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return copyProperties(from, to, false, collectProperties(from::class as KClass<From>, false))
|
||||
}
|
||||
|
||||
fun <From : Any, To : Any> copyInheritedFields(from: From, to: To) = copyFields(from, to, true, collectFieldsToCopy(from::class.java, true))
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <From : Any, To : Any> copyInheritedFields(from: From, to: To) =
|
||||
copyProperties(from, to, true, collectProperties(from::class as KClass<From>, true))
|
||||
|
||||
fun <From : Any, To : Any> copyFieldsSatisfying(from: From, to: To, predicate: (Field) -> Boolean) =
|
||||
copyFields(from, to, true, collectFieldsToCopy(from::class.java, false).filter(predicate))
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <From : Any, To : Any> copyFieldsSatisfying(from: From, to: To, predicate: (KProperty1<From, Any?>) -> Boolean) =
|
||||
copyProperties(from, to, true, collectProperties(from::class as KClass<From>, false).filter(predicate))
|
||||
|
||||
private fun <From : Any, To : Any> copyFields(from: From, to: To, deepCopyWhenNeeded: Boolean, fieldsToCopy: List<Field>): To {
|
||||
private fun <From : Any, To : Any> copyProperties(
|
||||
from: From,
|
||||
to: To,
|
||||
deepCopyWhenNeeded: Boolean,
|
||||
propertiesToCopy: List<KProperty1<From, Any?>>
|
||||
): To {
|
||||
if (from == to) return to
|
||||
|
||||
for (fromField in fieldsToCopy) {
|
||||
val toField = to::class.java.getField(fromField.name)
|
||||
val fromValue = fromField.get(from)
|
||||
toField.set(to, if (deepCopyWhenNeeded) fromValue?.copyValueIfNeeded() else fromValue)
|
||||
for (fromProperty in propertiesToCopy) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val toProperty = to::class.memberProperties.firstOrNull { it.name == fromProperty.name } as? KMutableProperty1<To, Any?>
|
||||
?: continue
|
||||
val fromValue = fromProperty.get(from)
|
||||
toProperty.set(to, if (deepCopyWhenNeeded) fromValue?.copyValueIfNeeded() else fromValue)
|
||||
}
|
||||
return to
|
||||
}
|
||||
@@ -72,19 +90,12 @@ private fun Any.copyValueIfNeeded(): Any {
|
||||
}
|
||||
}
|
||||
|
||||
fun collectFieldsToCopy(clazz: Class<*>, inheritedOnly: Boolean): List<Field> {
|
||||
val fromFields = ArrayList<Field>()
|
||||
|
||||
var currentClass: Class<*>? = if (inheritedOnly) clazz.superclass else clazz
|
||||
while (currentClass != null) {
|
||||
for (field in currentClass.declaredFields) {
|
||||
val modifiers = field.modifiers
|
||||
if (!Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) && !Modifier.isTransient(modifiers)) {
|
||||
fromFields.add(field)
|
||||
}
|
||||
}
|
||||
currentClass = currentClass.superclass
|
||||
fun <T : Any> collectProperties(kClass: KClass<T>, inheritedOnly: Boolean): List<KProperty1<T, Any?>> {
|
||||
val properties = ArrayList(kClass.memberProperties)
|
||||
if (inheritedOnly) {
|
||||
properties.removeAll(kClass.declaredMemberProperties)
|
||||
}
|
||||
|
||||
return fromFields
|
||||
}
|
||||
return properties.filter {
|
||||
it.visibility == KVisibility.PUBLIC && it.findAnnotation<Transient>() == null
|
||||
}
|
||||
}
|
||||
+23
-18
@@ -17,7 +17,9 @@
|
||||
package org.jetbrains.kotlin.cli.common.arguments
|
||||
|
||||
import com.intellij.util.SmartList
|
||||
import java.lang.reflect.Field
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
|
||||
annotation class Argument(
|
||||
val value: String,
|
||||
@@ -54,11 +56,13 @@ data class ArgumentParseErrors(
|
||||
|
||||
// Parses arguments into the passed [result] object. Errors related to the parsing will be collected into [CommonToolArguments.errors].
|
||||
fun <A : CommonToolArguments> parseCommandLineArguments(args: List<String>, result: A) {
|
||||
data class ArgumentField(val field: Field, val argument: Argument)
|
||||
data class ArgumentField(val property: KMutableProperty1<A, Any?>, val argument: Argument)
|
||||
|
||||
val fields = result::class.java.fields.mapNotNull { field ->
|
||||
val argument = field.getAnnotation(Argument::class.java)
|
||||
if (argument != null) ArgumentField(field, argument) else null
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val properties = result::class.memberProperties.mapNotNull { property ->
|
||||
if (property !is KMutableProperty1<*, *>) return@mapNotNull null
|
||||
val argument = property.findAnnotation<Argument>() ?: return@mapNotNull null
|
||||
ArgumentField(property as KMutableProperty1<A, Any?>, argument)
|
||||
}
|
||||
|
||||
val errors = result.errors
|
||||
@@ -72,7 +76,7 @@ fun <A : CommonToolArguments> parseCommandLineArguments(args: List<String>, resu
|
||||
}
|
||||
|
||||
if (argument.value == arg) {
|
||||
if (argument.isAdvanced && field.type != Boolean::class.java) {
|
||||
if (argument.isAdvanced && property.returnType.classifier != Boolean::class) {
|
||||
errors.extraArgumentsPassedInObsoleteForm.add(arg)
|
||||
}
|
||||
return true
|
||||
@@ -95,7 +99,7 @@ fun <A : CommonToolArguments> parseCommandLineArguments(args: List<String>, resu
|
||||
continue
|
||||
}
|
||||
|
||||
val argumentField = fields.firstOrNull { it.matches(arg) }
|
||||
val argumentField = properties.firstOrNull { it.matches(arg) }
|
||||
if (argumentField == null) {
|
||||
when {
|
||||
arg.startsWith(ADVANCED_ARGUMENT_PREFIX) -> errors.unknownExtraFlags.add(arg)
|
||||
@@ -105,9 +109,9 @@ fun <A : CommonToolArguments> parseCommandLineArguments(args: List<String>, resu
|
||||
continue
|
||||
}
|
||||
|
||||
val (field, argument) = argumentField
|
||||
val (property, argument) = argumentField
|
||||
val value: Any = when {
|
||||
field.type == Boolean::class.java -> true
|
||||
argumentField.property.returnType.classifier == Boolean::class -> true
|
||||
argument.isAdvanced && arg.startsWith(argument.value + "=") -> {
|
||||
arg.substring(argument.value.length + 1)
|
||||
}
|
||||
@@ -120,24 +124,25 @@ fun <A : CommonToolArguments> parseCommandLineArguments(args: List<String>, resu
|
||||
}
|
||||
}
|
||||
|
||||
if (!field.type.isArray && !visitedArgs.add(argument.value) && value is String && field.get(result) != value) {
|
||||
if (argumentField.property.returnType.classifier?.javaClass?.isArray == false
|
||||
&& !visitedArgs.add(argument.value) && value is String && property.get(result) != value) {
|
||||
errors.duplicateArguments.put(argument.value, value)
|
||||
}
|
||||
|
||||
updateField(field, result, value, argument.delimiter)
|
||||
updateField(property, result, value, argument.delimiter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <A : CommonToolArguments> updateField(field: Field, result: A, value: Any, delimiter: String) {
|
||||
when (field.type) {
|
||||
Boolean::class.java, String::class.java -> field.set(result, value)
|
||||
Array<String>::class.java -> {
|
||||
private fun <A : CommonToolArguments> updateField(property: KMutableProperty1<A, Any?>, result: A, value: Any, delimiter: String) {
|
||||
when (property.returnType.classifier) {
|
||||
Boolean::class, String::class -> property.set(result, value)
|
||||
Array<String>::class -> {
|
||||
val newElements = (value as String).split(delimiter).toTypedArray()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val oldValue = field.get(result) as Array<String>?
|
||||
field.set(result, if (oldValue != null) arrayOf(*oldValue, *newElements) else newElements)
|
||||
val oldValue = property.get(result) as Array<String>?
|
||||
property.set(result, if (oldValue != null) arrayOf(*oldValue, *newElements) else newElements)
|
||||
}
|
||||
else -> throw IllegalStateException("Unsupported argument type: ${field.type}")
|
||||
else -> throw IllegalStateException("Unsupported argument type: ${property.returnType}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user