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

main.name.remal.gradle_plugins.plugins.jpms.GenerateModuleInfoTask.kt Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
package name.remal.gradle_plugins.plugins.jpms

import groovy.lang.Closure
import groovy.lang.Closure.DELEGATE_FIRST
import groovy.lang.DelegatesTo
import name.remal.ASM_API
import name.remal.accept
import name.remal.buildMap
import name.remal.createParentDirectories
import name.remal.default
import name.remal.escapeRegex
import name.remal.forceDeleteRecursively
import name.remal.gradle_plugins.dsl.BuildTask
import name.remal.gradle_plugins.dsl.artifact.Artifact
import name.remal.gradle_plugins.dsl.artifact.ArtifactsCache
import name.remal.gradle_plugins.dsl.artifact.CachedArtifactsCollection
import name.remal.gradle_plugins.dsl.extensions.addClassesDir
import name.remal.gradle_plugins.dsl.extensions.all
import name.remal.gradle_plugins.dsl.extensions.classesDir
import name.remal.gradle_plugins.dsl.extensions.dirName
import name.remal.gradle_plugins.dsl.extensions.flattenAny
import name.remal.gradle_plugins.dsl.extensions.get
import name.remal.gradle_plugins.dsl.extensions.getRequiredResourceAsStream
import name.remal.gradle_plugins.dsl.extensions.include
import name.remal.gradle_plugins.dsl.extensions.isCompilingSourceSet
import name.remal.gradle_plugins.dsl.extensions.isVersionSet
import name.remal.gradle_plugins.dsl.extensions.javaModuleName
import name.remal.gradle_plugins.dsl.extensions.logDebug
import name.remal.gradle_plugins.dsl.extensions.matches
import name.remal.gradle_plugins.dsl.extensions.readBytes
import name.remal.gradle_plugins.dsl.extensions.requirePlugin
import name.remal.gradle_plugins.dsl.extensions.toConfigureKotlinFunction
import name.remal.gradle_plugins.dsl.extensions.visitFiles
import name.remal.gradle_plugins.dsl.utils.ClassInternalName
import name.remal.gradle_plugins.dsl.utils.ClassName
import name.remal.gradle_plugins.dsl.utils.DependenciesCollectorClassVisitor
import name.remal.gradle_plugins.dsl.utils.DependencyNotation
import name.remal.gradle_plugins.dsl.utils.DependencyNotationMatcher
import name.remal.gradle_plugins.dsl.utils.SkipInvisibleAnnotationsClassVisitor
import name.remal.gradle_plugins.dsl.utils.SkipOuterAndInnerClassesClassVisitor
import name.remal.gradle_plugins.dsl.utils.UsedServicesCollectorClassVisitor
import name.remal.gradle_plugins.dsl.utils.classInternalNameToClassName
import name.remal.gradle_plugins.dsl.utils.classNameToClassInternalName
import name.remal.gradle_plugins.dsl.utils.createDependencyNotation
import name.remal.gradle_plugins.dsl.utils.defaultValue
import name.remal.gradle_plugins.dsl.utils.writeOnce
import name.remal.gradle_plugins.plugins.java.JavaBasePluginId
import name.remal.gradle_plugins.plugins.jpms.RequireModuleMode.REQUIRE_NORMAL
import name.remal.gradle_plugins.plugins.jpms.RequireModuleMode.REQUIRE_STATIC
import name.remal.gradle_plugins.plugins.jpms.RequireModuleMode.REQUIRE_TRANSITIVE
import name.remal.loadProperties
import name.remal.logDebug
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ModuleVersionIdentifier
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedModuleVersion
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity.ABSOLUTE
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.compile.AbstractCompile
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes.ACC_MODULE
import org.objectweb.asm.Opcodes.ACC_OPEN
import org.objectweb.asm.Opcodes.ACC_STATIC
import org.objectweb.asm.Opcodes.ACC_STATIC_PHASE
import org.objectweb.asm.Opcodes.ACC_TRANSITIVE
import org.objectweb.asm.Opcodes.V9
import org.objectweb.asm.tree.ModuleExportNode
import org.objectweb.asm.tree.ModuleNode
import org.objectweb.asm.tree.ModuleProvideNode
import org.objectweb.asm.tree.ModuleRequireNode
import java.io.File
import java.nio.charset.StandardCharsets.UTF_8
import kotlin.LazyThreadSafetyMode.NONE

@DslMarker
private annotation class ModuleInfoDslMarker

@BuildTask
@CacheableTask
@ModuleInfoDslMarker
class GenerateModuleInfoTask : DefaultTask() {

    companion object {
        private val knownClassInternalNamesToModuleName = GenerateModuleInfoTask::class.java.getRequiredResourceAsStream("known-class-module-names.properties")
            .use(::loadProperties)
            .mapKeys { classNameToClassInternalName(it.key.toString()) }
            .mapValues { it.value.toString() }
    }

    init {
        requirePlugin(JavaBasePluginId)
    }

    @get:Input
    var moduleName: String by defaultValue(project::javaModuleName)

    @get:Input
    var isOpen: Boolean = true

    @get:Input
    @get:Optional
    var mainClassName: String? = null

    @Nested
    val requires = ModuleInfoRequires()

    fun requires(configurer: ModuleInfoRequires.() -> Unit) {
        configurer(requires)
    }

    fun requires(
        @DelegatesTo(ModuleInfoRequires::class, strategy = DELEGATE_FIRST)
        configurer: Closure<*>
    ) = requires(configurer.toConfigureKotlinFunction())

    @get:Nested
    val exports = ModuleInfoExports()

    fun exports(configurer: ModuleInfoExports.() -> Unit) {
        configurer(exports)
    }

    fun exports(
        @DelegatesTo(ModuleInfoExports::class, strategy = DELEGATE_FIRST)
        configurer: Closure<*>
    ) = exports(configurer.toConfigureKotlinFunction())

    @get:Nested
    val uses = ModuleInfoUses()

    fun uses(configurer: ModuleInfoUses.() -> Unit) {
        configurer(uses)
    }

    fun uses(@DelegatesTo(ModuleInfoUses::class, strategy = DELEGATE_FIRST) configurer: Closure<*>) = uses(configurer.toConfigureKotlinFunction())

    @get:OutputDirectory
    var destinationDir: File by defaultValue { project.classesDir.resolve("module-info/$dirName") }

    @get:InputFiles
    @get:PathSensitive(ABSOLUTE)
    val classesDirs: ConfigurableFileCollection = project.files()

    @get:InputFiles
    @get:Classpath
    var compileClasspathConfiguration: Configuration? = null

    @get:InputFiles
    @get:Classpath
    var runtimeClasspathConfiguration: Configuration? = null

    @get:InputFiles
    @get:Classpath
    var compileOnlyConfiguration: Configuration? = null

    @get:Input
    @get:Optional
    var classesWithAllowedDynamicServiceLoader: MutableSet = sortedSetOf()
        set(value) {
            field = value.toSortedSet()
        }

    @get:Internal
    var sourceSet: SourceSet by writeOnce { sourceSet ->
        project.classesDir.resolve("module-info/${sourceSet.name}").let { destinationDir ->
            this.destinationDir = destinationDir
            sourceSet.output.addClassesDir(destinationDir)
        }

        val compileTasks = project.tasks.withType(AbstractCompile::class.java).matching { it.isCompilingSourceSet(sourceSet) }
        compileTasks.all { dependsOn(it) }
        classesDirs.from(project.provider {
            compileTasks.mapTo(mutableSetOf(), AbstractCompile::getDestinationDir)
        })

        project.tasks.all(sourceSet.processResourcesTaskName) { dependsOn(it) }
        classesDirs.from(project.provider {
            sourceSet.output.resourcesDir
        })

        compileClasspathConfiguration = project.configurations[sourceSet.compileClasspathConfigurationName]
        runtimeClasspathConfiguration = project.configurations[sourceSet.runtimeClasspathConfigurationName]
        compileOnlyConfiguration = project.configurations[sourceSet.compileOnlyConfigurationName]?.let { conf ->
            conf.copy().apply {
                isCanBeResolved = true
                dependencies.clear()
                conf.allDependencies.all { dependencies.add(it) }
            }
        }

        project.tasks[sourceSet.classesTaskName].dependsOn(this)
    }

    @TaskAction
    @Suppress("LongMethod", "ComplexMethod")
    protected fun generateModuleInfo() {
        didWork = true
        val destinationDir = this.destinationDir.apply { forceDeleteRecursively() }

        val moduleModifiers: Int = if (isOpen) {
            ACC_OPEN
        } else {
            0
        }
        val moduleVersion: String? = if (project.isVersionSet) {
            project.version.toString()
        } else {
            null
        }

        val allPackages = sortedSetOf()
        val mainClassNames = sortedSetOf()
        val taskClassInternalNames = sortedSetOf()
        val usedServicesClassInternalNames = sortedSetOf()
        val dependencyClassInternalNames = sortedSetOf()
        classesDirs.asFileTree.include("**/*.class").visitFiles { classFileDetails ->
            try {
                val usedServicesVisitor = UsedServicesCollectorClassVisitor(classesWithAllowedDynamicServiceLoader)
                val commonInfoVisitor = CommonInfoClassVisitor(usedServicesVisitor)
                val dependenciesClassVisitor = DependenciesCollectorClassVisitor(commonInfoVisitor)

                var classVisitor: ClassVisitor = dependenciesClassVisitor
                classVisitor = SkipInvisibleAnnotationsClassVisitor(classVisitor)
                classVisitor = SkipOuterAndInnerClassesClassVisitor(classVisitor)

                val bytecode = classFileDetails.readBytes()
                val classReader = ClassReader(bytecode)
                classReader.accept(classVisitor)

                commonInfoVisitor.classInternalName?.also { classInternalName ->
                    taskClassInternalNames.add(classInternalName)

                    val className = classInternalNameToClassName(classInternalName)
                    allPackages.add(className.substringBeforeLast('.', ""))

                    if (commonInfoVisitor.hasMainMethod) {
                        mainClassNames.add(className)
                    }
                }

                usedServicesClassInternalNames.addAll(usedServicesVisitor.usedServiceClassInternalNames)
                dependencyClassInternalNames.addAll(dependenciesClassVisitor.dependencyClassInternalNames)

            } catch (e: Throwable) {
                throw GradleException("Error processing $classFileDetails", e)
            }
        }

        dependencyClassInternalNames.removeAll(taskClassInternalNames)


        val packagesToExport = exports.filterPackageNames(allPackages)

        val mainClassName: ClassName? = this.mainClassName.let { mainClassName ->
            if (mainClassName != null) {
                if (mainClassName !in mainClassNames) {
                    throw IllegalStateException("Main class '$mainClassName' can't be found in parsed main classes: ${mainClassNames.joinToString(", ")}")
                } else {
                    return@let mainClassName
                }
            } else if (mainClassNames.isEmpty()) {
                return@let null
            } else if (mainClassNames.size >= 2) {
                throw IllegalStateException(
                    "Main class should be set explicitly, as several main classes has been found: ${
                        mainClassNames.joinToString(
                            ", "
                        )
                    }"
                )
            } else {
                return@let mainClassNames.single()
            }
        }

        val requiredModules: Set = dependencyClassInternalNames.let { classInternalNames ->
            if (classInternalNames.isEmpty()) return@let emptySet()

            val resolvedArtifacts = compileClasspathConfiguration?.resolvedConfiguration?.resolvedArtifacts.default(emptySet())
                .plus(runtimeClasspathConfiguration?.resolvedConfiguration?.resolvedArtifacts.default(emptySet()))
            val artifactsCollection = CachedArtifactsCollection(resolvedArtifacts.map(ResolvedArtifact::getFile))
            val moduleToVersion: Map by lazy(NONE) {
                buildMap {
                    artifactsCollection.artifacts.forEach { artifact ->
                        val moduleName = artifact.javaModuleName ?: return@forEach
                        val version = resolvedArtifacts.firstOrNull { it.file.absoluteFile == artifact.file.absoluteFile }
                            ?.moduleVersion
                            ?.let(ResolvedModuleVersion::getId)
                            ?.let(ModuleVersionIdentifier::getVersion)
                            ?.takeUnless(String::isBlank)
                        logDebug("module: {}, version: {}", moduleName, version)
                        put(moduleName, version)
                    }
                }
            }

            val runtimeModuleNames: Set by lazy(NONE) {
                val conf = runtimeClasspathConfiguration ?: return@lazy emptySet()
                conf.files.asSequence()
                    .map(ArtifactsCache::get)
                    .mapNotNull(Artifact::javaModuleName)
                    .toHashSet()
            }

            val staticModuleNames: Set by lazy(NONE) {
                val conf = compileOnlyConfiguration ?: return@lazy emptySet()
                conf.files.asSequence()
                    .map(ArtifactsCache::get)
                    .mapNotNull(Artifact::javaModuleName)
                    .filter { it !in runtimeModuleNames }
                    .toHashSet()
            }

            classInternalNames.asSequence()
                .mapNotNull map@{ classInternalName ->
                    if (classInternalName.startsWith("java/")) {
                        knownClassInternalNamesToModuleName[classInternalName]?.let { moduleName ->
                            return@map ModuleInfo(moduleName, REQUIRE_NORMAL)
                        }

                    } else {
                        artifactsCollection.getArtifactForEntry("$classInternalName.class")?.let { artifact ->
                            artifact.javaModuleName?.let { moduleName ->
                                val requireMode: RequireModuleMode = if (moduleName in staticModuleNames) {
                                    REQUIRE_STATIC
                                } else {
                                    REQUIRE_NORMAL
                                }
                                return@map ModuleInfo(moduleName, requireMode, moduleToVersion[moduleName])
                            }
                        }

                        knownClassInternalNamesToModuleName[classInternalName]?.let { moduleName ->
                            return@map ModuleInfo(moduleName, REQUIRE_STATIC)
                        }
                    }

                    return@map null
                }
                .toSortedSet()
                .apply {
                    mapOf(
                        requires.staticModuleNames to REQUIRE_STATIC,
                        requires.normalModuleNames to REQUIRE_NORMAL,
                        requires.transitiveModuleNames to REQUIRE_TRANSITIVE
                    ).forEach { modulesNames, requireMode ->
                        modulesNames.forEach { moduleName ->
                            val moduleInfo = ModuleInfo(moduleName, requireMode, moduleToVersion[moduleName])
                            removeIf { it.moduleName == moduleInfo.moduleName }
                            add(moduleInfo)
                        }
                    }

                    mapOf(
                        requires.staticDependencyNotations to REQUIRE_STATIC,
                        requires.normalDependencyNotations to REQUIRE_NORMAL,
                        requires.transitiveDependencyNotations to REQUIRE_TRANSITIVE
                    ).forEach { dependencyNotations, requireMode ->
                        dependencyNotations.asSequence()
                            .map(::DependencyNotationMatcher)
                            .flatMap { matcher ->
                                resolvedArtifacts.asSequence()
                                    .filter { matcher.matches(it) }
                            }
                            .distinct()
                            .forEach { resolvedArtifact ->
                                ArtifactsCache[resolvedArtifact.file].javaModuleName?.let { moduleName ->
                                    val moduleInfo = ModuleInfo(moduleName, requireMode, resolvedArtifact.moduleVersion.id.version)
                                    removeIf { it.moduleName == moduleInfo.moduleName }
                                    add(moduleInfo)
                                }
                            }
                    }
                }
                .apply {
                    removeIf { it.moduleName == "java.base" }
                }
        }


        val excludedModuleNames: Set = hashSetOf().apply {
            val dependencyNotationsToExclude = requires.dependencyNotationsToExclude
            if (dependencyNotationsToExclude.isNotEmpty()) {
                val resolvedArtifacts = compileClasspathConfiguration?.resolvedConfiguration?.resolvedArtifacts.default(emptySet())
                requires.dependencyNotationsToExclude.asSequence()
                    .map(::DependencyNotationMatcher)
                    .flatMap { matcher ->
                        resolvedArtifacts.asSequence()
                            .filter(matcher::matches)
                    }
                    .mapNotNull { ArtifactsCache.get(it.file).javaModuleName }
                    .forEach { add(it) }
            }

            addAll(requires.moduleNamesToExclude)
        }


        val moduleNode = ModuleNode(moduleName, moduleModifiers, moduleVersion).apply {
            mainClass = mainClassName?.let(::classNameToClassInternalName)

            exports = mutableListOf()
            packagesToExport.forEach { pkg ->
                exports.add(ModuleExportNode(pkg, 0, null))
            }

            uses = hashSetOf().apply {
                usedServicesClassInternalNames.forEach { add(classInternalNameToClassName(it)) }
                addAll([email protected])
            }.toList().sorted()

            requires = mutableListOf()
            requiredModules.forEach { requiredModule ->
                val moduleName = requiredModule.moduleName
                if (moduleName in excludedModuleNames) return@forEach
                val modifiers: Int = when (requiredModule.requireMode) {
                    REQUIRE_NORMAL -> 0
                    REQUIRE_TRANSITIVE -> ACC_TRANSITIVE
                    REQUIRE_STATIC -> ACC_STATIC_PHASE
                }
                requires.add(ModuleRequireNode(moduleName, modifiers, requiredModule.version))
            }

            provides = mutableListOf()
            providedImplementations.forEach { serviceName, implementations ->
                provides.add(ModuleProvideNode(
                    classNameToClassInternalName(serviceName),
                    implementations.map { classNameToClassInternalName(it) }
                ))
            }
        }

        val classWriter = ClassWriter(0)
        classWriter.visit(V9, ACC_MODULE, "module-info", null, null, null)
        moduleNode.accept(classWriter)
        classWriter.visitEnd()
        destinationDir.resolve("module-info.class")
            .createParentDirectories()
            .writeBytes(classWriter.toByteArray())
    }

    private val providedImplementations: Map>
        get() = sortedMapOf>().apply {
            classesDirs.asFileTree.include("META-INF/services/*").visitFiles { serviceFileDetails ->
                try {
                    val serviceName = serviceFileDetails.name
                    val implementations = computeIfAbsent(serviceName, { mutableSetOf() })

                    String(serviceFileDetails.readBytes(), UTF_8).splitToSequence('\n')
                        .map { it.substringBefore('#') }
                        .map(String::trim)
                        .filter(String::isNotEmpty)
                        .forEach { implementations.add(it) }
                } catch (e: Throwable) {
                    throw GradleException("Error processing $serviceFileDetails", e)
                }
            }
        }

}

@ModuleInfoDslMarker
data class ModuleInfoRequires(

    @get:Input
    @get:Optional
    val normalModuleNames: MutableSet = sortedSetOf(),

    @get:Internal
    val normalDependencies: MutableSet = mutableSetOf(),

    @get:Input
    @get:Optional
    val transitiveModuleNames: MutableSet = sortedSetOf(),

    @get:Internal
    val transitiveDependencies: MutableSet = mutableSetOf(),

    @get:Input
    @get:Optional
    val staticModuleNames: MutableSet = sortedSetOf(),

    @get:Internal
    val staticDependencies: MutableSet = mutableSetOf(),

    @get:Input
    @get:Optional
    val moduleNamesToExclude: MutableSet = sortedSetOf(),

    @get:Internal
    val dependenciesToExclude: MutableSet = mutableSetOf()

) {

    fun add(moduleName: String) {
        normalModuleNames.add(moduleName)
    }

    fun addDependency(dependency: Any) {
        normalDependencies.addAll(dependency.flattenAny())
    }

    fun addDependencies(vararg dependencies: Any) = addDependency(dependencies)
    fun addDependencies(dependencies: Iterable) = addDependency(dependencies)

    @get:Input
    @get:Optional
    val normalDependencyNotations: Set
        get() = normalDependencies.asSequence()
            .map(::createDependencyNotation)
            .map(DependencyNotation::toString)
            .toSortedSet()


    fun addTransitive(moduleName: String) {
        transitiveModuleNames.add(moduleName)
    }

    fun addTransitiveDependency(dependency: Any) {
        transitiveDependencies.addAll(dependency.flattenAny())
    }

    fun addTransitiveDependencies(vararg dependencies: Any) = addTransitiveDependency(dependencies)
    fun addTransitiveDependencies(dependencies: Iterable) = addTransitiveDependency(dependencies)

    @get:Input
    @get:Optional
    val transitiveDependencyNotations: Set
        get() = transitiveDependencies.asSequence()
            .map(::createDependencyNotation)
            .map(DependencyNotation::toString)
            .toSortedSet()


    fun addStatic(moduleName: String) {
        staticModuleNames.add(moduleName)
    }

    fun addStaticDependency(dependency: Any) {
        staticDependencies.addAll(dependency.flattenAny())
    }

    fun addStaticDependencies(vararg dependencies: Any) = addStaticDependency(dependencies)
    fun addStaticDependencies(dependencies: Iterable) = addStaticDependency(dependencies)

    @get:Input
    @get:Optional
    val staticDependencyNotations: Set
        get() = staticDependencies.asSequence()
            .map(::createDependencyNotation)
            .map(DependencyNotation::toString)
            .toSortedSet()


    fun exclude(moduleName: String) {
        moduleNamesToExclude.add(moduleName)
    }

    fun excludeDependency(dependency: Any) {
        dependenciesToExclude.addAll(dependency.flattenAny())
    }

    fun excludeDependencies(vararg dependencies: Any) = excludeDependency(dependencies)
    fun excludeDependencies(dependencies: Iterable) = excludeDependency(dependencies)

    @get:Input
    @get:Optional
    val dependencyNotationsToExclude: Set
        get() = dependenciesToExclude.asSequence()
            .map(::createDependencyNotation)
            .map(DependencyNotation::toString)
            .toSortedSet()

}

