1 package org.jetbrains.dokka.Formats 2 3 import com.intellij.psi.PsiMember 4 import com.intellij.psi.PsiParameter 5 import org.jetbrains.dokka.* 6 import org.jetbrains.dokka.ExternalDocumentationLinkResolver.Companion.DOKKA_PARAM_PREFIX 7 import org.jetbrains.kotlin.asJava.toLightElements 8 import org.jetbrains.kotlin.descriptors.* 9 import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor 10 import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor 11 import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor 12 import org.jetbrains.kotlin.psi.KtDeclaration 13 import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe 14 import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject 15 import org.jetbrains.kotlin.types.KotlinType 16 17 class JavaLayoutHtmlPackageListService: PackageListService { 18 StringBuildernull19 private fun StringBuilder.appendParam(name: String, value: String) { 20 append(DOKKA_PARAM_PREFIX) 21 append(name) 22 append(":") 23 appendln(value) 24 } 25 formatPackageListnull26 override fun formatPackageList(module: DocumentationModule): String { 27 val packages = module.members(NodeKind.Package).map { it.name } 28 29 return buildString { 30 appendParam("format", "java-layout-html") 31 appendParam("mode", "kotlin") 32 for (p in packages) { 33 appendln(p) 34 } 35 } 36 } 37 38 } 39 40 class JavaLayoutHtmlInboundLinkResolutionService(private val paramMap: Map<String, List<String>>, 41 private val resolutionFacade: DokkaResolutionFacade) : InboundExternalLinkResolutionService { 42 43 constructor(asJava: Boolean, resolutionFacade: DokkaResolutionFacade) : 44 this(mapOf("mode" to listOf(if (asJava) "java" else "kotlin")), resolutionFacade) 45 46 47 private val isJavaMode = paramMap["mode"]!!.single() == "java" 48 getContainerPathnull49 private fun getContainerPath(symbol: DeclarationDescriptor): String? { 50 return when (symbol) { 51 is PackageFragmentDescriptor -> symbol.fqName.asString().replace('.', '/') + "/" 52 is ClassifierDescriptor -> getContainerPath(symbol.findPackage()) + symbol.nameWithOuter() + ".html" 53 else -> null 54 } 55 } 56 findPackagenull57 private fun DeclarationDescriptor.findPackage(): PackageFragmentDescriptor = 58 generateSequence(this) { it.containingDeclaration }.filterIsInstance<PackageFragmentDescriptor>().first() 59 nameWithOuternull60 private fun ClassifierDescriptor.nameWithOuter(): String = 61 generateSequence(this) { it.containingDeclaration as? ClassifierDescriptor } <lambda>null62 .toList().asReversed().joinToString(".") { it.name.asString() } 63 getJavaPagePathnull64 private fun getJavaPagePath(symbol: DeclarationDescriptor): String? { 65 66 val sourcePsi = symbol.sourcePsi() ?: return null 67 val source = (if (sourcePsi is KtDeclaration) { 68 sourcePsi.toLightElements().firstOrNull() 69 } else { 70 sourcePsi 71 }) as? PsiMember ?: return null 72 val desc = source.getJavaMemberDescriptor(resolutionFacade) ?: return null 73 return getPagePath(desc) 74 } 75 getPagePathnull76 private fun getPagePath(symbol: DeclarationDescriptor): String? { 77 return when (symbol) { 78 is PackageFragmentDescriptor -> getContainerPath(symbol) + "package-summary.html" 79 is EnumEntrySyntheticClassDescriptor -> getContainerPath(symbol.containingDeclaration) + "#" + symbol.signatureForAnchorUrlEncoded() 80 is ClassifierDescriptor -> getContainerPath(symbol) + "#" 81 is FunctionDescriptor, is PropertyDescriptor -> getContainerPath(symbol.containingDeclaration!!) + "#" + symbol.signatureForAnchorUrlEncoded() 82 else -> null 83 } 84 } 85 DeclarationDescriptornull86 private fun DeclarationDescriptor.signatureForAnchor(): String? { 87 88 fun ReceiverParameterDescriptor.extractReceiverName(): String { 89 var receiverClass: DeclarationDescriptor = type.constructor.declarationDescriptor!! 90 if (receiverClass.isCompanionObject()) { 91 receiverClass = receiverClass.containingDeclaration!! 92 } else if (receiverClass is TypeParameterDescriptor) { 93 val upperBoundClass = receiverClass.upperBounds.singleOrNull()?.constructor?.declarationDescriptor 94 if (upperBoundClass != null) { 95 receiverClass = upperBoundClass 96 } 97 } 98 99 return receiverClass.name.asString() 100 } 101 102 fun KotlinType.qualifiedNameForSignature(): String { 103 val desc = constructor.declarationDescriptor 104 return desc?.fqNameUnsafe?.asString() ?: "<ERROR TYPE NAME>" 105 } 106 107 fun StringBuilder.appendReceiverAndCompanion(desc: CallableDescriptor) { 108 if (desc.containingDeclaration.isCompanionObject()) { 109 append("Companion.") 110 } 111 desc.extensionReceiverParameter?.let { 112 append("(") 113 append(it.extractReceiverName()) 114 append(").") 115 } 116 } 117 118 return when (this) { 119 is EnumEntrySyntheticClassDescriptor -> buildString { 120 append("ENUM_VALUE:") 121 append(name.asString()) 122 } 123 is JavaMethodDescriptor -> buildString { 124 append(name.asString()) 125 valueParameters.joinTo(this, prefix = "(", postfix = ")") { 126 val param = it.sourcePsi() as PsiParameter 127 param.type.canonicalText 128 } 129 } 130 is JavaPropertyDescriptor -> buildString { 131 append(name.asString()) 132 } 133 is FunctionDescriptor -> buildString { 134 appendReceiverAndCompanion(this@signatureForAnchor) 135 append(name.asString()) 136 valueParameters.joinTo(this, prefix = "(", postfix = ")") { 137 it.type.qualifiedNameForSignature() 138 } 139 } 140 is PropertyDescriptor -> buildString { 141 appendReceiverAndCompanion(this@signatureForAnchor) 142 append(name.asString()) 143 append(":") 144 145 append(returnType?.qualifiedNameForSignature()) 146 } 147 else -> null 148 } 149 } 150 DeclarationDescriptornull151 private fun DeclarationDescriptor.signatureForAnchorUrlEncoded(): String? = signatureForAnchor()?.anchorEncoded() 152 153 override fun getPath(symbol: DeclarationDescriptor) = if (isJavaMode) getJavaPagePath(symbol) else getPagePath(symbol) 154 } 155