diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java index c00d6e837..6edb2f6f1 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatorImpl.java @@ -69,7 +69,7 @@ class EvaluatorImpl implements Evaluator { return resolve(new EvalContextImpl(tryParent, ref, parts.next(), resolutionContext), resolvers.iterator()) .thenCompose(r -> { if (parts.hasNext()) { - return resolveReference(false, r, parts, resolutionContext); + return resolveReference(tryParent, r, parts, resolutionContext); } else { return CompletableFuture.completedFuture(r); } @@ -82,7 +82,7 @@ class EvaluatorImpl implements Evaluator { if (evalContext.tryParent && parent != null) { // Continue with parent context return resolve( - new EvalContextImpl(false, parent.getData(), evalContext.name, parent), + new EvalContextImpl(true, parent.getData(), evalContext.name, parent), this.resolvers.iterator()); } LOGGER.tracef("Unable to resolve %s", evalContext); diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java index 8e7d061ae..de7e5f08c 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java @@ -47,7 +47,9 @@ public class LoopSectionHelper implements SectionHelper { } else if (it instanceof Integer) { iterator = IntStream.rangeClosed(1, (Integer) it).iterator(); } else { - throw new IllegalStateException("Cannot iterate over: " + it); + throw new IllegalStateException( + String.format("Cannot iterate over [%s] resolved for [%s] in template %s on line %s", it, + iterable.toOriginalString(), iterable.origin.getTemplateId(), iterable.origin.getLine())); } int idx = 0; while (iterator.hasNext()) { diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/LoopSectionTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/LoopSectionTest.java index 186959ec0..a55a87a06 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/LoopSectionTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/LoopSectionTest.java @@ -68,8 +68,8 @@ public class LoopSectionTest { @Test public void testNestedLoops() { - List data = new ArrayList<>(); - data.add("alpha"); + List list = new ArrayList<>(); + list.add("alpha"); Engine engine = Engine.builder() .addSectionHelper(new LoopSectionHelper.Factory()) @@ -92,14 +92,14 @@ public class LoopSectionTest { }) .build(); - String template = "{#for name in this}" + String template = "{#for name in list}" + "{count}.{name}: {#for char in name.chars}" - + "{name} - char at {index} = {char}{#if hasNext},{/}" + + "{name} {global} char at {index} = {char}{#if hasNext},{/}" + "{/}{/}"; assertEquals( "1.alpha: alpha - char at 0 = a,alpha - char at 1 = l,alpha - char at 2 = p,alpha - char at 3 = h,alpha - char at 4 = a", - engine.parse(template).render(data)); + engine.parse(template).data("global", "-").data("list", list).render()); } @Test diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java index 9948a4ce4..c77b16aeb 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ExtensionMethodGenerator.java @@ -8,12 +8,14 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import io.quarkus.gizmo.AssignableResultHandle; import io.quarkus.gizmo.BranchResult; import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.CatchBlockCreator; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; import io.quarkus.gizmo.FunctionCreator; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.TryBlock; import io.quarkus.qute.EvalContext; import io.quarkus.qute.TemplateExtension; import io.quarkus.qute.ValueResolver; @@ -196,12 +198,20 @@ public class ExtensionMethodGenerator { args[i + shift] = success.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_GET, paramResult); } - ResultHandle invokeRet = success + // try + TryBlock tryCatch = success.tryBlock(); + // catch (Throwable e) + CatchBlockCreator exception = tryCatch.addCatch(Throwable.class); + // CompletableFuture.completeExceptionally(Throwable) + exception.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, whenRet, + exception.getCaughtException()); + + ResultHandle invokeRet = tryCatch .invokeStaticMethod(MethodDescriptor.ofMethod(declaringClass.name().toString(), method.name(), method.returnType().name().toString(), method.parameters().stream().map(p -> p.name().toString()).collect(Collectors.toList()).toArray()), args); - success.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE, whenRet, invokeRet); + tryCatch.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE, whenRet, invokeRet); BytecodeCreator failure = throwableIsNull.falseBranch(); failure.invokeVirtualMethod(Descriptors.COMPLETABLE_FUTURE_COMPLETE_EXCEPTIONALLY, whenRet, diff --git a/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/MyService.java b/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/MyService.java index 1b9dd37da..90747afb7 100644 --- a/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/MyService.java +++ b/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/MyService.java @@ -1,6 +1,7 @@ package io.quarkus.qute.generator; import io.quarkus.qute.TemplateData; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -50,4 +51,8 @@ public class MyService { return CompletableFuture.completedFuture(param); } + public static List getDummy(MyService service, int limit, String dummy) { + return Collections.emptyList(); + } + } diff --git a/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/SimpleGeneratorTest.java b/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/SimpleGeneratorTest.java index 995687b00..427a736ab 100644 --- a/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/SimpleGeneratorTest.java +++ b/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/SimpleGeneratorTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import io.quarkus.qute.Engine; +import io.quarkus.qute.EngineBuilder; import io.quarkus.qute.EvalContext; import io.quarkus.qute.Expression; import io.quarkus.qute.ImmutableList; @@ -11,19 +12,29 @@ import io.quarkus.qute.ValueResolver; import java.io.IOException; import java.io.InputStream; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.Index; import org.jboss.jandex.Indexer; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.PrimitiveType; +import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class SimpleGeneratorTest { + static Set generatedTypes = new HashSet<>(); + @BeforeAll public static void init() throws IOException { TestClassOutput classOutput = new TestClassOutput(); @@ -31,11 +42,20 @@ public class SimpleGeneratorTest { CompletionStage.class, List.class); ValueResolverGenerator generator = new ValueResolverGenerator(index, classOutput, Collections.emptyMap()); - generator.generate(index.getClassByName(DotName.createSimple(MyService.class.getName()))); + ClassInfo myServiceClazz = index.getClassByName(DotName.createSimple(MyService.class.getName())); + generator.generate(myServiceClazz); generator.generate(index.getClassByName(DotName.createSimple(PublicMyService.class.getName()))); generator.generate(index.getClassByName(DotName.createSimple(MyItem.class.getName()))); generator.generate(index.getClassByName(DotName.createSimple(String.class.getName()))); generator.generate(index.getClassByName(DotName.createSimple(List.class.getName()))); + generatedTypes.addAll(generator.getGeneratedTypes()); + + ExtensionMethodGenerator extensionMethodGenerator = new ExtensionMethodGenerator(classOutput); + MethodInfo extensionMethod = index.getClassByName(DotName.createSimple(MyService.class.getName())).method( + "getDummy", Type.create(myServiceClazz.name(), Kind.CLASS), PrimitiveType.INT, + Type.create(DotName.createSimple(String.class.getName()), Kind.CLASS)); + extensionMethodGenerator.generate(extensionMethod, null); + generatedTypes.addAll(extensionMethodGenerator.getGeneratedTypes()); } @Test @@ -71,18 +91,38 @@ public class SimpleGeneratorTest { } catch (ClassNotFoundException | InstantiationException | IllegalAccessException expected) { } - Engine engine = Engine.builder().addDefaults() - .addValueResolver(newResolver("io.quarkus.qute.generator.MyService_ValueResolver")) - .addValueResolver(newResolver("io.quarkus.qute.generator.PublicMyService_ValueResolver")) - .addValueResolver(newResolver("io.quarkus.qute.generator.MyItem_ValueResolver")) - .addValueResolver(newResolver("io.quarkus.qute.String_ValueResolver")) - .addValueResolver(newResolver("io.quarkus.qute.List_ValueResolver")) - .build(); + EngineBuilder builder = Engine.builder().addDefaults(); + for (String generatedType : generatedTypes) { + builder.addValueResolver(newResolver(generatedType)); + } + Engine engine = builder.build(); assertEquals(" FOO ", engine.parse("{#if isActive} {name.toUpperCase} {/if}").render(new MyService())); assertEquals("OK", engine.parse("{#if this.getList(5).size == 5}OK{/if}").render(new MyService())); assertEquals("Martin NOT_FOUND OK NOT_FOUND", engine.parse("{name} {surname} {isStatic ?: 'OK'} {base}").render(new PublicMyService())); assertEquals("foo NOT_FOUND", engine.parse("{id} {bar}").render(new MyItem())); + try { + engine.parse("{this.getList(5,5)}").render(new MyService()); + fail(); + } catch (IllegalStateException e) { + assertClassCastException(e); + } + try { + engine.parse("{service.getDummy(5,resultNotFound)}").data("service", new MyService()).render(); + fail(); + } catch (IllegalStateException e) { + assertClassCastException(e); + } + } + + private void assertClassCastException(Exception e) { + if (e.getCause() instanceof ExecutionException) { + ExecutionException ex = (ExecutionException) e.getCause(); + if (ex.getCause() instanceof ClassCastException) { + return; + } + } + fail("Unexpected exception thrown: ", e); } private ValueResolver newResolver(String className)