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 a regression introduced in #32653 (3.0.1) #32999

Merged
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 @@ -1742,11 +1742,14 @@ public String apply(String name) {
}
});

// NOTE: We can't use this optimization for classes generated by ValueResolverGenerator because we cannot easily
// map a target class to a specific set of generated classes
ExistingValueResolvers existingValueResolvers = liveReloadBuildItem.getContextObject(ExistingValueResolvers.class);
if (existingValueResolvers == null) {
existingValueResolvers = new ExistingValueResolvers();
liveReloadBuildItem.setContextObject(ExistingValueResolvers.class, existingValueResolvers);
}
Set<String> generatedValueResolvers = new HashSet<>();

ValueResolverGenerator.Builder builder = ValueResolverGenerator.builder()
.setIndex(index).setClassOutput(classOutput);
Expand All @@ -1770,15 +1773,11 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
Set<DotName> controlled = new HashSet<>();
Map<DotName, AnnotationInstance> uncontrolled = new HashMap<>();
for (TemplateDataBuildItem data : templateData) {
processTemplateData(data, controlled, uncontrolled, builder, existingValueResolvers, applicationClassPredicate);
processTemplateData(data, controlled, uncontrolled, builder);
}

for (ImplicitValueResolverBuildItem implicit : implicitClasses) {
DotName implicitClassName = implicit.getClazz().name();
if (existingValueResolvers.contains(implicitClassName)) {
// A non-application value resolver already generated
continue;
}
if (controlled.contains(implicitClassName)) {
LOGGER.debugf("Implicit value resolver for %s ignored: class is annotated with @TemplateData",
implicitClassName);
Expand All @@ -1790,24 +1789,23 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
continue;
}
builder.addClass(implicit.getClazz(), implicit.getTemplateData());
existingValueResolvers.add(implicitClassName, applicationClassPredicate);
}

ValueResolverGenerator generator = builder.build();
generator.generate();

Set<String> generatedValueResolvers = new HashSet<>();
generatedValueResolvers.addAll(generator.getGeneratedTypes());

ExtensionMethodGenerator extensionMethodGenerator = new ExtensionMethodGenerator(index, classOutput);
Map<DotName, Map<String, List<TemplateExtensionMethodBuildItem>>> classToNamespaceExtensions = new HashMap<>();
Map<String, DotName> namespaceToClass = new HashMap<>();

for (TemplateExtensionMethodBuildItem templateExtension : templateExtensionMethods) {
if (existingValueResolvers.contains(templateExtension.getMethod())) {
String generatedValueResolverClass = existingValueResolvers.getGeneratedClass(templateExtension.getMethod());
if (generatedValueResolverClass != null) {
// A ValueResolver of a non-application class was already generated
generatedValueResolvers.add(generatedValueResolverClass);
continue;
}
existingValueResolvers.add(templateExtension.getMethod(), applicationClassPredicate);

if (templateExtension.hasNamespace()) {
// Group extension methods declared on the same class by namespace
Expand Down Expand Up @@ -1835,8 +1833,10 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
namespaceMethods.add(templateExtension);
} else {
// Generate ValueResolver per extension method
extensionMethodGenerator.generate(templateExtension.getMethod(), templateExtension.getMatchName(),
String generatedClass = extensionMethodGenerator.generate(templateExtension.getMethod(),
templateExtension.getMatchName(),
templateExtension.getMatchNames(), templateExtension.getMatchRegex(), templateExtension.getPriority());
existingValueResolvers.add(templateExtension.getMethod(), generatedClass, applicationClassPredicate);
}
}

Expand All @@ -1853,6 +1853,10 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
try (NamespaceResolverCreator namespaceResolverCreator = extensionMethodGenerator
.createNamespaceResolver(priorityEntry.getValue().get(0).getMethod().declaringClass(),
nsEntry.getKey(), priorityEntry.getKey())) {
for (TemplateExtensionMethodBuildItem extensionMethod : priorityEntry.getValue()) {
existingValueResolvers.add(extensionMethod.getMethod(), namespaceResolverCreator.getClassName(),
applicationClassPredicate);
}
try (ResolveCreator resolveCreator = namespaceResolverCreator.implementResolve()) {
for (TemplateExtensionMethodBuildItem method : priorityEntry.getValue()) {
resolveCreator.addMethod(method.getMethod(), method.getMatchName(), method.getMatchNames(),
Expand Down Expand Up @@ -1901,28 +1905,25 @@ public Function<FieldInfo, String> apply(ClassInfo clazz) {
*/
static class ExistingValueResolvers {

final Set<String> identifiers = new HashSet<>();

boolean contains(DotName className) {
return identifiers.contains(className.toString());
}
final Map<String, String> identifiersToGeneratedClass = new HashMap<>();

boolean contains(MethodInfo extensionMethod) {
return identifiers.contains(extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString());
return identifiersToGeneratedClass
.containsKey(toKey(extensionMethod));
}

boolean add(DotName className, Predicate<DotName> applicationClassPredicate) {
if (!applicationClassPredicate.test(className)) {
return identifiers.add(className.toString());
}
return false;
String getGeneratedClass(MethodInfo extensionMethod) {
return identifiersToGeneratedClass.get(toKey(extensionMethod));
}

boolean add(MethodInfo extensionMethod, Predicate<DotName> applicationClassPredicate) {
void add(MethodInfo extensionMethod, String className, Predicate<DotName> applicationClassPredicate) {
if (!applicationClassPredicate.test(extensionMethod.declaringClass().name())) {
return identifiers.add(extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString());
identifiersToGeneratedClass.put(toKey(extensionMethod), className);
}
return false;
}

private String toKey(MethodInfo extensionMethod) {
return extensionMethod.declaringClass().toString() + "#" + extensionMethod.toString();
}
}

Expand Down Expand Up @@ -2942,22 +2943,15 @@ private static boolean methodMatches(MethodInfo method, VirtualMethodPart virtua
}

private void processTemplateData(TemplateDataBuildItem templateData,
Set<DotName> controlled, Map<DotName, AnnotationInstance> uncontrolled, ValueResolverGenerator.Builder builder,
ExistingValueResolvers existingValueResolvers,
CompletedApplicationClassPredicateBuildItem applicationClassPredicate) {
Set<DotName> controlled, Map<DotName, AnnotationInstance> uncontrolled, ValueResolverGenerator.Builder builder) {
DotName targetClassName = templateData.getTargetClass().name();
if (existingValueResolvers.contains(targetClassName)) {
return;
}
if (templateData.isTargetAnnotatedType()) {
controlled.add(targetClassName);
builder.addClass(templateData.getTargetClass(), templateData.getAnnotationInstance());
existingValueResolvers.add(targetClassName, applicationClassPredicate);
} else {
// At this point we can be sure that multiple unequal @TemplateData do not exist for a specific target
uncontrolled.computeIfAbsent(targetClassName, name -> {
builder.addClass(templateData.getTargetClass(), templateData.getAnnotationInstance());
existingValueResolvers.add(targetClassName, applicationClassPredicate);
return templateData.getAnnotationInstance();
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.quarkus.qute.deployment.devmode;

import static io.restassured.RestAssured.given;

import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusDevModeTest;

/**
* Test that built-in extension value resolvers are correctly registered after live reload.
*/
public class ExistingValueResolversDevModeTest {

@RegisterExtension
static final QuarkusDevModeTest config = new QuarkusDevModeTest()
.withApplicationRoot(root -> root
.addClass(TestRoute.class)
.addAsResource(new StringAsset(
"{#let a = 3}{#let b = a.minus(2)}b={b}{/}{/}"),
"templates/let.html"));

@Test
public void testExistingValueResolvers() {
given().get("test")
.then()
.statusCode(200)
.body(Matchers.equalTo("b=1"));

config.modifyResourceFile("templates/let.html", t -> t.concat("::MODIFIED"));

given().get("test")
.then()
.statusCode(200)
.body(Matchers.equalTo("b=1::MODIFIED"));
}

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

import jakarta.inject.Inject;

import io.quarkus.qute.Template;
import io.quarkus.vertx.web.Route;
import io.vertx.ext.web.RoutingContext;

public class TestRoute {

@Inject
Template let;

@Route(path = "test")
public void test(RoutingContext ctx) {
ctx.end(let.render());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,16 @@ public static void validate(MethodInfo method, String namespace) {
}
}

public void generate(MethodInfo method, String matchName, List<String> matchNames, String matchRegex, Integer priority) {
/**
*
* @param method
* @param matchName
* @param matchNames
* @param matchRegex
* @param priority
* @return the fully qualified name of the generated class
*/
public String generate(MethodInfo method, String matchName, List<String> matchNames, String matchRegex, Integer priority) {

AnnotationInstance extensionAnnotation = method.annotation(TEMPLATE_EXTENSION);
List<Type> parameters = method.parameterTypes();
Expand Down Expand Up @@ -199,6 +208,7 @@ public void generate(MethodInfo method, String matchName, List<String> matchName
implementResolve(valueResolver, declaringClass, method, matchName, matchNames, patternField, params);

valueResolver.close();
return generatedName.replace('/', '.');
}

public NamespaceResolverCreator createNamespaceResolver(ClassInfo declaringClass, String namespace, int priority) {
Expand Down Expand Up @@ -449,6 +459,10 @@ public NamespaceResolverCreator(ClassInfo declaringClass, String namespace, int
implementGetPriority(namespaceResolver, priority);
}

public String getClassName() {
return namespaceResolver.getClassName().replace('/', '.');
}

public ResolveCreator implementResolve() {
return new ResolveCreator();
}
Expand Down