All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.doc.model.KotlinModel.kt Maven / Gradle / Ivy

There is a newer version: 0.12.1230
Show newest version
package org.jetbrains.kotlin.doc.model

import java.io.File
import java.util.*
import com.intellij.openapi.vfs.local.CoreLocalVirtualFile
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileSystemItem
import org.jetbrains.jet.lang.descriptors.CallableDescriptor
import org.jetbrains.jet.lang.descriptors.ClassDescriptor
import org.jetbrains.jet.lang.descriptors.ClassKind
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor
import org.jetbrains.jet.lang.descriptors.ModuleDescriptor
import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor
import org.jetbrains.jet.lang.descriptors.TypeParameterDescriptor
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor
import org.jetbrains.jet.lang.descriptors.Visibilities
import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils
import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils.LineAndColumn
import org.jetbrains.jet.lang.psi.JetFile
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.resolve.BindingContextUtils
import org.jetbrains.jet.lang.resolve.scopes.JetScope
import org.jetbrains.jet.lang.resolve.scopes.receivers.ExtensionReceiver
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lexer.JetTokens
import org.jetbrains.kotlin.doc.*
import org.jetbrains.kotlin.doc.highlighter.SyntaxHighligher
import org.jetbrains.kotlin.doc.templates.KDocTemplate
import org.pegdown.Extensions
import org.pegdown.LinkRenderer
import org.pegdown.LinkRenderer.Rendering
import org.pegdown.PegDownProcessor
import org.pegdown.ast.AutoLinkNode
import org.pegdown.ast.ExpLinkNode
import org.pegdown.ast.RefLinkNode
import org.pegdown.ast.WikiLinkNode

/**
* Returns the collection of functions with duplicate function names filtered out
* so only the first one is included
*/
fun filterDuplicateNames(functions: Collection): Collection {
    var lastName = ""
    return functions.filter{
        val name = it.name
        val answer = name != lastName
        lastName = name
        answer
    }
}

fun containerName(descriptor: DeclarationDescriptor): String = qualifiedName(descriptor.getContainingDeclaration())

fun qualifiedName(descriptor: DeclarationDescriptor?): String {
    if (descriptor == null || descriptor is ModuleDescriptor) {
        return ""
    } else {
        val parent = containerName(descriptor)
        var name = descriptor.getName()?.asString() ?: ""
        if (name.startsWith("<")) {
            name = ""
        }
        val answer = if (parent.length() > 0) parent + "." + name else name
        return if (answer.startsWith(".")) answer.substring(1) else answer
    }
}

fun warning(message: String) {
    println("Warning: $message")
}

fun info(message: String) {
    // println("info: $message")
}

// TODO for some reason the SortedMap causes kotlin to freak out a little :)
fun inheritedExtensionFunctions(functions: Collection): Map> {
    //fun inheritedExtensionFunctions(functions: Collection): SortedMap> {
    val map = extensionFunctions(functions)
    // for each class, lets walk its base classes and add any other extension functions from base classes
    val answer = TreeMap>()
    for (c in map.keySet()) {
        val allFunctions = map.get(c).orEmpty().toSortedSet()
        answer.put(c, allFunctions)
        val des = c.descendants()
        for (b in des) {
            val list = map.get(b)
            if (list != null) {
                if (allFunctions != null) {
                    for (f in list) {
                        if (f != null) {
                            // add the methods from the base class if we don't have a matching method
                            if (!allFunctions.any{ it.name == f.name && it.parameterTypeText == f.parameterTypeText}) {
                                allFunctions.add(f)
                            }
                        }
                    }
                }
            }
        }
    }
    return answer
}

// TODO for some reason the SortedMap causes kotlin to freak out a little :)
fun inheritedExtensionProperties(properties: Collection): Map> {
    val map = extensionProperties(properties)
    // for each class, lets walk its base classes and add any other extension properties from base classes
    val answer = TreeMap>()
    for (c in map.keySet()) {
        val allProperties = map.get(c).orEmpty().toSortedSet()
        answer.put(c, allProperties)
        val des = c.descendants()
        for (b in des) {
            val list = map.get(b)
            if (list != null) {
                if (allProperties != null) {
                    for (f in list) {
                        if (f != null) {
                            // add the proeprties from the base class if we don't have a matching method
                            if (!allProperties.any{ it.name == f.name}) {
                                allProperties.add(f)
                            }
                        }
                    }
                }
            }
        }
    }
    return answer
}


// TODO for some reason the SortedMap causes kotlin to freak out a little :)
fun extensionFunctions(functions: Collection): Map> {
    val map = TreeMap>()
    functions.filter{ it.extensionClass != null }.groupByTo(map){ it.extensionClass!! }
    return map
}

// TODO for some reason the SortedMap causes kotlin to freak out a little :)
fun extensionProperties(properties: Collection): Map> {
    val map = TreeMap>()
    properties.filter{ it.extensionClass != null }.groupByTo(map){ it.extensionClass!! }
    return map
}

abstract class KClassOrPackage(model: KModel, declarationDescriptor: DeclarationDescriptor): KAnnotated(model, declarationDescriptor) {

    public open val functions: SortedSet = TreeSet()

    public open val properties: SortedSet = TreeSet()

    fun findProperty(name: String): KProperty? {
        // TODO we should use a Map?
        return properties.find{ it.name == name }
    }

    fun findFunction(expression: String): KFunction? {
        val idx = expression.indexOf('(')
        val name = if (idx > 0) expression.substring(0, idx) else expression
        val postfix = if (idx > 0) expression.substring(idx).trimTrailing("()") else ""
        return functions.find{ it.name == name && it.parameterTypeText == postfix }
    }
}

// htmlPath does not include "html-src" prefix
class SourceInfo(val psi: JetFile, val relativePath: String, val htmlPath: String)

class KModel(val context: BindingContext, val config: KDocConfig, val sourceDirs: List, val sources: List) {
    // TODO generates java.lang.NoSuchMethodError: kotlin.util.UtilPackage.hashMap(Ljet/TypeInfo;Ljet/TypeInfo;)Ljava/util/HashMap;
    //val packages = sortedMap()
    public val packageMap: SortedMap = TreeMap()

    public val allPackages: Collection
    get() = packageMap.values()!!

    /** Returns the local packages */
    public val packages: Collection
    get() = allPackages.filter{ it.local && config.includePackage(it) }

    public val classes: Collection
    get() = packages.flatMap{ it.classes }

    public var markdownProcessor: PegDownProcessor = PegDownProcessor(Extensions.ALL)
    public var highlighter: SyntaxHighligher = SyntaxHighligher()

    public val title: String
    get() = config.title

    public val version: String
    get() = config.version

    private var _projectRootDir: String? = null

    /**
     * File names we look for in a package directory for the overall description of a package for KDoc
     */
    val packageDescriptionFiles = arrayList("readme.md", "ReadMe.md, readme.html, ReadMe.html")

    private val readMeDirsScanned = HashSet()



    val sourcesInfo: List
    ;{

        val normalizedSourceDirs: List =
        sourceDirs.map { file -> file.getCanonicalPath()!! }

        fun relativePath(psiFile: PsiFile): String {
            val file = File((psiFile.getVirtualFile() as CoreLocalVirtualFile).getPath()!!).getCanonicalFile()!!
            val filePath = file.getPath()!!
            for (sourceDirPath in normalizedSourceDirs) {
                if (filePath.startsWith(sourceDirPath) && filePath.length() > sourceDirPath.length()) {
                    return filePath.substring(sourceDirPath.length + 1)
                }
            }
            throw Exception("$file is not a child of any source roots $normalizedSourceDirs")
        }

        sourcesInfo = sources.map { source ->
            val relativePath = relativePath(source)
            val htmlPath = relativePath.replaceFirst("\\.kt$", "") + ".html"
            SourceInfo(source, relativePath, htmlPath)
        }
    }

    private val sourceInfoByFile = sourcesInfo.toHashMapMappingToKey { sourceInfo -> sourceInfo.psi }

    fun sourceInfoByFile(file: JetFile) = sourceInfoByFile.get(file)!!


    ;{
        /** Loads the model from the given set of source files */
        val allNamespaces = HashSet()
        for (source in sources) {
            // We retrieve a descriptor by a PSI element from the context
            val namespaceDescriptor = BindingContextUtils.namespaceDescriptor(context, source)
            if (namespaceDescriptor != null) {
                allNamespaces.add(namespaceDescriptor);
            } else {
                warning("No NamespaceDescriptor for source $source")
            }
        }
        val allClasses = HashSet()
        for (namespace in allNamespaces) {
            getPackage(namespace)
            for (descriptor in namespace.getMemberScope().getAllDescriptors()) {
                if (descriptor is ClassDescriptor) {
                    val klass = getClass(descriptor)
                    if (klass != null) {
                        allClasses.add(klass)
                    }
                } else if (descriptor is NamespaceDescriptor) {
                    getPackage(descriptor)
                }
            }
        }
    }




    /**
    * Returns the root project directory for calculating relative source links
    */
    fun projectRootDir(): String {
        if (_projectRootDir == null) {
            val rootDir = config.projectRootDir
            _projectRootDir = if (rootDir == null) {
                warning("KDocConfig does not have a projectRootDir defined so we cannot generate relative source Hrefs")
                ""
            } else {
                File(rootDir).getCanonicalPath() ?: ""
            }
        }
        return _projectRootDir ?: ""
    }


    /* Returns the package for the given name or null if it does not exist */
    fun getPackage(name: String): KPackage? = packageMap.get(name)

