martin-g commented on code in PR #3614:
URL: https://github.com/apache/avro/pull/3614#discussion_r2660654293


##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt:
##########
@@ -0,0 +1,87 @@
+package org.apache.avro.gradle.plugin
+
+import org.apache.avro.gradle.plugin.extension.GradlePluginExtension
+import org.apache.avro.gradle.plugin.tasks.CompileSchemaTask
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.internal.cc.base.logger

Review Comment:
   ```suggestion
   ```



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt:
##########
@@ -0,0 +1,87 @@
+package org.apache.avro.gradle.plugin
+
+import org.apache.avro.gradle.plugin.extension.GradlePluginExtension
+import org.apache.avro.gradle.plugin.tasks.CompileSchemaTask
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.internal.cc.base.logger
+import kotlin.collections.toSet
+
+abstract class GradlePlugin : Plugin<Project> {
+    override fun apply(project: Project) {
+        logger.info("Running Avro Gradle plugin for project: ${project.name}")
+
+        val extension: GradlePluginExtension = 
project.extensions.create("avro", GradlePluginExtension::class.java)
+
+        // Needed for Android support
+        project.pluginManager.apply("java")
+
+        project.tasks.register("avroGenerateJavaClasses", 
CompileSchemaTask::class.java) { compileSchemaTask ->
+            val sourceDirectory = extension.sourceDirectory.get()
+            val outputDirectory = extension.outputDirectory.get()
+            runPlugin(compileSchemaTask, extension, project, sourceDirectory, 
outputDirectory)
+        }
+
+        project.tasks.register("avroGenerateTestJavaClasses", 
CompileSchemaTask::class.java) {compileSchemaTask ->
+            val sourceDirectory = extension.testSourceDirectory.get()
+            val outputDirectory = extension.testOutputDirectory.get()
+            runPlugin(compileSchemaTask, extension, project, sourceDirectory, 
outputDirectory)
+        }
+    }
+
+    private fun runPlugin(
+        compileTask: CompileSchemaTask,
+        extension: GradlePluginExtension,
+        project: Project,
+        sourceDirectory: String,
+        outputDirectory: String
+    ) {
+        val schemaType: SchemaType = 
SchemaType.valueOf(extension.schemaType.get())
+
+        when (schemaType) {
+            SchemaType.schema -> {
+                compileTask.source(project.fileTree(sourceDirectory))
+                compileTask.sourceDirectory.set(sourceDirectory)
+                compileTask.outputDirectory.set(outputDirectory)
+                compileTask.fieldVisibility.set(extension.fieldVisibility)
+                compileTask.setExcludes(extension.excludes.get().toSet())
+                compileTask.setIncludes(setOf("**/*.avsc"))
+                compileTask.testExcludes.set(extension.testExcludes)
+                compileTask.stringType.set(extension.stringType)
+                
compileTask.velocityToolsClassesNames.set(extension.velocityToolsClassesNames.get())
+                compileTask.templateDirectory.set(extension.templateDirectory)
+                
compileTask.recordSpecificClass.set(extension.recordSpecificClass)
+                
compileTask.errorSpecificClass.set(extension.errorSpecificClass)
+                
compileTask.createOptionalGetters.set(extension.createOptionalGetters)
+                
compileTask.gettersReturnOptional.set(extension.gettersReturnOptional)
+                compileTask.createSetters.set(extension.createSetters)
+                
compileTask.createNullSafeAnnotations.set(extension.createNullSafeAnnotations)
+                
compileTask.nullSafeAnnotationNullable.set(extension.nullSafeAnnotationNullable)
+                
compileTask.nullSafeAnnotationNotNull.set(extension.nullSafeAnnotationNotNull)
+                
compileTask.optionalGettersForNullableFieldsOnly.set(extension.optionalGettersForNullableFieldsOnly)
+                compileTask.customConversions.set(extension.customConversions)
+                
compileTask.customLogicalTypeFactories.set(extension.customLogicalTypeFactories)
+                
compileTask.enableDecimalLogicalType.set(extension.enableDecimalLogicalType)
+
+                addGeneratedSourcesToProject(project, 
compileTask.outputDirectory.get())
+            }
+
+            SchemaType.idl -> TODO()
+        }
+    }
+
+    private fun addGeneratedSourcesToProject(project: Project, 
outputDirectory: String) {
+        val sourceSets = 
project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
+        val generatedSourcesDir = 
project.layout.buildDirectory.dir(outputDirectory)
+        project.logger.debug("Generated sources directory: 
${generatedSourcesDir.get()}")
+
+        // Add directory that contains the generated Java files to source set
+        sourceSets.getByName("main").java.srcDir(generatedSourcesDir)
+    }
+}
+
+enum class SchemaType {
+    schema,
+    idl

Review Comment:
   ```suggestion
       idl,
       protocol
   ```
   
   
https://github.com/apache/avro/pull/3614/changes#diff-ade3c72ba8026631dd132cd0c384b65f9c80282a26d99bab93813cf50fbccb58R11
 says that `protocol` is the third possible value



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt:
##########
@@ -0,0 +1,87 @@
+package org.apache.avro.gradle.plugin
+
+import org.apache.avro.gradle.plugin.extension.GradlePluginExtension
+import org.apache.avro.gradle.plugin.tasks.CompileSchemaTask
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.internal.cc.base.logger
+import kotlin.collections.toSet
+
+abstract class GradlePlugin : Plugin<Project> {
+    override fun apply(project: Project) {
+        logger.info("Running Avro Gradle plugin for project: ${project.name}")
+
+        val extension: GradlePluginExtension = 
project.extensions.create("avro", GradlePluginExtension::class.java)
+
+        // Needed for Android support
+        project.pluginManager.apply("java")
+
+        project.tasks.register("avroGenerateJavaClasses", 
CompileSchemaTask::class.java) { compileSchemaTask ->
+            val sourceDirectory = extension.sourceDirectory.get()
+            val outputDirectory = extension.outputDirectory.get()
+            runPlugin(compileSchemaTask, extension, project, sourceDirectory, 
outputDirectory)
+        }
+
+        project.tasks.register("avroGenerateTestJavaClasses", 
CompileSchemaTask::class.java) {compileSchemaTask ->
+            val sourceDirectory = extension.testSourceDirectory.get()
+            val outputDirectory = extension.testOutputDirectory.get()
+            runPlugin(compileSchemaTask, extension, project, sourceDirectory, 
outputDirectory)
+        }
+    }
+
+    private fun runPlugin(
+        compileTask: CompileSchemaTask,
+        extension: GradlePluginExtension,
+        project: Project,
+        sourceDirectory: String,
+        outputDirectory: String
+    ) {
+        val schemaType: SchemaType = 
SchemaType.valueOf(extension.schemaType.get())
+
+        when (schemaType) {
+            SchemaType.schema -> {
+                compileTask.source(project.fileTree(sourceDirectory))
+                compileTask.sourceDirectory.set(sourceDirectory)
+                compileTask.outputDirectory.set(outputDirectory)
+                compileTask.fieldVisibility.set(extension.fieldVisibility)
+                compileTask.setExcludes(extension.excludes.get().toSet())
+                compileTask.setIncludes(setOf("**/*.avsc"))
+                compileTask.testExcludes.set(extension.testExcludes)
+                compileTask.stringType.set(extension.stringType)
+                
compileTask.velocityToolsClassesNames.set(extension.velocityToolsClassesNames.get())
+                compileTask.templateDirectory.set(extension.templateDirectory)
+                
compileTask.recordSpecificClass.set(extension.recordSpecificClass)
+                
compileTask.errorSpecificClass.set(extension.errorSpecificClass)
+                
compileTask.createOptionalGetters.set(extension.createOptionalGetters)
+                
compileTask.gettersReturnOptional.set(extension.gettersReturnOptional)
+                compileTask.createSetters.set(extension.createSetters)
+                
compileTask.createNullSafeAnnotations.set(extension.createNullSafeAnnotations)
+                
compileTask.nullSafeAnnotationNullable.set(extension.nullSafeAnnotationNullable)
+                
compileTask.nullSafeAnnotationNotNull.set(extension.nullSafeAnnotationNotNull)
+                
compileTask.optionalGettersForNullableFieldsOnly.set(extension.optionalGettersForNullableFieldsOnly)
+                compileTask.customConversions.set(extension.customConversions)
+                
compileTask.customLogicalTypeFactories.set(extension.customLogicalTypeFactories)
+                
compileTask.enableDecimalLogicalType.set(extension.enableDecimalLogicalType)
+
+                addGeneratedSourcesToProject(project, 
compileTask.outputDirectory.get())
+            }
+
+            SchemaType.idl -> TODO()

Review Comment:
   What about `SchemaType.protocol` ?



##########
lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SchemaCompileTaskTest.kt:
##########
@@ -0,0 +1,271 @@
+package org.apache.avro.gradle.plugin
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
+import kotlin.io.path.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@ExperimentalPathApi

Review Comment:
   Why is this annotation needed ? Maybe add a comment explaining what is 
experimental 



##########
lang/java/pom.xml:
##########
@@ -334,6 +334,7 @@
         <groupId>com.diffplug.spotless</groupId>
         <artifactId>spotless-maven-plugin</artifactId>
         <configuration>
+          <skip>true</skip>

Review Comment:
   Please revert this



##########
lang/java/gradle-plugin/build.gradle.kts:
##########
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+    kotlin("jvm") version "2.2.10"
+    `java-gradle-plugin`
+    `maven-publish`
+}
+
+group = "org.apache.avro"
+version = "1.13.0-SNAPSHOT"
+
+repositories {
+    mavenCentral()
+    mavenLocal()
+}
+
+dependencies {
+    //implementation("org.gradle:gradle-tooling-api:7.1.1")

Review Comment:
   Please remove it if it is not needed.



##########
lang/java/gradle-plugin/README.md:
##########
@@ -0,0 +1,59 @@
+# Avro Gradle plugin (in development)
+
+Gradle plugin that generates Java code from Avro schemas
+
+## Usage
+
+### Add avro extension
+In `build.gradle.kts`:
+
+Add plugin
+
+```kotlin
+plugins {
+    id("eu.eventloopsoftware.avro-gradle-plugin") version "0.0.2"

Review Comment:
   ```suggestion
       id("org.apache.avro.gradle-plugin") version "1.13.0-SNAPSHOT"
   ```



##########
lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SchemaCompileTaskTest.kt:
##########
@@ -0,0 +1,271 @@
+package org.apache.avro.gradle.plugin
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
+import kotlin.io.path.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@ExperimentalPathApi
+class SchemaCompileTaskTest {
+
+    @TempDir
+    lateinit var tempDir: Path
+
+    @Test
+    fun `plugin executes avroGenerateJavaClasses task successfully`() {
+        // given
+        val tempSettingsFile = tempDir.resolve("settings.gradle.kts")
+        val tempBuildFile = tempDir.resolve("build.gradle.kts")
+        val tempAvroSrcDir = 
tempDir.resolve("src/test/avro").createDirectories()
+
+        val testAvroFiles = Path.of("src/test/avro")
+        val testAvroOutPutDir = Path.of("generated-sources/avro")
+
+        val testOutPutDirectory = 
tempDir.resolve("build/$testAvroOutPutDir/test")
+
+        testAvroFiles.copyToRecursively(
+            tempAvroSrcDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        tempSettingsFile.writeText("")
+        tempBuildFile.writeText(
+            """            
+            plugins {
+                id("org.apache.avro.avro-gradle-plugin")
+            }
+            
+            avro {
+                schemaType = "schema"
+                sourceDirectory = "$testAvroFiles"
+                outputDirectory = "$testAvroOutPutDir"
+            }
+        """.trimIndent()
+        )
+
+        // when
+        val result = GradleRunner.create()
+            .withProjectDir(tempDir.toFile())
+            .withArguments("avroGenerateJavaClasses")
+            .withPluginClasspath()
+            .forwardOutput() // to see printLn in code
+            .build()
+
+        val expectedFiles = setOf(
+            "SchemaPrivacy.java",
+            "SchemaUser.java",
+            "PrivacyImport.java",
+            "SchemaCustom.java",
+            "PrivacyDirectImport.java"
+        )
+
+        // then
+        assertEquals(TaskOutcome.SUCCESS, 
result.task(":avroGenerateJavaClasses")?.outcome)
+        assertFilesExist(testOutPutDirectory, expectedFiles)
+
+        val schemaUserContent = 
testOutPutDirectory.resolve("SchemaUser.java").readText()
+        assertTrue(schemaUserContent.contains("java.time.Instant"))
+    }
+
+
+    @Test
+    fun `plugin executes avroGenerateTestJavaClasses task successfully - for 
files in test directory`() {
+        // given
+        val tempSettingsFile = tempDir.resolve("settings.gradle.kts")
+        val tempBuildFile = tempDir.resolve("build.gradle.kts")
+        val tempAvroSrcDir = 
tempDir.resolve("src/test/avro").createDirectories()
+
+        val testAvroFiles = Path.of("src/test/avro")
+        val testAvroOutPutDir = Path.of("generated-test-sources-avro")
+
+        val testOutPutDirectory = 
tempDir.resolve("build/$testAvroOutPutDir/test")
+
+        testAvroFiles.copyToRecursively(
+            tempAvroSrcDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        tempSettingsFile.writeText("")
+        tempBuildFile.writeText(
+            """            
+            plugins {
+                id("org.apache.avro.avro-gradle-plugin")

Review Comment:
   ```suggestion
                   id("org.apache.avro.gradle-plugin")
   ```



##########
lang/java/gradle-plugin/pom.xml:
##########
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       https://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<project
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";
+  xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>avro-parent</artifactId>
+    <groupId>org.apache.avro</groupId>
+    <version>1.13.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <artifactId>avro-gradle-plugin</artifactId>

Review Comment:
   ```suggestion
     <artifactId>gradle-plugin</artifactId>
   ```



##########
lang/java/gradle-plugin/README.md:
##########
@@ -0,0 +1,59 @@
+# Avro Gradle plugin (in development)
+
+Gradle plugin that generates Java code from Avro schemas
+
+## Usage
+
+### Add avro extension
+In `build.gradle.kts`:
+
+Add plugin
+
+```kotlin
+plugins {
+    id("eu.eventloopsoftware.avro-gradle-plugin") version "0.0.2"
+}
+```
+Add Avro dependency
+
+```kotlin
+implementation("org.apache.avro:avro:1.12.1")
+```
+Configure Avro Gradle plugin
+```kotlin
+avro {
+    sourceDirectory = "src/main/avro"
+    // All properties are available in `GradlePluginExtension.kt`
+} 
+```
+
+In `settings.gradle.kts`:
+
+Plugin is published on Maven Central:
+```kotlin
+pluginManagement {
+    repositories {
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+```
+
+
+
+### Add a task hook
+For Intellij to recognize the newly generated Java files add this to 
`build.gradle.kts`:
+
+```kotlin
+tasks.named("compileKotlin") { 
dependsOn(tasks.named("avroGenerateJavaClasses")) }
+```
+
+### Generate Java classes
+
+`./gradlew avroGenerateJavaClasses`
+
+
+## Example project that uses avro-gradle-plugin

Review Comment:
   ```suggestion
   ## Example project that uses the Apache Avro gradle-plugin
   ```



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt:
##########
@@ -0,0 +1,72 @@
+package org.apache.avro.gradle.plugin.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.SourceTask
+
+abstract class AbstractCompileTask : SourceTask() {
+
+    @get:Input

Review Comment:
   Shouldn't this use `InputDirectory` + `DirectoryProperty` instead to support 
incremental builds ?



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt:
##########
@@ -0,0 +1,136 @@
+package org.apache.avro.gradle.plugin.tasks
+
+import org.apache.avro.SchemaParseException
+import org.apache.avro.SchemaParser
+import org.apache.avro.compiler.specific.SpecificCompiler
+import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility
+import org.apache.avro.generic.GenericData
+import org.gradle.api.Project
+import org.gradle.api.file.FileTree
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.io.IOException
+import java.util.*
+
+abstract class CompileSchemaTask : AbstractCompileTask() {
+
+    /**
+     * A set of Glob patterns used to select files from the source
+     * directory for processing. By default, the pattern "**&#47;*.avsc"
+     * is used to select avsc files.
+     *
+     * @parameter
+     */
+    //@get:Input
+    //val includes: Set<String> = setOf("**/*.avsc")
+
+    @TaskAction
+    fun compileSchema() {
+        project.logger.info("Generating Java files from Avro schemas...")
+
+        if (!source.isEmpty) {
+            val sourceDirectoryFullPath = 
getSourceDirectoryFullPath(sourceDirectory)
+            val outputDirectoryFullPath = 
getBuildDirectoryFullPath(outputDirectory)
+            compileSchemas(source, sourceDirectoryFullPath, 
outputDirectoryFullPath)
+        } else {
+            logger.warn("No Avro files found in $sourceDirectory. Nothing to 
compile")
+        }
+
+        project.logger.info("Done generating Java files from Avro schemas...")
+    }
+
+
+    private fun getSourceDirectoryFullPath(directoryProperty: 
Property<String>): File =
+        project.layout.projectDirectory.dir(directoryProperty.get()).asFile
+
+    private fun getBuildDirectoryFullPath(directoryProperty: 
Property<String>): File =
+        project.layout.buildDirectory.dir(directoryProperty).get().asFile
+
+    private fun compileSchemas(fileTree: FileTree, sourceDirectory: File, 
outputDirectory: File) {
+        val sourceFileForModificationDetection: File? =
+            fileTree
+                .files
+                .filter { file: File -> file.lastModified() > 0 }
+                .maxBy { it.lastModified() }
+
+        try {
+            val parser = SchemaParser()
+            for (sourceFile in fileTree.files) {
+                parser.parse(sourceFile)
+            }
+            val schemas = parser.parsedNamedSchemas
+
+            doCompile(sourceFileForModificationDetection, 
SpecificCompiler(schemas), outputDirectory)
+        } catch (ex: IOException) {
+            // TODO: more concrete exceptions
+            throw RuntimeException("IO ex: Error compiling a file in " + 
sourceDirectory + " to " + outputDirectory, ex)
+        } catch (ex: SchemaParseException) {
+            throw RuntimeException(
+                "SchemaParse ex Error compiling a file in " + sourceDirectory 
+ " to " + outputDirectory,
+                ex
+            )
+        }
+    }
+
+    private fun doCompile(
+        sourceFileForModificationDetection: File?,
+        compiler: SpecificCompiler,
+        outputDirectory: File
+    ) {
+        setCompilerProperties(compiler)
+        // TODO:
+        //  * customLogicalTypeFactories
+
+        try {
+            for (customConversion in customConversions.get()) {
+                
compiler.addCustomConversion(Thread.currentThread().getContextClassLoader().loadClass(customConversion))
+            }
+        } catch (e: ClassNotFoundException) {
+            throw IOException(e)
+        }
+        compiler.compileToDestination(sourceFileForModificationDetection, 
outputDirectory)
+    }
+
+
+    private fun setCompilerProperties(compiler: SpecificCompiler) {
+        
compiler.setTemplateDir(project.layout.projectDirectory.dir(templateDirectory.get()).asFile.absolutePath
 + "/")
+        
compiler.setStringType(GenericData.StringType.valueOf(stringType.get()))
+        compiler.setFieldVisibility(getFieldV())
+        compiler.setCreateOptionalGetters(createOptionalGetters.get())
+        compiler.setGettersReturnOptional(gettersReturnOptional.get())
+        
compiler.setOptionalGettersForNullableFieldsOnly(optionalGettersForNullableFieldsOnly.get())
+        compiler.setCreateSetters(createSetters.get())
+        compiler.setCreateNullSafeAnnotations(createNullSafeAnnotations.get())
+        
compiler.setNullSafeAnnotationNullable(nullSafeAnnotationNullable.get())
+        compiler.setNullSafeAnnotationNotNull(nullSafeAnnotationNotNull.get())
+        compiler.setEnableDecimalLogicalType(enableDecimalLogicalType.get())
+        // TODO: likely not needed
+//        
compiler.setOutputCharacterEncoding(project.getProperties().getProperty("project.build.sourceEncoding"))
+        
compiler.setAdditionalVelocityTools(instantiateAdditionalVelocityTools(velocityToolsClassesNames.get()))
+        compiler.setRecordSpecificClass(recordSpecificClass.get())
+        compiler.setErrorSpecificClass(errorSpecificClass.get())
+    }
+
+    private fun getFieldV(): FieldVisibility {
+        try {
+            val upperCaseFieldVisibility = 
fieldVisibility.get().trim().uppercase(Locale.getDefault())
+            return FieldVisibility.valueOf(upperCaseFieldVisibility)
+        } catch (e: IllegalArgumentException) {
+            logger.warn("Could not parse field visibility, using PRIVATE")

Review Comment:
   IMO it would be useful to print the field name too, for easier debugging.



##########
lang/java/gradle-plugin/README.md:
##########
@@ -0,0 +1,59 @@
+# Avro Gradle plugin (in development)
+
+Gradle plugin that generates Java code from Avro schemas
+
+## Usage
+
+### Add avro extension
+In `build.gradle.kts`:
+
+Add plugin
+
+```kotlin
+plugins {
+    id("eu.eventloopsoftware.avro-gradle-plugin") version "0.0.2"
+}
+```
+Add Avro dependency
+
+```kotlin
+implementation("org.apache.avro:avro:1.12.1")

Review Comment:
   ```suggestion
   implementation("org.apache.avro:avro:1.13.0-SNAPSHOT")
   ```



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt:
##########
@@ -0,0 +1,72 @@
+package org.apache.avro.gradle.plugin.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.SourceTask
+
+abstract class AbstractCompileTask : SourceTask() {
+
+    @get:Input
+    abstract val sourceDirectory: Property<String>
+
+    @get:Input
+    abstract val outputDirectory: Property<String>
+
+    @get:Input
+    abstract val fieldVisibility: Property<String>
+
+    //@get:Input
+    //abstract val excludes: ListProperty<String>

Review Comment:
   What is the issue with this ? Why is it commented out ?



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt:
##########
@@ -0,0 +1,87 @@
+package org.apache.avro.gradle.plugin
+
+import org.apache.avro.gradle.plugin.extension.GradlePluginExtension
+import org.apache.avro.gradle.plugin.tasks.CompileSchemaTask
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.internal.cc.base.logger
+import kotlin.collections.toSet
+
+abstract class GradlePlugin : Plugin<Project> {
+    override fun apply(project: Project) {
+        logger.info("Running Avro Gradle plugin for project: ${project.name}")

Review Comment:
   ```suggestion
           project.logger.info("Running Avro Gradle plugin for project: 
${project.name}")
   ```



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/GradlePlugin.kt:
##########
@@ -0,0 +1,87 @@
+package org.apache.avro.gradle.plugin
+
+import org.apache.avro.gradle.plugin.extension.GradlePluginExtension
+import org.apache.avro.gradle.plugin.tasks.CompileSchemaTask
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.internal.cc.base.logger
+import kotlin.collections.toSet
+
+abstract class GradlePlugin : Plugin<Project> {
+    override fun apply(project: Project) {
+        logger.info("Running Avro Gradle plugin for project: ${project.name}")
+
+        val extension: GradlePluginExtension = 
project.extensions.create("avro", GradlePluginExtension::class.java)
+
+        // Needed for Android support
+        project.pluginManager.apply("java")
+
+        project.tasks.register("avroGenerateJavaClasses", 
CompileSchemaTask::class.java) { compileSchemaTask ->
+            val sourceDirectory = extension.sourceDirectory.get()
+            val outputDirectory = extension.outputDirectory.get()
+            runPlugin(compileSchemaTask, extension, project, sourceDirectory, 
outputDirectory)
+        }
+
+        project.tasks.register("avroGenerateTestJavaClasses", 
CompileSchemaTask::class.java) {compileSchemaTask ->
+            val sourceDirectory = extension.testSourceDirectory.get()
+            val outputDirectory = extension.testOutputDirectory.get()
+            runPlugin(compileSchemaTask, extension, project, sourceDirectory, 
outputDirectory)
+        }
+    }
+
+    private fun runPlugin(
+        compileTask: CompileSchemaTask,
+        extension: GradlePluginExtension,
+        project: Project,
+        sourceDirectory: String,
+        outputDirectory: String
+    ) {
+        val schemaType: SchemaType = 
SchemaType.valueOf(extension.schemaType.get())
+
+        when (schemaType) {
+            SchemaType.schema -> {
+                compileTask.source(project.fileTree(sourceDirectory))
+                compileTask.sourceDirectory.set(sourceDirectory)
+                compileTask.outputDirectory.set(outputDirectory)
+                compileTask.fieldVisibility.set(extension.fieldVisibility)
+                compileTask.setExcludes(extension.excludes.get().toSet())
+                compileTask.setIncludes(setOf("**/*.avsc"))
+                compileTask.testExcludes.set(extension.testExcludes)
+                compileTask.stringType.set(extension.stringType)
+                
compileTask.velocityToolsClassesNames.set(extension.velocityToolsClassesNames.get())
+                compileTask.templateDirectory.set(extension.templateDirectory)
+                
compileTask.recordSpecificClass.set(extension.recordSpecificClass)
+                
compileTask.errorSpecificClass.set(extension.errorSpecificClass)
+                
compileTask.createOptionalGetters.set(extension.createOptionalGetters)
+                
compileTask.gettersReturnOptional.set(extension.gettersReturnOptional)
+                compileTask.createSetters.set(extension.createSetters)
+                
compileTask.createNullSafeAnnotations.set(extension.createNullSafeAnnotations)
+                
compileTask.nullSafeAnnotationNullable.set(extension.nullSafeAnnotationNullable)
+                
compileTask.nullSafeAnnotationNotNull.set(extension.nullSafeAnnotationNotNull)
+                
compileTask.optionalGettersForNullableFieldsOnly.set(extension.optionalGettersForNullableFieldsOnly)
+                compileTask.customConversions.set(extension.customConversions)
+                
compileTask.customLogicalTypeFactories.set(extension.customLogicalTypeFactories)
+                
compileTask.enableDecimalLogicalType.set(extension.enableDecimalLogicalType)
+
+                addGeneratedSourcesToProject(project, 
compileTask.outputDirectory.get())
+            }
+
+            SchemaType.idl -> TODO()
+        }
+    }
+
+    private fun addGeneratedSourcesToProject(project: Project, 
outputDirectory: String) {
+        val sourceSets = 
project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
+        val generatedSourcesDir = 
project.layout.buildDirectory.dir(outputDirectory)
+        project.logger.debug("Generated sources directory: 
${generatedSourcesDir.get()}")
+
+        // Add directory that contains the generated Java files to source set
+        sourceSets.getByName("main").java.srcDir(generatedSourcesDir)

Review Comment:
   This is used also for the test task - `avroGenerateTestJavaClasses`



##########
lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SchemaCompileTaskTest.kt:
##########
@@ -0,0 +1,271 @@
+package org.apache.avro.gradle.plugin
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
+import kotlin.io.path.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@ExperimentalPathApi
+class SchemaCompileTaskTest {
+
+    @TempDir
+    lateinit var tempDir: Path
+
+    @Test
+    fun `plugin executes avroGenerateJavaClasses task successfully`() {
+        // given
+        val tempSettingsFile = tempDir.resolve("settings.gradle.kts")
+        val tempBuildFile = tempDir.resolve("build.gradle.kts")
+        val tempAvroSrcDir = 
tempDir.resolve("src/test/avro").createDirectories()
+
+        val testAvroFiles = Path.of("src/test/avro")
+        val testAvroOutPutDir = Path.of("generated-sources/avro")
+
+        val testOutPutDirectory = 
tempDir.resolve("build/$testAvroOutPutDir/test")
+
+        testAvroFiles.copyToRecursively(
+            tempAvroSrcDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        tempSettingsFile.writeText("")
+        tempBuildFile.writeText(
+            """            
+            plugins {
+                id("org.apache.avro.avro-gradle-plugin")
+            }
+            
+            avro {
+                schemaType = "schema"
+                sourceDirectory = "$testAvroFiles"
+                outputDirectory = "$testAvroOutPutDir"
+            }
+        """.trimIndent()
+        )
+
+        // when
+        val result = GradleRunner.create()
+            .withProjectDir(tempDir.toFile())
+            .withArguments("avroGenerateJavaClasses")
+            .withPluginClasspath()
+            .forwardOutput() // to see printLn in code
+            .build()
+
+        val expectedFiles = setOf(
+            "SchemaPrivacy.java",
+            "SchemaUser.java",
+            "PrivacyImport.java",
+            "SchemaCustom.java",
+            "PrivacyDirectImport.java"
+        )
+
+        // then
+        assertEquals(TaskOutcome.SUCCESS, 
result.task(":avroGenerateJavaClasses")?.outcome)
+        assertFilesExist(testOutPutDirectory, expectedFiles)
+
+        val schemaUserContent = 
testOutPutDirectory.resolve("SchemaUser.java").readText()
+        assertTrue(schemaUserContent.contains("java.time.Instant"))
+    }
+
+
+    @Test
+    fun `plugin executes avroGenerateTestJavaClasses task successfully - for 
files in test directory`() {
+        // given
+        val tempSettingsFile = tempDir.resolve("settings.gradle.kts")
+        val tempBuildFile = tempDir.resolve("build.gradle.kts")
+        val tempAvroSrcDir = 
tempDir.resolve("src/test/avro").createDirectories()
+
+        val testAvroFiles = Path.of("src/test/avro")
+        val testAvroOutPutDir = Path.of("generated-test-sources-avro")
+
+        val testOutPutDirectory = 
tempDir.resolve("build/$testAvroOutPutDir/test")
+
+        testAvroFiles.copyToRecursively(
+            tempAvroSrcDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        tempSettingsFile.writeText("")
+        tempBuildFile.writeText(
+            """            
+            plugins {
+                id("org.apache.avro.avro-gradle-plugin")
+            }
+            
+            avro {
+                schemaType = "schema"
+                testSourceDirectory = "$testAvroFiles"
+                testOutputDirectory = "$testAvroOutPutDir"
+            }
+        """.trimIndent()
+        )
+
+        // when
+        val result = GradleRunner.create()
+            .withProjectDir(tempDir.toFile())
+            .withArguments("avroGenerateTestJavaClasses")
+            .withPluginClasspath()
+            .forwardOutput() // to see printLn in code
+            .build()
+
+        val expectedFiles = setOf(
+            "SchemaPrivacy.java",
+            "SchemaUser.java",
+            "PrivacyImport.java",
+            "SchemaCustom.java",
+            "PrivacyDirectImport.java"
+        )
+
+        // then
+        assertEquals(TaskOutcome.SUCCESS, 
result.task(":avroGenerateTestJavaClasses")?.outcome)
+        assertFilesExist(testOutPutDirectory, expectedFiles)
+
+        val schemaUserContent = 
testOutPutDirectory.resolve("SchemaUser.java").readText()
+        assertTrue(schemaUserContent.contains("java.time.Instant"))
+    }
+
+    @Test
+    fun `plugin executes avroGenerateJavaClasses task successfully - with 
Velocity class names`() {
+        // given
+        val tempSettingsFile = tempDir.resolve("settings.gradle.kts")
+        val tempBuildFile = tempDir.resolve("build.gradle.kts")
+        val tempAvroSrcDir = 
tempDir.resolve("src/test/avro").createDirectories()
+        //val bla = tempDir.resolve("src/aap").createDirectories()
+        val tempVelocityToolClassesDir = 
tempDir.resolve("src/test/resources/templates").createDirectories()
+
+        val testAvroFilesDir = Path.of("src/test/avro")
+        val testAvroOutPutDir = Path.of("generated-sources-avro")
+        val testVelocityToolClassesDir = 
Path.of("src/test/resources/templates")
+
+        val testOutPutDirectory = 
tempDir.resolve("build/$testAvroOutPutDir/test")
+
+        testAvroFilesDir.copyToRecursively(
+            tempAvroSrcDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        testVelocityToolClassesDir.copyToRecursively(
+            tempVelocityToolClassesDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        tempSettingsFile.writeText("")
+        tempBuildFile.writeText(
+            """            
+            plugins {
+                id("org.apache.avro.avro-gradle-plugin")

Review Comment:
   ```suggestion
                   id("org.apache.avro.gradle-plugin")
   ```



##########
lang/java/gradle-plugin/src/test/avro/AvdlClasspathImport.avdl:
##########
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+namespace test;
+
+import idl "avro/User.avdl";
+
+/** Ignored Doc Comment */
+/** IDL User */

Review Comment:
   What is the purpose of the test ? I don't see `IdlUserWrapper` being 
referenced anywhere else. Should we assert something on it or it is loaded 
automatically and if there is no error then it is OK ?
   
   Why there are two Javadocs ?



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt:
##########
@@ -0,0 +1,136 @@
+package org.apache.avro.gradle.plugin.tasks
+
+import org.apache.avro.SchemaParseException
+import org.apache.avro.SchemaParser
+import org.apache.avro.compiler.specific.SpecificCompiler
+import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility
+import org.apache.avro.generic.GenericData
+import org.gradle.api.Project
+import org.gradle.api.file.FileTree
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.io.IOException
+import java.util.*
+
+abstract class CompileSchemaTask : AbstractCompileTask() {
+
+    /**
+     * A set of Glob patterns used to select files from the source
+     * directory for processing. By default, the pattern "**&#47;*.avsc"
+     * is used to select avsc files.
+     *
+     * @parameter
+     */
+    //@get:Input
+    //val includes: Set<String> = setOf("**/*.avsc")
+
+    @TaskAction
+    fun compileSchema() {
+        project.logger.info("Generating Java files from Avro schemas...")
+
+        if (!source.isEmpty) {
+            val sourceDirectoryFullPath = 
getSourceDirectoryFullPath(sourceDirectory)
+            val outputDirectoryFullPath = 
getBuildDirectoryFullPath(outputDirectory)
+            compileSchemas(source, sourceDirectoryFullPath, 
outputDirectoryFullPath)
+        } else {
+            logger.warn("No Avro files found in $sourceDirectory. Nothing to 
compile")
+        }
+
+        project.logger.info("Done generating Java files from Avro schemas...")
+    }
+
+
+    private fun getSourceDirectoryFullPath(directoryProperty: 
Property<String>): File =
+        project.layout.projectDirectory.dir(directoryProperty.get()).asFile
+
+    private fun getBuildDirectoryFullPath(directoryProperty: 
Property<String>): File =
+        project.layout.buildDirectory.dir(directoryProperty).get().asFile
+
+    private fun compileSchemas(fileTree: FileTree, sourceDirectory: File, 
outputDirectory: File) {
+        val sourceFileForModificationDetection: File? =
+            fileTree
+                .files
+                .filter { file: File -> file.lastModified() > 0 }
+                .maxBy { it.lastModified() }
+
+        try {
+            val parser = SchemaParser()
+            for (sourceFile in fileTree.files) {
+                parser.parse(sourceFile)
+            }
+            val schemas = parser.parsedNamedSchemas
+
+            doCompile(sourceFileForModificationDetection, 
SpecificCompiler(schemas), outputDirectory)
+        } catch (ex: IOException) {
+            // TODO: more concrete exceptions
+            throw RuntimeException("IO ex: Error compiling a file in " + 
sourceDirectory + " to " + outputDirectory, ex)
+        } catch (ex: SchemaParseException) {
+            throw RuntimeException(
+                "SchemaParse ex Error compiling a file in " + sourceDirectory 
+ " to " + outputDirectory,
+                ex
+            )
+        }
+    }
+
+    private fun doCompile(
+        sourceFileForModificationDetection: File?,
+        compiler: SpecificCompiler,
+        outputDirectory: File
+    ) {
+        setCompilerProperties(compiler)
+        // TODO:
+        //  * customLogicalTypeFactories
+
+        try {
+            for (customConversion in customConversions.get()) {
+                
compiler.addCustomConversion(Thread.currentThread().getContextClassLoader().loadClass(customConversion))
+            }
+        } catch (e: ClassNotFoundException) {
+            throw IOException(e)
+        }
+        compiler.compileToDestination(sourceFileForModificationDetection, 
outputDirectory)
+    }
+
+
+    private fun setCompilerProperties(compiler: SpecificCompiler) {
+        
compiler.setTemplateDir(project.layout.projectDirectory.dir(templateDirectory.get()).asFile.absolutePath
 + "/")

Review Comment:
   Does this need to use absolute paths ?
   Can it use classpath instead ?



##########
lang/java/gradle-plugin/pom.xml:
##########
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       https://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<project
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";
+  xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>avro-parent</artifactId>
+    <groupId>org.apache.avro</groupId>
+    <version>1.13.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <artifactId>avro-gradle-plugin</artifactId>
+  <packaging>pom</packaging>
+
+  <name>Apache Avro Gradle Plugin</name>
+  <description>Gradle plugin for Avro IDL and Specific API 
Compilers</description>
+
+  <properties>
+    <main.basedir>${project.parent.parent.basedir}</main.basedir>
+    <pluginTestingVersion>3.3.0</pluginTestingVersion>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>3.1.0</version>
+
+        <executions>
+          <execution>
+            <id>run-gradle-task</id>
+            <phase>package</phase>

Review Comment:
   Do we need similar executions for `compile`/`test`/`deploy` ?



##########
lang/java/gradle-plugin/build.gradle.kts:
##########
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+    kotlin("jvm") version "2.2.10"
+    `java-gradle-plugin`
+    `maven-publish`
+}
+
+group = "org.apache.avro"
+version = "1.13.0-SNAPSHOT"
+
+repositories {
+    mavenCentral()
+    mavenLocal()
+}
+
+dependencies {
+    //implementation("org.gradle:gradle-tooling-api:7.1.1")
+    implementation("org.apache.avro:avro-compiler:${version}")
+    testImplementation(kotlin("test"))
+}
+
+tasks.test {
+    useJUnitPlatform()
+}
+kotlin {
+    jvmToolchain(17)
+}
+
+java {
+    withSourcesJar()
+}
+
+
+gradlePlugin {
+    plugins {
+        create("gradlePlugin") {
+            id = "org.apache.avro.avro-gradle-plugin"

Review Comment:
   ```suggestion
               id = "org.apache.avro.gradle-plugin"
   ```
   No need of several `avro` occurrences



##########
lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SchemaCompileTaskTest.kt:
##########
@@ -0,0 +1,271 @@
+package org.apache.avro.gradle.plugin
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
+import kotlin.io.path.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@ExperimentalPathApi
+class SchemaCompileTaskTest {
+
+    @TempDir
+    lateinit var tempDir: Path
+
+    @Test
+    fun `plugin executes avroGenerateJavaClasses task successfully`() {
+        // given
+        val tempSettingsFile = tempDir.resolve("settings.gradle.kts")
+        val tempBuildFile = tempDir.resolve("build.gradle.kts")
+        val tempAvroSrcDir = 
tempDir.resolve("src/test/avro").createDirectories()
+
+        val testAvroFiles = Path.of("src/test/avro")
+        val testAvroOutPutDir = Path.of("generated-sources/avro")
+
+        val testOutPutDirectory = 
tempDir.resolve("build/$testAvroOutPutDir/test")
+
+        testAvroFiles.copyToRecursively(
+            tempAvroSrcDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        tempSettingsFile.writeText("")
+        tempBuildFile.writeText(
+            """            
+            plugins {
+                id("org.apache.avro.avro-gradle-plugin")

Review Comment:
   ```suggestion
                   id("org.apache.avro.gradle-plugin")
   ```



##########
lang/java/gradle-plugin/src/test/kotlin/org/apache/avro/gradle/plugin/SchemaCompileTaskTest.kt:
##########
@@ -0,0 +1,271 @@
+package org.apache.avro.gradle.plugin
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
+import kotlin.io.path.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@ExperimentalPathApi
+class SchemaCompileTaskTest {
+
+    @TempDir
+    lateinit var tempDir: Path
+
+    @Test
+    fun `plugin executes avroGenerateJavaClasses task successfully`() {
+        // given
+        val tempSettingsFile = tempDir.resolve("settings.gradle.kts")
+        val tempBuildFile = tempDir.resolve("build.gradle.kts")
+        val tempAvroSrcDir = 
tempDir.resolve("src/test/avro").createDirectories()
+
+        val testAvroFiles = Path.of("src/test/avro")
+        val testAvroOutPutDir = Path.of("generated-sources/avro")
+
+        val testOutPutDirectory = 
tempDir.resolve("build/$testAvroOutPutDir/test")
+
+        testAvroFiles.copyToRecursively(
+            tempAvroSrcDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        tempSettingsFile.writeText("")
+        tempBuildFile.writeText(
+            """            
+            plugins {
+                id("org.apache.avro.avro-gradle-plugin")
+            }
+            
+            avro {
+                schemaType = "schema"
+                sourceDirectory = "$testAvroFiles"
+                outputDirectory = "$testAvroOutPutDir"
+            }
+        """.trimIndent()
+        )
+
+        // when
+        val result = GradleRunner.create()
+            .withProjectDir(tempDir.toFile())
+            .withArguments("avroGenerateJavaClasses")
+            .withPluginClasspath()
+            .forwardOutput() // to see printLn in code
+            .build()
+
+        val expectedFiles = setOf(
+            "SchemaPrivacy.java",
+            "SchemaUser.java",
+            "PrivacyImport.java",
+            "SchemaCustom.java",
+            "PrivacyDirectImport.java"
+        )
+
+        // then
+        assertEquals(TaskOutcome.SUCCESS, 
result.task(":avroGenerateJavaClasses")?.outcome)
+        assertFilesExist(testOutPutDirectory, expectedFiles)
+
+        val schemaUserContent = 
testOutPutDirectory.resolve("SchemaUser.java").readText()
+        assertTrue(schemaUserContent.contains("java.time.Instant"))
+    }
+
+
+    @Test
+    fun `plugin executes avroGenerateTestJavaClasses task successfully - for 
files in test directory`() {
+        // given
+        val tempSettingsFile = tempDir.resolve("settings.gradle.kts")
+        val tempBuildFile = tempDir.resolve("build.gradle.kts")
+        val tempAvroSrcDir = 
tempDir.resolve("src/test/avro").createDirectories()
+
+        val testAvroFiles = Path.of("src/test/avro")
+        val testAvroOutPutDir = Path.of("generated-test-sources-avro")
+
+        val testOutPutDirectory = 
tempDir.resolve("build/$testAvroOutPutDir/test")
+
+        testAvroFiles.copyToRecursively(
+            tempAvroSrcDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        tempSettingsFile.writeText("")
+        tempBuildFile.writeText(
+            """            
+            plugins {
+                id("org.apache.avro.avro-gradle-plugin")
+            }
+            
+            avro {
+                schemaType = "schema"
+                testSourceDirectory = "$testAvroFiles"
+                testOutputDirectory = "$testAvroOutPutDir"
+            }
+        """.trimIndent()
+        )
+
+        // when
+        val result = GradleRunner.create()
+            .withProjectDir(tempDir.toFile())
+            .withArguments("avroGenerateTestJavaClasses")
+            .withPluginClasspath()
+            .forwardOutput() // to see printLn in code
+            .build()
+
+        val expectedFiles = setOf(
+            "SchemaPrivacy.java",
+            "SchemaUser.java",
+            "PrivacyImport.java",
+            "SchemaCustom.java",
+            "PrivacyDirectImport.java"
+        )
+
+        // then
+        assertEquals(TaskOutcome.SUCCESS, 
result.task(":avroGenerateTestJavaClasses")?.outcome)
+        assertFilesExist(testOutPutDirectory, expectedFiles)
+
+        val schemaUserContent = 
testOutPutDirectory.resolve("SchemaUser.java").readText()
+        assertTrue(schemaUserContent.contains("java.time.Instant"))
+    }
+
+    @Test
+    fun `plugin executes avroGenerateJavaClasses task successfully - with 
Velocity class names`() {
+        // given
+        val tempSettingsFile = tempDir.resolve("settings.gradle.kts")
+        val tempBuildFile = tempDir.resolve("build.gradle.kts")
+        val tempAvroSrcDir = 
tempDir.resolve("src/test/avro").createDirectories()
+        //val bla = tempDir.resolve("src/aap").createDirectories()
+        val tempVelocityToolClassesDir = 
tempDir.resolve("src/test/resources/templates").createDirectories()
+
+        val testAvroFilesDir = Path.of("src/test/avro")
+        val testAvroOutPutDir = Path.of("generated-sources-avro")
+        val testVelocityToolClassesDir = 
Path.of("src/test/resources/templates")
+
+        val testOutPutDirectory = 
tempDir.resolve("build/$testAvroOutPutDir/test")
+
+        testAvroFilesDir.copyToRecursively(
+            tempAvroSrcDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        testVelocityToolClassesDir.copyToRecursively(
+            tempVelocityToolClassesDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        tempSettingsFile.writeText("")
+        tempBuildFile.writeText(
+            """            
+            plugins {
+                id("org.apache.avro.avro-gradle-plugin")
+            }
+            
+            avro {
+                schemaType = "schema"
+                sourceDirectory = "$testAvroFilesDir"
+                outputDirectory = "$testAvroOutPutDir"
+                templateDirectory = "$testVelocityToolClassesDir"
+                velocityToolsClassesNames = listOf("java.lang.String")
+            }
+        """.trimIndent()
+        )
+
+        // when
+        val result = GradleRunner.create()
+            .withProjectDir(tempDir.toFile())
+            .withArguments("avroGenerateJavaClasses")
+            .withPluginClasspath()
+            .forwardOutput() // to see printLn in code
+            .build()
+
+        val expectedFiles = setOf(
+            "SchemaPrivacy.java",
+            "SchemaUser.java",
+            "PrivacyImport.java",
+            "SchemaCustom.java",
+            "PrivacyDirectImport.java"
+        )
+
+        // then
+        assertEquals(TaskOutcome.SUCCESS, 
result.task(":avroGenerateJavaClasses")?.outcome)
+        assertFilesExist(testOutPutDirectory, expectedFiles)
+
+        val schemaUserContent = 
testOutPutDirectory.resolve("SchemaUser.java").readText()
+        assertTrue(schemaUserContent.contains("It works!"))
+    }
+
+    @Test
+    fun `plugin executes avroGenerateJavaClasses task successfully - custom 
recordSpecificClass`() {
+        // given
+        val tempSettingsFile = tempDir.resolve("settings.gradle.kts")
+        val tempBuildFile = tempDir.resolve("build.gradle.kts")
+        val tempAvroSrcDir = 
tempDir.resolve("src/test/avro").createDirectories()
+
+        val testAvroFiles = Path.of("src/test/avro")
+        val testAvroOutPutDir = Path.of("generated-sources/avro")
+
+        val testOutPutDirectory = 
tempDir.resolve("build/$testAvroOutPutDir/test")
+
+        testAvroFiles.copyToRecursively(
+            tempAvroSrcDir,
+            overwrite = true,
+            followLinks = false
+        )
+
+        tempSettingsFile.writeText("")
+        tempBuildFile.writeText(
+            """            
+            plugins {
+                id("org.apache.avro.avro-gradle-plugin")

Review Comment:
   ```suggestion
                   id("org.apache.avro.gradle-plugin")
   ```



##########
lang/java/gradle-plugin/pom.xml:
##########
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       https://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<project
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";
+  xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>avro-parent</artifactId>
+    <groupId>org.apache.avro</groupId>
+    <version>1.13.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <artifactId>avro-gradle-plugin</artifactId>
+  <packaging>pom</packaging>
+
+  <name>Apache Avro Gradle Plugin</name>
+  <description>Gradle plugin for Avro IDL and Specific API 
Compilers</description>
+
+  <properties>
+    <main.basedir>${project.parent.parent.basedir}</main.basedir>
+    <pluginTestingVersion>3.3.0</pluginTestingVersion>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>3.1.0</version>
+
+        <executions>
+          <execution>
+            <id>run-gradle-task</id>
+            <phase>package</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <configuration>
+              <executable>./gradlew</executable>

Review Comment:
   nit: What about the Windows users ?
   I guess everyone uses WSL these days.



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt:
##########
@@ -0,0 +1,72 @@
+package org.apache.avro.gradle.plugin.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.SourceTask
+
+abstract class AbstractCompileTask : SourceTask() {
+
+    @get:Input
+    abstract val sourceDirectory: Property<String>
+
+    @get:Input

Review Comment:
   Shouldn't this use `OutputDirectory` + `DirectoryProperty` instead to 
support incremental builds ?



##########
lang/java/gradle-plugin/settings.gradle.kts:
##########
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+    id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
+}
+rootProject.name = "avro-gradle-plugin"

Review Comment:
   ```suggestion
   rootProject.name = "gradle-plugin"
   ```



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/AbstractCompileTask.kt:
##########
@@ -0,0 +1,72 @@
+package org.apache.avro.gradle.plugin.tasks
+
+import org.gradle.api.DefaultTask

Review Comment:
   ```suggestion
   ```
   Seems unused



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt:
##########
@@ -0,0 +1,136 @@
+package org.apache.avro.gradle.plugin.tasks
+
+import org.apache.avro.SchemaParseException
+import org.apache.avro.SchemaParser
+import org.apache.avro.compiler.specific.SpecificCompiler
+import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility
+import org.apache.avro.generic.GenericData
+import org.gradle.api.Project
+import org.gradle.api.file.FileTree
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.io.IOException
+import java.util.*
+
+abstract class CompileSchemaTask : AbstractCompileTask() {

Review Comment:
   Why is this class `abstract` ? It is being used directly in GradlePlugin.kt
   Also `instantiateAdditionalVelocityTools` is `protected` but this class is 
not `open`, so it cannot be overridden.



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt:
##########
@@ -0,0 +1,136 @@
+package org.apache.avro.gradle.plugin.tasks
+
+import org.apache.avro.SchemaParseException
+import org.apache.avro.SchemaParser
+import org.apache.avro.compiler.specific.SpecificCompiler
+import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility
+import org.apache.avro.generic.GenericData
+import org.gradle.api.Project
+import org.gradle.api.file.FileTree
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.io.IOException
+import java.util.*
+
+abstract class CompileSchemaTask : AbstractCompileTask() {
+
+    /**
+     * A set of Glob patterns used to select files from the source
+     * directory for processing. By default, the pattern "**&#47;*.avsc"
+     * is used to select avsc files.
+     *
+     * @parameter
+     */
+    //@get:Input
+    //val includes: Set<String> = setOf("**/*.avsc")

Review Comment:
   Please add a comment why this is commented out / what issue needs to be 
resolved ?



##########
lang/java/gradle-plugin/src/test/avro/AvdlClasspathImport.avdl:
##########
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+namespace test;
+
+import idl "avro/User.avdl";

Review Comment:
   ```suggestion
   import idl "./User.avdl";
   ```
   There is no `avro/` folder next to AvdlClasspathImport.avdl



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt:
##########
@@ -0,0 +1,136 @@
+package org.apache.avro.gradle.plugin.tasks
+
+import org.apache.avro.SchemaParseException
+import org.apache.avro.SchemaParser
+import org.apache.avro.compiler.specific.SpecificCompiler
+import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility
+import org.apache.avro.generic.GenericData
+import org.gradle.api.Project
+import org.gradle.api.file.FileTree
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.io.IOException
+import java.util.*
+
+abstract class CompileSchemaTask : AbstractCompileTask() {
+
+    /**
+     * A set of Glob patterns used to select files from the source
+     * directory for processing. By default, the pattern "**&#47;*.avsc"
+     * is used to select avsc files.
+     *
+     * @parameter
+     */
+    //@get:Input
+    //val includes: Set<String> = setOf("**/*.avsc")
+
+    @TaskAction
+    fun compileSchema() {
+        project.logger.info("Generating Java files from Avro schemas...")
+
+        if (!source.isEmpty) {
+            val sourceDirectoryFullPath = 
getSourceDirectoryFullPath(sourceDirectory)
+            val outputDirectoryFullPath = 
getBuildDirectoryFullPath(outputDirectory)
+            compileSchemas(source, sourceDirectoryFullPath, 
outputDirectoryFullPath)
+        } else {
+            logger.warn("No Avro files found in $sourceDirectory. Nothing to 
compile")
+        }
+
+        project.logger.info("Done generating Java files from Avro schemas...")
+    }
+
+
+    private fun getSourceDirectoryFullPath(directoryProperty: 
Property<String>): File =
+        project.layout.projectDirectory.dir(directoryProperty.get()).asFile
+
+    private fun getBuildDirectoryFullPath(directoryProperty: 
Property<String>): File =
+        project.layout.buildDirectory.dir(directoryProperty).get().asFile
+
+    private fun compileSchemas(fileTree: FileTree, sourceDirectory: File, 
outputDirectory: File) {
+        val sourceFileForModificationDetection: File? =
+            fileTree
+                .files
+                .filter { file: File -> file.lastModified() > 0 }
+                .maxBy { it.lastModified() }
+
+        try {
+            val parser = SchemaParser()
+            for (sourceFile in fileTree.files) {
+                parser.parse(sourceFile)
+            }
+            val schemas = parser.parsedNamedSchemas
+
+            doCompile(sourceFileForModificationDetection, 
SpecificCompiler(schemas), outputDirectory)
+        } catch (ex: IOException) {
+            // TODO: more concrete exceptions
+            throw RuntimeException("IO ex: Error compiling a file in " + 
sourceDirectory + " to " + outputDirectory, ex)
+        } catch (ex: SchemaParseException) {
+            throw RuntimeException(
+                "SchemaParse ex Error compiling a file in " + sourceDirectory 
+ " to " + outputDirectory,
+                ex
+            )
+        }
+    }
+
+    private fun doCompile(
+        sourceFileForModificationDetection: File?,
+        compiler: SpecificCompiler,
+        outputDirectory: File
+    ) {
+        setCompilerProperties(compiler)
+        // TODO:
+        //  * customLogicalTypeFactories
+
+        try {
+            for (customConversion in customConversions.get()) {
+                
compiler.addCustomConversion(Thread.currentThread().getContextClassLoader().loadClass(customConversion))
+            }
+        } catch (e: ClassNotFoundException) {
+            throw IOException(e)
+        }
+        compiler.compileToDestination(sourceFileForModificationDetection, 
outputDirectory)
+    }
+
+
+    private fun setCompilerProperties(compiler: SpecificCompiler) {
+        
compiler.setTemplateDir(project.layout.projectDirectory.dir(templateDirectory.get()).asFile.absolutePath
 + "/")

Review Comment:
   If absolute path is needed then maybe use `File.separator` instead of `/`



##########
lang/java/gradle-plugin/src/main/kotlin/org/apache/avro/gradle/plugin/tasks/CompileSchemaTask.kt:
##########
@@ -0,0 +1,136 @@
+package org.apache.avro.gradle.plugin.tasks
+
+import org.apache.avro.SchemaParseException
+import org.apache.avro.SchemaParser
+import org.apache.avro.compiler.specific.SpecificCompiler
+import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility
+import org.apache.avro.generic.GenericData
+import org.gradle.api.Project
+import org.gradle.api.file.FileTree
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.TaskAction
+import java.io.File
+import java.io.IOException
+import java.util.*
+
+abstract class CompileSchemaTask : AbstractCompileTask() {
+
+    /**
+     * A set of Glob patterns used to select files from the source
+     * directory for processing. By default, the pattern "**&#47;*.avsc"
+     * is used to select avsc files.
+     *
+     * @parameter
+     */
+    //@get:Input
+    //val includes: Set<String> = setOf("**/*.avsc")
+
+    @TaskAction
+    fun compileSchema() {
+        project.logger.info("Generating Java files from Avro schemas...")
+
+        if (!source.isEmpty) {
+            val sourceDirectoryFullPath = 
getSourceDirectoryFullPath(sourceDirectory)
+            val outputDirectoryFullPath = 
getBuildDirectoryFullPath(outputDirectory)
+            compileSchemas(source, sourceDirectoryFullPath, 
outputDirectoryFullPath)
+        } else {
+            logger.warn("No Avro files found in $sourceDirectory. Nothing to 
compile")
+        }
+
+        project.logger.info("Done generating Java files from Avro schemas...")
+    }
+
+
+    private fun getSourceDirectoryFullPath(directoryProperty: 
Property<String>): File =
+        project.layout.projectDirectory.dir(directoryProperty.get()).asFile
+
+    private fun getBuildDirectoryFullPath(directoryProperty: 
Property<String>): File =
+        project.layout.buildDirectory.dir(directoryProperty).get().asFile
+
+    private fun compileSchemas(fileTree: FileTree, sourceDirectory: File, 
outputDirectory: File) {
+        val sourceFileForModificationDetection: File? =
+            fileTree
+                .files
+                .filter { file: File -> file.lastModified() > 0 }
+                .maxBy { it.lastModified() }
+
+        try {
+            val parser = SchemaParser()
+            for (sourceFile in fileTree.files) {
+                parser.parse(sourceFile)
+            }
+            val schemas = parser.parsedNamedSchemas
+
+            doCompile(sourceFileForModificationDetection, 
SpecificCompiler(schemas), outputDirectory)
+        } catch (ex: IOException) {
+            // TODO: more concrete exceptions
+            throw RuntimeException("IO ex: Error compiling a file in " + 
sourceDirectory + " to " + outputDirectory, ex)
+        } catch (ex: SchemaParseException) {
+            throw RuntimeException(
+                "SchemaParse ex Error compiling a file in " + sourceDirectory 
+ " to " + outputDirectory,
+                ex
+            )
+        }
+    }
+
+    private fun doCompile(
+        sourceFileForModificationDetection: File?,
+        compiler: SpecificCompiler,
+        outputDirectory: File
+    ) {
+        setCompilerProperties(compiler)
+        // TODO:
+        //  * customLogicalTypeFactories
+
+        try {
+            for (customConversion in customConversions.get()) {
+                
compiler.addCustomConversion(Thread.currentThread().getContextClassLoader().loadClass(customConversion))
+            }
+        } catch (e: ClassNotFoundException) {
+            throw IOException(e)

Review Comment:
   Why wrapping ?
   IMO it would be better to handle both at line 65: ` } catch (ex: IOException 
| ClassNotFoundException) {`



##########
lang/java/gradle-plugin/src/test/avro/multipleSchemas/ApplicationEvent.avsc:
##########
@@ -0,0 +1,44 @@
+{
+  "namespace": "model",
+  "type": "record",
+  "doc": "",
+  "name": "ApplicationEvent",
+  "fields": [
+    {
+      "name": "applicationId",
+      "type": "string",
+      "doc": "Application ID"
+    },
+    {
+      "name": "status",
+      "type": "string",
+      "doc": "Application Status"
+    },
+    {
+      "name": "documents",
+      "type": ["null", {
+        "type": "array",
+        "items": "model.DocumentInfo"
+      }],
+      "doc": "",
+      "default": null
+    },
+    {
+      "name": "response",
+      "type": {
+        "namespace": "model",
+        "type": "record",
+        "doc": "",
+        "name": "MyResponse",

Review Comment:
   This clashes with a similar record in MyResponse.avsc. Let's use different 
names to avoid confusion.



##########
lang/java/gradle-plugin/src/test/avro/multipleSchemas/README.md:
##########
@@ -0,0 +1,8 @@
+## test for parsing multiple files.
+This folder aims to test `public List<Schema> Schema.parse(Iterable<File> 
sources) throws IOException` method.
+
+The objective is to check that a record schema define in a file can be use in 
another record schema as a field type.
+Here, ApplicationEvent.avsc file contains a field of type DocumentInfo, 
defined in file DocumentInfo.avsc.
+
+The is written at TestSchema.testParseMultipleFile.

Review Comment:
   ```suggestion
   The test is written at TestSchema.testParseMultipleFile.
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to