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

org.jetbrains.kotlin.kapt3.base.incremental.classStructureCache.kt Maven / Gradle / Ivy

/*
 * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.kapt3.base.incremental

import java.io.File
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
import java.lang.IllegalArgumentException
import java.net.URI
import java.util.regex.Pattern

class JavaClassCache() : Serializable {
    private var sourceCache = mutableMapOf()

    /** Record these separately because we only need to know where each generated type is coming from. */
    private var generatedTypes = mutableMapOf>()

    /** Map from types to files they are mentioned in. */
    @Transient
    private var dependencyCache = mutableMapOf>()
    @Transient
    private var nonTransitiveCache = mutableMapOf>()

    fun addSourceStructure(sourceStructure: SourceFileStructure) {
        sourceCache[sourceStructure.sourceFile] = sourceStructure
    }

    fun addGeneratedType(type: String, generatedFile: File) {
        val typesInFile = generatedTypes[generatedFile] ?: ArrayList(1)
        typesInFile.add(type)
        generatedTypes[generatedFile] = typesInFile
    }

    fun invalidateGeneratedTypes(files: List): Set {
        return files.mapNotNull { generatedTypes.remove(it) }.flatten().toSet()
    }

    private fun readObject(input: ObjectInputStream) {
        @Suppress("UNCHECKED_CAST")
        sourceCache = input.readObject() as MutableMap
        @Suppress("UNCHECKED_CAST")
        generatedTypes = input.readObject() as MutableMap>

        dependencyCache = HashMap(sourceCache.size * 4)
        for (sourceInfo in sourceCache.values) {
            for (mentionedType in sourceInfo.getMentionedTypes()) {
                val dependants = dependencyCache[mentionedType] ?: mutableSetOf()
                dependants.add(sourceInfo.sourceFile)
                dependencyCache[mentionedType] = dependants
            }
            // Treat referred constants as ABI dependencies until we start supporting per-constant classpath updates.
            for (mentionedConstants in sourceInfo.getMentionedConstants().keys) {
                val dependants = dependencyCache[mentionedConstants] ?: mutableSetOf()
                dependants.add(sourceInfo.sourceFile)
                dependencyCache[mentionedConstants] = dependants
            }
        }
        nonTransitiveCache = HashMap(sourceCache.size * 2)
        for (sourceInfo in sourceCache.values) {
            for (privateType in sourceInfo.getPrivateTypes()) {
                val dependants = nonTransitiveCache[privateType] ?: mutableSetOf()
                dependants.add(sourceInfo.sourceFile)
                nonTransitiveCache[privateType] = dependants
            }
        }
    }

    private fun writeObject(output: ObjectOutputStream) {
        output.writeObject(sourceCache)
        output.writeObject(generatedTypes)
    }

    fun isAlreadyProcessed(sourceFile: URI): Boolean {
        if (!sourceFile.isAbsolute) {
            // we never want to process non-absolute URIs, see https://youtrack.jetbrains.com/issue/KT-33617
            return true
        }
        if (sourceFile.isOpaque) {
            // we never want to process non-hierarchical URIs, https://youtrack.jetbrains.com/issue/KT-33617
            return true
        }
        return try {
            val fileFromUri = File(sourceFile)
            sourceCache.containsKey(sourceFile) || generatedTypes.containsKey(fileFromUri)
        } catch (e: IllegalArgumentException) {
            // unable to create File instance, avoid processing these files
            true
        }
    }

    /** Used for testing only. */
    internal fun getStructure(sourceFile: File) = sourceCache[sourceFile.toURI()]

    /**
     * Invalidate cache entries for the specified files, and any files that depend on the changed ones. It returns the set of files that
     * should be re-processed.
     * */
    fun invalidateEntriesForChangedFiles(changes: Changes): SourcesToReprocess {
        val allDirtyFiles = mutableSetOf()
        var currentDirtyFiles = changes.sourceChanges.map { it.toURI() }.toMutableSet()

        for (classpathFqName in changes.dirtyFqNamesFromClasspath) {
            nonTransitiveCache[classpathFqName]?.let {
                allDirtyFiles.addAll(it)
            }

            dependencyCache[classpathFqName]?.let {
                currentDirtyFiles.addAll(it)
            }
        }

        val allDirtyTypes = mutableSetOf()

        while (currentDirtyFiles.isNotEmpty()) {

            val nextRound = mutableSetOf()
            for (dirtyFile in currentDirtyFiles) {
                allDirtyFiles.add(dirtyFile)

                val structure = sourceCache.remove(dirtyFile) ?: continue
                val dirtyTypes = structure.getDeclaredTypes()
                allDirtyTypes.addAll(dirtyTypes)

                dirtyTypes.forEach { type ->
                    nonTransitiveCache[type]?.let {
                        allDirtyFiles.addAll(it)
                    }

                    dependencyCache[type]?.let {
                        nextRound.addAll(it)
                    }
                }
            }

            currentDirtyFiles = nextRound.filter { !allDirtyFiles.contains(it) }.toMutableSet()
        }

        return SourcesToReprocess.Incremental(allDirtyFiles.map { File(it) }, allDirtyTypes)
    }

    /**
     * For aggregating annotation processors, we always need to reprocess all files annotated with an annotation claimed by the aggregating
     * annotation processor. This search is not transitive.
     */
    fun invalidateEntriesAnnotatedWith(annotations: Set): Set {
        val patterns: List = if ("*" in annotations) {
            // optimize this case - create only one pattern
            listOf(Pattern.compile(".*"))
        } else {
            annotations.map {
                Pattern.compile(
                    // These are already valid import statements, otherwise run fails when loading the annotation processor.
                    // Handles structure; TypeName [.*] e.g. org.jetbrains.annotations.NotNull and org.jetbrains.annotations.*
                    it.replace(".", "\\.").replace("*", ".+")
                )
            }
        }
        val matchesAnyPattern = { name: String -> patterns.any { it.matcher(name).matches() } }

        val toReprocess = mutableSetOf()

        for (cacheEntry in sourceCache) {
            if (cacheEntry.value.getMentionedAnnotations().any(matchesAnyPattern)) {
                toReprocess.add(cacheEntry.key)
            }
        }

        toReprocess.forEach {
            sourceCache.remove(it)
        }

        return toReprocess.map { File(it) }.toSet()
    }

    internal fun invalidateAll() {
        sourceCache.clear()
        generatedTypes.clear()
    }
}


private val IGNORE_TYPES = { name: String -> name == "java.lang.Object" }

class SourceFileStructure(
    val sourceFile: URI
) : Serializable {

    private val declaredTypes: MutableSet = mutableSetOf()

    private val mentionedTypes: MutableSet = mutableSetOf()
    private val privateTypes: MutableSet = mutableSetOf()

    private val mentionedAnnotations: MutableSet = mutableSetOf()
    private val mentionedConstants: MutableMap> = mutableMapOf()

    fun getDeclaredTypes(): Set = declaredTypes
    fun getMentionedTypes(): Set = mentionedTypes
    fun getPrivateTypes(): Set = privateTypes
    fun getMentionedAnnotations(): Set = mentionedAnnotations
    fun getMentionedConstants(): Map> = mentionedConstants

    fun addDeclaredType(declaredType: String) {
        declaredTypes.add(declaredType)
    }

    fun addMentionedType(mentionedType: String) {
        mentionedType.takeUnless(IGNORE_TYPES)?.let {
            mentionedTypes.add(it)
        }
    }

    fun addMentionedAnnotations(name: String) {
        mentionedAnnotations.add(name)
    }

    fun addPrivateType(name: String) {
        privateTypes.add(name)
    }

    fun addMentionedConstant(containingClass: String, name: String) {
        if (!declaredTypes.contains(containingClass)) {
            mentionedConstants.getOrPut(containingClass) { HashSet() }.add(name)
        }
    }
}


class Changes(val sourceChanges: Collection, val dirtyFqNamesFromClasspath: Set)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy