diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/CompletedStage.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/CompletedStage.java index 4c961a79ad032..372fa9df306de 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/CompletedStage.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/CompletedStage.java @@ -20,16 +20,33 @@ */ public final class CompletedStage implements CompletionStage, Supplier { - static final CompletedStage VOID = new CompletedStage<>(null, null); + @SuppressWarnings("rawtypes") + static final CompletedStage NULL = new CompletedStage<>(null, null); + @SuppressWarnings("unchecked") public static CompletedStage of(T result) { + if (result == null) { + // Use a shared constant for nulls + return (CompletedStage) NULL; + } return new CompletedStage(result, null); } public static CompletedStage failure(Throwable t) { + Objects.requireNonNull(t); return new CompletedStage(null, t); } + @SuppressWarnings("unchecked") + public static CompletedStage ofVoid() { + return NULL; + } + + @SuppressWarnings("unchecked") + public static CompletedStage ofNull() { + return NULL; + } + private final T result; private final Throwable exception; @@ -84,7 +101,7 @@ public CompletionStage thenAccept(Consumer action) { } catch (Throwable e) { return new CompletedStage<>(null, e); } - return VOID; + return ofVoid(); } return new CompletedStage<>(null, exception); } @@ -108,7 +125,7 @@ public CompletionStage thenRun(Runnable action) { } catch (final Throwable e) { return new CompletedStage<>(null, e); } - return VOID; + return ofVoid(); } return new CompletedStage<>(null, exception); } diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatedParams.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatedParams.java index 00bf9b021121d..24f8817122d4e 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatedParams.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/EvaluatedParams.java @@ -11,7 +11,7 @@ public final class EvaluatedParams { - static final EvaluatedParams EMPTY = new EvaluatedParams(CompletedStage.VOID, new Supplier[0]); + static final EvaluatedParams EMPTY = new EvaluatedParams(CompletedStage.ofVoid(), new Supplier[0]); /** * @@ -52,7 +52,7 @@ public static EvaluatedParams evaluate(EvalContext context) { } CompletionStage cs; if (asyncResults == null) { - cs = failure != null ? failure : CompletedStage.VOID; + cs = failure != null ? failure : CompletedStage.ofVoid(); } else if (asyncResults.size() == 1) { cs = asyncResults.get(0); } else { @@ -101,7 +101,7 @@ public static EvaluatedParams evaluateMessageParams(EvalContext context) { } CompletionStage cs; if (asyncResults == null) { - cs = failure != null ? failure : CompletedStage.VOID; + cs = failure != null ? failure : CompletedStage.ofVoid(); } else if (asyncResults.size() == 1) { cs = asyncResults.get(0); } else { diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Results.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Results.java index dd9e314cf08b3..079ceec677c3e 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Results.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Results.java @@ -15,7 +15,7 @@ public final class Results { public static final CompletedStage FALSE = CompletedStage.of(false); public static final CompletedStage TRUE = CompletedStage.of(true); - public static final CompletedStage NULL = CompletedStage.of(null); + public static final CompletedStage NULL = CompletedStage.NULL; private Results() { } diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/Descriptors.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/Descriptors.java index 57799bf42d767..735f670ab9266 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/Descriptors.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/Descriptors.java @@ -35,7 +35,7 @@ private Descriptors() { public static final MethodDescriptor EVALUATE = MethodDescriptor.ofMethod(EvalContext.class, "evaluate", CompletionStage.class, Expression.class); static final MethodDescriptor LIST_GET = MethodDescriptor.ofMethod(List.class, "get", Object.class, int.class); - static final MethodDescriptor COMPLETED_STAGE = MethodDescriptor.ofMethod(CompletedStage.class, + static final MethodDescriptor COMPLETED_STAGE_OF = MethodDescriptor.ofMethod(CompletedStage.class, "of", CompletedStage.class, Object.class); public static final MethodDescriptor COMPLETABLE_FUTURE_ALL_OF = MethodDescriptor.ofMethod(CompletableFuture.class, "allOf", @@ -58,6 +58,8 @@ private Descriptors() { CompletionStage.class, BiConsumer.class); public static final MethodDescriptor BOOLEAN_LOGICAL_OR = MethodDescriptor.ofMethod(Boolean.class, "logicalOr", boolean.class, boolean.class, boolean.class); + public static final MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.ofMethod(Boolean.class, "booleanValue", + boolean.class); public static final MethodDescriptor EVALUATED_PARAMS_EVALUATE = MethodDescriptor.ofMethod(EvaluatedParams.class, "evaluate", EvaluatedParams.class, @@ -96,5 +98,7 @@ private Descriptors() { public static final FieldDescriptor EVALUATED_PARAMS_STAGE = FieldDescriptor.of(EvaluatedParams.class, "stage", CompletionStage.class); + public static final FieldDescriptor RESULTS_TRUE = FieldDescriptor.of(Results.class, "TRUE", CompletedStage.class); + public static final FieldDescriptor RESULTS_FALSE = FieldDescriptor.of(Results.class, "FALSE", CompletedStage.class); } 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 bb1a50f4b6bfe..fc04327aaf28f 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 @@ -261,7 +261,7 @@ private void implementResolve(ClassCreator valueResolver, ClassInfo declaringCla if (returnsCompletionStage) { ret = result; } else { - ret = resolve.invokeStaticMethod(Descriptors.COMPLETED_STAGE, result); + ret = resolve.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, result); } } else { ret = resolve @@ -520,7 +520,7 @@ public void addMethod(MethodInfo method, String matchName, List matchNam matchScope.load(param.name)); } } - matchScope.returnValue(matchScope.invokeStaticMethod(Descriptors.COMPLETED_STAGE, + matchScope.returnValue(matchScope.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, matchScope.invokeStaticMethod(MethodDescriptor.of(method), args))); } else { ResultHandle ret = matchScope.newInstance(MethodDescriptor.ofConstructor(CompletableFuture.class)); diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java index 8d0a792ad82c0..4be90f58db7e6 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java @@ -1,6 +1,7 @@ package io.quarkus.qute.generator; import static java.util.function.Predicate.not; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import java.lang.reflect.Modifier; @@ -33,6 +34,7 @@ import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.PrimitiveType; +import org.jboss.jandex.PrimitiveType.Primitive; import org.jboss.jandex.Type; import org.jboss.logging.Logger; @@ -46,11 +48,13 @@ import io.quarkus.gizmo.FieldDescriptor; import io.quarkus.gizmo.FunctionCreator; import io.quarkus.gizmo.Gizmo; +import io.quarkus.gizmo.IfThenElse; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.gizmo.Switch; import io.quarkus.gizmo.TryBlock; +import io.quarkus.qute.CompletedStage; import io.quarkus.qute.EvalContext; import io.quarkus.qute.EvaluatedParams; import io.quarkus.qute.NamespaceResolver; @@ -76,7 +80,6 @@ public static Builder builder() { public static final String NESTED_SEPARATOR = "$_"; private static final Logger LOGGER = Logger.getLogger(ValueResolverGenerator.class); - public static final String GET_PREFIX = "get"; public static final String IS_PREFIX = "is"; public static final String HAS_PREFIX = "has"; @@ -373,8 +376,8 @@ private boolean implementResolve(ClassCreator valueResolver, String clazzName, C Consumer invokeMethod = new Consumer() { @Override public void accept(BytecodeCreator bc) { - ResultHandle ret; - boolean hasCompletionStage = !skipMemberType(method.returnType()) + Type returnType = method.returnType(); + boolean hasCompletionStage = !skipMemberType(returnType) && hasCompletionStageInTypeClosure(index.getClassByName(method.returnType().name()), index); ResultHandle invokeRet; if (Modifier.isInterface(clazz.flags())) { @@ -383,11 +386,20 @@ public void accept(BytecodeCreator bc) { invokeRet = bc.invokeVirtualMethod(MethodDescriptor.of(method), base); } if (hasCompletionStage) { - ret = invokeRet; + bc.returnValue(invokeRet); } else { - ret = bc.invokeStaticMethod(Descriptors.COMPLETED_STAGE, invokeRet); + // Try to use some shared CompletedStage constants + if (returnType.kind() == org.jboss.jandex.Type.Kind.PRIMITIVE + && returnType.asPrimitiveType().primitive() == Primitive.BOOLEAN) { + completeBoolean(bc, invokeRet); + } else if (method.returnType().name().equals(DotNames.BOOLEAN)) { + completeBoolean(bc, bc.invokeVirtualMethod(Descriptors.BOOLEAN_VALUE, invokeRet)); + } else if (isEnum(returnType)) { + completeEnum(index.getClassByName(returnType.name()), valueResolver, invokeRet, bc); + } else { + bc.returnValue(bc.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, invokeRet)); + } } - bc.returnValue(ret); } }; nameSwitch.caseOf(matchingNames, invokeMethod); @@ -410,7 +422,7 @@ public void accept(BytecodeCreator bc) { MethodDescriptor.ofMethod(clazz.name().toString(), getterName, DescriptorUtils.typeToString(field.type())), base); - bc.returnValue(bc.invokeStaticMethod(Descriptors.COMPLETED_STAGE, value)); + bc.returnValue(bc.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, value)); } }; nameSwitch.caseOf(matching, invokeMethod); @@ -422,7 +434,7 @@ public void accept(BytecodeCreator bc) { ResultHandle value = bc.readInstanceField( FieldDescriptor.of(clazzName, field.name(), field.type().name().toString()), base); - ResultHandle ret = bc.invokeStaticMethod(Descriptors.COMPLETED_STAGE, value); + ResultHandle ret = bc.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, value); bc.returnValue(ret); } }; @@ -471,6 +483,71 @@ public void accept(BytecodeCreator bc) { return true; } + private void completeBoolean(BytecodeCreator bc, ResultHandle result) { + BranchResult isTrue = bc.ifTrue(result); + BytecodeCreator trueBranch = isTrue.trueBranch(); + trueBranch.returnValue(trueBranch.readStaticField(Descriptors.RESULTS_TRUE)); + BytecodeCreator falseBranch = isTrue.falseBranch(); + falseBranch.returnValue(falseBranch.readStaticField(Descriptors.RESULTS_FALSE)); + } + + private boolean isEnum(Type returnType) { + if (returnType.kind() != org.jboss.jandex.Type.Kind.CLASS) { + return false; + } + ClassInfo maybeEnum = index.getClassByName(returnType.name()); + return maybeEnum != null && maybeEnum.isEnum(); + } + + private boolean completeEnum(ClassInfo enumClass, ClassCreator valueResolver, ResultHandle result, BytecodeCreator bc) { + IfThenElse ifThenElse = null; + for (FieldInfo enumConstant : enumClass.enumConstants()) { + String name = enumClass.name().toString().replace(".", "_") + "$$" + + enumConstant.name(); + FieldDescriptor enumConstantField = FieldDescriptor.of(enumClass.name().toString(), + enumConstant.name(), enumClass.name().toString()); + + // Additional methods and fields are generated for enums that are part of the index + // We don't care about visibility and atomicity here + // private CompletedStage org_acme_MyEnum$$CONSTANT; + FieldDescriptor csField = valueResolver + .getFieldCreator(name, CompletedStage.class).setModifiers(ACC_PRIVATE) + .getFieldDescriptor(); + // private CompletedStage org_acme_MyEnum$$CONSTANT() { + // if (org_acme_MyEnum$$CONSTANT == null) { + // org_acme_MyEnum$$CONSTANT = CompletedStage.of(MyEnum.CONSTANT); + // } + // return org_acme_MyEnum$$CONSTANT; + // } + MethodCreator enumConstantMethod = valueResolver.getMethodCreator(name, + CompletedStage.class).setModifiers(ACC_PRIVATE); + BytecodeCreator isNull = enumConstantMethod.ifNull(enumConstantMethod + .readInstanceField(csField, enumConstantMethod.getThis())) + .trueBranch(); + ResultHandle val = isNull.readStaticField(enumConstantField); + isNull.writeInstanceField(csField, enumConstantMethod.getThis(), + isNull.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, val)); + enumConstantMethod.returnValue(enumConstantMethod + .readInstanceField(csField, enumConstantMethod.getThis())); + + // Unfortunately, we can't use the BytecodeCreator#enumSwitch() here because the enum class is not loaded + // if(val.equals(MyEnum.CONSTANT)) + // return org_acme_MyEnum$$CONSTANT(); + BytecodeCreator match; + if (ifThenElse == null) { + ifThenElse = bc.ifThenElse( + Gizmo.equals(bc, result, bc.readStaticField(enumConstantField))); + match = ifThenElse.then(); + } else { + match = ifThenElse.elseIf( + b -> Gizmo.equals(b, result, b.readStaticField(enumConstantField))); + } + match.returnValue(match.invokeVirtualMethod( + enumConstantMethod.getMethodDescriptor(), match.getThis())); + } + return true; + } + private boolean implementNamespaceResolve(ClassCreator valueResolver, String clazzName, ClassInfo clazz, Predicate filter) { MethodCreator resolve = valueResolver.getMethodCreator("resolve", CompletionStage.class, EvalContext.class) @@ -512,7 +589,7 @@ private boolean implementNamespaceResolve(ClassCreator valueResolver, String cla .trueBranch(); ResultHandle value = fieldMatch .readStaticField(FieldDescriptor.of(clazzName, field.name(), field.type().name().toString())); - fieldMatch.returnValue(fieldMatch.invokeStaticMethod(Descriptors.COMPLETED_STAGE, value)); + fieldMatch.returnValue(fieldMatch.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, value)); } } @@ -541,7 +618,7 @@ private boolean implementNamespaceResolve(ClassCreator valueResolver, String cla if (hasCompletionStage) { ret = invokeRet; } else { - ret = matchScope.invokeStaticMethod(Descriptors.COMPLETED_STAGE, invokeRet); + ret = matchScope.invokeStaticMethod(Descriptors.COMPLETED_STAGE_OF, invokeRet); } matchScope.returnValue(ret); } 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 db5a78ca59a98..9e7327d27ffa8 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 @@ -33,7 +33,7 @@ public boolean hasName() { return name != null; } - public boolean isActive() { + public Boolean isActive() { return true; } @@ -41,6 +41,10 @@ public boolean hasItems() { return false; } + public MyEnum myEnum() { + return MyEnum.BAR; + } + public List getList(int limit, String dummy) { AtomicInteger idx = new AtomicInteger(0); return Stream.generate(() -> "" + idx.getAndIncrement())