package org.jetbrains.dokka.javadoc import com.sun.javadoc.* import org.jetbrains.dokka.* import java.lang.reflect.Modifier import java.util.* import kotlin.reflect.KClass private interface HasModule { val module: ModuleNodeAdapter } private interface HasDocumentationNode { val node: DocumentationNode } open class DocumentationNodeBareAdapter(override val node: DocumentationNode) : Doc, HasDocumentationNode { private var rawCommentText_: String? = null override fun name(): String = node.name override fun position(): SourcePosition? = SourcePositionAdapter(node) override fun inlineTags(): Array? = emptyArray() override fun firstSentenceTags(): Array? = emptyArray() override fun tags(): Array = emptyArray() override fun tags(tagname: String?): Array? = tags().filter { it.kind() == tagname || it.kind() == "@$tagname" }.toTypedArray() override fun seeTags(): Array? = tags().filterIsInstance().toTypedArray() override fun commentText(): String = "" override fun setRawCommentText(rawDocumentation: String?) { rawCommentText_ = rawDocumentation ?: "" } override fun getRawCommentText(): String = rawCommentText_ ?: "" override fun isError(): Boolean = false override fun isException(): Boolean = node.kind == NodeKind.Exception override fun isEnumConstant(): Boolean = node.kind == NodeKind.EnumItem override fun isEnum(): Boolean = node.kind == NodeKind.Enum override fun isMethod(): Boolean = node.kind == NodeKind.Function override fun isInterface(): Boolean = node.kind == NodeKind.Interface override fun isField(): Boolean = node.kind == NodeKind.Field override fun isClass(): Boolean = node.kind == NodeKind.Class override fun isAnnotationType(): Boolean = node.kind == NodeKind.AnnotationClass override fun isConstructor(): Boolean = node.kind == NodeKind.Constructor override fun isOrdinaryClass(): Boolean = node.kind == NodeKind.Class override fun isAnnotationTypeElement(): Boolean = node.kind == NodeKind.Annotation override fun compareTo(other: Any?): Int = when (other) { !is DocumentationNodeAdapter -> 1 else -> node.name.compareTo(other.node.name) } override fun equals(other: Any?): Boolean = node.qualifiedName() == (other as? DocumentationNodeAdapter)?.node?.qualifiedName() override fun hashCode(): Int = node.name.hashCode() override fun isIncluded(): Boolean = node.kind != NodeKind.ExternalClass } // TODO think of source position instead of null // TODO tags open class DocumentationNodeAdapter(override val module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeBareAdapter(node), HasModule { override fun inlineTags(): Array = buildInlineTags(module, this, node.content).toTypedArray() override fun firstSentenceTags(): Array = buildInlineTags(module, this, node.summary).toTypedArray() override fun tags(): Array { val result = ArrayList(buildInlineTags(module, this, node.content)) node.content.sections.flatMapTo(result) { when (it.tag) { ContentTags.SeeAlso -> buildInlineTags(module, this, it) else -> emptyList() } } node.deprecation?.let { val content = it.content.asText() if (content != null) { result.add(TagImpl(this, "deprecated", content)) } } return result.toTypedArray() } } // should be extension property but can't because of KT-8745 private fun nodeAnnotations(self: T): List where T : HasModule, T : HasDocumentationNode = self.node.annotations.map { AnnotationDescAdapter(self.module, it) } private fun DocumentationNode.hasAnnotation(klass: KClass<*>) = klass.qualifiedName in annotations.map { it.qualifiedName() } private fun DocumentationNode.hasModifier(name: String) = details(NodeKind.Modifier).any { it.name == name } class PackageAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), PackageDoc { private val allClasses = listOf(node).collectAllTypesRecursively() override fun findClass(className: String?): ClassDoc? = allClasses.get(className)?.let { ClassDocumentationNodeAdapter(module, it) } override fun annotationTypes(): Array = emptyArray() override fun annotations(): Array = node.members(NodeKind.AnnotationClass).map { AnnotationDescAdapter(module, it) }.toTypedArray() override fun exceptions(): Array = node.members(NodeKind.Exception).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() override fun ordinaryClasses(): Array = node.members(NodeKind.Class).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() override fun interfaces(): Array = node.members(NodeKind.Interface).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() override fun errors(): Array = emptyArray() override fun enums(): Array = node.members(NodeKind.Enum).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() override fun allClasses(filter: Boolean): Array = allClasses.values.map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() override fun allClasses(): Array = allClasses(true) override fun isIncluded(): Boolean = node.name in module.allPackages } class AnnotationTypeDocAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ClassDocumentationNodeAdapter(module, node), AnnotationTypeDoc { override fun elements(): Array? = emptyArray() // TODO } class AnnotationDescAdapter(val module: ModuleNodeAdapter, val node: DocumentationNode) : AnnotationDesc { override fun annotationType(): AnnotationTypeDoc? = AnnotationTypeDocAdapter(module, node) // TODO ????? override fun isSynthesized(): Boolean = false override fun elementValues(): Array? = emptyArray() // TODO } open class ProgramElementAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), ProgramElementDoc { override fun isPublic(): Boolean = true override fun isPackagePrivate(): Boolean = false override fun isStatic(): Boolean = node.hasModifier("static") override fun modifierSpecifier(): Int = Modifier.PUBLIC + if (isStatic) Modifier.STATIC else 0 override fun qualifiedName(): String? = node.qualifiedName() override fun annotations(): Array? = nodeAnnotations(this).toTypedArray() override fun modifiers(): String? = "public ${if (isStatic) "static" else ""}".trim() override fun isProtected(): Boolean = false override fun isFinal(): Boolean = node.hasModifier("final") override fun containingPackage(): PackageDoc? { if (node.kind == NodeKind.Type) { return null } var owner: DocumentationNode? = node while (owner != null) { if (owner.kind == NodeKind.Package) { return PackageAdapter(module, owner) } owner = owner.owner } return null } override fun containingClass(): ClassDoc? { if (node.kind == NodeKind.Type) { return null } var owner = node.owner while (owner != null) { if (owner.kind in NodeKind.classLike) { return ClassDocumentationNodeAdapter(module, owner) } owner = owner.owner } return null } override fun isPrivate(): Boolean = false override fun isIncluded(): Boolean = containingPackage()?.isIncluded ?: false && containingClass()?.let { it.isIncluded } ?: true } open class TypeAdapter(override val module: ModuleNodeAdapter, override val node: DocumentationNode) : Type, HasDocumentationNode, HasModule { private val javaLanguageService = JavaLanguageService() override fun qualifiedTypeName(): String = javaLanguageService.getArrayElementType(node)?.qualifiedNameFromType() ?: node.qualifiedNameFromType() override fun typeName(): String = javaLanguageService.getArrayElementType(node)?.simpleName() ?: node.simpleName() override fun simpleTypeName(): String = typeName() // TODO difference typeName() vs simpleTypeName() override fun dimension(): String = Collections.nCopies(javaLanguageService.getArrayDimension(node), "[]").joinToString("") override fun isPrimitive(): Boolean = simpleTypeName() in setOf("int", "long", "short", "byte", "char", "double", "float", "boolean", "void") override fun asClassDoc(): ClassDoc? = if (isPrimitive) null else elementType?.asClassDoc() ?: when (node.kind) { in NodeKind.classLike, NodeKind.ExternalClass, NodeKind.Exception -> module.classNamed(qualifiedTypeName()) ?: ClassDocumentationNodeAdapter(module, node) else -> when { node.links.isNotEmpty() -> TypeAdapter(module, node.links.first()).asClassDoc() else -> ClassDocumentationNodeAdapter(module, node) // TODO ? } } override fun asTypeVariable(): TypeVariable? = if (node.kind == NodeKind.TypeParameter) TypeVariableAdapter(module, node) else null override fun asParameterizedType(): ParameterizedType? = if (node.details(NodeKind.Type).isNotEmpty() && javaLanguageService.getArrayElementType(node) == null) ParameterizedTypeAdapter(module, node) else null override fun asAnnotationTypeDoc(): AnnotationTypeDoc? = if (node.kind == NodeKind.AnnotationClass) AnnotationTypeDocAdapter(module, node) else null override fun asAnnotatedType(): AnnotatedType? = if (node.annotations.isNotEmpty()) AnnotatedTypeAdapter(module, node) else null override fun getElementType(): Type? = javaLanguageService.getArrayElementType(node)?.let { et -> TypeAdapter(module, et) } override fun asWildcardType(): WildcardType? = null override fun toString(): String = qualifiedTypeName() + dimension() override fun hashCode(): Int = node.name.hashCode() override fun equals(other: Any?): Boolean = other is TypeAdapter && toString() == other.toString() } class NotAnnotatedTypeAdapter(typeAdapter: AnnotatedTypeAdapter) : Type by typeAdapter { override fun asAnnotatedType() = null } class AnnotatedTypeAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), AnnotatedType { override fun underlyingType(): Type? = NotAnnotatedTypeAdapter(this) override fun annotations(): Array = nodeAnnotations(this).toTypedArray() } class WildcardTypeAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), WildcardType { override fun extendsBounds(): Array = node.details(NodeKind.UpperBound).map { TypeAdapter(module, it) }.toTypedArray() override fun superBounds(): Array = node.details(NodeKind.LowerBound).map { TypeAdapter(module, it) }.toTypedArray() } class TypeVariableAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), TypeVariable { override fun owner(): ProgramElementDoc = node.owner!!.let { owner -> when (owner.kind) { NodeKind.Function, NodeKind.Constructor -> ExecutableMemberAdapter(module, owner) NodeKind.Class, NodeKind.Interface, NodeKind.Enum -> ClassDocumentationNodeAdapter(module, owner) else -> ProgramElementAdapter(module, node.owner!!) } } override fun bounds(): Array? = node.details(NodeKind.UpperBound).map { TypeAdapter(module, it) }.toTypedArray() override fun annotations(): Array? = node.members(NodeKind.Annotation).map { AnnotationDescAdapter(module, it) }.toTypedArray() override fun qualifiedTypeName(): String = node.name override fun simpleTypeName(): String = node.name override fun typeName(): String = node.name override fun hashCode(): Int = node.name.hashCode() override fun equals(other: Any?): Boolean = other is Type && other.typeName() == typeName() && other.asTypeVariable()?.owner() == owner() override fun asTypeVariable(): TypeVariableAdapter = this } class ParameterizedTypeAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : TypeAdapter(module, node), ParameterizedType { override fun typeArguments(): Array = node.details(NodeKind.Type).map { TypeVariableAdapter(module, it) }.toTypedArray() override fun superclassType(): Type? = node.lookupSuperClasses(module) .firstOrNull { it.kind == NodeKind.Class || it.kind == NodeKind.ExternalClass } ?.let { ClassDocumentationNodeAdapter(module, it) } override fun interfaceTypes(): Array = node.lookupSuperClasses(module) .filter { it.kind == NodeKind.Interface } .map { ClassDocumentationNodeAdapter(module, it) } .toTypedArray() override fun containingType(): Type? = when (node.owner?.kind) { NodeKind.Package -> null NodeKind.Class, NodeKind.Interface, NodeKind.Object, NodeKind.Enum -> ClassDocumentationNodeAdapter(module, node.owner!!) else -> null } } class ParameterAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : DocumentationNodeAdapter(module, node), Parameter { override fun typeName(): String? = JavaLanguageService().renderType(node.detail(NodeKind.Type)) override fun type(): Type? = TypeAdapter(module, node.detail(NodeKind.Type)) override fun annotations(): Array = nodeAnnotations(this).toTypedArray() } class ReceiverParameterAdapter(module: ModuleNodeAdapter, val receiverType: DocumentationNode, val parent: ExecutableMemberAdapter) : DocumentationNodeAdapter(module, receiverType), Parameter { override fun typeName(): String? = receiverType.name override fun type(): Type? = TypeAdapter(module, receiverType) override fun annotations(): Array = nodeAnnotations(this).toTypedArray() override fun name(): String = tryName("receiver") private tailrec fun tryName(name: String): String = when (name) { in parent.parameters().drop(1).map { it.name() } -> tryName("$$name") else -> name } } fun classOf(fqName: String, kind: NodeKind = NodeKind.Class) = DocumentationNode(fqName.substringAfterLast(".", fqName), Content.Empty, kind).let { node -> val pkg = fqName.substringBeforeLast(".", "") if (pkg.isNotEmpty()) { node.append(DocumentationNode(pkg, Content.Empty, NodeKind.Package), RefKind.Owner) } node } private fun DocumentationNode.hasNonEmptyContent() = this.content.summary !is ContentEmpty || this.content.description !is ContentEmpty || this.content.sections.isNotEmpty() open class ExecutableMemberAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ProgramElementAdapter(module, node), ExecutableMemberDoc { override fun isSynthetic(): Boolean = false override fun isNative(): Boolean = node.annotations.any { it.name == "native" } override fun thrownExceptions(): Array = emptyArray() // TODO override fun throwsTags(): Array = node.content.sections .filter { it.tag == ContentTags.Exceptions && it.subjectName != null } .map { ThrowsTagAdapter(this, ClassDocumentationNodeAdapter(module, classOf(it.subjectName!!, NodeKind.Exception)), it.children) } .toTypedArray() override fun isVarArgs(): Boolean = node.details(NodeKind.Parameter).any { false } // TODO override fun isSynchronized(): Boolean = node.annotations.any { it.name == "synchronized" } override fun paramTags(): Array = collectParamTags(NodeKind.Parameter, sectionFilter = { it.subjectName in parameters().map { it.name() } }) override fun thrownExceptionTypes(): Array = emptyArray() override fun receiverType(): Type? = receiverNode()?.let { receiver -> TypeAdapter(module, receiver) } override fun flatSignature(): String = node.details(NodeKind.Parameter).map { JavaLanguageService().renderType(it) }.joinToString(", ", "(", ")") override fun signature(): String = node.details(NodeKind.Parameter).map { JavaLanguageService().renderType(it) }.joinToString(", ", "(", ")") // TODO it should be FQ types override fun parameters(): Array = ((receiverNode()?.let { receiver -> listOf(ReceiverParameterAdapter(module, receiver, this)) } ?: emptyList()) + node.details(NodeKind.Parameter).map { ParameterAdapter(module, it) } ).toTypedArray() override fun typeParameters(): Array = node.details(NodeKind.TypeParameter).map { TypeVariableAdapter(module, it) }.toTypedArray() override fun typeParamTags(): Array = collectParamTags(NodeKind.TypeParameter, sectionFilter = { it.subjectName in typeParameters().map { it.simpleTypeName() } }) private fun receiverNode() = node.details(NodeKind.Receiver).let { receivers -> when { receivers.isNotEmpty() -> receivers.single().detail(NodeKind.Type) else -> null } } } class ConstructorAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ExecutableMemberAdapter(module, node), ConstructorDoc { override fun name(): String = node.owner?.name ?: throw IllegalStateException("No owner for $node") override fun containingClass(): ClassDoc? { return super.containingClass() } } class MethodAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ExecutableMemberAdapter(module, node), MethodDoc { override fun overrides(meth: MethodDoc?): Boolean = false // TODO override fun overriddenType(): Type? = node.overrides.firstOrNull()?.owner?.let { owner -> TypeAdapter(module, owner) } override fun overriddenMethod(): MethodDoc? = node.overrides.map { MethodAdapter(module, it) }.firstOrNull() override fun overriddenClass(): ClassDoc? = overriddenMethod()?.containingClass() override fun isAbstract(): Boolean = false // TODO override fun isDefault(): Boolean = false override fun returnType(): Type = TypeAdapter(module, node.detail(NodeKind.Type)) override fun tags(tagname: String?) = super.tags(tagname) override fun tags(): Array { val tags = super.tags().toMutableList() node.content.findSectionByTag(ContentTags.Return)?.let { tags += ReturnTagAdapter(module, this, it.children) } return tags.toTypedArray() } } class FieldAdapter(module: ModuleNodeAdapter, node: DocumentationNode) : ProgramElementAdapter(module, node), FieldDoc { override fun isSynthetic(): Boolean = false override fun constantValueExpression(): String? = node.detailOrNull(NodeKind.Value)?.let { it.name } override fun constantValue(): Any? = constantValueExpression() override fun type(): Type = TypeAdapter(module, node.detail(NodeKind.Type)) override fun isTransient(): Boolean = node.hasAnnotation(Transient::class) override fun serialFieldTags(): Array = emptyArray() override fun isVolatile(): Boolean = node.hasAnnotation(Volatile::class) } open class ClassDocumentationNodeAdapter(module: ModuleNodeAdapter, val classNode: DocumentationNode) : ProgramElementAdapter(module, classNode), Type by TypeAdapter(module, classNode), ClassDoc { override fun name(): String { val parent = classNode.owner if (parent?.kind in NodeKind.classLike) { return parent!!.name + "." + classNode.name } return classNode.simpleName() } override fun constructors(filter: Boolean): Array = classNode.members(NodeKind.Constructor).map { ConstructorAdapter(module, it) }.toTypedArray() override fun constructors(): Array = constructors(true) override fun importedPackages(): Array = emptyArray() override fun importedClasses(): Array? = emptyArray() override fun typeParameters(): Array = classNode.details(NodeKind.TypeParameter).map { TypeVariableAdapter(module, it) }.toTypedArray() override fun asTypeVariable(): TypeVariable? = if (classNode.kind == NodeKind.Class) TypeVariableAdapter(module, classNode) else null override fun isExternalizable(): Boolean = interfaces().any { it.qualifiedName() == "java.io.Externalizable" } override fun definesSerializableFields(): Boolean = false override fun methods(filter: Boolean): Array = classNode.members(NodeKind.Function).map { MethodAdapter(module, it) }.toTypedArray() // TODO include get/set methods override fun methods(): Array = methods(true) override fun enumConstants(): Array? = classNode.members(NodeKind.EnumItem).map { FieldAdapter(module, it) }.toTypedArray() override fun isAbstract(): Boolean = classNode.details(NodeKind.Modifier).any { it.name == "abstract" } override fun interfaceTypes(): Array = classNode.lookupSuperClasses(module) .filter { it.kind == NodeKind.Interface } .map { ClassDocumentationNodeAdapter(module, it) } .toTypedArray() override fun interfaces(): Array = classNode.lookupSuperClasses(module) .filter { it.kind == NodeKind.Interface } .map { ClassDocumentationNodeAdapter(module, it) } .toTypedArray() override fun typeParamTags(): Array = collectParamTags(NodeKind.TypeParameter, sectionFilter = { it.subjectName in typeParameters().map { it.simpleTypeName() } }) override fun fields(): Array = fields(true) override fun fields(filter: Boolean): Array = classNode.members(NodeKind.Field).map { FieldAdapter(module, it) }.toTypedArray() override fun findClass(className: String?): ClassDoc? = null // TODO !!! override fun serializableFields(): Array = emptyArray() override fun superclassType(): Type? = classNode.lookupSuperClasses(module).singleOrNull { it.kind == NodeKind.Class }?.let { ClassDocumentationNodeAdapter(module, it) } override fun serializationMethods(): Array = emptyArray() // TODO override fun superclass(): ClassDoc? = classNode.lookupSuperClasses(module).singleOrNull { it.kind == NodeKind.Class }?.let { ClassDocumentationNodeAdapter(module, it) } override fun isSerializable(): Boolean = false // TODO override fun subclassOf(cd: ClassDoc?): Boolean { if (cd == null) { return false } val expectedFQName = cd.qualifiedName() val types = arrayListOf(classNode) val visitedTypes = HashSet() while (types.isNotEmpty()) { val type = types.removeAt(types.lastIndex) val fqName = type.qualifiedName() if (expectedFQName == fqName) { return true } visitedTypes.add(fqName) types.addAll(type.details(NodeKind.Supertype).filter { it.qualifiedName() !in visitedTypes }) } return false } override fun innerClasses(): Array = classNode.members(NodeKind.Class).map { ClassDocumentationNodeAdapter(module, it) }.toTypedArray() override fun innerClasses(filter: Boolean): Array = innerClasses() } fun DocumentationNode.lookupSuperClasses(module: ModuleNodeAdapter) = details(NodeKind.Supertype) .map { it.links.firstOrNull() } .map { module.allTypes[it?.qualifiedName()] } .filterNotNull() fun List.collectAllTypesRecursively(): Map { val result = hashMapOf() fun DocumentationNode.collectTypesRecursively() { val classLikeMembers = NodeKind.classLike.flatMap { members(it) } classLikeMembers.forEach { result.put(it.qualifiedName(), it) it.collectTypesRecursively() } } forEach { it.collectTypesRecursively() } return result } class ModuleNodeAdapter(val module: DocumentationModule, val reporter: DocErrorReporter, val outputPath: String) : DocumentationNodeBareAdapter(module), DocErrorReporter by reporter, RootDoc { val allPackages = module.members(NodeKind.Package).associateBy { it.name } val allTypes = module.members(NodeKind.Package).collectAllTypesRecursively() override fun packageNamed(name: String?): PackageDoc? = allPackages[name]?.let { PackageAdapter(this, it) } override fun classes(): Array = allTypes.values.map { ClassDocumentationNodeAdapter(this, it) }.toTypedArray() override fun options(): Array> = arrayOf( arrayOf("-d", outputPath), arrayOf("-docencoding", "UTF-8"), arrayOf("-charset", "UTF-8"), arrayOf("-keywords") ) override fun specifiedPackages(): Array? = module.members(NodeKind.Package).map { PackageAdapter(this, it) }.toTypedArray() override fun classNamed(qualifiedName: String?): ClassDoc? = allTypes[qualifiedName]?.let { ClassDocumentationNodeAdapter(this, it) } override fun specifiedClasses(): Array = classes() } private fun DocumentationNodeAdapter.collectParamTags(kind: NodeKind, sectionFilter: (ContentSection) -> Boolean) = (node.details(kind) .filter(DocumentationNode::hasNonEmptyContent) .map { ParamTagAdapter(module, this, it.name, true, it.content.children) } + node.content.sections .filter(sectionFilter) .map { ParamTagAdapter(module, this, it.subjectName ?: "?", true, it.children) }) .toTypedArray()