Fix reflection-based operations on compiler arguments after conversion

This commit is contained in:
Alexey Sedunov
2017-07-27 15:20:57 +03:00
parent 50599c933f
commit 2984a5a19f
10 changed files with 162 additions and 95 deletions
@@ -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
}
}
@@ -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}")
}
}