/
DescriptorAccessorConventionUtil.kt
145 lines (130 loc) · 5.37 KB
/
DescriptorAccessorConventionUtil.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package org.jetbrains.dokka.base.translators.descriptors
import org.jetbrains.dokka.base.translators.firstNotNullOfOrNull
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName
import org.jetbrains.kotlin.load.java.propertyNamesBySetMethodName
internal data class DescriptorFunctionsHolder(
val regularFunctions: List<FunctionDescriptor>,
val accessors: Map<PropertyDescriptor, DescriptorAccessorHolder>
)
internal data class DescriptorAccessorHolder(
val getter: FunctionDescriptor? = null,
val setter: FunctionDescriptor? = null
)
/**
* Separate regular Kotlin/Java functions and inherited Java accessors
* to properly display properties inherited from Java.
*
* Take this example:
* ```
* // java
* public class JavaClass {
* private int a = 1;
* public int getA() { return a; }
* public void setA(int a) { this.a = a; }
* }
*
* // kotlin
* class Bar : JavaClass() {
* fun foo() {}
* }
* ```
*
* It should result in:
* - 1 regular function `foo`
* - Map a=[`getA`, `setA`]
*/
internal fun splitFunctionsAndInheritedAccessors(
properties: List<PropertyDescriptor>,
functions: List<FunctionDescriptor>
): DescriptorFunctionsHolder {
val (javaMethods, kotlinFunctions) = functions.partition { it is JavaMethodDescriptor }
if (javaMethods.isEmpty()) {
return DescriptorFunctionsHolder(regularFunctions = kotlinFunctions, emptyMap())
}
val propertiesByName = properties.associateBy { it.name.asString() }
val regularFunctions = ArrayList<FunctionDescriptor>(kotlinFunctions)
val accessors = mutableMapOf<PropertyDescriptor, DescriptorAccessorHolder>()
javaMethods.forEach { function ->
val possiblePropertyNamesForFunction = function.toPossiblePropertyNames()
val property = possiblePropertyNamesForFunction.firstNotNullOfOrNull { propertiesByName[it] }
if (property != null && function.isAccessorFor(property)) {
accessors.compute(property) { prop, accessorHolder ->
if (function.isGetterFor(prop))
accessorHolder?.copy(getter = function) ?: DescriptorAccessorHolder(getter = function)
else
accessorHolder?.copy(setter = function) ?: DescriptorAccessorHolder(setter = function)
}
} else {
regularFunctions.add(function)
}
}
val accessorLookalikes = removeNonAccessorsReturning(accessors)
regularFunctions.addAll(accessorLookalikes)
return DescriptorFunctionsHolder(regularFunctions, accessors)
}
/**
* If a field has no getter, it's not accessible as a property from Kotlin's perspective,
* but it still might have a setter lookalike. In this case, this "setter" should be just a regular function
*
* @return removed elements
*/
private fun removeNonAccessorsReturning(
propertyAccessors: MutableMap<PropertyDescriptor, DescriptorAccessorHolder>
): List<FunctionDescriptor> {
val nonAccessors = mutableListOf<FunctionDescriptor>()
propertyAccessors.entries.removeIf { (_, accessors) ->
if (accessors.getter == null && accessors.setter != null) {
nonAccessors.add(accessors.setter)
true
} else {
false
}
}
return nonAccessors
}
private fun FunctionDescriptor.toPossiblePropertyNames(): List<String> {
val stringName = this.name.asString()
return when {
JvmAbi.isSetterName(stringName) -> propertyNamesBySetMethodName(this.name).map { it.asString() }
JvmAbi.isGetterName(stringName) -> propertyNamesByGetMethod(this)
else -> listOf()
}
}
private fun propertyNamesByGetMethod(functionDescriptor: FunctionDescriptor): List<String> {
val stringName = functionDescriptor.name.asString()
// In java, the convention for boolean property accessors is as follows:
// - `private boolean active;`
// - `private boolean isActive();`
//
// Whereas in Kotlin, because there are no explicit accessors, the convention is
// - `val isActive: Boolean`
//
// This makes it difficult to guess the name of the accessor property in case of Java
val javaPropName = if (functionDescriptor is JavaMethodDescriptor && JvmAbi.startsWithIsPrefix(stringName)) {
val javaPropName = stringName.removePrefix("is").let { newName ->
newName.replaceFirst(newName[0], newName[0].toLowerCase())
}
javaPropName
} else {
null
}
val kotlinPropName = propertyNameByGetMethodName(functionDescriptor.name)?.asString()
return listOfNotNull(javaPropName, kotlinPropName)
}
private fun FunctionDescriptor.isAccessorFor(property: PropertyDescriptor): Boolean {
return (this.isGetterFor(property) || this.isSetterFor(property))
&& !property.visibility.isPublicAPI
&& this.visibility.isPublicAPI
}
private fun FunctionDescriptor.isGetterFor(property: PropertyDescriptor): Boolean {
return this.returnType == property.returnType
&& this.valueParameters.isEmpty()
}
private fun FunctionDescriptor.isSetterFor(property: PropertyDescriptor): Boolean {
return this.valueParameters.size == 1
&& this.valueParameters[0].type == property.returnType
}