diff --git a/extensions/qute/deployment/pom.xml b/extensions/qute/deployment/pom.xml
index 615e03457e555..9cf6840eca4ed 100644
--- a/extensions/qute/deployment/pom.xml
+++ b/extensions/qute/deployment/pom.xml
@@ -25,6 +25,11 @@
io.quarkus
quarkus-arc-deployment
+
+
+ io.quarkus
+ quarkus-panache-common-deployment
+
io.quarkus.qute
qute-generator
diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
index fff3d51b8d1f9..3173fa08980fc 100644
--- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
+++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
@@ -80,6 +80,7 @@
import io.quarkus.dev.console.DevConsoleManager;
import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem;
import io.quarkus.gizmo.ClassOutput;
+import io.quarkus.panache.common.deployment.PanacheEntityClassesBuildItem;
import io.quarkus.qute.Engine;
import io.quarkus.qute.EngineBuilder;
import io.quarkus.qute.Expression;
@@ -841,7 +842,8 @@ void generateValueResolvers(QuteConfig config, BuildProducer implicitClasses,
TemplatesAnalysisBuildItem templatesAnalysis,
BuildProducer generatedResolvers,
- BuildProducer reflectiveClass) {
+ BuildProducer reflectiveClass,
+ List panacheEntityClasses) {
IndexView index = beanArchiveIndex.getIndex();
ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, new Predicate() {
@@ -864,7 +866,22 @@ public boolean test(String name) {
}
});
- ValueResolverGenerator.Builder builder = ValueResolverGenerator.builder().setIndex(index).setClassOutput(classOutput);
+ ValueResolverGenerator.Builder builder = ValueResolverGenerator.builder()
+ .setIndex(index).setClassOutput(classOutput);
+
+ if (!panacheEntityClasses.isEmpty()) {
+ Set entityClasses = new HashSet<>();
+ for (PanacheEntityClassesBuildItem panaecheEntityClasses : panacheEntityClasses) {
+ entityClasses.addAll(panaecheEntityClasses.getEntityClasses());
+ }
+ builder.setForceGettersPredicate(new Predicate() {
+ @Override
+ public boolean test(ClassInfo clazz) {
+ return entityClasses.contains(clazz.name().toString());
+ }
+ });
+ }
+
Set controlled = new HashSet<>();
Map uncontrolled = new HashMap<>();
for (AnnotationInstance templateData : index.getAnnotations(ValueResolverGenerator.TEMPLATE_DATA)) {
diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java
index 16b8f1d28416a..4359e014736f0 100644
--- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java
+++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java
@@ -8,6 +8,7 @@
import io.quarkus.gizmo.CatchBlockCreator;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
+import io.quarkus.gizmo.DescriptorUtils;
import io.quarkus.gizmo.FieldDescriptor;
import io.quarkus.gizmo.FunctionCreator;
import io.quarkus.gizmo.MethodCreator;
@@ -88,21 +89,16 @@ public static Builder builder() {
private final ClassOutput classOutput;
private final Map nameToClass;
private final Map nameToTemplateData;
+ private final Predicate forceGettersPredicate;
- /**
- *
- * @param index
- * @param classOutput
- * @param nameToClass
- * @param nameToTemplateData
- */
ValueResolverGenerator(IndexView index, ClassOutput classOutput, Map nameToClass,
- Map nameToTemplateData) {
+ Map nameToTemplateData, Predicate forceGettersPredicate) {
this.generatedTypes = new HashSet<>();
this.classOutput = classOutput;
this.index = index;
this.nameToClass = new HashMap<>(nameToClass);
this.nameToTemplateData = new HashMap<>(nameToTemplateData);
+ this.forceGettersPredicate = forceGettersPredicate;
}
public Set getGeneratedTypes() {
@@ -226,33 +222,9 @@ private void implementResolve(ClassCreator valueResolver, String clazzName, Clas
ResultHandle name = resolve.invokeInterfaceMethod(Descriptors.GET_NAME, evalContext);
ResultHandle params = resolve.invokeInterfaceMethod(Descriptors.GET_PARAMS, evalContext);
ResultHandle paramsCount = resolve.invokeInterfaceMethod(Descriptors.COLLECTION_SIZE, params);
+ boolean forceGetters = forceGettersPredicate != null ? forceGettersPredicate.test(clazz) : false;
- // Fields
- List fields = clazz.fields().stream().filter(filter::test).collect(Collectors.toList());
- if (!fields.isEmpty()) {
- BytecodeCreator zeroParamsBranch = resolve.ifNonZero(paramsCount).falseBranch();
- for (FieldInfo field : fields) {
- LOGGER.debugf("Field added: %s", field);
- // Match field name
- BytecodeCreator fieldMatch = zeroParamsBranch
- .ifNonZero(
- zeroParamsBranch.invokeVirtualMethod(Descriptors.EQUALS,
- resolve.load(field.name()), name))
- .trueBranch();
- ResultHandle value;
- if (Modifier.isStatic(field.flags())) {
- value = fieldMatch
- .readStaticField(FieldDescriptor.of(clazzName, field.name(), field.type().name().toString()));
- } else {
- value = fieldMatch
- .readInstanceField(FieldDescriptor.of(clazzName, field.name(), field.type().name().toString()),
- base);
- }
- fieldMatch.returnValue(fieldMatch.invokeStaticMethod(Descriptors.COMPLETED_FUTURE, value));
- }
- }
-
- // Sort methods (getters must come before is/has properties, etc.)
+ // First collect and sort methods (getters must come before is/has properties, etc.)
List methods = clazz.methods().stream().filter(filter::test).map(MethodKey::new).sorted()
.collect(Collectors.toList());
if (!ignoreSuperclasses) {
@@ -270,6 +242,58 @@ private void implementResolve(ClassCreator valueResolver, String clazzName, Clas
}
}
+ List fields = clazz.fields().stream().filter(filter::test).collect(Collectors.toList());
+ if (!fields.isEmpty()) {
+ BytecodeCreator zeroParamsBranch = resolve.ifNonZero(paramsCount).falseBranch();
+ for (FieldInfo field : fields) {
+ String getterName;
+ if ((field.type().kind() == org.jboss.jandex.Type.Kind.PRIMITIVE
+ && field.type().asPrimitiveType().equals(PrimitiveType.BOOLEAN))
+ || (field.type().kind() == org.jboss.jandex.Type.Kind.CLASS
+ && field.type().name().equals(BOOLEAN))) {
+ getterName = IS_PREFIX + capitalize(field.name());
+ } else {
+ getterName = GET_PREFIX + capitalize(field.name());
+ }
+ if (forceGetters && methods.stream().noneMatch(m -> m.name.equals(getterName))) {
+ LOGGER.debugf("Forced getter added: %s", field);
+ BytecodeCreator getterMatch = zeroParamsBranch.createScope();
+ // Match the getter name
+ BytecodeCreator notMatched = getterMatch.ifNonZero(getterMatch.invokeVirtualMethod(Descriptors.EQUALS,
+ getterMatch.load(getterName),
+ name))
+ .falseBranch();
+ // Match the property name
+ notMatched.ifNonZero(notMatched.invokeVirtualMethod(Descriptors.EQUALS,
+ notMatched.load(field.name()),
+ name)).falseBranch().breakScope(getterMatch);
+ ResultHandle value = getterMatch.invokeVirtualMethod(
+ MethodDescriptor.ofMethod(clazz.name().toString(), getterName,
+ DescriptorUtils.typeToString(field.type())),
+ base);
+ getterMatch.returnValue(getterMatch.invokeStaticMethod(Descriptors.COMPLETED_FUTURE, value));
+ } else {
+ LOGGER.debugf("Field added: %s", field);
+ // Match field name
+ BytecodeCreator fieldMatch = zeroParamsBranch
+ .ifNonZero(
+ zeroParamsBranch.invokeVirtualMethod(Descriptors.EQUALS,
+ resolve.load(field.name()), name))
+ .trueBranch();
+ ResultHandle value;
+ if (Modifier.isStatic(field.flags())) {
+ value = fieldMatch
+ .readStaticField(FieldDescriptor.of(clazzName, field.name(), field.type().name().toString()));
+ } else {
+ value = fieldMatch
+ .readInstanceField(FieldDescriptor.of(clazzName, field.name(), field.type().name().toString()),
+ base);
+ }
+ fieldMatch.returnValue(fieldMatch.invokeStaticMethod(Descriptors.COMPLETED_FUTURE, value));
+ }
+ }
+ }
+
if (!methods.isEmpty()) {
// name, number of params -> list of methods
Map> matches = new HashMap<>();
@@ -671,7 +695,6 @@ private BytecodeCreator createMatchScope(BytecodeCreator bytecodeCreator, String
// Match number of params
if (methodParams >= 0) {
matchScope.ifIntegerEqual(matchScope.load(methodParams), paramsCount).falseBranch().breakScope(matchScope);
-
}
return matchScope;
}
@@ -700,6 +723,7 @@ public static class Builder {
private ClassOutput classOutput;
private final Map nameToClass = new HashMap<>();
private final Map nameToTemplateData = new HashMap<>();
+ private Predicate forceGettersPredicate;
public Builder setIndex(IndexView index) {
this.index = index;
@@ -711,6 +735,17 @@ public Builder setClassOutput(ClassOutput classOutput) {
return this;
}
+ /**
+ * If a class for which a value resolver is generated matches the predicate then all fields are accessed via getters.
+ *
+ * @param forceGettersPredicate
+ * @return self
+ */
+ public Builder setForceGettersPredicate(Predicate forceGettersPredicate) {
+ this.forceGettersPredicate = forceGettersPredicate;
+ return this;
+ }
+
public Builder addClass(ClassInfo clazz) {
return addClass(clazz, null);
}
@@ -724,7 +759,7 @@ public Builder addClass(ClassInfo clazz, AnnotationInstance templateData) {
}
public ValueResolverGenerator build() {
- return new ValueResolverGenerator(index, classOutput, nameToClass, nameToTemplateData);
+ return new ValueResolverGenerator(index, classOutput, nameToClass, nameToTemplateData, forceGettersPredicate);
}
}
@@ -833,6 +868,18 @@ static String decapitalize(String name) {
return new String(chars);
}
+ static String capitalize(String name) {
+ if (name == null || name.length() == 0) {
+ return name;
+ }
+ if (Character.isUpperCase(name.charAt(0))) {
+ return name;
+ }
+ char chars[] = name.toCharArray();
+ chars[0] = Character.toUpperCase(chars[0]);
+ return new String(chars);
+ }
+
/**
*
* @param clazz