Skip to content

Commit 37cbb41

Browse files
committed
Update GraalVM processor docs for Java 17 and record support
Document the Java 17 requirement for graalvm-annotations-processor 1.6.0, show updated Gradle usage, and describe automatic declared-constructor reflection metadata for Java records.
1 parent 8e86a7f commit 37cbb41

4 files changed

Lines changed: 138 additions & 20 deletions

File tree

README.md

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,105 @@
1-
# Gradle Graalvm Annotations Processor
1+
# GraalVM Annotations Processor
22

33
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
44

5-
Gradle Annotations Processor for [FormKiQ's Graalvm Annotations](https://github.com/formkiq/graalvm-annotations). The annotations are used to generate a [Graalvm Reflection File](https://github.com/oracle/graal/blob/master/substratevm/Reflection.md).
5+
Annotation processor for [FormKiQ's GraalVM Annotations](https://github.com/formkiq/graalvm-annotations). The processor scans supported annotations at compile time and generates a GraalVM native-image reflection configuration file.
66

7-
The generated file can be found in ```$buildDir/classes/java/main/META-INF/graal/reflect.json```.
7+
The generated file is written to:
88

9-
## Gradle Installation
9+
```text
10+
$buildDir/classes/java/main/META-INF/native-image/<package>/reflect-config.json
11+
```
12+
13+
## Requirements
14+
15+
Version 1.6.0 and later requires Java 17 or newer to run the annotation processor.
16+
17+
Use version 1.5.x if your build must run the annotation processor on Java 11.
18+
19+
## Installation
1020

11-
Add the following to your build.gradle to enable processing [FormKiQ's Graalvm Annotations](https://github.com/formkiq/graalvm-annotations).
21+
Add the annotations library and annotation processor to your Gradle build:
1222

23+
```gradle
24+
dependencies {
25+
implementation 'com.formkiq:graalvm-annotations:1.2.0'
26+
annotationProcessor 'com.formkiq:graalvm-annotations-processor:1.6.0'
27+
}
1328
```
29+
30+
For Gradle Kotlin DSL:
31+
32+
```kotlin
1433
dependencies {
15-
annotationProcessor 'com.formkiq:graalvm-annotations-processor:1.1.1'
16-
<!-- Replace 1.1.1 with the version you want to use -->
34+
implementation("com.formkiq:graalvm-annotations:1.2.0")
35+
annotationProcessor("com.formkiq:graalvm-annotations-processor:1.6.0")
1736
}
1837
```
1938

39+
## Usage
40+
41+
Annotate a class or record with `@Reflectable`:
42+
43+
```java
44+
import com.formkiq.graalvm.annotations.Reflectable;
45+
46+
@Reflectable
47+
public record Event(String name) {
48+
}
49+
```
50+
51+
The processor generates a `reflect-config.json` entry similar to:
52+
53+
```json
54+
[
55+
{
56+
"name": "sample.Event",
57+
"allDeclaredConstructors": true,
58+
"allPublicConstructors": true,
59+
"allDeclaredMethods": true,
60+
"allDeclaredFields": true,
61+
"allPublicFields": true,
62+
"allPublicMethods": true
63+
}
64+
]
65+
```
66+
67+
You can override the generated reflection flags:
68+
69+
```java
70+
@Reflectable(
71+
allDeclaredConstructors = true,
72+
allPublicConstructors = false,
73+
allDeclaredMethods = true,
74+
allDeclaredFields = true)
75+
public final class Event {
76+
}
77+
```
78+
79+
## Java Records
80+
81+
Version 1.6.0 and later automatically sets `allDeclaredConstructors` to `true` for Java records annotated with `@Reflectable` or referenced through `@ReflectableClass`.
82+
83+
This is required by libraries such as Gson 2.13, which use the canonical record constructor through `Class.getDeclaredConstructor(...)` when deserializing records in GraalVM native images.
84+
85+
## Build and Test
86+
87+
Run the test suite with Java 17 or newer:
88+
89+
```bash
90+
./gradlew test
91+
```
92+
93+
Publish locally for testing with downstream projects:
94+
95+
```bash
96+
./gradlew publishToMavenLocal
97+
```
98+
2099
## Samples
21-
See [Samples](https://github.com/formkiq/graalvm-annotations-processor/tree/master/samples) for examples on how to use the Graalvm Annotations and Processor.
100+
101+
See [Samples](https://github.com/formkiq/graalvm-annotations-processor/tree/master/samples) for examples on how to use the GraalVM annotations and processor.
102+
103+
## License
104+
105+
This project is licensed under the Apache License 2.0.

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ plugins {
1010
}
1111

1212
group 'com.formkiq'
13-
version '1.5.2'
13+
version '1.6.0'
1414

1515
dependencies {
1616
annotationProcessor group: 'com.google.auto.service', name: 'auto-service', version: '1.1.1'

src/main/java/com/formkiq/graalvm/processors/GraalvmReflectAnnontationProcessor.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"com.formkiq.graalvm.annotations.ReflectableClasses",
6767
"com.formkiq.graalvm.annotations.ReflectableClass",
6868
"com.formkiq.graalvm.annotations.ReflectableClass.ReflectableClasses"})
69-
@SupportedSourceVersion(SourceVersion.RELEASE_11)
69+
@SupportedSourceVersion(SourceVersion.RELEASE_17)
7070
public class GraalvmReflectAnnontationProcessor extends AbstractProcessor {
7171

7272
/** The inner class separator character: '$'. */
@@ -280,6 +280,17 @@ private Reflect processClass(final Reflect reflect, final Reflectable reflectabl
280280
return reflect;
281281
}
282282

283+
/**
284+
* Process Record Class.
285+
*
286+
* @param reflect {@link Reflect}
287+
* @param reflectable {@link Reflectable}
288+
* @return {@link Reflect}
289+
*/
290+
private Reflect processRecordClass(final Reflect reflect, final Reflectable reflectable) {
291+
return processClass(reflect, reflectable).allDeclaredConstructors(Boolean.TRUE);
292+
}
293+
283294
/**
284295
* Process Class.
285296
*
@@ -300,6 +311,17 @@ private Reflect processClass(final Reflect reflect, final ReflectableClass refle
300311
return reflect;
301312
}
302313

314+
/**
315+
* Process Record Class.
316+
*
317+
* @param reflect {@link Reflect}
318+
* @param reflectable {@link ReflectableClass}
319+
* @return {@link Reflect}
320+
*/
321+
private Reflect processRecordClass(final Reflect reflect, final ReflectableClass reflectable) {
322+
return processClass(reflect, reflectable).allDeclaredConstructors(Boolean.TRUE);
323+
}
324+
303325
/**
304326
* Process Imported Class using {@link Class}.
305327
*
@@ -313,6 +335,9 @@ private void processImportedClass(final String clazz) {
313335

314336
if (reflectable != null) {
315337
processClass(reflect, reflectable);
338+
if (forName.isRecord()) {
339+
reflect.allDeclaredConstructors(Boolean.TRUE);
340+
}
316341
}
317342

318343
for (Field field : forName.getDeclaredFields()) {
@@ -424,7 +449,8 @@ private void processingReflectable(final RoundEnvironment roundEnv) {
424449
reflect.addMethod(methodName, parameterTypes);
425450
break;
426451
default:
427-
reflect = processClass(reflect, reflectable);
452+
reflect = isRecord(element) ? processRecordClass(reflect, reflectable)
453+
: processClass(reflect, reflectable);
428454
break;
429455
}
430456
}
@@ -465,12 +491,14 @@ private String getPackage(final Element element) {
465491
private void processReflectableClass(final ReflectableClass reflectable) {
466492

467493
String className = null;
494+
boolean isRecord = false;
468495
try {
469496
reflectable.className();
470497
} catch (MirroredTypeException e) {
471498

472499
TypeMirror typeMirror = e.getTypeMirror();
473500
TypeElement asTypeElement = asTypeElement(typeMirror);
501+
isRecord = isRecord(asTypeElement);
474502

475503
className = asTypeElement.getQualifiedName().toString();
476504

@@ -482,7 +510,8 @@ private void processReflectableClass(final ReflectableClass reflectable) {
482510
}
483511

484512
Reflect reflect = getReflect(className);
485-
reflect = processClass(reflect, reflectable);
513+
reflect =
514+
isRecord ? processRecordClass(reflect, reflectable) : processClass(reflect, reflectable);
486515

487516
for (ReflectableField field : reflectable.fields()) {
488517
LOGGER.log(LOGLEVEL, "adding Field " + field.name() + " to " + className);
@@ -542,6 +571,11 @@ private String removePartsContainingDotFollowedByCapital(final String input) {
542571
.collect(Collectors.joining("."));
543572
}
544573

574+
private boolean isRecord(final Element element) {
575+
return "RECORD".equals(element.getKind().name()) || element.getEnclosedElements().stream()
576+
.anyMatch(e -> "RECORD_COMPONENT".equals(e.getKind().name()));
577+
}
578+
545579
/** Write Output File. */
546580
private void writeOutput() {
547581

src/test/java/com/formkiq/graalvm/processors/GraalvmReflectAnnontationProcessorTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,7 @@ public record TestRecord(String name) {}
830830
assertEquals(Boolean.TRUE, map.get(0).get("allPublicConstructors"));
831831
assertEquals(Boolean.TRUE, map.get(0).get("allPublicMethods"));
832832
assertEquals(Boolean.TRUE, map.get(0).get("allPublicFields"));
833-
assertEquals(Boolean.FALSE, map.get(0).get("allDeclaredConstructors"));
833+
assertEquals(Boolean.TRUE, map.get(0).get("allDeclaredConstructors"));
834834
assertEquals(Boolean.TRUE, map.get(0).get("allDeclaredMethods"));
835835
assertEquals(Boolean.TRUE, map.get(0).get("allDeclaredFields"));
836836
}
@@ -864,17 +864,17 @@ public record NestedTestRecord(String name) {
864864
assertEquals(Boolean.TRUE, map.get(0).get("allPublicConstructors"));
865865
assertEquals(Boolean.TRUE, map.get(0).get("allPublicMethods"));
866866
assertEquals(Boolean.TRUE, map.get(0).get("allPublicFields"));
867-
assertEquals(Boolean.FALSE, map.get(0).get("allDeclaredConstructors"));
867+
assertEquals(Boolean.TRUE, map.get(0).get("allDeclaredConstructors"));
868868
assertEquals(Boolean.TRUE, map.get(0).get("allDeclaredMethods"));
869869
assertEquals(Boolean.TRUE, map.get(0).get("allDeclaredFields"));
870870

871871
assertEquals("com.example.TestRecord$NestedTestRecord", map.get(1).get("name"));
872-
assertEquals(Boolean.TRUE, map.get(0).get("allPublicConstructors"));
873-
assertEquals(Boolean.TRUE, map.get(0).get("allPublicMethods"));
874-
assertEquals(Boolean.TRUE, map.get(0).get("allPublicFields"));
875-
assertEquals(Boolean.FALSE, map.get(0).get("allDeclaredConstructors"));
876-
assertEquals(Boolean.TRUE, map.get(0).get("allDeclaredMethods"));
877-
assertEquals(Boolean.TRUE, map.get(0).get("allDeclaredFields"));
872+
assertEquals(Boolean.TRUE, map.get(1).get("allPublicConstructors"));
873+
assertEquals(Boolean.TRUE, map.get(1).get("allPublicMethods"));
874+
assertEquals(Boolean.TRUE, map.get(1).get("allPublicFields"));
875+
assertEquals(Boolean.TRUE, map.get(1).get("allDeclaredConstructors"));
876+
assertEquals(Boolean.TRUE, map.get(1).get("allDeclaredMethods"));
877+
assertEquals(Boolean.TRUE, map.get(1).get("allDeclaredFields"));
878878
}
879879

880880
/**

0 commit comments

Comments
 (0)