Skip to content

Commit

Permalink
Merge pull request #14937 from mkouba/issue-14918
Browse files Browse the repository at this point in the history
Qute - force getters access for panache entities
  • Loading branch information
mkouba authored Feb 10, 2021
2 parents c794b5a + 9c4d1b2 commit 5343f76
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 38 deletions.
5 changes: 5 additions & 0 deletions extensions/qute/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<!-- Panache SPI-like module to collect entity classes -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-panache-common-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus.qute</groupId>
<artifactId>qute-generator</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -841,7 +842,8 @@ void generateValueResolvers(QuteConfig config, BuildProducer<GeneratedClassBuild
List<ImplicitValueResolverBuildItem> implicitClasses,
TemplatesAnalysisBuildItem templatesAnalysis,
BuildProducer<GeneratedValueResolverBuildItem> generatedResolvers,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
List<PanacheEntityClassesBuildItem> panacheEntityClasses) {

IndexView index = beanArchiveIndex.getIndex();
ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, new Predicate<String>() {
Expand All @@ -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<String> entityClasses = new HashSet<>();
for (PanacheEntityClassesBuildItem panaecheEntityClasses : panacheEntityClasses) {
entityClasses.addAll(panaecheEntityClasses.getEntityClasses());
}
builder.setForceGettersPredicate(new Predicate<ClassInfo>() {
@Override
public boolean test(ClassInfo clazz) {
return entityClasses.contains(clazz.name().toString());
}
});
}

Set<DotName> controlled = new HashSet<>();
Map<DotName, AnnotationInstance> uncontrolled = new HashMap<>();
for (AnnotationInstance templateData : index.getAnnotations(ValueResolverGenerator.TEMPLATE_DATA)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -88,21 +89,16 @@ public static Builder builder() {
private final ClassOutput classOutput;
private final Map<DotName, ClassInfo> nameToClass;
private final Map<DotName, AnnotationInstance> nameToTemplateData;
private final Predicate<ClassInfo> forceGettersPredicate;

/**
*
* @param index
* @param classOutput
* @param nameToClass
* @param nameToTemplateData
*/
ValueResolverGenerator(IndexView index, ClassOutput classOutput, Map<DotName, ClassInfo> nameToClass,
Map<DotName, AnnotationInstance> nameToTemplateData) {
Map<DotName, AnnotationInstance> nameToTemplateData, Predicate<ClassInfo> 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<String> getGeneratedTypes() {
Expand Down Expand Up @@ -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<FieldInfo> 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<MethodKey> methods = clazz.methods().stream().filter(filter::test).map(MethodKey::new).sorted()
.collect(Collectors.toList());
if (!ignoreSuperclasses) {
Expand All @@ -270,6 +242,58 @@ private void implementResolve(ClassCreator valueResolver, String clazzName, Clas
}
}

List<FieldInfo> 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<Match, List<MethodInfo>> matches = new HashMap<>();
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -700,6 +723,7 @@ public static class Builder {
private ClassOutput classOutput;
private final Map<DotName, ClassInfo> nameToClass = new HashMap<>();
private final Map<DotName, AnnotationInstance> nameToTemplateData = new HashMap<>();
private Predicate<ClassInfo> forceGettersPredicate;

public Builder setIndex(IndexView index) {
this.index = index;
Expand All @@ -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<ClassInfo> forceGettersPredicate) {
this.forceGettersPredicate = forceGettersPredicate;
return this;
}

public Builder addClass(ClassInfo clazz) {
return addClass(clazz, null);
}
Expand All @@ -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);
}

}
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 5343f76

Please sign in to comment.