Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a way to use custom annotations in bytecode recording #29731

Merged
merged 2 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public class BytecodeRecorderImpl implements RecorderContext {

private final List<ObjectLoader> loaders = new ArrayList<>();
private final Map<Class<?>, ConstantHolder<?>> constants = new HashMap<>();
private final Set<Class> classesToUseRecorableConstructor = new HashSet<>();
private final Set<Class> classesToUseRecordableConstructor = new HashSet<>();
private final boolean useIdentityComparison;

/**
Expand Down Expand Up @@ -394,7 +394,7 @@ public void run() {
}

public void markClassAsConstructorRecordable(Class<?> clazz) {
classesToUseRecorableConstructor.add(clazz);
classesToUseRecordableConstructor.add(clazz);
}

private ProxyInstance getProxyInstance(Class<?> returnType) throws InstantiationException, IllegalAccessException {
Expand Down Expand Up @@ -1173,7 +1173,7 @@ public void prepare(MethodContext context) {
nonDefaultConstructorHandles[i] = loadObjectInstance(obj, existing,
parameterTypes[count++], relaxedValidation);
}
} else if (classesToUseRecorableConstructor.contains(param.getClass())) {
} else if (classesToUseRecordableConstructor.contains(param.getClass())) {
Constructor<?> current = null;
int count = 0;
for (var c : param.getClass().getConstructors()) {
Expand Down Expand Up @@ -1219,7 +1219,7 @@ public void prepare(MethodContext context) {
for (Property i : desc) {
if (!i.getDeclaringClass().getPackageName().startsWith("java.")) {
// check if the getter is ignored
if ((i.getReadMethod() != null) && (i.getReadMethod().getAnnotation(IgnoreProperty.class) != null)) {
if ((i.getReadMethod() != null) && RecordingAnnotationsUtil.isIgnored(i.getReadMethod())) {
continue;
}
// check if the matching field is ignored
Expand Down Expand Up @@ -1556,7 +1556,10 @@ ResultHandle createValue(MethodContext context, MethodCreator method, ResultHand
* Returns {@code true} iff the field is annotated {@link IgnoreProperty} or the field is marked as {@code transient}
*/
private static boolean ignoreField(Field field) {
return (field.getAnnotation(IgnoreProperty.class) != null) || Modifier.isTransient(field.getModifiers());
if (Modifier.isTransient(field.getModifiers())) {
return true;
}
return RecordingAnnotationsUtil.isIgnored(field);
}

private DeferredParameter findLoaded(final Object param) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.deployment.recording;

import java.lang.annotation.Annotation;

public interface RecordingAnnotationsProvider {

Class<? extends Annotation> ignoredProperty();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.quarkus.deployment.recording;

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

import io.quarkus.runtime.annotations.IgnoreProperty;

final class RecordingAnnotationsUtil {

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

static {
Set<Class<? extends Annotation>> ignoredPropertyAnnotations = new HashSet<>();
ignoredPropertyAnnotations.add(IgnoreProperty.class);
for (RecordingAnnotationsProvider provider : ServiceLoader.load(RecordingAnnotationsProvider.class)) {
Class<? extends Annotation> ignoredProperty = provider.ignoredProperty();
if (ignoredProperty != null) {
ignoredPropertyAnnotations.add(ignoredProperty);
}
}
IGNORED_PROPERTY_ANNOTATIONS = List.copyOf(ignoredPropertyAnnotations);
}

private RecordingAnnotationsUtil() {
}

static boolean isIgnored(AccessibleObject object) {
for (int i = 0; i < IGNORED_PROPERTY_ANNOTATIONS.size(); i++) {
Class<? extends Annotation> annotation = IGNORED_PROPERTY_ANNOTATIONS.get(i);
if (object.isAnnotationPresent(annotation)) {
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,9 @@ public void testIgnoredProperties() throws Exception {
ignoredProperties.setNotIgnored("Shows up");
ignoredProperties.setIgnoredField("Does not show up");
ignoredProperties.setAnotherIgnoredField("Does not show up either");
ignoredProperties.setCustomIgnoredField("Does not show up either");
recorder.ignoredProperties(ignoredProperties);
}, new IgnoredProperties("Shows up", null, null));
}, new IgnoredProperties("Shows up", null, null, null));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ public class IgnoredProperties {

private transient String anotherIgnoredField;

@TestRecordingAnnotationsProvider.TestIgnoreProperty
private String customIgnoredField;

public IgnoredProperties() {
}

public IgnoredProperties(String notIgnored, String ignoredField, String anotherIgnoredField) {
public IgnoredProperties(String notIgnored, String ignoredField, String anotherIgnoredField, String customIgnoredField) {
this.notIgnored = notIgnored;
this.ignoredField = ignoredField;
this.anotherIgnoredField = anotherIgnoredField;
this.customIgnoredField = customIgnoredField;
}

public String getNotIgnored() {
Expand All @@ -45,6 +49,14 @@ public void setAnotherIgnoredField(String anotherIgnoredField) {
this.anotherIgnoredField = anotherIgnoredField;
}

public String getCustomIgnoredField() {
return customIgnoredField;
}

public void setCustomIgnoredField(String customIgnoredField) {
this.customIgnoredField = customIgnoredField;
}

@IgnoreProperty
public String getSomethingElse() {
throw new IllegalStateException("This should not have been called");
Expand All @@ -59,19 +71,22 @@ public boolean equals(Object o) {
IgnoredProperties that = (IgnoredProperties) o;
return Objects.equals(notIgnored, that.notIgnored) &&
Objects.equals(ignoredField, that.ignoredField) &&
Objects.equals(anotherIgnoredField, that.anotherIgnoredField);
Objects.equals(anotherIgnoredField, that.anotherIgnoredField) &&
Objects.equals(customIgnoredField, that.customIgnoredField);
}

@Override
public int hashCode() {
return Objects.hash(notIgnored, ignoredField, anotherIgnoredField);
return Objects.hash(notIgnored, ignoredField, anotherIgnoredField, customIgnoredField);
}

@Override
public String toString() {
return "IgnoredProperties{" +
"notIgnored='" + notIgnored + '\'' +
", ignoredField='" + ignoredField + '\'' +
", anotherIgnoredField='" + anotherIgnoredField + '\'' +
", customIgnoredField='" + customIgnoredField + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.quarkus.deployment.recording;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class TestRecordingAnnotationsProvider implements RecordingAnnotationsProvider {

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD })
public @interface TestIgnoreProperty {
}

@Override
public Class<? extends Annotation> ignoredProperty() {
return TestIgnoreProperty.class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.quarkus.deployment.recording.TestRecordingAnnotationsProvider
7 changes: 7 additions & 0 deletions docs/src/main/asciidoc/writing-extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1522,6 +1522,13 @@ The following objects can be passed to recorders:
- Any arbitrary object via the `io.quarkus.deployment.recording.RecorderContext#registerSubstitution(Class, Class, Class)` mechanism
- Arrays, Lists and Maps of the above

[NOTE]
====
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.
====

==== Injecting Configuration into Recorders

Configuration objects with phase `RUNTIME` or `BUILD_AND_RUNTIME_FIXED` can be injected into recorders via constructor
Expand Down