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

com.autonomousapps.tasks.FindDeclaredProcsTask.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.DependencyConfiguration
import com.autonomousapps.internal.utils.fromJsonSet
import com.autonomousapps.internal.utils.getAndDelete
import com.autonomousapps.internal.utils.toJson
import com.autonomousapps.internal.utils.toPrettyString
import com.autonomousapps.services.InMemoryCache
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.ArtifactCollection
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.api.tasks.Optional
import java.io.BufferedReader
import java.io.File
import java.io.Writer
import java.net.URLClassLoader
import java.util.*
import java.util.zip.ZipFile
import javax.annotation.processing.Filer
import javax.annotation.processing.Messager
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.Processor
import javax.lang.model.SourceVersion
import javax.lang.model.element.*
import javax.lang.model.type.*
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import javax.naming.OperationNotSupportedException
import javax.tools.Diagnostic
import javax.tools.FileObject
import javax.tools.JavaFileManager
import javax.tools.JavaFileObject

/**
 * Sketch of proc algo
 * 1. Gather all APs by looking at both kapt and annotationProcessor
 *    configurations
 * 2. Create a ClassLoader with all of the resultant jars on the classpath
 * 3. Look through each jar for META-INF/services/javax.annotation.processing.Processor, parsing
 *    that file for any APs in the jar.
 * 4. For each AP found in step 3, create instance via reflection and invoke
 *    `getSupportedAnnotationTypes()`. It may also be necessary to invoke `init()` beforehand.
 * 5. Associate each supported annotation type with its processor.
 * 6. Parse bytecode for presence of annotation types
 */
@CacheableTask
abstract class FindDeclaredProcsTask : DefaultTask() {

  init {
    group = TASK_GROUP_DEP
    description = "Produces a report of all supported annotation types and their annotation processors"
  }

  companion object {
    internal const val ANNOTATION_PROCESSOR_PATH = "META-INF/services/javax.annotation.processing.Processor"
  }

  private var kaptArtifacts: ArtifactCollection? = null
  private var annotationProcessorArtifacts: ArtifactCollection? = null

  fun setKaptArtifacts(artifacts: ArtifactCollection) {
    kaptArtifacts = artifacts
  }

  fun setAnnotationProcessorArtifacts(artifacts: ArtifactCollection) {
    annotationProcessorArtifacts = artifacts
  }

  @Optional
  @Classpath
  fun getKaptArtifactFiles(): FileCollection? = kaptArtifacts?.artifactFiles

  @Optional
  @Classpath
  fun getAnnotationProcessorArtifactFiles(): FileCollection? = annotationProcessorArtifacts?.artifactFiles

  @get:PathSensitive(PathSensitivity.NONE)
  @get:InputFile
  abstract val dependencyConfigurations: RegularFileProperty

  @get:OutputFile
  abstract val output: RegularFileProperty

  @get:OutputFile
  abstract val outputPretty: RegularFileProperty

  @get:Internal
  abstract val inMemoryCacheProvider: Property

  private val kaptClassLoader: ClassLoader? by lazy {
    val urls = getKaptArtifactFiles()?.toList()?.map { it.toURI().toURL() }?.toTypedArray()
    urls?.let { URLClassLoader(urls, javaClass.classLoader) }
  }

  private val annotationProcessorClassLoader: ClassLoader? by lazy {
    val urls = getAnnotationProcessorArtifactFiles()?.toList()?.map { it.toURI().toURL() }?.toTypedArray()
    urls?.let { URLClassLoader(urls, javaClass.classLoader) }
  }

  private val inMemoryCache by lazy { inMemoryCacheProvider.get() }

  @TaskAction fun action() {
    val outputFile = output.getAndDelete()
    val outputPrettyFile = outputPretty.getAndDelete()

    val kaptProcs = procs(kaptArtifacts, kaptClassLoader)
    val annotationProcessorProcs = procs(annotationProcessorArtifacts, annotationProcessorClassLoader)
    val procs = kaptProcs + annotationProcessorProcs

    outputFile.writeText(procs.toJson())
    outputPrettyFile.writeText(procs.toPrettyString())
  }

