Skip to content

Commit 34f4a8d

Browse files
committed
Qute debugging support inside Java file
Signed-off-by: azerr <[email protected]>
1 parent 50c9111 commit 34f4a8d

File tree

10 files changed

+648
-8
lines changed

10 files changed

+648
-8
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.acme.sample;
2+
3+
import io.quarkus.qute.TemplateContents;
4+
import io.quarkus.qute.TemplateInstance;
5+
import jakarta.ws.rs.GET;
6+
import jakarta.ws.rs.Path;
7+
import jakarta.ws.rs.QueryParam;
8+
9+
public class TemplateContentsResource {
10+
11+
@TemplateContents(value = "Hello {name}!")
12+
record Hello(String name) implements TemplateInstance {}
13+
14+
@GET
15+
public TemplateInstance hello(@QueryParam("name") String name) {
16+
return new Hello(name);
17+
}
18+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Red Hat Inc. and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* which accompanies this distribution, and is available at
5+
* http://www.eclipse.org/legal/epl-v20.html
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat Inc. - initial API and implementation
11+
*******************************************************************************/
12+
package com.redhat.qute.jdt.debug;
13+
14+
import static com.redhat.qute.jdt.QuteProjectTest.getJDTUtils;
15+
import static com.redhat.qute.jdt.QuteProjectTest.loadMavenProject;
16+
import static org.junit.Assert.assertEquals;
17+
18+
import org.eclipse.core.runtime.NullProgressMonitor;
19+
import org.junit.Test;
20+
21+
import com.redhat.qute.jdt.QuteProjectTest.QuteMavenProjectName;
22+
import com.redhat.qute.jdt.QuteSupportForDebug;
23+
24+
public class DebugGetStartLineTest {
25+
26+
@Test
27+
public void findStartLine() throws Exception {
28+
loadMavenProject(QuteMavenProjectName.qute_record);
29+
30+
assertStartLine(loc("org.acme.sample.TemplateContentsResource$Hello", "io.quarkus.qute.TemplateContents"), 11);
31+
}
32+
33+
private static void assertStartLine(JavaSourceLocationArguments args, Integer actual) {
34+
JavaSourceLocationResponse response = QuteSupportForDebug.getInstance().resolveJavaSource(args, getJDTUtils(),
35+
new NullProgressMonitor());
36+
Integer expected = response != null ? response.getStartLine() : null;
37+
assertEquals(expected, actual);
38+
}
39+
40+
private static JavaSourceLocationArguments loc(String typeName, String annotation) {
41+
return loc(typeName, null, annotation);
42+
}
43+
44+
private static JavaSourceLocationArguments loc(String typeName, String metthod, String annotation) {
45+
JavaSourceLocationArguments args = new JavaSourceLocationArguments();
46+
args.setTypeName(typeName);
47+
args.setMethod(metthod);
48+
args.setAnnotation(annotation);
49+
return args;
50+
}
51+
}

qute.jdt/com.redhat.qute.jdt/META-INF/MANIFEST.MF

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Export-Package: com.redhat.qute.commons,
2525
com.redhat.qute.commons.jaxrs,
2626
com.redhat.qute.commons.usertags,
2727
com.redhat.qute.jdt,
28+
com.redhat.qute.jdt.debug,
2829
com.redhat.qute.jdt.internal.java;x-friends:="com.redhat.qute.jdt.test",
2930
com.redhat.qute.jdt.internal.ls;x-friends:="com.redhat.qute.jdt.test",
3031
com.redhat.qute.jdt.internal.template;x-friends:="com.redhat.qute.jdt.test",

qute.jdt/com.redhat.qute.jdt/plugin.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@
3737
</delegateCommandHandler>
3838
</extension>
3939

40+
<!-- Delegate command handler for Qute debugging -->
41+
<extension point="org.eclipse.jdt.ls.core.delegateCommandHandler">
42+
<delegateCommandHandler class="com.redhat.qute.jdt.internal.ls.QuteSupportForDebugDelegateCommandHandler">
43+
<command id="qute/debug/resolveJavaSource"/>
44+
</delegateCommandHandler>
45+
</extension>
46+
4047
<!-- =========== Qute core & Quarkus Integration =========== -->
4148

4249
<!-- Template root path providers for Qute core (src/main/resources/templates) -->
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Red Hat Inc. and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* which accompanies this distribution, and is available at
5+
* http://www.eclipse.org/legal/epl-v20.html
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Red Hat Inc. - initial API and implementation
11+
*******************************************************************************/
12+
package com.redhat.qute.jdt;
13+
14+
import java.util.logging.Level;
15+
import java.util.logging.Logger;
16+
17+
import org.eclipse.core.resources.IProject;
18+
import org.eclipse.core.resources.IWorkspaceRoot;
19+
import org.eclipse.core.resources.ResourcesPlugin;
20+
import org.eclipse.core.runtime.IProgressMonitor;
21+
import org.eclipse.jdt.core.ICompilationUnit;
22+
import org.eclipse.jdt.core.IJavaProject;
23+
import org.eclipse.jdt.core.IType;
24+
import org.eclipse.jdt.core.JavaCore;
25+
import org.eclipse.jdt.core.dom.AST;
26+
import org.eclipse.jdt.core.dom.ASTParser;
27+
import org.eclipse.jdt.core.dom.CompilationUnit;
28+
29+
import com.redhat.qute.jdt.debug.JavaSourceLocationArguments;
30+
import com.redhat.qute.jdt.debug.JavaSourceLocationResponse;
31+
import com.redhat.qute.jdt.internal.debug.TemplateAndMethodVisitor;
32+
import com.redhat.qute.jdt.utils.IJDTUtils;
33+
34+
/**
35+
* AST-based version of QuteSupportForDebug.
36+
*
37+
* - Resolves annotation-based templates (@TemplateContents) - Resolves methods
38+
* (start line of method name) - Returns 0-based start lines (DAP) - Supports
39+
* TextBlock, StringLiteral, and value="" in annotations
40+
*/
41+
public class QuteSupportForDebug {
42+
43+
private static final Logger LOGGER = Logger.getLogger(QuteSupportForDebug.class.getName());
44+
45+
private static final QuteSupportForDebug INSTANCE = new QuteSupportForDebug();
46+
47+
public static QuteSupportForDebug getInstance() {
48+
return INSTANCE;
49+
}
50+
51+
public JavaSourceLocationResponse resolveJavaSource(JavaSourceLocationArguments args, IJDTUtils utils,
52+
IProgressMonitor monitor) {
53+
try {
54+
// 🔹 Resolve type
55+
IType type = findType(args.getTypeName(), monitor);
56+
if (type == null)
57+
return null;
58+
59+
ICompilationUnit cu = type.getCompilationUnit();
60+
if (cu == null)
61+
return null;
62+
63+
String javaFileUri = utils.toUri(cu);
64+
65+
// 🔹 1️⃣ Annotation-based template
66+
if (args.getAnnotation() != null) {
67+
int line = resolveAnnotationOrMethodStartLine(cu, args.getAnnotation(), null);
68+
if (line != -1) {
69+
return new JavaSourceLocationResponse(javaFileUri, line);
70+
}
71+
}
72+
73+
// 🔹 2️⃣ Method-based resolution
74+
if (args.getMethod() != null) {
75+
int line = resolveAnnotationOrMethodStartLine(cu, null, args.getMethod());
76+
if (line != -1) {
77+
return new JavaSourceLocationResponse(javaFileUri, line);
78+
}
79+
}
80+
81+
return null;
82+
83+
} catch (Exception e) {
84+
LOGGER.log(Level.SEVERE, "Failed to resolve Java source location", e);
85+
return null;
86+
}
87+
}
88+
89+
/* ---------------------------------------------------------------------- */
90+
/* Type resolution */
91+
/* ---------------------------------------------------------------------- */
92+
93+
private static IType findType(String className, IProgressMonitor monitor) {
94+
try {
95+
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
96+
97+
for (IProject project : root.getProjects()) {
98+
if (!project.isOpen() || !project.hasNature(JavaCore.NATURE_ID))
99+
continue;
100+
101+
IJavaProject javaProject = JavaCore.create(project);
102+
103+
IType type = javaProject.findType(className, monitor);
104+
if (type != null)
105+
return type;
106+
107+
// Fallback java.lang
108+
if (!className.contains(".")) {
109+
type = javaProject.findType("java.lang." + className, monitor);
110+
if (type != null)
111+
return type;
112+
}
113+
}
114+
} catch (Exception e) {
115+
LOGGER.log(Level.SEVERE, "Error while finding type '" + className + "'", e);
116+
}
117+
return null;
118+
}
119+
120+
/* ---------------------------------------------------------------------- */
121+
/* AST parser */
122+
/* ---------------------------------------------------------------------- */
123+
124+
private static CompilationUnit parse(ICompilationUnit cu) {
125+
ASTParser parser = ASTParser.newParser(AST.JLS_Latest);
126+
parser.setSource(cu);
127+
parser.setResolveBindings(false);
128+
return (CompilationUnit) parser.createAST(null);
129+
}
130+
131+
/* ---------------------------------------------------------------------- */
132+
/* Annotation or Method start line resolution */
133+
/* ---------------------------------------------------------------------- */
134+
135+
/**
136+
* Returns the 0-based start line of the annotation or method, or -1 if not
137+
* found
138+
*/
139+
private static int resolveAnnotationOrMethodStartLine(ICompilationUnit cu, String annotationFqn,
140+
String methodName) {
141+
CompilationUnit ast = parse(cu);
142+
TemplateAndMethodVisitor visitor = new TemplateAndMethodVisitor(annotationFqn, methodName);
143+
ast.accept(visitor);
144+
return visitor.getStartLine();
145+
}
146+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package com.redhat.qute.jdt.debug;
2+
3+
/**
4+
* Arguments describing a Java element referenced from a Qute template.
5+
* <p>
6+
* Sent by the Debug Adapter via {@link JavaSourceResolver#resolveJavaSource}
7+
* to the client in order to locate the corresponding Java source file and position.
8+
* </p>
9+
*
10+
* <h2>Example of a qute-java URI</h2>
11+
*
12+
* <pre>
13+
* qute-java://com.acme.Bean#[email protected]
14+
* </pre>
15+
*
16+
* <p>
17+
* Interpretation:
18+
* </p>
19+
* <ul>
20+
* <li>javaElementUri = "qute-java://com.acme.Bean#[email protected]"</li>
21+
* <li>typeName = "com.acme.Bean"</li>
22+
* <li>method = "process" (optional; if null, the annotation is applied on the class)</li>
23+
* <li>annotation = "io.quarkus.qute.TemplateContents"</li>
24+
* </ul>
25+
*/
26+
public class JavaSourceLocationArguments {
27+
28+
/** The qute-java URI used to locate the Java element from a template. */
29+
private String javaElementUri;
30+
31+
/** Fully qualified Java class, interface name (e.g., "com.acme.Bean"). */
32+
private String typeName;
33+
34+
/**
35+
* Java method name.
36+
* <p>
37+
* Optional: if {@code null}, the annotation is applied to the class itself.
38+
* </p>
39+
*/
40+
private String method;
41+
42+
/** Fully qualified Java annotation name (typically "io.quarkus.qute.TemplateContents"). */
43+
private String annotation;
44+
45+
/**
46+
* Returns the qute-java URI used to locate the Java element.
47+
*
48+
* @return the URI string
49+
*/
50+
public String getJavaElementUri() {
51+
return javaElementUri;
52+
}
53+
54+
/**
55+
* Sets the qute-java URI used to locate the Java element.
56+
*
57+
* @param javaElementUri the URI string to set
58+
*/
59+
public void setJavaElementUri(String javaElementUri) {
60+
this.javaElementUri = javaElementUri;
61+
}
62+
63+
/**
64+
* Returns the fully qualified Java class, interface name.
65+
*
66+
* @return the class name
67+
*/
68+
public String getTypeName() {
69+
return typeName;
70+
}
71+
72+
/**
73+
* Sets the fully qualified Java class, interface name.
74+
*
75+
* @param typeName the class, interface name to set
76+
*/
77+
public void setTypeName(String typeName) {
78+
this.typeName = typeName;
79+
}
80+
81+
/**
82+
* Returns the Java method name.
83+
*
84+
* @return the method name, or {@code null} if the annotation applies to the class
85+
*/
86+
public String getMethod() {
87+
return method;
88+
}
89+
90+
/**
91+
* Sets the Java method name.
92+
*
93+
* @param method the method name to set, or {@code null} if the annotation applies to the class
94+
*/
95+
public void setMethod(String method) {
96+
this.method = method;
97+
}
98+
99+
/**
100+
* Returns the fully qualified Java annotation name.
101+
*
102+
* @return the annotation name (e.g., "io.quarkus.qute.TemplateContents")
103+
*/
104+
public String getAnnotation() {
105+
return annotation;
106+
}
107+
108+
/**
109+
* Sets the fully qualified Java annotation name.
110+
*
111+
* @param annotation the annotation name to set
112+
*/
113+
public void setAnnotation(String annotation) {
114+
this.annotation = annotation;
115+
}
116+
}

0 commit comments

Comments
 (0)