Repository: quarkusio/quarkus
Issue: Qute - fix default template extensions for java.util.Map

`Map.get()` and `Map.containsKey()` are not handled correctly. A workaround exists:

```java
@TemplateExtension
static Object get(Map<?, ?> map, Object key) {
   return map.get(key);
 }
 @TemplateExtension
 static boolean containsKey(Map<?, ?> map, Object key) {
    return map.containsKey(key);
 }
```

Changed files:
extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/DefaultTemplateExtensions.java
independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java
extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateExtensionMethodsTest.java
extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java

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 3ee57fc28ec..8179de01cf6 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
@@ -82,7 +82,7 @@
 import io.quarkus.qute.deployment.TemplatesAnalysisBuildItem.TemplateAnalysis;
 import io.quarkus.qute.generator.ExtensionMethodGenerator;
 import io.quarkus.qute.generator.ValueResolverGenerator;
-import io.quarkus.qute.runtime.DefaultTemplateExtensions;
+import io.quarkus.qute.runtime.BuiltinTemplateExtensions;
 import io.quarkus.qute.runtime.EngineProducer;
 import io.quarkus.qute.runtime.QuteConfig;
 import io.quarkus.qute.runtime.QuteRecorder;
@@ -145,7 +145,7 @@ AdditionalBeanBuildItem additionalBeans() {
         return AdditionalBeanBuildItem.builder()
                 .setUnremovable()
                 .addBeanClasses(EngineProducer.class, TemplateProducer.class, VariantTemplateProducer.class, ResourcePath.class,
-                        Template.class, TemplateInstance.class, DefaultTemplateExtensions.class)
+                        Template.class, TemplateInstance.class, BuiltinTemplateExtensions.class)
                 .build();
     }
 
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateExtensionMethodsTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateExtensionMethodsTest.java
index 55e99c70c52..9b3a16dcca4 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateExtensionMethodsTest.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/TemplateExtensionMethodsTest.java
@@ -5,6 +5,8 @@
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.inject.Inject;
 
@@ -57,6 +59,19 @@ public void testMatchAnyWithParameter() {
                 engine.getTemplate("any.txt").data("anyInt", 10).render());
     }
 
+    @Test
+    public void testBuiltinExtensions() {
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("alpha", "1");
+        map.put("bravo", "2");
+        map.put("charlie", "3");
+        assertEquals("3:1:NOT_FOUND:1:false:true",
+                engine.parse(
+                        "{myMap.size}:{myMap.alpha}:{myMap.missing}:{myMap.get(key)}:{myMap.empty}:{myMap.containsKey('charlie')}")
+                        .data("myMap", map).data("key", "alpha").render());
+
+    }
+
     @TemplateExtension
     public static class Extensions {
 
diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/DefaultTemplateExtensions.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/BuiltinTemplateExtensions.java
similarity index 62%
rename from extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/DefaultTemplateExtensions.java
rename to extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/BuiltinTemplateExtensions.java
index 068b6962ad1..5db9fb017a7 100644
--- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/DefaultTemplateExtensions.java
+++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/runtime/BuiltinTemplateExtensions.java
@@ -7,11 +7,12 @@
 import io.quarkus.qute.Results.Result;
 import io.quarkus.qute.TemplateExtension;
 
-public class DefaultTemplateExtensions {
+@TemplateExtension
+public class BuiltinTemplateExtensions {
 
-    @SuppressWarnings("rawtypes")
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     @TemplateExtension(matchName = ANY)
-    public static Object map(Map map, String name) {
+    static Object map(Map map, String name) {
         Object val = map.get(name);
         if (val != null) {
             return val;
@@ -27,13 +28,17 @@ public static Object map(Map map, String name) {
             case "empty":
             case "isEmpty":
                 return map.isEmpty();
-            case "get":
-                return map.get(name);
-            case "containsKey":
-                return map.containsKey(name);
             default:
-                return Result.NOT_FOUND;
+                return map.getOrDefault(name, Result.NOT_FOUND);
         }
     }
 
+    static Object get(Map<?, ?> map, Object key) {
+        return map.get(key);
+    }
+
+    static boolean containsKey(Map<?, ?> map, Object key) {
+        return map.containsKey(key);
+    }
+
 }
diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java
index a4b30207dc4..874d06faee5 100644
--- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java
+++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/ValueResolvers.java
@@ -193,9 +193,6 @@ private static Object entryResolve(Entry<?, ?> entry, String name) {
     @SuppressWarnings("rawtypes")
     private static CompletionStage<Object> mapResolveAsync(EvalContext context) {
         Map map = (Map) context.getBase();
-        if (map.containsKey(context.getName())) {
-            return CompletableFuture.completedFuture(map.get(context.getName()));
-        }
         switch (context.getName()) {
             case "keys":
             case "keySet":
@@ -220,7 +217,8 @@ private static CompletionStage<Object> mapResolveAsync(EvalContext context) {
                     });
                 }
             default:
-                return Results.NOT_FOUND;
+                return map.containsKey(context.getName()) ? CompletableFuture.completedFuture(map.get(context.getName()))
+                        : Results.NOT_FOUND;
         }
     }