Skip to content

Commit

Permalink
Merge pull request #29731 from geoand/bytecode-rec-configurable-annot…
Browse files Browse the repository at this point in the history
…ations

Introduce a way to use custom annotations in bytecode recording
  • Loading branch information
geoand authored Dec 12, 2022
2 parents d4e5ec7 + 6b01e5a commit b3b357d
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 9 deletions.
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

0 comments on commit b3b357d

Please sign in to comment.