From bdb5e8db4548a9ab80c18d5d39e075cacf362ef7 Mon Sep 17 00:00:00 2001 From: Pavel Lazarev Date: Fri, 12 Apr 2024 15:00:15 +0500 Subject: [PATCH] #52: Fix annotation processor self registration. Also, move documentation to README.md --- ext-meta-generator/README.md | 145 ++++++++++++++---- ext-meta-generator/pom.xml | 8 + .../FieldGeneratorAnnotationProcessor.java | 94 +----------- .../javax.annotation.processing.Processor | 1 - pom.xml | 1 + 5 files changed, 131 insertions(+), 118 deletions(-) delete mode 100644 ext-meta-generator/src/main/resources/services/javax.annotation.processing.Processor diff --git a/ext-meta-generator/README.md b/ext-meta-generator/README.md index 99f51405..007046fd 100644 --- a/ext-meta-generator/README.md +++ b/ext-meta-generator/README.md @@ -1,11 +1,95 @@ -Add annotation process to the compilation stage. Example for maven: +## Goal +Generates a "-Fields" classes for all entities in a module. +This is useful for building queries like `where(AuditEvent.Id.TRAIL_ID).eq(trailId)` +Assume that we have an entity: +```java + @Table + class MyTable implements Entity{ + String strField; + ComplexClass complexField; + OtherClass fieldOfOtherClass; + Id id; + transient String aTransientField; + + static class ComplexClass { + String value; + OneMoreLevel deepField1; + OneMoreLevel deepField2; + + static class OneMoreLevel { + Integer field; + } + } + static class Id implements Entity.Id { + String anyName; + } + } +``` +The generated class will look like: +```java +// Annotation processors must create a new class, so the name is different +final class MyTableFields { + private MyTableFields(){} // It must not be instantiated or inherited + + // Fields considered as 'simple' if their type is not another class nested into current one + public static final String STR_FIELD = "strField"; + // `fieldOfOtherClass` is a simple field because `OtherClass` is not inside the given class + public static final String FIELD_OF_OTHER_CLASS = "fieldOfOtherClass"; + + + // ComplexField is not simple because it has a type ComplexClass and there is a nested class ComplexClass + // Pay attention that the generated nested class `ComplexField` uses the field's name! It's necessary because we might + // have several fields of the same type + + // Alongside with the nested class, a 'simple' version of the field will be generated with suffix _OBJ + // It allows to write queries like `where(MyTableFields.COMPLEX_FIELD_OBJ).eq(new ComplexClass(...))` + public static final String COMPLEX_FIELD_OBJ = "complexField"; + + // Aforementioned nested class + public static class ComplexField { + private IdField(){} + + // Mind that the value has "complexField." prefix + public static final String VALUE = "complexField.value"; + + // The annotation processor works recursively + // Also it's the example of several fields with the same type + public static final String DEEP_FIELD1_OBJ = "complexField.deepField1"; + public static final class DeepField1 { + private DeepField1(){} + + public static final String FIELD = "complexField.deepField1.field"; + } + + public static final String DEEP_FIELD2_OBJ = "complexField.deepField2"; + public static final class DeepField2 { + private DeepField2(){} + + public static final String FIELD = "complexField.deepField2.field"; + } + } + + // a field of the `Entity.Id` type with a single field inside it is a special case. It will be collapsed into + // a single field, just like the ORM expects. (even if there are several nested classes that can be reduced + // to a single field + public static final String ID = "id"; + + // the transient field `aTransientField` is ignored because the ORM ignores transient fields +} +``` +Additional info: +- Support Records +- Support Kotlin data classes + +## Installation +Add an annotation processor to the compilation stage. +- Example for Maven: ```xml org.apache.maven.plugins maven-compiler-plugin 3.11.0 - true tech.ydb.yoj @@ -13,40 +97,45 @@ Add annotation process to the compilation stage. Example for maven: ${yoj.version} - - tech.ydb.yoj.generator.FieldGeneratorAnnotationProcessor - ``` -The annotation will process classes annotated with @Table such that -```java -package some.pack; +- example for Gradle: +```kotlin + dependencies { + + annotationProcessor("tech.ydb.yoj:yoj-ext-meta-generator:${yoj.version}") + } +``` -@Table(name = "audit_event_record") -public class TypicalEntity { - @Column - private final Id id; +## Known issues - public static class Id { - private final String topicName; - private final int topicPartition; - private final long offset; - } - @Nullable - private final Instant lastUpdated; +- if entity doesn't have `@Table` it won't be processed even if it's implements the `Entity` interface +- We assume that the annotation `@Table` is used on a top-level class +- The AP will break in case of two nested classes which refer each other (i.e. a circular dependency) +- Will generate nested classes disregarding `@Column`'s flatten option +- No logs are written +- if a field has the type of a class which is not nested inside the annotated class, the field will be ignored +- There is a rare situation when generated code won't compile. The following source: +```java +public final class Name { + Class1 nameClash; + public static final class Class1 { + Class2 nameClash; + public static final class Class2 { + String value; + } + } } ``` -and will generate meta-classes like: +will produce ```java -package some.pack.generated; - -public class TypicalEntityFields { - public static final String LAST_UPDATED = "lastUpdated"; - public class Id { - public static final String TOPIC_NAME = "id.topicName"; - public static final String TOPIC_PARTITION = "id.topicPartition"; - public static final String OFFSET = "id.offset"; +public final class NameFields { + public static final class NameClash { + public static final class NameClash { + public static final String VALUE = "nameClash.nameClash.value"; + } } } ``` +which won't compile due to 2 NameClash classes. diff --git a/ext-meta-generator/pom.xml b/ext-meta-generator/pom.xml index be5cc072..6093cbd6 100644 --- a/ext-meta-generator/pom.xml +++ b/ext-meta-generator/pom.xml @@ -56,5 +56,13 @@ + + + com.google.auto.service + auto-service + ${auto-service.version} + provided + + \ No newline at end of file diff --git a/ext-meta-generator/src/main/java/tech/ydb/yoj/generator/FieldGeneratorAnnotationProcessor.java b/ext-meta-generator/src/main/java/tech/ydb/yoj/generator/FieldGeneratorAnnotationProcessor.java index a1118bf5..5e887a44 100644 --- a/ext-meta-generator/src/main/java/tech/ydb/yoj/generator/FieldGeneratorAnnotationProcessor.java +++ b/ext-meta-generator/src/main/java/tech/ydb/yoj/generator/FieldGeneratorAnnotationProcessor.java @@ -1,11 +1,13 @@ package tech.ydb.yoj.generator; +import com.google.auto.service.AutoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tech.ydb.yoj.ExperimentalApi; import tech.ydb.yoj.databind.schema.Table; import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; @@ -20,95 +22,8 @@ import java.util.Set; /** - * Generates a "-Fields" classes for all entities in a module. - *