  private fun procs(artifacts: ArtifactCollection?, classLoader: ClassLoader?): List {
    if (artifacts == null) return emptyList()

    return artifacts.mapNotNull { artifact ->
      val procs = findProcs(artifact.file)
      if (procs != null) artifact to procs else null
    }.flatMap { (artifact, procs) ->
      procs.mapNotNull { procName ->
        inMemoryCache.proc(procName) ?: procFor(artifact, procName, classLoader!!).also { proc ->
          proc?.let { inMemoryCache.procs(procName, it) }
        }
      }
    }
  }

  @Suppress("UNCHECKED_CAST")
  private fun procFor(
    artifact: ResolvedArtifactResult, procName: String, classLoader: ClassLoader
  ): AnnotationProcessor? {
    val candidates = dependencyConfigurations.fromJsonSet()
    return try {
      val procClass = classLoader.loadClass(procName) as Class
      val types = getSupportedAnnotationTypes(procClass)
      types?.let { AnnotationProcessor(procName, it, artifact.id.componentIdentifier, candidates) }
    } catch (_: ClassNotFoundException) {
      logger.warn("Could not load $procName from class loader")
      null
    }
  }

  private fun findProcs(file: File): List? {
    val zip = ZipFile(file)
    return zip.getEntry(ANNOTATION_PROCESSOR_PATH)?.let {
      zip.getInputStream(it).bufferedReader().use(BufferedReader::readLines)
    }
  }

  private fun  getSupportedAnnotationTypes(procClass: Class): Set? {
    return try {
      val proc = procClass.getDeclaredConstructor().newInstance()
      proc.init(StubProcessingEnvironment())
      proc.supportedAnnotationTypes
    } catch (t: Throwable) {
      logger.warn("Could not reflectively access processor class ${procClass.name}")
      null
    }
  }
}

private class StubProcessingEnvironment : ProcessingEnvironment {
  override fun getElementUtils(): Elements = StubElements()

  override fun getTypeUtils(): Types = StubTypes()

  override fun getMessager(): Messager = StubMessager()

  override fun getLocale(): Locale {
    throw OperationNotSupportedException()
  }

  override fun getSourceVersion(): SourceVersion = SourceVersion.latestSupported()

  override fun getOptions(): MutableMap = mutableMapOf()

  override fun getFiler(): Filer = StubFiler()

  private class StubElements : Elements {
    override fun hides(hider: Element?, hidden: Element?): Boolean {
      throw OperationNotSupportedException()
    }

    override fun overrides(overrider: ExecutableElement?, overridden: ExecutableElement?, type: TypeElement?): Boolean {
      throw OperationNotSupportedException()
    }

    override fun getName(cs: CharSequence?): Name {
      throw OperationNotSupportedException()
    }

    override fun isFunctionalInterface(type: TypeElement?): Boolean {
      throw OperationNotSupportedException()
    }

    override fun getElementValuesWithDefaults(a: AnnotationMirror?): MutableMap {
      throw OperationNotSupportedException()
    }

    override fun getBinaryName(type: TypeElement?): Name {
      throw OperationNotSupportedException()
    }

    override fun getDocComment(e: Element?): String {
      throw OperationNotSupportedException()
    }

    override fun isDeprecated(e: Element?): Boolean {
      throw OperationNotSupportedException()
    }

    override fun getAllMembers(type: TypeElement?): MutableList {
      throw OperationNotSupportedException()
    }

    override fun printElements(w: Writer?, vararg elements: Element?) {
      throw OperationNotSupportedException()
    }

    override fun getPackageElement(name: CharSequence?): PackageElement {
      throw OperationNotSupportedException()
    }

    override fun getTypeElement(name: CharSequence?): TypeElement {
      throw OperationNotSupportedException()
    }

    override fun getConstantExpression(value: Any?): String {
      throw OperationNotSupportedException()
    }

    override fun getPackageOf(type: Element?): PackageElement {
      throw OperationNotSupportedException()
    }

