<lambda>null1 package org.jetbrains.dokka
2 
3 import com.google.inject.Inject
4 import com.google.inject.Singleton
5 import com.intellij.ide.highlighter.JavaFileType
6 import com.intellij.psi.PsiClass
7 import com.intellij.psi.PsiFileFactory
8 import com.intellij.psi.util.PsiTreeUtil
9 import com.intellij.util.LocalTimeCounter
10 import org.intellij.markdown.MarkdownElementTypes
11 import org.intellij.markdown.MarkdownTokenTypes
12 import org.intellij.markdown.parser.LinkMap
13 import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
14 import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
15 import java.io.File
16 
17 @Singleton
18 class PackageDocs
19         @Inject constructor(val linkResolver: DeclarationLinkResolver?,
20                             val logger: DokkaLogger,
21                             val environment: KotlinCoreEnvironment,
22                             val refGraph: NodeReferenceGraph,
23                             val elementSignatureProvider: ElementSignatureProvider)
24 {
25     val moduleContent: MutableContent = MutableContent()
26     private val _packageContent: MutableMap<String, MutableContent> = hashMapOf()
27     val packageContent: Map<String, Content>
28         get() = _packageContent
29 
30     fun parse(fileName: String, linkResolveContext: List<PackageFragmentDescriptor>) {
31         val file = File(fileName)
32         if (file.exists()) {
33             val text = file.readText()
34             val tree = parseMarkdown(text)
35             val linkMap = LinkMap.buildLinkMap(tree.node, text)
36             var targetContent: MutableContent = moduleContent
37             tree.children.forEach {
38                 if (it.type == MarkdownElementTypes.ATX_1) {
39                     val headingText = it.child(MarkdownTokenTypes.ATX_CONTENT)?.text
40                     if (headingText != null) {
41                         targetContent = findTargetContent(headingText.trimStart())
42                     }
43                 } else {
44                     buildContentTo(it, targetContent, LinkResolver(linkMap, { resolveContentLink(fileName, it, linkResolveContext) }))
45                 }
46             }
47         } else {
48             logger.warn("Include file $file was not found.")
49         }
50     }
51 
52     private fun parseHtmlAsJavadoc(text: String, packageName: String, file: File) {
53         val javadocText = text
54                 .replace("*/", "*&#47;")
55                 .removeSurrounding("<html>", "</html>", true).trim()
56                 .removeSurrounding("<body>", "</body>", true)
57                 .lineSequence()
58                 .map { "* $it" }
59                 .joinToString (separator = "\n", prefix = "/**\n", postfix = "\n*/")
60         parseJavadoc(javadocText, packageName, file)
61     }
62 
63     private fun CharSequence.removeSurrounding(prefix: CharSequence, suffix: CharSequence, ignoringCase: Boolean = false): CharSequence {
64         if ((length >= prefix.length + suffix.length) && startsWith(prefix, ignoringCase) && endsWith(suffix, ignoringCase)) {
65             return subSequence(prefix.length, length - suffix.length)
66         }
67         return subSequence(0, length)
68     }
69 
70 
71     private fun parseJavadoc(text: String, packageName: String, file: File) {
72 
73         val psiFileFactory = PsiFileFactory.getInstance(environment.project)
74         val psiFile = psiFileFactory.createFileFromText(
75                 file.nameWithoutExtension + ".java",
76                 JavaFileType.INSTANCE,
77                 "package $packageName; $text\npublic class C {}",
78                 LocalTimeCounter.currentTime(),
79                 false,
80                 true
81         )
82 
83         val psiClass = PsiTreeUtil.getChildOfType(psiFile, PsiClass::class.java)!!
84         val parser = JavadocParser(refGraph, logger, elementSignatureProvider, linkResolver?.externalDocumentationLinkResolver!!)
85         findOrCreatePackageContent(packageName).apply {
86             val content = parser.parseDocumentation(psiClass).content
87             children.addAll(content.children)
88             content.sections.forEach {
89                 addSection(it.tag, it.subjectName).children.addAll(it.children)
90             }
91         }
92     }
93 
94 
95     fun parseJava(fileName: String, packageName: String) {
96         val file = File(fileName)
97         if (file.exists()) {
98             val text = file.readText()
99 
100             val trimmedText = text.trim()
101 
102             if (trimmedText.startsWith("/**")) {
103                 parseJavadoc(text, packageName, file)
104             } else if (trimmedText.toLowerCase().startsWith("<html>")) {
105                 parseHtmlAsJavadoc(trimmedText, packageName, file)
106             }
107         }
108     }
109 
110     private fun findTargetContent(heading: String): MutableContent {
111         if (heading.startsWith("Module") || heading.startsWith("module")) {
112             return moduleContent
113         }
114         if (heading.startsWith("Package") || heading.startsWith("package")) {
115             return findOrCreatePackageContent(heading.substring("package".length).trim())
116         }
117         return findOrCreatePackageContent(heading)
118     }
119 
120     private fun findOrCreatePackageContent(packageName: String) =
121         _packageContent.getOrPut(packageName) { -> MutableContent() }
122 
123     private fun resolveContentLink(fileName: String, href: String, linkResolveContext: List<PackageFragmentDescriptor>): ContentBlock {
124         if (linkResolver != null) {
125             linkResolveContext
126                     .asSequence()
127                     .map { p -> linkResolver.tryResolveContentLink(p, href) }
128                     .filterNotNull()
129                     .firstOrNull()
130                     ?.let { return it }
131         }
132         logger.warn("Unresolved link to `$href` in include ($fileName)")
133         return ContentExternalLink("#")
134     }
135 }