Skip to content

Commit

Permalink
#52: Fix annotation processor self registration.
Browse files Browse the repository at this point in the history
Also, move documentation to README.md
  • Loading branch information
Pavel Lazarev authored and nvamelichev committed Apr 12, 2024
1 parent eb9103b commit bdb5e8d
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 118 deletions.
145 changes: 117 additions & 28 deletions ext-meta-generator/README.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,141 @@
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<MyTable>{
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<MyTable> {
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
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<debug>true</debug>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>tech.ydb.yoj</groupId>
<artifactId>yoj-ext-meta-generator</artifactId>
<version>${yoj.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
<annotationProcessors>
<annotationProcessor>tech.ydb.yoj.generator.FieldGeneratorAnnotationProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
```
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.
8 changes: 8 additions & 0 deletions ext-meta-generator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,13 @@
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>${auto-service.version}</version>
<scope>provided</scope>
</dependency>

</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -20,102 +22,16 @@
import java.util.Set;

/**
* Generates a "-Fields" classes for all entities in a module.
* <p>This is useful for building queries like '<code>where(AuditEvent.Id.TRAIL_ID).eq(trailId)</code>'</p>
* <p>Assume that we have an entity:</p>
* <pre>{@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
* <pre>{@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";
* }
* }
* }
* }
* <ul>
* <b>Additional info:</b>
* <li>Support Records</li>
* <li>Support Kotlin data classes</li>
* </ul>
* <ul>
* <b>Known issues (should be fixed in future):</b>
* <li>if entity doesn't have @Table it won't be processed even if it's implements Entity interface</li>
* <li>We assume that annotation @Table is used on top-level class</li>
* <li>The AP will break in case of two nested classes which refer each other (i.e. circular dependency) </li>
* <li>Will generate nested classes even disregarding @Column's flatten option </li>
* <li>No logs are written</li>
* <li>if a field has type of a class which is not nested inside the annotated class, the field will be ignored</li>
* <li>There is a rare situation when generated code won't compile. The following source class
* <pre>{@code
* class Name{
* Class1 nameClash;
* public static class Class1 {
* Class2 nameClash;
* class Class2{
* String nameClash;
* }
* }
* }
* }
* will produce
* <pre>{@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.
* </li>
* </ul>
*
* @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")
@SupportedAnnotationTypes({
"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);
Expand Down

This file was deleted.

1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
<prometheus.version>0.6.0</prometheus.version>
<error-prone-annotations.version>2.14.0</error-prone-annotations.version>
<gson-version>2.10.1</gson-version>
<auto-service.version>1.1.1</auto-service.version>

<!-- optional dependencies (Kotlin support) -->
<kotlin.version>1.9.22</kotlin.version>
Expand Down

0 comments on commit bdb5e8d

Please sign in to comment.