    override fun getAllAnnotationMirrors(e: Element?): MutableList {
      throw OperationNotSupportedException()
    }
  }

  private class StubMessager : Messager {
    override fun printMessage(kind: Diagnostic.Kind?, msg: CharSequence?) {
      throw OperationNotSupportedException()
    }

    override fun printMessage(kind: Diagnostic.Kind?, msg: CharSequence?, e: Element?) {
      throw OperationNotSupportedException()
    }

    override fun printMessage(kind: Diagnostic.Kind?, msg: CharSequence?, e: Element?, a: AnnotationMirror?) {
      throw OperationNotSupportedException()
    }

    override fun printMessage(kind: Diagnostic.Kind?, msg: CharSequence?, e: Element?, a: AnnotationMirror?, v: AnnotationValue?) {
      throw OperationNotSupportedException()
    }
  }

  private class StubFiler : Filer {
    override fun createSourceFile(name: CharSequence?, vararg originatingElements: Element?): JavaFileObject {
      throw OperationNotSupportedException()
    }

    override fun createClassFile(name: CharSequence?, vararg originatingElements: Element?): JavaFileObject {
      throw OperationNotSupportedException()
    }

    override fun getResource(location: JavaFileManager.Location?, pkg: CharSequence?, relativeName: CharSequence?): FileObject {
      throw OperationNotSupportedException()
    }

    override fun createResource(location: JavaFileManager.Location?, pkg: CharSequence?, relativeName: CharSequence?, vararg originatingElements: Element?): FileObject {
      throw OperationNotSupportedException()
    }
  }

  private class StubTypes : Types {
    override fun contains(t1: TypeMirror?, t2: TypeMirror?): Boolean {
      throw OperationNotSupportedException()
    }

    override fun boxedClass(p: PrimitiveType?): TypeElement {
      throw OperationNotSupportedException()
    }

    override fun getArrayType(componentType: TypeMirror?): ArrayType {
      throw OperationNotSupportedException()
    }

    override fun getDeclaredType(typeElem: TypeElement?, vararg typeArgs: TypeMirror?): DeclaredType {
      throw OperationNotSupportedException()
    }

    override fun getDeclaredType(containing: DeclaredType?, typeElem: TypeElement?, vararg typeArgs: TypeMirror?): DeclaredType {
      throw OperationNotSupportedException()
    }

    override fun isAssignable(t1: TypeMirror?, t2: TypeMirror?): Boolean {
      throw OperationNotSupportedException()
    }

    override fun asMemberOf(containing: DeclaredType?, element: Element?): TypeMirror {
      throw OperationNotSupportedException()
    }

    override fun getNullType(): NullType {
      throw OperationNotSupportedException()
    }

    override fun getWildcardType(extendsBound: TypeMirror?, superBound: TypeMirror?): WildcardType {
      throw OperationNotSupportedException()
    }

    override fun unboxedType(t: TypeMirror?): PrimitiveType {
      throw OperationNotSupportedException()
    }

    override fun isSameType(t1: TypeMirror?, t2: TypeMirror?): Boolean {
      throw OperationNotSupportedException()
    }

    override fun getPrimitiveType(kind: TypeKind?): PrimitiveType {
      throw OperationNotSupportedException()
    }

    override fun getNoType(kind: TypeKind?): NoType {
      throw OperationNotSupportedException()
    }

    override fun isSubsignature(m1: ExecutableType?, m2: ExecutableType?): Boolean {
      throw OperationNotSupportedException()
    }

    override fun capture(t: TypeMirror?): TypeMirror {
      throw OperationNotSupportedException()
    }

    override fun erasure(t: TypeMirror?): TypeMirror {
      throw OperationNotSupportedException()
    }

    override fun asElement(t: TypeMirror?): Element {
      throw OperationNotSupportedException()
    }

    override fun directSupertypes(t: TypeMirror?): MutableList {
      throw OperationNotSupportedException()
    }

    override fun isSubtype(t1: TypeMirror?, t2: TypeMirror?): Boolean {
      throw OperationNotSupportedException()
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy