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 c93625a39f8ad..b9c0eacaa9a9e 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 @@ -1578,7 +1578,9 @@ private static AnnotationTarget findTemplateExtensionMethod(Info info, Type matc * @return the property or null */ private static AnnotationTarget findProperty(String name, ClassInfo clazz, IndexView index) { + Set interfaceNames = new HashSet<>(); while (clazz != null) { + interfaceNames.addAll(clazz.interfaceNames()); // Fields for (FieldInfo field : clazz.fields()) { if (!Modifier.isPublic(field.flags()) || ValueResolverGenerator.isSynthetic(field.flags())) { @@ -1608,6 +1610,22 @@ private static AnnotationTarget findProperty(String name, ClassInfo clazz, Index clazz = index.getClassByName(clazz.superName()); } } + // Try the default methods + for (DotName interfaceName : interfaceNames) { + ClassInfo interfaceClassInfo = index.getClassByName(interfaceName); + if (interfaceClassInfo != null) { + for (MethodInfo method : interfaceClassInfo.methods()) { + // A default method is a public non-abstract instance method + if (Modifier.isPublic(method.flags()) && !Modifier.isStatic(method.flags()) + && !ValueResolverGenerator.isSynthetic(method.flags()) && !Modifier.isAbstract(method.flags()) + && (method.name().equals(name) + || ValueResolverGenerator.getPropertyName(method.name()).equals(name))) { + return method; + } + } + } + } + // No matching method found return null; } @@ -1624,61 +1642,14 @@ private static AnnotationTarget findProperty(String name, ClassInfo clazz, Index */ private static AnnotationTarget findMethod(VirtualMethodPart virtualMethod, ClassInfo clazz, Expression expression, IndexView index, Function templateIdToPathFun, Map results) { + Set interfaceNames = new HashSet<>(); while (clazz != null) { + interfaceNames.addAll(clazz.interfaceNames()); for (MethodInfo method : clazz.methods()) { if (Modifier.isPublic(method.flags()) && !Modifier.isStatic(method.flags()) && !ValueResolverGenerator.isSynthetic(method.flags()) - && method.name().equals(virtualMethod.getName())) { - boolean isVarArgs = ValueResolverGenerator.isVarArgs(method); - List parameters = method.parameters(); - int lastParamIdx = parameters.size() - 1; - - if (isVarArgs) { - // For varargs methods match the minimal number of params - if (lastParamIdx > virtualMethod.getParameters().size()) { - continue; - } - } else { - if (virtualMethod.getParameters().size() != parameters.size()) { - // Number of params must be equal - continue; - } - } - - // Check parameter types if available - boolean matches = true; - byte idx = 0; - - for (Expression param : virtualMethod.getParameters()) { - Match result = results.get(param.toOriginalString()); - if (result != null && !result.isEmpty()) { - // Type info available - validate parameter type - Type paramType; - if (isVarArgs && idx >= lastParamIdx) { - // Replace the type for varargs methods - paramType = parameters.get(lastParamIdx).asArrayType().component(); - } else { - paramType = parameters.get(idx); - } - if (!Types.isAssignableFrom(paramType, - result.type, index)) { - matches = false; - break; - } - } else { - LOGGER.debugf( - "Type info not available - skip validation for parameter [%s] of method [%s] for expression [%s] in template [%s] on line %s", - method.parameterName(idx), - method.declaringClass().name() + "#" + method, - expression.toOriginalString(), - templateIdToPathFun.apply(expression.getOrigin().getTemplateId()), - expression.getOrigin().getLine()); - } - idx++; - } - if (matches) { - return method; - } + && methodMatches(method, virtualMethod, expression, index, templateIdToPathFun, results)) { + return method; } } DotName superName = clazz.superName(); @@ -1688,9 +1659,81 @@ private static AnnotationTarget findMethod(VirtualMethodPart virtualMethod, Clas clazz = index.getClassByName(clazz.superName()); } } + // Try the default methods + for (DotName interfaceName : interfaceNames) { + ClassInfo interfaceClassInfo = index.getClassByName(interfaceName); + if (interfaceClassInfo != null) { + for (MethodInfo method : interfaceClassInfo.methods()) { + // A default method is a public non-abstract instance method + if (Modifier.isPublic(method.flags()) && !Modifier.isStatic(method.flags()) + && !ValueResolverGenerator.isSynthetic(method.flags()) && !Modifier.isAbstract(method.flags()) + && methodMatches(method, virtualMethod, expression, index, templateIdToPathFun, results)) { + return method; + } + } + } + } + // No matching method found return null; } + private static boolean methodMatches(MethodInfo method, VirtualMethodPart virtualMethod, Expression expression, + IndexView index, Function templateIdToPathFun, Map results) { + + if (!method.name().equals(virtualMethod.getName())) { + return false; + } + + boolean isVarArgs = ValueResolverGenerator.isVarArgs(method); + List parameters = method.parameters(); + int lastParamIdx = parameters.size() - 1; + + if (isVarArgs) { + // For varargs methods match the minimal number of params + if (lastParamIdx > virtualMethod.getParameters().size()) { + return false; + } + } else { + if (virtualMethod.getParameters().size() != parameters.size()) { + // Number of params must be equal + return false; + } + } + + // Check parameter types if available + boolean matches = true; + byte idx = 0; + + for (Expression param : virtualMethod.getParameters()) { + Match result = results.get(param.toOriginalString()); + if (result != null && !result.isEmpty()) { + // Type info available - validate parameter type + Type paramType; + if (isVarArgs && idx >= lastParamIdx) { + // Replace the type for varargs methods + paramType = parameters.get(lastParamIdx).asArrayType().component(); + } else { + paramType = parameters.get(idx); + } + if (!Types.isAssignableFrom(paramType, + result.type, index)) { + matches = false; + break; + } + } else { + LOGGER.debugf( + "Type info not available - skip validation for parameter [%s] of method [%s] for expression [%s] in template [%s] on line %s", + method.parameterName(idx), + method.declaringClass().name() + "#" + method, + expression.toOriginalString(), + templateIdToPathFun.apply(expression.getOrigin().getTemplateId()), + expression.getOrigin().getLine()); + } + idx++; + } + return matches; + } + private void processsTemplateData(IndexView index, AnnotationInstance templateData, AnnotationTarget annotationTarget, Set controlled, Map uncontrolled, ValueResolverGenerator.Builder builder) { AnnotationValue targetValue = templateData.value("target"); diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/OrEmptyTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/OrEmptyTest.java index 9e9d9d6937816..ce8736aa09421 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/OrEmptyTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/OrEmptyTest.java @@ -10,8 +10,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.qute.CheckedTemplate; import io.quarkus.qute.TemplateInstance; -import io.quarkus.qute.api.CheckedTemplate; import io.quarkus.test.QuarkusUnitTest; public class OrEmptyTest { diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/inject/InjectionTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/inject/InjectionTest.java index 5117e1ab4c60f..b348d83f76927 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/inject/InjectionTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/inject/InjectionTest.java @@ -15,7 +15,6 @@ import io.quarkus.qute.Engine; import io.quarkus.qute.Location; import io.quarkus.qute.Template; -import io.quarkus.qute.api.ResourcePath; import io.quarkus.test.QuarkusUnitTest; public class InjectionTest { @@ -50,10 +49,10 @@ public static class SimpleBean { @Inject Template foo; - @ResourcePath("foo.qute.html") + @Location("foo.qute.html") Template foo2; - @ResourcePath("bars/bar") + @Location("bars/bar") Template bar; @Location("bars/bar") diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/CheckedTemplateConflictTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/CheckedTemplateConflictTest.java index 89cb9c648f15a..c7124e4ae8053 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/CheckedTemplateConflictTest.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/CheckedTemplateConflictTest.java @@ -8,9 +8,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.qute.CheckedTemplate; import io.quarkus.qute.TemplateException; import io.quarkus.qute.TemplateInstance; -import io.quarkus.qute.api.CheckedTemplate; import io.quarkus.test.QuarkusUnitTest; public class CheckedTemplateConflictTest { diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/DefaultMethodValidationSuccessTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/DefaultMethodValidationSuccessTest.java new file mode 100644 index 0000000000000..691e5e391ca8c --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/DefaultMethodValidationSuccessTest.java @@ -0,0 +1,51 @@ +package io.quarkus.qute.deployment.typesafe; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.Template; +import io.quarkus.test.QuarkusUnitTest; + +public class DefaultMethodValidationSuccessTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Movie.class, MovieExtensions.class) + .addAsResource(new StringAsset( + "{@io.quarkus.qute.deployment.typesafe.DefaultMethodValidationSuccessTest$Name name}Hello {name.fullName()}::{name.fullName}!"), + "templates/name.html")); + + @Inject + Template name; + + @Test + public void testResult() { + // Validation succeeded! Yay! + assertEquals("Hello Name Surname::Name Surname!", name.data("name", new Name()).render()); + } + + public static class Name implements Something { + + public String name() { + return "Name"; + } + } + + interface Something { + + String name(); + + default String fullName() { + return name() + " Surname"; + } + } + +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/Monks.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/Monks.java index 64f76f0e0d5a5..8b5fbf7bf7fce 100644 --- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/Monks.java +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/typesafe/Monks.java @@ -1,7 +1,7 @@ package io.quarkus.qute.deployment.typesafe; +import io.quarkus.qute.CheckedTemplate; import io.quarkus.qute.TemplateInstance; -import io.quarkus.qute.api.CheckedTemplate; public class Monks {