    /** Returns the package for the given descriptor, creating one if its not available */
    fun getPackage(descriptor: NamespaceDescriptor): KPackage {
        val name = qualifiedName(descriptor)
        var created = false
        val pkg = packageMap.getOrPut(name) {
            created = true
            KPackage(this, descriptor, name)
        }
        if (created) {
            configureComments(pkg, descriptor)
            val scope = descriptor.getMemberScope()
            addFunctions(pkg, scope)
            pkg.local = isLocal(descriptor)
            pkg.useExternalLink = pkg.model.config.resolveLink(pkg.name, false).isNotEmpty()

            if (pkg.wikiDescription.isEmpty()) {
                // lets try find a custom doc
                var file = config.packageDescriptionFiles[name]
                loadWikiDescription(pkg, file)
            }
        }
        return pkg;
    }

    protected fun loadWikiDescription(pkg: KPackage, file: String?): Unit {
        if (file != null) {
            try {
                pkg.wikiDescription = File(file).readText()
            } catch (e: Throwable) {
                warning("Failed to load package ${pkg.name} documentation file $file. Reason $e")
            }
        }
    }

    /**
     * If a package has no detailed description lets try load it from the descriptors
     * source directory if we've not checked that directory before
     */
    fun tryLoadReadMe(pkg: KPackage, descriptor: DeclarationDescriptor): Unit {
        if (pkg.wikiDescription.isEmpty()) {
            // lets try find the package.html or package.md file
            val srcPath =  pkg.model.filePath(descriptor)
            if (srcPath != null) {
                val srcFile = File(srcPath)
                val dir = if (srcFile.isDirectory()) srcFile else srcFile.getParentFile()
                if (dir != null && readMeDirsScanned.add(dir.getPath()!!)) {
                    val f = packageDescriptionFiles.map{ File(dir, it) }.find{ it.exists() }
                    if (f != null) {
                        val file = f.getCanonicalPath()
                        loadWikiDescription(pkg, file)
                    }
                    else {
                        info("package ${pkg.name} has no ReadMe.(html|md) in $dir")
                    }
                }
            }
        }
    }

    fun wikiConvert(text: String, linkRenderer: LinkRenderer, fileName: String?): String {
        return markdownProcessor.markdownToHtml(text, linkRenderer)!!
    }

    fun sourceLinkFor(filePath: String, sourceLine: Int, lineLinkText: String = "#L"): String? {
        val root = config.sourceRootHref
        if (root != null) {
            // lets remove the root project directory
            val rootDir = projectRootDir()
            val canonicalFile = File(filePath).getCanonicalPath() ?: ""
            //println("=========== root dir for filePath: $canonicalFile is $rootDir")
            val relativeFile =
                if (canonicalFile.startsWith(rootDir))
                    canonicalFile.substring(rootDir.length())
                else
                    canonicalFile
            val cleanRoot = root.trimTrailing("/")
            val cleanPath = relativeFile.trimLeading("/")
            return "$cleanRoot/$cleanPath$lineLinkText$sourceLine"
        }
        return null
    }

    protected fun isLocal(descriptor: DeclarationDescriptor): Boolean {
        return if (descriptor is ModuleDescriptor) {
            true
        } else {
            val parent = descriptor.getContainingDeclaration()
            if (parent != null) {
                isLocal(parent)
            } else {
                false
            }
        }
    }

    fun addFunctions(owner: KClassOrPackage, scope: JetScope): Unit {
        try {
            val descriptors = scope.getAllDescriptors()
            for (descriptor in descriptors) {
                if (descriptor is PropertyDescriptor) {
                    val name = descriptor.getName().asString()
                    val returnType = getType(descriptor.getReturnType())
                    if (returnType != null) {
                        val receiver = descriptor.getReceiverParameter()
                        val extensionClass = if (receiver != null) {
                            getType(receiver.getType())
                        } else null

                        val property = KProperty(owner, descriptor, name, returnType, extensionClass?.klass)
                        owner.properties.add(property)
                    }
                } else if (descriptor is CallableDescriptor) {
                    val function = createFunction(owner, descriptor)
                    if (function != null) {
                        owner.functions.add(function)
                    }
                }
            }
        } catch (e: Throwable) {
            warning("Caught exception finding function declarations on $owner $e")
            e.printStackTrace()
        }

    }

    protected fun createFunction(owner: KClassOrPackage, descriptor: CallableDescriptor): KFunction? {
        val returnType = getType(descriptor.getReturnType())
        if (returnType != null) {
            val name = descriptor.getName().asString()
            val parameters = ArrayList()
            val params = descriptor.getValueParameters()
            for (param in params) {
                if (param != null) {
                    val p = createParameter(param)
                    if (p != null) {
                        parameters.add(p)
                    }
                }
            }
            val function = KFunction(descriptor, owner, name, returnType, parameters)
            addTypeParameters(function.typeParameters, descriptor.getTypeParameters())
            configureComments(function, descriptor)
            val receiver = descriptor.getReceiverParameter()
            if (receiver != null) {
                val receiverType = getType(receiver.getType())
                function.receiverType = receiverType
                function.extensionClass = receiverType?.klass
            }
            return function
        }
        return null
    }

    fun addTypeParameters(answer: MutableList, descriptors: List): Unit {
        for (typeParam in descriptors) {
            if (typeParam != null) {
                val p = createTypeParameter(typeParam)
                if (p != null){
                    answer.add(p)
                }
            }
        }
    }

    protected fun createTypeParameter(descriptor: TypeParameterDescriptor): KTypeParameter? {
        val name = descriptor.getName().asString()
        val answer = KTypeParameter(name, descriptor, this)
        configureComments(answer, descriptor)
        return answer
    }

    protected fun createParameter(descriptor: ValueParameterDescriptor): KParameter? {
        val returnType = getType(descriptor.getReturnType())
        if (returnType != null) {
            val name = descriptor.getName().asString()
            val answer = KParameter(descriptor, name, returnType)
            configureComments(answer, descriptor)
            return answer
        }
        return null
    }

    fun locationFor(descriptor: DeclarationDescriptor): LineAndColumn? {
        val psiElement =  getPsiElement(descriptor)
        if (psiElement != null) {
            val document = psiElement.getContainingFile()?.getViewProvider()?.getDocument()
            if (document != null) {
                val offset = psiElement.getTextOffset()
                return DiagnosticUtils.offsetToLineAndColumn(document, offset)
            }
        }
        return null
    }

    fun fileFor(descriptor: DeclarationDescriptor): String? {
        val psiElement = getPsiElement(descriptor)
        return psiElement?.getContainingFile()?.getName()
    }

    fun filePath(descriptor: DeclarationDescriptor): String? {
        val psiElement = getPsiElement(descriptor)
        val file = psiElement?.getContainingFile()
        return filePath(file)
    }


    fun getPsiElement(descriptor: DeclarationDescriptor): PsiElement? {
        return try {
            BindingContextUtils.descriptorToDeclaration(context, descriptor)
        } catch (e: Throwable) {
            // ignore exceptions on fake descriptors
            null
        }
    }

    protected fun commentsFor(descriptor: DeclarationDescriptor): String {
        val psiElement =  getPsiElement(descriptor)

        // This method is a hack. Doc comments should be easily accessible, but they aren't for now.
        if (psiElement != null) {
            var node = psiElement.getNode()?.getTreePrev()
            while (node != null && (node?.getElementType() == JetTokens.WHITE_SPACE || node?.getElementType() == JetTokens.BLOCK_COMMENT)) {
                node = node?.getTreePrev()
            }
            if (node == null) return ""
            if (node?.getElementType() != JetTokens.DOC_COMMENT) return ""
            var text = node?.getText() ?: ""
            // lets remove the comment tokens
            val lines = text.trim().split("\\n")
            // lets remove the /** ... * ... */ tokens
            val buffer = StringBuilder()
            val last = lines.size - 1
            for (i in 0.rangeTo(last)) {
                var text = lines[i] ?: ""
                text = text.trim()
                if (i == 0) {
                    text = text.trimLeading("/**").trimLeading("/*")
                } else {
                    buffer.append("\n")
                }
                if (i >= last) {
                    text = text.trimTrailing("*/")
                } else if (i > 0) {
                    text = text.trimLeading("* ")
                    if (text == "*") text = ""
                }
                text = processMacros(text, psiElement)
                buffer.append(text)
            }
            return buffer.toString() ?: ""
        }
        return ""
    }

    protected fun processMacros(textWithWhitespace: String, psiElement: PsiElement): String {
        val text = textWithWhitespace.trim()
        // lets check for javadoc style @ tags and macros
        if (text.startsWith("@")) {
            val remaining = text.substring(1)
            val macro = "includeFunctionBody"
            if (remaining.startsWith(macro)) {
                val next = remaining.substring(macro.length()).trim()
                val words = next.split("\\s")
                // TODO we could default the test function name to match that of the
                // source code function if folks adopted a convention of naming the test method after the
                // method its acting as a demo/test for
                if (words.size > 1) {
                    val includeFile = words[0]!!
                    val fnName = words[1]!!
                    val content = findFunctionInclude(psiElement, includeFile, fnName)
                    if (content != null) {
                        return content
                    } else {
                        warning("could not find function $fnName in file $includeFile from source file ${psiElement.getContainingFile()}")
                    }
                }
            } else {
                warning("Unknown kdoc macro @$remaining")
            }
        }
        return textWithWhitespace
    }

    protected fun findFunctionInclude(psiElement: PsiElement, includeFile: String, functionName: String): String? {
        var dir = psiElement.getContainingFile()?.getParent()
        if (dir != null) {
            val file = relativeFile(dir!!, includeFile)
            if (file != null) {
                val text = file.getText()
                if (text != null) {
                    // lets find the function definition
                    val regex = """fun\s+$functionName\(.*\)""".toRegex()
                    val matcher = regex.matcher(text)!!
                    if (matcher.find()) {
                        val idx = matcher.end()
                        val remaining = text.substring(idx)
                        val content = extractBlock(remaining)
                        if (content != null) {
                            val highlight = highlighter.highlight(content)
                            val filePath = filePath(file)
                            val sourceLine = text.substring(0, idx).count{ it == '\n'} + 1
                            val link = if (filePath != null) sourceLinkFor(filePath, sourceLine) else  null
                            return if (link != null)
                                """
$highlight"""
                            else highlight
                        }
                    }
                }
            }
        }
        return null
    }

    protected fun filePath(file: PsiFileSystemItem?): String? {
        if (file != null) {
            var dir = file.getParent()
            if (dir != null) {
                val parentName = filePath(dir) ?: ""
                return parentName + "/" + file.getName()
            }
        }
        return null
    }

    /**
     * Extracts the block of code within { .. } tokens or returning null if it can't be found
     */
    protected fun extractBlock(text: String): String? {
        val idx = text.indexOf('{')
        if (idx >= 0) {
            var remaining = text.substring(idx + 1)
            // lets remove any leading blank lines
            while (true) {
                val nidx = remaining.indexOf('\n')
                if (nidx >= 0) {
                    val line = remaining.substring(0, nidx).trim()
                    if (line.isEmpty()) {
                        remaining = remaining.substring(nidx + 1)
                        continue
                    }
                }
                break
            }
            var count = 1
            for (i in 0.rangeTo(remaining.size - 1)) {
                val ch = remaining[i]
                if (ch == '{') count ++
                else if (ch == '}') {
                    if (--count <= 0) {
                        return remaining.substring(0, i)
                    }
                }
            }
            warning("missing } in code block for $remaining")
            return remaining
        }
        return null
    }

    protected fun relativeFile(directory: PsiDirectory, relativeName: String): PsiFile? {
        // TODO would have thought there's some helper function already to resolve relative names!

        var dir: PsiDirectory? = directory

        // lets try resolve the include name relative to this file
        val paths = relativeName.split("/")
        val size = paths.size
        for (i in 0.rangeTo(size - 2)) {
            val path = paths[i]
            if (path == ".") continue
            else if (path == "..") dir = dir?.getParent()
            else dir = dir?.findSubdirectory(path)
        }
        val name = paths[size - 1]
        if (dir != null) {
            val file = dir?.findFile(name)
            if (file != null) {
                return file
            } else {
                warning("could not find file $relativeName in $dir with name $name")
            }
        }
        return null
    }

    fun configureComments(annotated: KAnnotated, descriptor: DeclarationDescriptor): Unit {
        val detailedText = commentsFor(descriptor).trim()
        annotated.wikiDescription = detailedText
    }

    fun getType(aType: JetType?): KType? {
        if (aType != null) {
            val classifierDescriptor = aType.getConstructor().getDeclarationDescriptor()
            val klass = if (classifierDescriptor is ClassDescriptor) {
                getClass(classifierDescriptor)
            } else null
            return KType(aType, this, klass)
        }
        return null
    }

    /**
     * Returns the [[KClass]] for the fully qualified name or null if it could not be found
     */
    fun getClass(qualifiedName: String): KClass? {
        // TODO warning this only works for top level classes
        // a better algorithm is to walk down each dot path dealing with nested packages/classes
        val idx = qualifiedName.lastIndexOf('.')
        val pkgName = if (idx >= 0) qualifiedName.substring(0, idx) else ""
        val pkg = getPackage(pkgName)
        if (pkg != null) {
            val simpleName = if (idx >= 0) qualifiedName.substring(idx + 1) else qualifiedName
            return pkg.classMap.get(simpleName)
        }
        return null
    }

    fun getClass(classElement: ClassDescriptor): KClass? {
        val name = classElement.getName().asString()
        var dec: DeclarationDescriptor? = classElement.getContainingDeclaration()
        while (dec != null) {
            val container = dec
            if (container is NamespaceDescriptor) {
                val pkg = getPackage(container)
                return pkg.getClass(classElement)
            } else {
                dec = dec?.getContainingDeclaration()
            }
        }
        warning("no package found for class $name")
        return null
    }

    fun previous(pkg: KPackage): KPackage? {
        // TODO
        return null
    }

    fun next(pkg: KPackage): KPackage? {
        // TODO
        return null
    }
}

class TemplateLinkRenderer(val annotated: KAnnotated, val template: KDocTemplate): LinkRenderer() {
    // TODO dirty hack - remove when this issue is fixed
    // http://youtrack.jetbrains.com/issue/KT-1524
    val hackedLinks = hashMap(
            Pair("IllegalArgumentException", Pair("java.lang", "java/lang/IllegalArgumentException.html")),
            Pair("IllegalStateException", Pair("java.lang", "java/lang/IllegalStateException.html")),
            Pair("Map.Entry", Pair("java.util", "java/util/Map.Entry.html")),
            Pair("System.in", Pair("java.lang", "java/lang/System.html#in")),
            Pair("System.out", Pair("java.lang", "java/lang/System.html#in")),
            Pair("#equals()", Pair("java.lang", "java/lang/Object.html#equals(java.lang.Object)")),
            Pair("#hashCode()", Pair("java.lang", "java/lang/Object.html#hashCode()"))
    )


    public override fun render(node: WikiLinkNode?): Rendering? {
        val answer = super.render(node)
        if (answer != null) {
            val text = answer.text
            if (text != null) {
                val qualified = resolveToQualifiedName(text)
                var href = resolveClassNameLink(qualified)
                if (href != null) {
                    answer.href = href
                } else {

                    // TODO really dirty hack alert!!!
                    // until the resolver is working, lets try adding a few prefixes :)
                    for (prefix in arrayList("java.lang", "java.util", "java.util.concurrent", "java.util.regex", "java.io",
                            "jet", "java.awt", "java.awt.event", "java.sql", "java.beans",
                            "javax.swing", "javax.swing.event",
                            "org.w3c.dom",
                            "kotlin.template")) {
                        if (href == null) {
                            href = resolveClassNameLink(prefix + "." + qualified)
                            if (href != null) {
                                break
                            }
                        }
                    }
                }
                if (href == null) {
                    // TODO even hacker than the above hack!
                    val link = hackedLinks.get(text)
                    if (link != null) {
                        href = annotated.model.config.resolveLink(link.first) + link.second
                    }
                }
                if (href != null) {
                    answer.href = href
                } else {
                    answer.href = "#NotImplementedYet"
                    warning("could not resolve expression: $qualified into a wiki link")
                }
            }
        }
        return answer
    }

    /**
     * Try to resolve a fully qualified class name as a link
     */
    protected fun resolveClassNameLink(qualifiedName: String): String? {
        val model = annotated.model
        val pkg = model.getPackage(qualifiedName)
        if (pkg != null) {
            return template.href(pkg)
        }
        val klass = model.getClass(qualifiedName)
        if (klass != null) {
            return template.href(klass)
        } else {
            // is it a method?
            val idx = qualifiedName.lastIndexOf('.')
            if (idx > 0) {
                val className = qualifiedName.substring(0, idx)
                val c = model.getClass(className)
                if (c != null) {
                    // lets try find method...
                    val remaining = qualifiedName.substring(idx + 1)
                    // lets try find the function
                    val fn = c.findFunction(remaining)
                    if (fn != null) {
                        return template.href(fn)
                    }
                    val p = c.findProperty(remaining)
                    if (p != null) {
                        return template.href(p)
                    }
                }
            }
            return null
        }
    }
    /**
     * Attempts to resolve the class, method or property expression using the
     * current imports and declaraiton
     */
    protected fun resolveToQualifiedName(text: String): String {
        // TODO use the CompletionContributors maybe to figure out what local names are imported???
        return text
        /*
                val scope = findWritableScope(annotated.declarationDescriptor)
                if (scope != null) {
                    val classifierDescriptor = scope.getClassifier(text)
                    if (classifierDescriptor == null) {
                        val o = scope.getObjectDescriptor(text)
                        println("Attempt to resolve HREF: $text Found objectDescriptor $o")
                    } else {
                        println("Attempt to resolve HREF: $text Found classifierDescriptor $classifierDescriptor")
                    }
                }
            }

            protected fun findWritableScope(declarationDescriptor: DeclarationDescriptor) : WritableScopeImpl? {
                val container = declarationDescriptor.getContainingDeclaration()
                if (container is NamespaceDescriptor) {
                    val scope = container.getMemberScope()
                    if (scope is WritableScopeImpl) {
                        return scope
                    }
                } else if (container != null) {
                    return findWritableScope(container)
                }
                return null

        */
    }

    public override fun render(node: RefLinkNode?, url: String?, title: String?, text: String?): Rendering? {
        return super.render(node, url, title, text)
    }

    public override fun render(node: AutoLinkNode?): Rendering? {
        return super.render(node)
    }

    public override fun render(node: ExpLinkNode?, text: String?): Rendering? {
        return super.render(node, text)
    }


}

abstract class KAnnotated(val model: KModel, val declarationDescriptor: DeclarationDescriptor) {
    public open var wikiDescription: String = ""

    public open var deprecated: Boolean = false

