Skip to content

Commit a1980b3

Browse files
committed
Qute debugging support inside Java file
Signed-off-by: azerr <azerr@redhat.com>
1 parent 50c9111 commit a1980b3

File tree

9 files changed

+641
-8
lines changed

9 files changed

+641
-8
lines changed

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: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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+
package com.redhat.qute.jdt;
10+
11+
import java.util.logging.Level;
12+
import java.util.logging.Logger;
13+
14+
import org.eclipse.core.resources.IProject;
15+
import org.eclipse.core.resources.IWorkspaceRoot;
16+
import org.eclipse.core.resources.ResourcesPlugin;
17+
import org.eclipse.core.runtime.IProgressMonitor;
18+
import org.eclipse.jdt.core.IAnnotation;
19+
import org.eclipse.jdt.core.ICompilationUnit;
20+
import org.eclipse.jdt.core.IJavaProject;
21+
import org.eclipse.jdt.core.IMethod;
22+
import org.eclipse.jdt.core.ISourceRange;
23+
import org.eclipse.jdt.core.IType;
24+
import org.eclipse.jdt.core.JavaCore;
25+
import org.eclipse.jdt.core.JavaModelException;
26+
import org.eclipse.jdt.core.dom.AST;
27+
import org.eclipse.jdt.core.dom.ASTParser;
28+
import org.eclipse.jdt.core.dom.CompilationUnit;
29+
30+
import com.redhat.qute.jdt.debug.JavaSourceLocationArguments;
31+
import com.redhat.qute.jdt.debug.JavaSourceLocationEventArguments;
32+
import com.redhat.qute.jdt.debug.JavaSourceLocationEventResponse;
33+
import com.redhat.qute.jdt.debug.JavaSourceLocationResponse;
34+
import com.redhat.qute.jdt.utils.IJDTUtils;
35+
36+
/**
37+
* JDT-side implementation for resolving Java source locations referenced from
38+
* Qute templates.
39+
*
40+
* Supports: - type resolution - method resolution - annotation-based template
41+
* resolution (@TemplateContents) - text blocks (""" """)
42+
*/
43+
public class QuteSupportForDebug {
44+
45+
private static final Logger LOGGER = Logger.getLogger(QuteSupportForDebug.class.getName());
46+
47+
private static final QuteSupportForDebug INSTANCE = new QuteSupportForDebug();
48+
49+
public static QuteSupportForDebug getInstance() {
50+
return INSTANCE;
51+
}
52+
53+
public JavaSourceLocationEventResponse resolveJavaSource(JavaSourceLocationEventArguments event, IJDTUtils utils,
54+
IProgressMonitor monitor) {
55+
56+
JavaSourceLocationResponse response = new JavaSourceLocationResponse();
57+
58+
try {
59+
JavaSourceLocationArguments args = event.getArgs();
60+
61+
IType type = findType(args.getTypeName(), monitor);
62+
if (type == null) {
63+
return new JavaSourceLocationEventResponse(event.getId(), response);
64+
}
65+
66+
ICompilationUnit cu = type.getCompilationUnit();
67+
if (cu == null) {
68+
return new JavaSourceLocationEventResponse(event.getId(), response);
69+
}
70+
71+
response.setJavaFileUri(utils.toUri(cu));
72+
73+
// 1️⃣ Annotation-based template (highest priority)
74+
if (args.getAnnotation() != null) {
75+
IAnnotation annotation = findAnnotation(type, args.getAnnotation());
76+
77+
if (annotation != null) {
78+
int offset = computeTemplateContentOffset(cu, annotation);
79+
int line = computeLine(cu, offset);
80+
response.setStartLine(line);
81+
return new JavaSourceLocationEventResponse(event.getId(), response);
82+
}
83+
}
84+
85+
// 2️⃣ Method-based resolution
86+
if (args.getMethod() != null) {
87+
IMethod method = findMethod(type, args.getMethod());
88+
if (method != null) {
89+
int offset = method.getNameRange().getOffset();
90+
int line = computeLine(cu, offset);
91+
response.setStartLine(line);
92+
return new JavaSourceLocationEventResponse(event.getId(), response);
93+
}
94+
}
95+
96+
// 3️⃣ Fallback: type declaration
97+
int offset = type.getNameRange().getOffset();
98+
int line = computeLine(cu, offset);
99+
response.setStartLine(line);
100+
101+
} catch (Exception e) {
102+
LOGGER.log(Level.SEVERE, "Failed to resolve Java source location", e);
103+
}
104+
105+
return new JavaSourceLocationEventResponse(event.getId(), response);
106+
}
107+
108+
/* ---------------------------------------------------------------------- */
109+
/* Resolution helpers */
110+
/* ---------------------------------------------------------------------- */
111+
112+
private static IType findType(String className, IProgressMonitor monitor) {
113+
try {
114+
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
115+
116+
for (IProject project : root.getProjects()) {
117+
if (!project.isOpen() || !project.hasNature(JavaCore.NATURE_ID)) {
118+
continue;
119+
}
120+
121+
IJavaProject javaProject = JavaCore.create(project);
122+
123+
IType type = javaProject.findType(className, monitor);
124+
if (type != null) {
125+
return type;
126+
}
127+
128+
// Fallback java.lang
129+
if (!className.contains(".")) {
130+
type = javaProject.findType("java.lang." + className, monitor);
131+
if (type != null) {
132+
return type;
133+
}
134+
}
135+
}
136+
} catch (Exception e) {
137+
LOGGER.log(Level.SEVERE, "Error while finding type '" + className + "'", e);
138+
}
139+
return null;
140+
}
141+
142+
private static IMethod findMethod(IType type, String methodName) throws JavaModelException {
143+
144+
for (IMethod method : type.getMethods()) {
145+
if (method.getElementName().equals(methodName)) {
146+
return method;
147+
}
148+
}
149+
return null;
150+
}
151+
152+
private static IAnnotation findAnnotation(IType type, String annotationFqn) throws JavaModelException {
153+
154+
String simpleName = simpleName(annotationFqn);
155+
156+
for (IAnnotation ann : type.getAnnotations()) {
157+
if (ann.getElementName().equals(simpleName)) {
158+
return ann;
159+
}
160+
}
161+
return null;
162+
}
163+
164+
private static String simpleName(String fqn) {
165+
int idx = fqn.lastIndexOf('.');
166+
return idx == -1 ? fqn : fqn.substring(idx + 1);
167+
}
168+
169+
/* ---------------------------------------------------------------------- */
170+
/* Offset & line computation */
171+
/* ---------------------------------------------------------------------- */
172+
173+
private static int computeTemplateContentOffset(ICompilationUnit cu, IAnnotation annotation)
174+
throws JavaModelException {
175+
176+
ISourceRange range = annotation.getSourceRange();
177+
String source = cu.getSource();
178+
179+
int start = range.getOffset();
180+
int end = start + range.getLength();
181+
182+
String annotationSource = source.substring(start, end);
183+
184+
int paren = annotationSource.indexOf('(');
185+
if (paren == -1) {
186+
return start;
187+
}
188+
189+
int offset = start + paren + 1;
190+
191+
// Skip whitespace
192+
while (offset < source.length() && Character.isWhitespace(source.charAt(offset))) {
193+
offset++;
194+
}
195+
196+
// Text block
197+
if (source.startsWith("\"\"\"", offset)) {
198+
return offset + 3;
199+
}
200+
201+
// Classic string
202+
if (source.charAt(offset) == '"') {
203+
return offset + 1;
204+
}
205+
206+
return offset;
207+
}
208+
209+
private static int computeLine(ICompilationUnit cu, int offset) throws JavaModelException {
210+
211+
CompilationUnit ast = parse(cu);
212+
return ast.getLineNumber(offset) - 1; // DAP is 0-based
213+
}
214+
215+
private static CompilationUnit parse(ICompilationUnit cu) {
216+
ASTParser parser = ASTParser.newParser(AST.JLS_Latest);
217+
parser.setSource(cu);
218+
parser.setResolveBindings(false);
219+
return (CompilationUnit) parser.createAST(null);
220+
}
221+
}
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#process@io.quarkus.qute.TemplateContents
14+
* </pre>
15+
*
16+
* <p>
17+
* Interpretation:
18+
* </p>
19+
* <ul>
20+
* <li>javaElementUri = "qute-java://com.acme.Bean#process@io.quarkus.qute.TemplateContents"</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)