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

com.autonomousapps.tasks.FindUnusedProcsTask.kt Maven / Gradle / Ivy

There is a newer version: 2.0.2
Show newest version
package com.autonomousapps.tasks

import com.autonomousapps.TASK_GROUP_DEP
import com.autonomousapps.internal.AnnotationProcessor
import com.autonomousapps.internal.Imports
import com.autonomousapps.internal.ProcClassVisitor
import com.autonomousapps.internal.asm.ClassReader
import com.autonomousapps.internal.utils.*
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.*
import java.io.File
import java.util.zip.ZipFile
import kotlin.text.RegexOption.IGNORE_CASE

@CacheableTask
abstract class FindUnusedProcsTask : DefaultTask() {

  init {
    group = TASK_GROUP_DEP
    description = "Produces a report of unused annotation processors"
  }

  /**
   * This project's compiled source.
   */
  @get:Optional
  @get:Classpath
  abstract val jar: RegularFileProperty

  /**
   * This project's compiled source. Class files generated by Kotlin source. May be empty.
   */
  @get:Classpath
  @get:InputFiles
  abstract val kotlinClasses: ConfigurableFileCollection

  /**
   * This project's compiled source. Class files generated by Java source. May be empty.
   */
  @get:Classpath
  @get:InputFiles
  abstract val javaClasses: ConfigurableFileCollection

  /**
   * All the imports in the Java and Kotlin source in this project. Needed for annotation processors
   * that support annotation types that have
   * [RetentionPolicy.SOURCE][java.lang.annotation.RetentionPolicy.SOURCE] or
   * [AnnotationRetention.SOURCE] retention.
   */
  @get:PathSensitive(PathSensitivity.RELATIVE)
  @get:InputFile
  abstract val imports: RegularFileProperty

  /**
   * Annotation processors that are present in the project (on `kapt` or `annotationProcessor`
   * configurations).
   */
  @get:PathSensitive(PathSensitivity.NONE)
  @get:InputFile
  abstract val annotationProcessorsProperty: RegularFileProperty

  @get:OutputFile
  abstract val output: RegularFileProperty

  private val annotationProcessors by lazy {
    annotationProcessorsProperty.fromJsonSet()
  }

  @TaskAction fun action() {
    // Output
    val outputFile = output.getAndDelete()

    // Inputs
    val classFiles = javaClasses.plus(kotlinClasses)
    val jarFile = jar.orNull?.asFile

    val a = findUsedProcsInClassFiles(classFiles)
    val b = jarFile?.let { findUsedProcsInJar(it) } ?: emptySet()
    val c = findUsedProcsInImports()
    val usedProcs = a + b + c
    val unusedProcs = annotationProcessors - usedProcs

    outputFile.writeText(unusedProcs.toJson())
  }

  private fun findUsedProcsInClassFiles(classFiles: FileCollection): Set {
    return classFiles
      .filterToClassFiles()
      .flatMapToSet { classFile ->
        val visitor = ProcClassVisitor(logger, annotationProcessors)
        val reader = classFile.inputStream().use { ClassReader(it.readBytes()) }
        reader.accept(visitor, 0)
        visitor.usedProcs()
      }
  }

  private fun findUsedProcsInJar(jarFile: File): Set {
    val zip = ZipFile(jarFile)

    return zip.asClassFiles()
      .flatMapToSet { classEntry ->
        val visitor = ProcClassVisitor(logger, annotationProcessors)
        val reader = zip.getInputStream(classEntry).use { ClassReader(it.readBytes()) }
        reader.accept(visitor, 0)
        visitor.usedProcs()
      }
  }

  private fun findUsedProcsInImports(): Set {
    val imports = imports.fromJsonList().flatten()

    val usedProcs = mutableSetOf()
    for (proc in annotationProcessors) {
      if (imports.containsSupportedType(proc.supportedAnnotationTypes)) {
        usedProcs.add(proc)
      }
    }

    return usedProcs
  }

  private fun Set.containsSupportedType(supportedTypes: Set): Boolean {
    // convert ["lombok.*"] to [lombok.(package) regex]
    val stars = supportedTypes
      .filter { it.endsWith("*") }
      .map { it.replace(".", "\\.") }
      .map { it.replace("*", JAVA_SUB_PACKAGE) }
      .map { it.toRegex(setOf(IGNORE_CASE)) }

    for (import in this) {
      if (supportedTypes.contains(import) || stars.any { it.matches(import) }) {
        return true
      }
    }
    return false
  }

  private fun List.flatten(): Set {
    val destination = mutableSetOf()
    for (i in this) {
      destination.addAll(i.imports)
    }
    return destination
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy