converters.KotlinToJavaConverter.kt Maven / Gradle / Ivy
package org.jetbrains.dokka.kotlinAsJava.converters
import org.jetbrains.dokka.kotlinAsJava.hasJvmOverloads
import org.jetbrains.dokka.kotlinAsJava.hasJvmSynthetic
import org.jetbrains.dokka.kotlinAsJava.jvmField
import org.jetbrains.dokka.kotlinAsJava.transformers.JvmNameProvider
import org.jetbrains.dokka.kotlinAsJava.transformers.withCallableName
import org.jetbrains.dokka.links.Callable
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.links.PointingToDeclaration
import org.jetbrains.dokka.links.withClass
import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
val jvmNameProvider = JvmNameProvider()
private val DProperty.isConst: Boolean
get() = extra[AdditionalModifiers]
?.content
?.any { (_, modifiers) ->
ExtraModifiers.KotlinOnlyModifiers.Const in modifiers
} == true
private val DProperty.isJvmField: Boolean
get() = jvmField() != null
internal fun DPackage.asJava(): DPackage {
val syntheticClasses =
(properties.map { jvmNameProvider.nameForSyntheticClass(it) to it }
+ functions.map { jvmNameProvider.nameForSyntheticClass(it) to it })
.groupBy({ it.first }) { it.second }
.map { (syntheticClassName, nodes) ->
DClass(
dri = dri.withClass(syntheticClassName.name),
name = syntheticClassName.name,
properties = nodes
.filterIsInstance()
.filterNot { it.hasJvmSynthetic() }
.map { it.asJava(true) },
constructors = emptyList(),
functions = (
nodes
.filterIsInstance()
.filterNot { it.isConst || it.isJvmField || it.hasJvmSynthetic() }
.flatMap { it.javaAccessors(relocateToClass = syntheticClassName.name) } +
nodes
.filterIsInstance()
.flatMap { it.asJava(syntheticClassName.name, true) })
.filterNot { it.hasJvmSynthetic() },
classlikes = emptyList(),
sources = emptyMap(),
expectPresentInSet = null,
visibility = sourceSets.map {
it to JavaVisibility.Public
}.toMap(),
companion = null,
generics = emptyList(),
supertypes = emptyMap(),
documentation = emptyMap(),
modifier = sourceSets.map { it to JavaModifier.Final }.toMap(),
sourceSets = sourceSets,
isExpectActual = false,
extra = PropertyContainer.empty()
)
}
return copy(
functions = emptyList(),
properties = emptyList(),
classlikes = classlikes.map { it.asJava() } + syntheticClasses,
typealiases = emptyList()
)
}
internal fun DProperty.asJava(isTopLevel: Boolean = false, relocateToClass: String? = null) =
copy(
dri = if (relocateToClass.isNullOrBlank()) {
dri
} else {
dri.withClass(relocateToClass)
},
modifier = javaModifierFromSetter(),
visibility = visibility.mapValues {
if (isTopLevel && isConst) {
JavaVisibility.Public
} else if (jvmField() != null) {
it.value.asJava()
} else {
it.value.propertyVisibilityAsJava()
}
},
type = type.asJava(), // TODO: check
setter = null,
getter = null, // Removing getters and setters as they will be available as functions
extra = if (isTopLevel) extra +
extra.mergeAdditionalModifiers(
sourceSets.map {
it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)
}.toMap()
)
else extra
)
internal fun Visibility.asJava() =
when (this) {
is JavaVisibility -> this
is KotlinVisibility.Public, KotlinVisibility.Internal -> JavaVisibility.Public
is KotlinVisibility.Private -> JavaVisibility.Private
is KotlinVisibility.Protected -> JavaVisibility.Protected
}
internal fun DProperty.javaModifierFromSetter() =
modifier.mapValues {
when {
it.value is JavaModifier -> it.value
setter == null -> JavaModifier.Final
else -> JavaModifier.Empty
}
}
internal fun DProperty.javaAccessors(isTopLevel: Boolean = false, relocateToClass: String? = null): List =
listOfNotNull(
getter?.let { getter ->
val name = "get" + name.capitalize()
getter.copy(
dri = if (relocateToClass.isNullOrBlank()) {
getter.dri
} else {
getter.dri.withClass(relocateToClass)
}.withCallableName(name),
name = name,
modifier = javaModifierFromSetter(),
visibility = visibility.mapValues { JavaVisibility.Public },
type = getter.type.asJava(),
extra = if (isTopLevel) getter.extra +
getter.extra.mergeAdditionalModifiers(
sourceSets.map {
it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)
}.toMap()
)
else getter.extra
)
},
setter?.let { setter ->
val name = "set" + name.capitalize()
val baseDRI = (if (relocateToClass.isNullOrBlank()) {
setter.dri
} else {
setter.dri.withClass(relocateToClass)
}).withCallableName(name)
setter.copy(
dri = baseDRI,
name = name,
parameters = setter.parameters.map {
it.copy(
dri = baseDRI.copy(
target = it.dri.target,
extra = it.dri.extra
), type = it.type.asJava()
)
},
modifier = javaModifierFromSetter(),
visibility = visibility.mapValues { JavaVisibility.Public },
type = Void,
extra = if (isTopLevel) setter.extra + setter.extra.mergeAdditionalModifiers(
sourceSets.map {
it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)
}.toMap()
)
else setter.extra
)
}
)
private fun DFunction.asJava(
containingClassName: String,
newName: String,
parameters: List,
isTopLevel: Boolean = false
): DFunction {
return copy(
dri = dri.copy(classNames = containingClassName, callable = dri.callable?.copy(name = newName)),
name = newName,
type = type.asJava(),
modifier = if (modifier.all { (_, v) -> v is KotlinModifier.Final } && isConstructor)
sourceSets.map { it to JavaModifier.Empty }.toMap()
else sourceSets.map { it to modifier.values.first() }.toMap(),
parameters = listOfNotNull(receiver?.asJava()) + parameters.map { it.asJava() },
visibility = visibility.map { (sourceSet, visibility) -> Pair(sourceSet, visibility.asJava()) }.toMap(),
receiver = null,
extra = if (isTopLevel) {
extra + extra.mergeAdditionalModifiers(
sourceSets.map {
it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)
}.toMap()
)
} else {
extra
}
)
}
private fun DFunction.withJvmOverloads(
containingClassName: String,
newName: String,
isTopLevel: Boolean = false
): List? {
val (paramsWithDefaults, paramsWithoutDefaults) = parameters
.withIndex()
.partition { (_, p) -> p.extra[DefaultValue] != null }
return paramsWithDefaults
.runningFold(paramsWithoutDefaults) { acc, param -> (acc + param) }
.map { params ->
asJava(
containingClassName,
newName,
params
.sortedBy(IndexedValue::index)
.map { it.value },
isTopLevel
)
}
.reversed()
.takeIf { it.isNotEmpty() }
}
internal fun DFunction.asJava(containingClassName: String, isTopLevel: Boolean = false): List {
val newName = when {
isConstructor -> containingClassName
else -> name
}
val baseFunction = asJava(containingClassName, newName, parameters, isTopLevel)
return if (hasJvmOverloads()) {
withJvmOverloads(containingClassName, newName, isTopLevel) ?: listOf(baseFunction)
} else {
listOf(baseFunction)
}
}
internal fun DClasslike.asJava(): DClasslike = when (this) {
is DClass -> asJava()
is DEnum -> asJava()
is DAnnotation -> asJava()
is DObject -> asJava()
is DInterface -> asJava()
else -> throw IllegalArgumentException("$this shouldn't be here")
}
internal fun DClass.asJava(): DClass = copy(
constructors = constructors
.filterNot { it.hasJvmSynthetic() }
.flatMap { it.asJava(dri.classNames ?: name) }, // name may not always be valid here, however classNames should always be not null
functions = functionsInJava(),
properties = properties
.filterNot { it.hasJvmSynthetic() }
.map { it.asJava() },
classlikes = classlikes.map { it.asJava() },
generics = generics.map { it.asJava() },
supertypes = supertypes.mapValues { it.value.map { it.asJava() } },
modifier = if (modifier.all { (_, v) -> v is KotlinModifier.Empty }) sourceSets.map { it to JavaModifier.Final }
.toMap()
else sourceSets.map { it to modifier.values.first() }.toMap()
)
internal fun DClass.functionsInJava(): List =
properties
.filter { it.jvmField() == null && !it.hasJvmSynthetic() }
.flatMap { property -> listOfNotNull(property.getter, property.setter) }
.plus(functions)
.filterNot { it.hasJvmSynthetic() }
.flatMap { it.asJava(dri.classNames ?: name) }
private fun DTypeParameter.asJava(): DTypeParameter = copy(
variantTypeParameter = variantTypeParameter.withDri(dri.possiblyAsJava()),
bounds = bounds.map { it.asJava() }
)
private fun Projection.asJava(): Projection = when (this) {
is Star -> Star
is Covariance<*> -> copy(inner.asJava())
is Contravariance<*> -> copy(inner.asJava())
is Invariance<*> -> copy(inner.asJava())
is Bound -> asJava()
}
private fun Bound.asJava(): Bound = when (this) {
is TypeParameter -> copy(dri.possiblyAsJava())
is GenericTypeConstructor -> copy(
dri = dri.possiblyAsJava(),
projections = projections.map { it.asJava() }
)
is FunctionalTypeConstructor -> copy(
dri = dri.possiblyAsJava(),
projections = projections.map { it.asJava() }
)
is TypeAliased -> copy(
typeAlias = typeAlias.asJava(),
inner = inner.asJava()
)
is Nullable -> copy(inner.asJava())
is PrimitiveJavaType -> this
is Void -> this
is JavaObject -> this
is Dynamic -> this
is UnresolvedBound -> this
}
internal fun DEnum.asJava(): DEnum = copy(
constructors = constructors.flatMap { it.asJava(dri.classNames ?: name) },
functions = functions
.plus(
properties
.filterNot { it.hasJvmSynthetic() }
.flatMap { listOf(it.getter, it.setter) }
)
.filterNotNull()
.filterNot { it.hasJvmSynthetic() }
.flatMap { it.asJava(dri.classNames ?: name) },
properties = properties
.filterNot { it.hasJvmSynthetic() }
.map { it.asJava() },
classlikes = classlikes.map { it.asJava() },
supertypes = supertypes.mapValues { it.value.map { it.asJava() } }
// , entries = entries.map { it.asJava() }
)
internal fun DObject.asJava(): DObject = copy(
functions = functions
.plus(
properties
.filterNot { it.hasJvmSynthetic() }
.flatMap { listOf(it.getter, it.setter) }
)
.filterNotNull()
.filterNot { it.hasJvmSynthetic() }
.flatMap { it.asJava(dri.classNames ?: name.orEmpty()) },
properties = properties
.filterNot { it.hasJvmSynthetic() }
.map { it.asJava() } +
DProperty(
name = "INSTANCE",
modifier = sourceSets.map { it to JavaModifier.Final }.toMap(),
dri = dri.copy(callable = Callable("INSTANCE", null, emptyList())),
documentation = emptyMap(),
sources = emptyMap(),
visibility = sourceSets.map {
it to JavaVisibility.Public
}.toMap(),
type = GenericTypeConstructor(dri, emptyList()),
setter = null,
getter = null,
sourceSets = sourceSets,
receiver = null,
generics = emptyList(),
expectPresentInSet = expectPresentInSet,
isExpectActual = false,
extra = PropertyContainer.withAll(sourceSets.map {
mapOf(it to setOf(ExtraModifiers.JavaOnlyModifiers.Static)).toAdditionalModifiers()
})
),
classlikes = classlikes.map { it.asJava() },
supertypes = supertypes.mapValues { it.value.map { it.asJava() } }
)
internal fun DInterface.asJava(): DInterface = copy(
functions = functions
.plus(
properties
.filterNot { it.hasJvmSynthetic() }
.flatMap { listOf(it.getter, it.setter) }
)
.filterNotNull()
.filterNot { it.hasJvmSynthetic() }
.flatMap { it.asJava(dri.classNames ?: name) },
properties = emptyList(),
classlikes = classlikes.map { it.asJava() }, // TODO: public static final class DefaultImpls with impls for methods
generics = generics.map { it.asJava() },
supertypes = supertypes.mapValues { it.value.map { it.asJava() } }
)
internal fun DAnnotation.asJava(): DAnnotation = copy(
properties = properties.map { it.asJava() },
constructors = emptyList(),
classlikes = classlikes.map { it.asJava() }
) // TODO investigate if annotation class can have methods and properties not from constructor
internal fun DParameter.asJava(): DParameter = copy(
type = type.asJava(),
name = if (name.isNullOrBlank()) "\$self" else name
)
internal fun Visibility.propertyVisibilityAsJava(): Visibility =
if (this is JavaVisibility) this
else JavaVisibility.Private
internal fun String.getAsPrimitive(): JvmPrimitiveType? = org.jetbrains.kotlin.builtins.PrimitiveType.values()
.find { it.typeFqName.asString() == this }
?.let { JvmPrimitiveType.get(it) }
private fun DRI.partialFqName() = packageName?.let { "$it." } + classNames
private fun DRI.possiblyAsJava() = this.partialFqName().mapToJava()?.toDRI(this) ?: this
private fun TypeConstructor.possiblyAsJava(): TypeConstructor = when (this) {
is GenericTypeConstructor -> copy(dri = this.dri.possiblyAsJava())
is FunctionalTypeConstructor -> copy(dri = this.dri.possiblyAsJava())
}
private fun String.mapToJava(): ClassId? =
JavaToKotlinClassMap.mapKotlinToJava(FqName(this).toUnsafe())
internal fun ClassId.toDRI(dri: DRI?): DRI = DRI(
packageName = packageFqName.asString(),
classNames = classNames(),
callable = dri?.callable,//?.asJava(), TODO: check this
extra = null,
target = PointingToDeclaration
)
internal fun TypeConstructorWithKind.asJava(): TypeConstructorWithKind =
TypeConstructorWithKind(
typeConstructor = typeConstructor.possiblyAsJava(),
kind = kind.asJava()
)
internal fun ClassKind.asJava(): ClassKind {
return when (this) {
is JavaClassKindTypes -> this
KotlinClassKindTypes.CLASS -> JavaClassKindTypes.CLASS
KotlinClassKindTypes.INTERFACE -> JavaClassKindTypes.INTERFACE
KotlinClassKindTypes.ENUM_CLASS -> JavaClassKindTypes.ENUM_CLASS
KotlinClassKindTypes.ENUM_ENTRY -> JavaClassKindTypes.ENUM_ENTRY
KotlinClassKindTypes.ANNOTATION_CLASS -> JavaClassKindTypes.ANNOTATION_CLASS
KotlinClassKindTypes.OBJECT -> JavaClassKindTypes.CLASS
else -> throw IllegalStateException("Non exchaustive match while trying to convert $this to Java")
}
}
private fun PropertyContainer.mergeAdditionalModifiers(second: SourceSetDependent>) =
this[AdditionalModifiers]?.squash(AdditionalModifiers(second)) ?: AdditionalModifiers(second)
private fun AdditionalModifiers.squash(second: AdditionalModifiers) =
AdditionalModifiers(content + second.content)
internal fun ClassId.classNames(): String =
shortClassName.identifier + (outerClassId?.classNames()?.let { ".$it" } ?: "")
© 2015 - 2025 Weber Informatics LLC | Privacy Policy