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

Qute - fix handling of Panache entity getters for boolean properties #15340

Merged
merged 1 commit into from
Feb 26, 2021
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 @@ -47,6 +47,7 @@
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.ParameterizedType;
import org.jboss.jandex.PrimitiveType;
import org.jboss.jandex.PrimitiveType.Primitive;
import org.jboss.jandex.Type;
import org.jboss.jandex.TypeVariable;
import org.jboss.logging.Logger;
Expand Down Expand Up @@ -138,6 +139,20 @@ public class QuteProcessor {

private static final Logger LOGGER = Logger.getLogger(QuteProcessor.class);

private static final Function<FieldInfo, String> GETTER_FUN = new Function<FieldInfo, String>() {
@Override
public String apply(FieldInfo field) {
String prefix;
if (field.type().kind() == org.jboss.jandex.Type.Kind.PRIMITIVE
&& field.type().asPrimitiveType().primitive() == Primitive.BOOLEAN) {
prefix = ValueResolverGenerator.IS_PREFIX;
} else {
prefix = ValueResolverGenerator.GET_PREFIX;
}
return prefix + ValueResolverGenerator.capitalize(field.name());
}
};

@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(Feature.QUTE);
Expand Down Expand Up @@ -880,10 +895,13 @@ public boolean test(String name) {
for (PanacheEntityClassesBuildItem panaecheEntityClasses : panacheEntityClasses) {
entityClasses.addAll(panaecheEntityClasses.getEntityClasses());
}
builder.setForceGettersPredicate(new Predicate<ClassInfo>() {
builder.setForceGettersFunction(new Function<ClassInfo, Function<FieldInfo, String>>() {
@Override
public boolean test(ClassInfo clazz) {
return entityClasses.contains(clazz.name().toString());
public Function<FieldInfo, String> apply(ClassInfo clazz) {
if (entityClasses.contains(clazz.name().toString())) {
return GETTER_FUN;
}
return null;
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -69,9 +70,9 @@ public static Builder builder() {

private static final Logger LOGGER = Logger.getLogger(ValueResolverGenerator.class);

private static final String GET_PREFIX = "get";
private static final String IS_PREFIX = "is";
private static final String HAS_PREFIX = "has";
public static final String GET_PREFIX = "get";
public static final String IS_PREFIX = "is";
public static final String HAS_PREFIX = "has";

public static final String TARGET = "target";
public static final String IGNORE_SUPERCLASSES = "ignoreSuperclasses";
Expand All @@ -85,16 +86,18 @@ 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;

private Function<ClassInfo, Function<FieldInfo, String>> forceGettersFunction;

ValueResolverGenerator(IndexView index, ClassOutput classOutput, Map<DotName, ClassInfo> nameToClass,
Map<DotName, AnnotationInstance> nameToTemplateData, Predicate<ClassInfo> forceGettersPredicate) {
Map<DotName, AnnotationInstance> nameToTemplateData,
Function<ClassInfo, Function<FieldInfo, String>> forceGettersFunction) {
this.generatedTypes = new HashSet<>();
this.classOutput = classOutput;
this.index = index;
this.nameToClass = new HashMap<>(nameToClass);
this.nameToTemplateData = new HashMap<>(nameToTemplateData);
this.forceGettersPredicate = forceGettersPredicate;
this.forceGettersFunction = forceGettersFunction;
}

public Set<String> getGeneratedTypes() {
Expand Down Expand Up @@ -218,7 +221,7 @@ 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;
Function<FieldInfo, String> fieldToGetterFun = forceGettersFunction != null ? forceGettersFunction.apply(clazz) : null;

// 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()
Expand All @@ -242,16 +245,8 @@ private void implementResolve(ClassCreator valueResolver, String clazzName, Clas
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(DotNames.BOOLEAN))) {
getterName = IS_PREFIX + capitalize(field.name());
} else {
getterName = GET_PREFIX + capitalize(field.name());
}
if (forceGetters && methods.stream().noneMatch(m -> m.name.equals(getterName))) {
String getterName = fieldToGetterFun != null ? fieldToGetterFun.apply(field) : null;
if (getterName != null && noneMethodMatches(methods, getterName)) {
LOGGER.debugf("Forced getter added: %s", field);
BytecodeCreator getterMatch = zeroParamsBranch.createScope();
// Match the getter name
Expand Down Expand Up @@ -719,7 +714,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;
private Function<ClassInfo, Function<FieldInfo, String>> forceGettersFunction;

public Builder setIndex(IndexView index) {
this.index = index;
Expand All @@ -732,13 +727,18 @@ public Builder setClassOutput(ClassOutput classOutput) {
}

/**
* If a class for which a value resolver is generated matches the predicate then all fields are accessed via getters.
* The function returns:
* <ul>
* <li>a function that returns the getter name for a specific field or {@code null} if getter should not be forced for
* the given field</li>
* <li>{@code null} if getters are not forced for the given class</li>
* </ul>
*
* @param forceGettersPredicate
* @param forceGettersFunction
* @return self
*/
public Builder setForceGettersPredicate(Predicate<ClassInfo> forceGettersPredicate) {
this.forceGettersPredicate = forceGettersPredicate;
public Builder setForceGettersFunction(Function<ClassInfo, Function<FieldInfo, String>> forceGettersFunction) {
this.forceGettersFunction = forceGettersFunction;
return this;
}

Expand All @@ -755,7 +755,7 @@ public Builder addClass(ClassInfo clazz, AnnotationInstance templateData) {
}

public ValueResolverGenerator build() {
return new ValueResolverGenerator(index, classOutput, nameToClass, nameToTemplateData, forceGettersPredicate);
return new ValueResolverGenerator(index, classOutput, nameToClass, nameToTemplateData, forceGettersFunction);
}

}
Expand Down Expand Up @@ -864,7 +864,7 @@ static String decapitalize(String name) {
return new String(chars);
}

static String capitalize(String name) {
public static String capitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
Expand Down Expand Up @@ -933,6 +933,15 @@ static String generatedNameFromTarget(String targetPackage, String baseName, Str
}
}

private static boolean noneMethodMatches(List<MethodKey> methods, String name) {
for (MethodKey method : methods) {
if (method.name.equals(name)) {
return false;
}
}
return true;
}

public static boolean hasCompletionStageInTypeClosure(ClassInfo classInfo,
IndexView index) {
return hasClassInTypeClosure(classInfo, DotNames.COMPLETION_STAGE, index);
Expand Down
51 changes: 50 additions & 1 deletion integration-tests/qute/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<version>999-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-integration-test-qute</artifactId>
<name>Quarkus - Integration Tests - Qute</name>
<description>Qute integration test module</description>
Expand All @@ -22,8 +22,21 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
Expand Down Expand Up @@ -62,10 +75,46 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
Expand Down
16 changes: 16 additions & 0 deletions integration-tests/qute/src/main/java/io/quarkus/it/qute/Beer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.it.qute;

import javax.persistence.Entity;

import io.quarkus.hibernate.orm.panache.PanacheEntity;

@Entity
public class Beer extends PanacheEntity {

public String name;

public Boolean completed;

public boolean done;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.it.qute;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;

@Path("/beer")
public class BeerResource {

@CheckedTemplate
static class Templates {

static native TemplateInstance beer(Beer beer);

}

@GET
@Produces(MediaType.TEXT_HTML)
public TemplateInstance get() {
Beer beer = Beer.find("name", "Pilsner").firstResult();
return Templates.beer(beer);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.it.qute;

import javax.enterprise.event.Observes;
import javax.transaction.Transactional;

import io.quarkus.runtime.StartupEvent;

public class Brewery {

@Transactional
void onStart(@Observes StartupEvent event) {
Beer myBeer = new Beer();
myBeer.name = "Pilsner";
myBeer.completed = true;
myBeer.done = true;
myBeer.persist();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test
quarkus.datasource.jdbc.max-size=8

quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect
quarkus.hibernate-orm.database.generation=drop-and-create
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Beer!</title>
</head>
<body>
<p>Beer {beer.name}, completed: {beer.completed}, done: {beer.done}</p>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
public class QuteTestCase {

@Test
public void testTemplate() throws InterruptedException {
public void testTemplates() throws InterruptedException {
RestAssured.when().get("/hello?name=Ciri").then().body(containsString("Hello Ciri!"));
RestAssured.when().get("/hello").then().body(containsString("Hello world!"));
RestAssured.given().accept(ContentType.HTML).when().get("/hello-route")
Expand All @@ -24,6 +24,7 @@ public void testTemplate() throws InterruptedException {
RestAssured.given().accept(ContentType.HTML).when().get("/hello-route?name=Ciri").then()
.contentType(is(ContentType.HTML.toString()))
.body(containsString("Hello Ciri!"));
RestAssured.when().get("/beer").then().body(containsString("Beer Pilsner, completed: true, done: true"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.it.qute;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.h2.H2DatabaseTestResource;

@QuarkusTestResource(H2DatabaseTestResource.class)
public class TestResources {
}