This is useful for building queries like 'where(AuditEvent.Id.TRAIL_ID).eq(trailId)'

- *

Assume that we have an entity:

- *
{@code
- *     @Table(...)
- *     class MyTable {
- *         String strField;
- *         Object anyTypeField;
- *         Id idField;
- *         OtherClass fieldOfOtherClass;
- *
- *         static class Id {
- *             String value;
- *             OneMoreLevel deepField1;
- *             OneMoreLevel deepField2;
- *
- *             static class OneMoreLevel {
- *                 Integer field;
- *             }
- *         }
- *     }
- * }
- * A generated class will be
- *  
{@code
- *      class MyTableFields { //  Annotation processors must create a new class, so the name is different
- *          // fields considered as 'simple' if there is no nested class of the field's type
- *          public static final String STR_FIELD = "strField";
- *          public static final String ANY_TYPE_FIELD = "anyTypeField";
- *          // `fieldOfOtherClass` is a simple field because `OtherClass` is not inside the given class
- *          public static final String FIELD_OF_OTHER_CLASS = "fieldOfOtherClass";
- *
- *          // idField is not simple because it has a type Id and there is a nested class Id
- *          // Pay attention that the generated nested class uses the field's name! It's necessary because we might
- *          // have several fields of the same type
- *          public static class IdField {
- *              public static final String VALUE = "idField.value"; // Mind that the value has "idField." prefix
- *
- *              // The Annotation Processor works recursively
- *              // Also it's the example of several fields with the same type
- *              static class DeepField1 {
- *                  public static final String FIELD = "idField.deepField1.field";
- *              }
- *              static class DeepField2 {
- *                  // Pay attention that ".deepField2." is used here
- *                  public static final String FIELD = "idField.deepField2.field";
- *              }
- *          }
- *      }
- *  }
- *  
    - * Additional info: - *
  • Support Records
  • - *
  • Support Kotlin data classes
  • - *
- *
    - * Known issues (should be fixed in future): - *
  • if entity doesn't have @Table it won't be processed even if it's implements Entity interface
  • - *
  • We assume that annotation @Table is used on top-level class
  • - *
  • The AP will break in case of two nested classes which refer each other (i.e. circular dependency)
  • - *
  • Will generate nested classes even disregarding @Column's flatten option
  • - *
  • No logs are written
  • - *
  • if a field has type of a class which is not nested inside the annotated class, the field will be ignored
  • - *
  • There is a rare situation when generated code won't compile. The following source class - *
    {@code
    - *          class Name{
    - *              Class1 nameClash;
    - *              public static class Class1 {
    - *                  Class2 nameClash;
    - *                  class Class2{
    - *                      String nameClash;
    - *                  }
    - *              }
    - *          }
    - *     }
    - *     will produce
    - *     
    {@code
    - *         public class NameFields {
    - *             public class NameClash {
    - *                 public class NameClash {
    - *                     public static final String NAME_CLASH = "nameClash.nameClash.nameClash";
    - *                 }
    - *             }
    - *         }
    - *     }
    - *     which won't compile due to 2 NameClash classes.
    - *     
  • - *
- * - * @author pavel-lazarev + * See README.md in the root of the module + * @author lazarev-pv */ @ExperimentalApi(issue = "https://github.com/ydb-platform/yoj-project/pull/57") @@ -116,6 +31,7 @@ "tech.ydb.yoj.databind.schema.Table", }) @SupportedSourceVersion(SourceVersion.RELEASE_17) +@AutoService(Processor.class) public class FieldGeneratorAnnotationProcessor extends AbstractProcessor { private static final Logger log = LoggerFactory.getLogger(FieldGeneratorAnnotationProcessor.class); diff --git a/ext-meta-generator/src/main/resources/services/javax.annotation.processing.Processor b/ext-meta-generator/src/main/resources/services/javax.annotation.processing.Processor deleted file mode 100644 index f3a3382d..00000000 --- a/ext-meta-generator/src/main/resources/services/javax.annotation.processing.Processor +++ /dev/null @@ -1 +0,0 @@ -tech.ydb.yoj.generator.FieldGeneratorAnnotationProcessor diff --git a/pom.xml b/pom.xml index 5c3ddb2b..06d532b1 100644 --- a/pom.xml +++ b/pom.xml @@ -137,6 +137,7 @@ 0.6.0 2.14.0 2.10.1 + 1.1.1 1.9.22