@ModuleInfoDslMarker
data class ModuleInfoExports(

    @get:Input
    @get:Optional
    var includes: MutableSet = sortedSetOf(),

    @get:Input
    @get:Optional
    var excludes: MutableSet = sortedSetOf(
        "*.internal.*",
        "*.shaded.*",
        "*.shadow.*"
    )

) {

    fun filterPackageNames(packageNames: Iterable): List {
        val includeRegexps = includes.toRegexps()
        val excludeRegexps = excludes.toRegexps()
        return packageNames.asSequence()
            .distinct()
            .filter { pkg ->
                if (includeRegexps.isNotEmpty() && includeRegexps.none(pkg::matches)) {
                    logDebug("skip export: {} - not in includes", pkg)
                    return@filter false
                }
                if (excludeRegexps.isNotEmpty() && excludeRegexps.any(pkg::matches)) {
                    logDebug("skip export: {} - in excludes", pkg)
                    return@filter false
                }
                logDebug("export: {}", pkg)
                return@filter true
            }
            .sorted()
            .toList()
    }

    private fun Iterable.toRegexps(): List = map { pattern ->
        if (pattern.trim('*', '.').isEmpty()) {
            return@map matchAll
        }
        val normalizedPattern = pattern.replace(manyStars, "*")
        return@map Regex(buildString {
            if (normalizedPattern.startsWith("*.")) {
                append("(?:[^.]+\\.)*")
            }
            append(
                normalizedPattern.trim('*', '.')
                    .split(".*.")
                    .map(::escapeRegex)
                    .joinToString("(?:\\.[^.]+)*\\.")
            )
            if (normalizedPattern.endsWith(".*")) {
                append("(?:\\.[^.]+)*")
            }
        })
    }

    companion object {
        private val matchAll = Regex(".*")
        private val manyStars = Regex("\\*{2,}")
    }

}

@ModuleInfoDslMarker
data class ModuleInfoUses(

    @get:Input
    @get:Optional
    val services: MutableSet = sortedSetOf()

) {

    fun add(service: ClassName) {
        services.add(service)
    }

    fun add(service: Class<*>) {
        services.add(service.name)
    }

}

private class CommonInfoClassVisitor(delegate: ClassVisitor?) : ClassVisitor(ASM_API, delegate) {

    var classInternalName: ClassInternalName? = null
    var hasMainMethod: Boolean = false

    override fun visit(version: Int, access: Int, name: ClassInternalName, signature: String?, superName: ClassInternalName?, interfaces: Array?) {
        classInternalName = name
        super.visit(version, access, name, signature, superName, interfaces)
    }

    override fun visitMethod(access: Int, name: String, descriptor: String, signature: String?, exceptions: Array?): MethodVisitor? {
        if ((access and ACC_STATIC) != 0
            && name == "main"
            && descriptor == "([Ljava/lang/String;)V"
        ) {
            hasMainMethod = true
        }
        return super.visitMethod(access, name, descriptor, signature, exceptions)
    }

}

private data class ModuleInfo(
    val moduleName: String,
    val requireMode: RequireModuleMode = REQUIRE_NORMAL,
    val version: String? = null
) : Comparable {

    override fun equals(other: Any?) = other is ModuleInfo && moduleName == other.moduleName
    override fun hashCode() = moduleName.hashCode()

    override fun compareTo(other: ModuleInfo): Int {
        requireMode.compareTo(other.requireMode).let { if (it != 0) return it }
        return moduleName.compareTo(other.moduleName)
    }

}

private enum class RequireModuleMode {
    REQUIRE_TRANSITIVE,
    REQUIRE_NORMAL,
    REQUIRE_STATIC
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy