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