Skip to content

Commit 531116e

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

File tree

8 files changed

+557
-8
lines changed

8 files changed

+557
-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: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package com.redhat.qute.jdt;
2+
3+
import java.util.logging.Level;
4+
import java.util.logging.Logger;
5+
6+
import org.eclipse.core.resources.IProject;
7+
import org.eclipse.core.resources.IWorkspaceRoot;
8+
import org.eclipse.core.resources.ResourcesPlugin;
9+
import org.eclipse.core.runtime.IProgressMonitor;
10+
import org.eclipse.jdt.core.ICompilationUnit;
11+
import org.eclipse.jdt.core.IJavaProject;
12+
import org.eclipse.jdt.core.IType;
13+
import org.eclipse.jdt.core.JavaCore;
14+
import org.eclipse.jdt.core.dom.AST;
15+
import org.eclipse.jdt.core.dom.ASTParser;
16+
import org.eclipse.jdt.core.dom.CompilationUnit;
17+
18+
import com.redhat.qute.jdt.debug.JavaSourceLocationArguments;
19+
import com.redhat.qute.jdt.debug.JavaSourceLocationResponse;
20+
import com.redhat.qute.jdt.internal.debug.TemplateAndMethodVisitor;
21+
import com.redhat.qute.jdt.utils.IJDTUtils;
22+
23+
/**
24+
* AST-based version of QuteSupportForDebug.
25+
*
26+
* - Resolves annotation-based templates (@TemplateContents) - Resolves methods
27+
* (start line of method name) - Returns 0-based start lines (DAP) - Supports
28+
* TextBlock, StringLiteral, and value="" in annotations
29+
*/
30+
public class QuteSupportForDebug {
31+
32+
private static final Logger LOGGER = Logger.getLogger(QuteSupportForDebug.class.getName());
33+
34+
private static final QuteSupportForDebug INSTANCE = new QuteSupportForDebug();
35+
36+
public static QuteSupportForDebug getInstance() {
37+
return INSTANCE;
38+
}
39+
40+
public JavaSourceLocationResponse resolveJavaSource(JavaSourceLocationArguments args, IJDTUtils utils,
41+
IProgressMonitor monitor) {
42+
try {
43+
// 🔹 Resolve type
44+
IType type = findType(args.getTypeName(), monitor);
45+
if (type == null)
46+
return null;
47+
48+
ICompilationUnit cu = type.getCompilationUnit();
49+
if (cu == null)
50+
return null;
51+
52+
String javaFileUri = utils.toUri(cu);
53+
54+
// 🔹 1️⃣ Annotation-based template
55+
if (args.getAnnotation() != null) {
56+
int line = resolveAnnotationOrMethodStartLine(cu, args.getAnnotation(), null);
57+
if (line != -1) {
58+
return new JavaSourceLocationResponse(javaFileUri, line);
59+
}
60+
}
61+
62+
// 🔹 2️⃣ Method-based resolution
63+
if (args.getMethod() != null) {
64+
int line = resolveAnnotationOrMethodStartLine(cu, null, args.getMethod());
65+
if (line != -1) {
66+
return new JavaSourceLocationResponse(javaFileUri, line);
67+
}
68+
}
69+
70+
return null;
71+
72+
} catch (Exception e) {
73+
LOGGER.log(Level.SEVERE, "Failed to resolve Java source location", e);
74+
return null;
75+
}
76+
}
77+
78+
/* ---------------------------------------------------------------------- */
79+
/* Type resolution */
80+
/* ---------------------------------------------------------------------- */
81+
82+
private static IType findType(String className, IProgressMonitor monitor) {
83+
try {
84+
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
85+
86+
for (IProject project : root.getProjects()) {
87+
if (!project.isOpen() || !project.hasNature(JavaCore.NATURE_ID))
88+
continue;
89+
90+
IJavaProject javaProject = JavaCore.create(project);
91+
92+
IType type = javaProject.findType(className, monitor);
93+
if (type != null)
94+
return type;
95+
96+
// Fallback java.lang
97+
if (!className.contains(".")) {
98+
type = javaProject.findType("java.lang." + className, monitor);
99+
if (type != null)
100+
return type;
101+
}
102+
}
103+
} catch (Exception e) {
104+
LOGGER.log(Level.SEVERE, "Error while finding type '" + className + "'", e);
105+
}
106+
return null;
107+
}
108+
109+
/* ---------------------------------------------------------------------- */
110+
/* AST parser */
111+
/* ---------------------------------------------------------------------- */
112+
113+
private static CompilationUnit parse(ICompilationUnit cu) {
114+
ASTParser parser = ASTParser.newParser(AST.JLS_Latest);
115+
parser.setSource(cu);
116+
parser.setResolveBindings(false);
117+
return (CompilationUnit) parser.createAST(null);
118+
}
119+
120+
/* ---------------------------------------------------------------------- */
121+
/* Annotation or Method start line resolution */
122+
/* ---------------------------------------------------------------------- */
123+
124+
/**
125+
* Returns the 0-based start line of the annotation or method, or -1 if not
126+
* found
127+
*/
128+
private static int resolveAnnotationOrMethodStartLine(ICompilationUnit cu, String annotationFqn,
129+
String methodName) {
130+
CompilationUnit ast = parse(cu);
131+
TemplateAndMethodVisitor visitor = new TemplateAndMethodVisitor(annotationFqn, methodName);
132+
ast.accept(visitor);
133+
return visitor.getStartLine();
134+
}
135+
}
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+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.redhat.qute.jdt.debug;
2+
3+
/**
4+
* Response containing the location of a Java method, class, or template content
5+
* referenced from a Qute template.
6+
* <p>
7+
* Typically returned by {@link JavaSourceResolver#resolveJavaSource}.
8+
* </p>
9+
*
10+
* <h2>Example</h2>
11+
*
12+
* <pre>
13+
* qute-java://org.acme.quarkus.sample.HelloResource$Hello@io.quarkus.qute.TemplateContents
14+
* → javaFileUri = "file:///project/src/main/java/org/acme/quarkus/sample/HelloResource.java"
15+
* → startLine = 16
16+
* </pre>
17+
*
18+
* <p>
19+
* The {@code startLine} represents the line where the template content or the
20+
* Java element declaration starts. Lines are 1-based in this API (first line =
21+
* 1).
22+
* </p>
23+
*/
24+
public class JavaSourceLocationResponse {
25+
26+
/** URI of the Java source file containing the resolved element. */
27+
private String javaFileUri;
28+
29+
/**
30+
* Start line of the Java element or template content in the file.
31+
* <p>
32+
* 1-based index of the line where the element or template content starts.
33+
* </p>
34+
*/
35+
private int startLine;
36+
37+
public JavaSourceLocationResponse() {
38+
}
39+
40+
public JavaSourceLocationResponse(String javaFileUri, int startLine) {
41+
setJavaFileUri(javaFileUri);
42+
setStartLine(startLine);
43+
}
44+
45+
/**
46+
* Returns the URI of the Java source file containing the resolved element.
47+
*
48+
* @return the file URI
49+
*/
50+
public String getJavaFileUri() {
51+
return javaFileUri;
52+
}
53+
54+
/**
55+
* Sets the URI of the Java source file containing the resolved element.
56+
*
57+
* @param javaFileUri the file URI to set
58+
*/
59+
public void setJavaFileUri(String javaFileUri) {
60+
this.javaFileUri = javaFileUri;
61+
}
62+
63+
/**
64+
* Returns the start line of the Java element or template content in the source
65+
* file.
66+
*
67+
* @return 1-based start line of the element or template content
68+
*/
69+
public int getStartLine() {
70+
return startLine;
71+
}
72+
73+
/**
74+
* Sets the start line of the Java element or template content in the source
75+
* file.
76+
*
77+
* @param startLine 1-based start line of the element or template content
78+
*/
79+
public void setStartLine(int startLine) {
80+
this.startLine = startLine;
81+
}
82+
}

qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/AbstractQuteExtensionPointRegistry.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,12 @@ private synchronized void loadExtensionProviders() {
8080
LOGGER.log(Level.INFO, "->- Loading ." + getProviderExtensionId() + " extension point ->-");
8181

8282
IExtensionRegistry registry = Platform.getExtensionRegistry();
83-
IConfigurationElement[] cf = registry.getConfigurationElementsFor(QutePlugin.PLUGIN_ID,
84-
getProviderExtensionId());
85-
addExtensionProviders(cf);
86-
addRegistryListenerIfNeeded();
87-
83+
if (registry != null) {
84+
IConfigurationElement[] cf = registry.getConfigurationElementsFor(QutePlugin.PLUGIN_ID,
85+
getProviderExtensionId());
86+
addExtensionProviders(cf);
87+
addRegistryListenerIfNeeded();
88+
}
8889
LOGGER.log(Level.INFO, "-<- Done loading ." + getProviderExtensionId() + " extension point -<-");
8990
}
9091

@@ -142,12 +143,17 @@ private void addRegistryListenerIfNeeded() {
142143
return;
143144

144145
IExtensionRegistry registry = Platform.getExtensionRegistry();
145-
registry.addRegistryChangeListener(this, QutePlugin.PLUGIN_ID);
146-
registryListenerIntialized = true;
146+
if (registry != null) {
147+
registry.addRegistryChangeListener(this, QutePlugin.PLUGIN_ID);
148+
registryListenerIntialized = true;
149+
}
147150
}
148151

149152
public void destroy() {
150-
Platform.getExtensionRegistry().removeRegistryChangeListener(this);
153+
IExtensionRegistry registry = Platform.getExtensionRegistry();
154+
if (registry != null) {
155+
registry.removeRegistryChangeListener(this);
156+
}
151157
}
152158

153159
public void initialize() {

0 commit comments

Comments
 (0)