Skip to content

Commit

Permalink
Support custom @RecordableConstructor annotations in RecordingAnnotat…
Browse files Browse the repository at this point in the history
…ionsProvider
  • Loading branch information
geoand committed Dec 14, 2022
1 parent ab21b79 commit 87d974e
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1198,7 +1198,7 @@ public void prepare(MethodContext context) {
}
} else {
for (Constructor<?> ctor : param.getClass().getConstructors()) {
if (ctor.isAnnotationPresent(RecordableConstructor.class)) {
if (RecordingAnnotationsUtil.isRecordableConstructor(ctor)) {
nonDefaultConstructorHolder = new NonDefaultConstructorHolder(ctor, null);
nonDefaultConstructorHandles = new DeferredParameter[ctor.getParameterCount()];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@

public interface RecordingAnnotationsProvider {

Class<? extends Annotation> ignoredProperty();
default Class<? extends Annotation> ignoredProperty() {
return null;
}

default Class<? extends Annotation> recordableConstructor() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,39 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;

import io.quarkus.runtime.annotations.IgnoreProperty;
import io.quarkus.runtime.annotations.RecordableConstructor;

final class RecordingAnnotationsUtil {

static final List<Class<? extends Annotation>> IGNORED_PROPERTY_ANNOTATIONS;
static final List<Class<? extends Annotation>> RECORDABLE_CONSTRUCTOR_ANNOTATIONS;

static {
Set<Class<? extends Annotation>> ignoredPropertyAnnotations = new HashSet<>();
ignoredPropertyAnnotations.add(IgnoreProperty.class);
Set<Class<? extends Annotation>> recordableConstructorAnnotations = new HashSet<>();
recordableConstructorAnnotations.add(RecordableConstructor.class);

for (RecordingAnnotationsProvider provider : ServiceLoader.load(RecordingAnnotationsProvider.class)) {
Class<? extends Annotation> ignoredProperty = provider.ignoredProperty();
if (ignoredProperty != null) {
ignoredPropertyAnnotations.add(ignoredProperty);
}
Class<? extends Annotation> recordableConstructor = provider.recordableConstructor();
if (recordableConstructor != null) {
recordableConstructorAnnotations.add(recordableConstructor);
}
}

IGNORED_PROPERTY_ANNOTATIONS = List.copyOf(ignoredPropertyAnnotations);
RECORDABLE_CONSTRUCTOR_ANNOTATIONS = List.copyOf(recordableConstructorAnnotations);
}

private RecordingAnnotationsUtil() {
Expand All @@ -37,4 +49,14 @@ static boolean isIgnored(AccessibleObject object) {
}
return false;
}

static boolean isRecordableConstructor(Constructor<?> ctor) {
for (int i = 0; i < RECORDABLE_CONSTRUCTOR_ANNOTATIONS.size(); i++) {
Class<? extends Annotation> annotation = RECORDABLE_CONSTRUCTOR_ANNOTATIONS.get(i);
if (ctor.isAnnotationPresent(annotation)) {
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ public void testRecordableConstructor() throws Exception {
TestRecorder recorder = generator.getRecordingProxy(TestRecorder.class);
recorder.bean(bean);
}, new TestConstructorBean("John", "Citizen").setAge(30));

runTest(generator -> {
OtherTestConstructorBean bean = new OtherTestConstructorBean("Jane", "Citizen");
bean.setAge(30);
TestRecorder recorder = generator.getRecordingProxy(TestRecorder.class);
recorder.bean(bean);
}, new OtherTestConstructorBean("Jane", "Citizen").setAge(30));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.quarkus.deployment.recording;

import java.util.Objects;

public class OtherTestConstructorBean {

String first;
final String last;
int age;

@TestRecordingAnnotationsProvider.TestRecordableConstructor
public OtherTestConstructorBean(String first, String last) {
this.first = first;
this.last = last;
}

public void setFirst(String first) {
//should not be called, as it was initialized in the constructor
this.first = "Mrs " + first;
}

public String getFirst() {
return first;
}

public String getLast() {
return last;
}

public int getAge() {
return age;
}

public OtherTestConstructorBean setAge(int age) {
this.age = age;
return this;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
OtherTestConstructorBean that = (OtherTestConstructorBean) o;
return age == that.age &&
Objects.equals(first, that.first) &&
Objects.equals(last, that.last);
}

@Override
public int hashCode() {
return Objects.hash(first, last, age);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ public void bean(TestConstructorBean bean) {
RESULT.add(bean);
}

public void bean(OtherTestConstructorBean bean) {
RESULT.add(bean);
}

public void result(RuntimeValue<TestJavaBean> bean) {
RESULT.add(bean.getValue());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,18 @@ public class TestRecordingAnnotationsProvider implements RecordingAnnotationsPro
public @interface TestIgnoreProperty {
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)
public @interface TestRecordableConstructor {
}

@Override
public Class<? extends Annotation> ignoredProperty() {
return TestIgnoreProperty.class;
}

@Override
public Class<? extends Annotation> recordableConstructor() {
return TestRecordableConstructor.class;
}
}
2 changes: 2 additions & 0 deletions docs/src/main/asciidoc/writing-extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,8 @@ The following objects can be passed to recorders:
In cases where some fields of an object to be recorded should be ignored (i.e. the value that being at build time should not be reflected at runtime), the `@IgnoreProperty` can be placed on the field.
If the class cannot depend on Quarkus, then Quarkus can use any custom annotation, as long as the extension implements the `io.quarkus.deployment.recording.RecordingAnnotationsProvider` SPI.
This same SPI can also be used to provide a custom annotation that will substitute for `@RecordableConstructor`.
====

==== Injecting Configuration into Recorders
Expand Down

0 comments on commit 87d974e

Please sign in to comment.