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 63d39c44ca44b..39481c4302fd3 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
@@ -118,6 +118,7 @@
import io.quarkus.qute.Expression;
import io.quarkus.qute.Expression.VirtualMethodPart;
import io.quarkus.qute.Identifiers;
+import io.quarkus.qute.JavaElementUriBuilder;
import io.quarkus.qute.LoopSectionHelper;
import io.quarkus.qute.NamespaceResolver;
import io.quarkus.qute.ParameterDeclaration;
@@ -129,6 +130,7 @@
import io.quarkus.qute.SectionHelperFactory;
import io.quarkus.qute.SetSectionHelper;
import io.quarkus.qute.Template;
+import io.quarkus.qute.TemplateContents;
import io.quarkus.qute.TemplateData;
import io.quarkus.qute.TemplateException;
import io.quarkus.qute.TemplateExtension;
@@ -2627,6 +2629,7 @@ void collecTemplateContents(BeanArchiveIndexBuildItem index, List
+ * These URIs have the format:
+ *
+ *
+ * qute-java://<fully-qualified-class-name>[#method][@annotation]
+ *
+ *
+ * Examples:
+ *
+ *
+ * qute-java://com.acme.Bean@io.quarkus.qute.TemplateContentsqute-java://com.acme.Bean#process@io.quarkus.qute.TemplateContents
+ * This builder is used to construct such URIs in a type-safe way and to provide + * utility methods to identify and parse them. It is aligned with + * {@link io.quarkus.qute.debug.client.JavaSourceLocationArguments#javaElementUri}. + *
+ */ +public class JavaElementUriBuilder { + + /** Scheme used for Qute Java URIs. */ + public static final String QUTE_JAVA_SCHEME = "qute-java"; + + /** Prefix for Qute Java URIs. */ + public static final String QUTE_JAVA_URI_PREFIX = QUTE_JAVA_SCHEME + "://"; + + private final String typeName; + private String method; + private String annotation; + + private JavaElementUriBuilder(String typeName) { + this.typeName = typeName; + } + + /** + * Returns the fully qualified Java class name for this URI. + * + * @return the class name + */ + public String getTypeName() { + return typeName; + } + + /** + * Returns the Java method name (nullable if not specified). + * + * @return the method name or {@code null} + */ + public String getMethod() { + return method; + } + + /** + * Sets the Java method name. + * + * @param method the method name to set + * @return this builder + */ + public JavaElementUriBuilder setMethod(String method) { + this.method = method; + return this; + } + + /** + * Returns the fully qualified Java annotation name (nullable if not specified). + * + * @return the annotation name or {@code null} + */ + public String getAnnotation() { + return annotation; + } + + /** + * Sets the fully qualified Java annotation name. + * + * @param annotation the annotation name to set + * @return this builder + */ + public JavaElementUriBuilder setAnnotation(String annotation) { + this.annotation = annotation; + return this; + } + + /** + * Creates a new builder for the given Java class name. + * + * @param typeName fully qualified Java class name + * @return a new {@link JavaElementUriBuilder} + */ + public static JavaElementUriBuilder builder(String typeName) { + return new JavaElementUriBuilder(typeName); + } + + /** + * Builds the Qute Java URI representing the element. + * + * @return a {@link URI} for the Java element + */ + public URI build() { + StringBuilder uri = new StringBuilder(QUTE_JAVA_URI_PREFIX); + uri.append(typeName); + if (method != null) { + uri.append("#").append(method); + } + if (annotation != null) { + uri.append("@").append(annotation); + } + return URI.create(uri.toString()); + } + + /** + * Returns true if the given URI uses the qute-java scheme. + * + * @param uri the URI to check + * @return {@code true} if this URI is a Qute Java element URI + */ + public static boolean isJavaUri(URI uri) { + return uri != null && QUTE_JAVA_SCHEME.equals(uri.getScheme()); + } + + /** + * Returns true if the given string starts with the qute-java URI prefix. + * + * @param uri the URI string to check + * @return {@code true} if this string is a Qute Java element URI + */ + public static boolean isJavaUri(String uri) { + return uri != null && uri.startsWith(QUTE_JAVA_URI_PREFIX); + } +} diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/StringTemplateLocation.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/StringTemplateLocation.java index a64bbb03d2dec..f6b9cd3462e84 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/StringTemplateLocation.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/StringTemplateLocation.java @@ -1,6 +1,7 @@ package io.quarkus.qute; import java.io.Reader; +import java.net.URI; import java.util.Objects; import java.util.Optional; @@ -11,14 +12,20 @@ public class StringTemplateLocation implements TemplateLocation { private final String content; private final Optionaldiff --git a/independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/source/SourceTemplateRegistry.java b/independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/source/SourceTemplateRegistry.java index a0336304b05d7..dfc44e89c49c3 100644 --- a/independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/source/SourceTemplateRegistry.java +++ b/independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/agent/source/SourceTemplateRegistry.java @@ -10,29 +10,39 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import org.eclipse.lsp4j.debug.Source; import io.quarkus.qute.Engine; +import io.quarkus.qute.JavaElementUriBuilder; import io.quarkus.qute.debug.agent.breakpoints.BreakpointsRegistry; +import io.quarkus.qute.debug.client.JavaSourceLocationArguments; +import io.quarkus.qute.debug.client.JavaSourceLocationResponse; +import io.quarkus.qute.debug.client.JavaSourceResolver; /** - * Registry responsible for resolving and managing mappings between Qute template IDs - * and their corresponding {@link RemoteSource} instances. + * Registry responsible for resolving and managing mappings between Qute + * template IDs and their corresponding {@link RemoteSource} instances. *
- * This class plays a key role in the Qute debugger by allowing the Debug Adapter Protocol (DAP) - * to locate and serve the correct source file for a given template, whether it originates - * from a local filesystem or a JAR. + * This class plays a key role in the Qute debugger by allowing the Debug + * Adapter Protocol (DAP) to locate and serve the correct source file for a + * given template, whether it originates from a local filesystem or a JAR. *
* *- * The registry maintains a cache of resolved template IDs to avoid redundant lookups - * and supports flexible base paths and file extensions to locate template files. + * The registry maintains a cache of resolved template IDs to avoid redundant + * lookups and supports flexible base paths and file extensions to locate + * template files. *
*/ public class SourceTemplateRegistry { + private static final String JAR_SCHEME = "jar"; + private final Map- * This method first checks the internal cache. If the source is not yet registered, - * it attempts to resolve it from: + * This method first checks the internal cache. If the source is not yet + * registered, it attempts to resolve it from: *
* This method is used as a fallback when the Qute engine cannot resolve a * template ID to a physical location. @@ -167,7 +264,8 @@ private static URI getSourceUriFromEngine(String templateId, Engine engine) { * *
+ * Sent by the Debug Adapter via {@link JavaSourceResolver#resolveJavaSource} + * to the client in order to locate the corresponding Java source file and position. + *
+ * + *+ * qute-java://com.acme.Bean#process@io.quarkus.qute.TemplateContents + *+ * + *
+ * Interpretation: + *
+ *+ * Optional: if {@code null}, the annotation is applied to the class itself. + *
+ */ + private String method; + + /** Fully qualified Java annotation name (typically "io.quarkus.qute.TemplateContents"). */ + private String annotation; + + /** + * Returns the qute-java URI used to locate the Java element. + * + * @return the URI string + */ + public String getJavaElementUri() { + return javaElementUri; + } + + /** + * Sets the qute-java URI used to locate the Java element. + * + * @param javaElementUri the URI string to set + */ + public void setJavaElementUri(String javaElementUri) { + this.javaElementUri = javaElementUri; + } + + /** + * Returns the fully qualified Java class, interface name. + * + * @return the class name + */ + public String getTypeName() { + return typeName; + } + + /** + * Sets the fully qualified Java class, interface name. + * + * @param typeName the class, interface name to set + */ + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + /** + * Returns the Java method name. + * + * @return the method name, or {@code null} if the annotation applies to the class + */ + public String getMethod() { + return method; + } + + /** + * Sets the Java method name. + * + * @param method the method name to set, or {@code null} if the annotation applies to the class + */ + public void setMethod(String method) { + this.method = method; + } + + /** + * Returns the fully qualified Java annotation name. + * + * @return the annotation name (e.g., "io.quarkus.qute.TemplateContents") + */ + public String getAnnotation() { + return annotation; + } + + /** + * Sets the fully qualified Java annotation name. + * + * @param annotation the annotation name to set + */ + public void setAnnotation(String annotation) { + this.annotation = annotation; + } +} diff --git a/independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/client/JavaSourceLocationResponse.java b/independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/client/JavaSourceLocationResponse.java new file mode 100644 index 0000000000000..5a45265f52c06 --- /dev/null +++ b/independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/client/JavaSourceLocationResponse.java @@ -0,0 +1,71 @@ +package io.quarkus.qute.debug.client; + +/** + * Response containing the location of a Java method, class, or template content + * referenced from a Qute template. + *+ * Typically returned by {@link JavaSourceResolver#resolveJavaSource}. + *
+ * + *+ * qute-java://org.acme.quarkus.sample.HelloResource$Hello@io.quarkus.qute.TemplateContents + * → javaFileUri = "file:///project/src/main/java/org/acme/quarkus/sample/HelloResource.java" + * → startLine = 16 + *+ * + *
+ * The {@code startLine} represents the line where the template content or the Java + * element declaration starts. Lines are 1-based in this API (first line = 1). + *
+ */ +public class JavaSourceLocationResponse { + + /** URI of the Java source file containing the resolved element. */ + private String javaFileUri; + + /** + * Start line of the Java element or template content in the file. + *+ * 1-based index of the line where the element or template content starts. + *
+ */ + private int startLine; + + /** + * Returns the URI of the Java source file containing the resolved element. + * + * @return the file URI + */ + public String getJavaFileUri() { + return javaFileUri; + } + + /** + * Sets the URI of the Java source file containing the resolved element. + * + * @param javaFileUri the file URI to set + */ + public void setJavaFileUri(String javaFileUri) { + this.javaFileUri = javaFileUri; + } + + /** + * Returns the start line of the Java element or template content in the source file. + * + * @return 1-based start line of the element or template content + */ + public int getStartLine() { + return startLine; + } + + /** + * Sets the start line of the Java element or template content in the source file. + * + * @param startLine 1-based start line of the element or template content + */ + public void setStartLine(int startLine) { + this.startLine = startLine; + } +} diff --git a/independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/client/JavaSourceResolver.java b/independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/client/JavaSourceResolver.java new file mode 100644 index 0000000000000..b3065d62cd582 --- /dev/null +++ b/independent-projects/qute/debug/src/main/java/io/quarkus/qute/debug/client/JavaSourceResolver.java @@ -0,0 +1,92 @@ +package io.quarkus.qute.debug.client; + +import java.util.concurrent.CompletableFuture; + +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; + +/** + * Resolver for Java source locations referenced from Qute templates. + *+ * Implementations of this interface provide a way to resolve a Qute template reference + * (via a {@code qute-java://} URI) to the corresponding Java source file and template position. + *
+ * + *+ * This resolver is used by the Debug Adapter Protocol (DAP) **only to support breakpoints** + * on Java methods or classes related to Qute templates. + *
+ * + *
+ * package org.acme.quarkus.sample;
+ *
+ * import jakarta.ws.rs.GET;
+ * import jakarta.ws.rs.Path;
+ * import jakarta.ws.rs.QueryParam;
+ * import jakarta.ws.rs.Produces;
+ * import jakarta.ws.rs.core.MediaType;
+ *
+ * import io.quarkus.qute.TemplateContents;
+ * import io.quarkus.qute.TemplateInstance;
+ *
+ * @Path("hello")
+ * public class HelloResource {
+ *
+ * @TemplateContents("""
+ * <p>Hello {name ?: "Qute"}</p>!
+ * """)
+ * record Hello(String name) implements TemplateInstance {
+ * }
+ *
+ * @GET
+ * @Produces(MediaType.TEXT_PLAIN)
+ * public TemplateInstance get(@QueryParam("name") String name) {
+ * return new Hello(name);
+ * }
+ * }
+ *
+ *
+ * + * Corresponding {@code qute-java://} URI for the record: + *
+ * + *+ * qute-java://org.acme.quarkus.sample.HelloResource$Hello@io.quarkus.qute.TemplateContents + *+ * + *
+ * Parsed into {@link JavaSourceLocationArguments}: + *
+ *+ * Example resulting {@link JavaSourceLocationResponse}: + *
+ *+ * The {@link JavaSourceLocationArguments} contains the parsed information from + * a {@code qute-java://} URI. The method returns a {@link CompletableFuture} that + * completes with a {@link JavaSourceLocationResponse} containing the Java file URI + * and the start line of the template content (or the method/class if applicable). + *
+ * + * @param args the arguments describing the Java element to resolve + * @return a future completing with the resolved Java source location + */ + @JsonRequest("qute/resolveJavaSource") + CompletableFuture+ * Extends the standard {@link IDebugProtocolClient} to include + * the {@link JavaSourceResolver} functionality for resolving + * Java sources referenced from Qute templates via {@code qute-java://} URIs. + *
+ * + *+ * Implementations of this interface can: + *