    open fun description(template: KDocTemplate): String {
        val detailedText = detailedDescription(template)
        val idx = detailedText.indexOf("

") return if (idx > 0) { detailedText.substring(0, idx).trimLeading("

") } else { detailedText } } fun detailedDescription(template: KDocTemplate): String { val wiki = wikiDescription return wikiConvert(wiki, template) } protected fun wikiConvert(wiki: String, template: KDocTemplate): String { val file = model.fileFor(declarationDescriptor) return model.wikiConvert(wiki, TemplateLinkRenderer(this, template), file) } fun isLinkToSourceRepo(): Boolean { return model.config.sourceRootHref != null } fun sourceTargetAttribute(): String { return if (isLinkToSourceRepo()) " target=\"_top\" class=\"repoSourceCode\"" else "" } fun sourceLink(): String { val file = filePath() if (file != null) { val link = model.sourceLinkFor(file, sourceLine) if (link != null) return link } return "" } fun filePath(): String? = model.filePath(declarationDescriptor) fun location(): LineAndColumn? = model.locationFor(declarationDescriptor) val sourceLine: Int get() { val loc = location() return if (loc != null) loc.getLine() else 1 } } abstract class KNamed(val name: String, model: KModel, declarationDescriptor: DeclarationDescriptor): KAnnotated(model, declarationDescriptor), Comparable { public override fun compareTo(other: KNamed): Int = name.compareTo(other.name) open fun equals(other: KPackage) = name == other.name open fun toString() = name } class KPackage(model: KModel, val descriptor: NamespaceDescriptor, val name: String, var local: Boolean = false, var useExternalLink: Boolean = false): KClassOrPackage(model, descriptor), Comparable { // TODO generates java.lang.NoSuchMethodError: kotlin.util.UtilPackage.hashMap(Ljet/TypeInfo;Ljet/TypeInfo;)Ljava/util/HashMap; //val classes = sortedMap() public val classMap: SortedMap = TreeMap() public val classes: Collection get() = classMap.values()!!.filter{ it.isApi() } public val annotations: Collection = ArrayList() public override fun compareTo(other: KPackage): Int = name.compareTo(other.name) fun equals(other: KPackage) = name == other.name fun toString() = "KPackage($name)" fun getClass(descriptor: ClassDescriptor): KClass { val name = descriptor.getName().asString() var created = false val klass = classMap.getOrPut(name) { created = true val psiFile = model.getPsiElement(descriptor)?.getContainingFile() val jetFile = psiFile as? JetFile val sourceInfo = if (jetFile != null) model.sourceInfoByFile(jetFile) else null KClass(this, descriptor, sourceInfo) } if (created) { // sometimes we may have source files for a package in different source directories // such as the kotlin package in generated directory; so lets always check if we can find // the readme model.tryLoadReadMe(this, descriptor) model.configureComments(klass, descriptor) val typeConstructor = descriptor.getTypeConstructor() val superTypes = typeConstructor.getSupertypes() for (st in superTypes) { val sc = model.getType(st) if (sc != null) { klass.baseClasses.add(sc) } } val scope = descriptor.getDefaultType().getMemberScope() model.addFunctions(klass, scope) model.addTypeParameters(klass.typeParameters, typeConstructor.getParameters()) } return klass } /** Returns the name as a directory using '/' instead of '.' */ public val nameAsPath: String get() = if (name.length() == 0) "." else name.replace('.', '/') /** Returns a list of all the paths in the package name */ public val namePaths: List get() { val answer = ArrayList() for (n in name.split("\\.")) { answer.add(n) } return answer; } /** Returns a relative path like ../.. for each path in the name */ public val nameAsRelativePath: String get() { val answer = namePaths.map{ ".." }.makeString("/") return if (answer.length == 0) "" else answer + "/" } override fun description(template: KDocTemplate): String { // lets see if we can find a custom summary val text = model.config.packageSummaryText[name] return if (text != null) wikiConvert(text, template).trimLeading("

").trimTrailing("

") else super.description(template) } fun qualifiedName(simpleName: String): String { return if (name.length() > 0) { "${name}.${simpleName}" } else { simpleName } } fun previous(pkg: KClass): KClass? { // TODO return null } fun next(pkg: KClass): KClass? { // TODO return null } fun groupClassMap(): Map> { return classes.groupByTo(TreeMap>()){it.group} } fun packageFunctions() = functions.filter{ it.extensionClass == null } fun packageProperties() = properties.filter{ it.extensionClass == null && it.isPublic() } } class KType(val jetType: JetType, model: KModel, val klass: KClass?, val arguments: MutableList = ArrayList()) : KNamed(klass?.name ?: jetType.toString()!!, model, jetType.getConstructor().getDeclarationDescriptor()!!) { { if (klass != null) { this.wikiDescription = klass.wikiDescription } for (arg in jetType.getArguments()) { if (arg != null) { val argJetType = arg.getType() val t = model.getType(argJetType) if (t != null) { arguments.add(t) } } } } override fun toString() = if (nullable) "$name?" else name val nullable: Boolean get() = jetType.isNullable() } class KClass( val pkg: KPackage, val descriptor: ClassDescriptor, val sourceInfo: SourceInfo?) : KClassOrPackage(pkg.model, descriptor), Comparable { val simpleName = descriptor.getName().asString() var group: String = "Other" var annotations: List = arrayList() var typeParameters: MutableList = arrayList() var since: String = "" var authors: List = arrayList() var baseClasses: MutableList = arrayList() var nestedClasses: List = arrayList() public override fun compareTo(other: KClass): Int = name.compareTo(other.name) fun equals(other: KClass) = name == other.name fun toString() = "$kind($name)" fun isApi(): Boolean { val visibility = descriptor.getVisibility() return visibility.isPublicAPI() } val kind: String get() { val k = descriptor.getKind() return if (k == ClassKind.TRAIT) "trait" else if (k == ClassKind.OBJECT) "object" else if (k == ClassKind.ENUM_CLASS || k == ClassKind.ENUM_ENTRY) "enum" else if (k == ClassKind.ANNOTATION_CLASS) "annotation" else "class" } val kindCode: String get() { val k = descriptor.getKind() return if (k == ClassKind.TRAIT) "trait" else if (k == ClassKind.OBJECT) "object" else if (k == ClassKind.ENUM_CLASS || k == ClassKind.ENUM_ENTRY) "enum class" else if (k == ClassKind.ANNOTATION_CLASS) "class" else "class" } val visibility: String get() { val v = descriptor.getVisibility() return if (v == Visibilities.PUBLIC) "public" else if (v == Visibilities.PROTECTED) "protected" else if (v == Visibilities.PRIVATE) "private" else "" } /** Link to the type which is relative if its a local type but could be a type in a different library or null if no link */ public var url: String? = null get() { if ($url == null) $url = "${nameAsPath}.html" return $url } public val name: String = pkg.qualifiedName(descriptor.getName().asString()) public val packageName: String = pkg.name /** Returns the name as a directory using '/' instead of '.' */ public val nameAsPath: String get() = name.replace('.', '/') fun isAnnotation() = kind == "annotation" fun isInterface() = kind == "interface" /** Returns all of the base classes and all of their descendants */ fun descendants(answer: MutableSet = LinkedHashSet()): Set { for (b in baseClasses) { val c = b.klass if (c != null) { answer.add(c) c.descendants(answer) } } return answer } } class KFunction(val descriptor: CallableDescriptor, val owner: KClassOrPackage, val name: String, var returnType: KType, var parameters: List, var receiverType: KType? = null, var extensionClass: KClass? = null, var modifiers: List = arrayList(), var typeParameters: MutableList = arrayList(), var exceptions: List = arrayList(), var annotations: List = arrayList()): KAnnotated(owner.model, descriptor), Comparable { public val parameterTypeText: String = parameters.map{ it.aType.name }.makeString(", ") public override fun compareTo(other: KFunction): Int { var answer = name.compareTo(other.name) if (answer == 0) { answer = parameterTypeText.compareTo(other.parameterTypeText) if (answer == 0) { val ec1 = extensionClass?.name ?: "" val ec2 = other.extensionClass?.name ?: "" answer = ec1.compareTo(ec2) } } return answer } fun equals(other: KFunction) = name == other.name && this.parameterTypeText == other.parameterTypeText && this.extensionClass == other.extensionClass && this.owner == other.owner fun toString() = "fun $name($parameterTypeText): $returnType" public val link: String = "$name($parameterTypeText)" /** Returns a list of generic type parameter names kinds like "A, I" */ public val typeParametersText: String get() = typeParameters.map{ it.name }.makeString(", ") } class KProperty(val owner: KClassOrPackage, val descriptor: PropertyDescriptor, val name: String, val returnType: KType, val extensionClass: KClass?): KAnnotated(owner.model, descriptor), Comparable { public override fun compareTo(other: KProperty): Int = name.compareTo(other.name) public val link: String = "$name" fun equals(other: KFunction) = name == other.name fun isVar(): Boolean = descriptor.isVar() fun kind(): String = if (isVar()) "var" else "val" fun isPublic(): Boolean { val visibility = descriptor.getVisibility() return visibility.isPublicAPI() } fun toString() = "property $name" } class KParameter(val descriptor: ValueParameterDescriptor, val name: String, var aType: KType): KAnnotated(aType.model, aType.declarationDescriptor) { fun toString() = "$name: ${aType.name}" fun isVarArg(): Boolean = descriptor.getVarargElementType() != null fun hasDefaultValue(): Boolean = descriptor.hasDefaultValue() fun varArgType(): KType? { val varType = descriptor.getVarargElementType() return if (varType != null) { aType.model.getType(varType) } else null } } class KTypeParameter(val name: String, val descriptor: TypeParameterDescriptor, model: KModel, var extends: List = arrayList()): KAnnotated(model, descriptor) { fun toString() = "$name" } class KAnnotation(var klass: KClass): KAnnotated(klass.model, klass.descriptor) { // TODO add some parameter values? fun toString() = "@$klass.simpleName" }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy