Skip to content

Commit d817830

Browse files
fbriconangelozerr
authored andcommitted
fix: respect basePath in @CheckedTemplate to find templates
Signed-off-by: Fred Bricon <fbricon@gmail.com>
1 parent 9c95979 commit d817830

File tree

13 files changed

+316
-59
lines changed

13 files changed

+316
-59
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.acme.qute;
2+
3+
import java.math.BigDecimal;
4+
import java.util.ArrayList;
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
import javax.ws.rs.GET;
9+
import javax.ws.rs.Path;
10+
import javax.ws.rs.Produces;
11+
import javax.ws.rs.core.MediaType;
12+
13+
import io.quarkus.qute.CheckedTemplate;
14+
import io.quarkus.qute.TemplateExtension;
15+
import io.quarkus.qute.TemplateInstance;
16+
17+
@Path("items3")
18+
public class ItemResourceWithCustomBasePath {
19+
20+
@CheckedTemplate(basePath="ItemResourceWithFragment")
21+
static class Templates {
22+
static native TemplateInstance items(List<Item> items);
23+
static native TemplateInstance items$id1(List<Item> items);
24+
static native TemplateInstance items3$id2(List<Item> items);
25+
static native TemplateInstance items3$(List<Item> items);
26+
}
27+
28+
@GET
29+
@Produces(MediaType.TEXT_HTML)
30+
public TemplateInstance get() {
31+
List<Item> items = new ArrayList<>();
32+
items.add(new Item(new BigDecimal(10), "Apple"));
33+
items.add(new Item(new BigDecimal(16), "Pear"));
34+
items.add(new Item(new BigDecimal(30), "Orange"));
35+
return Templates.items(items);
36+
}
37+
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.acme.qute;
2+
3+
import java.util.List;
4+
import io.quarkus.qute.CheckedTemplate;
5+
import io.quarkus.qute.TemplateInstance;
6+
7+
@CheckedTemplate(basePath="ItemResourceWithFragment")
8+
public class ItemTemplatesCustomBasePath {
9+
10+
static native TemplateInstance items(List<Item> items);
11+
static native TemplateInstance items$id1(List<Item> items);
12+
static native TemplateInstance items3$id2(List<Item> items);
13+
static native TemplateInstance items3$(List<Item> items);
14+
15+
}

src/main/java/com/redhat/devtools/intellij/qute/psi/internal/QuteJavaConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public class QuteJavaConstants {
4949
public static final String OLD_CHECKED_TEMPLATE_ANNOTATION = "io.quarkus.qute.api.CheckedTemplate";
5050

5151
public static final String CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS = "ignoreFragments";
52+
public static final String CHECKED_TEMPLATE_ANNOTATION_BASE_PATH = "basePath";
5253

5354
// @TemplateExtension
5455

src/main/java/com/redhat/devtools/intellij/qute/psi/internal/java/AbstractQuteTemplateLinkCollector.java

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.OLD_CHECKED_TEMPLATE_ANNOTATION;
4242
import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS;
4343
import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.TEMPLATE_CLASS;
44+
import static com.redhat.devtools.intellij.qute.psi.internal.template.datamodel.CheckedTemplateSupport.getBasePath;
45+
import static com.redhat.devtools.intellij.qute.psi.internal.template.datamodel.CheckedTemplateSupport.isIgnoreFragments;
4446

4547
/**
4648
* Abstract class which collects {@link PsiMethod} or
@@ -108,7 +110,7 @@ public void visitField(PsiField node) {
108110
.getLocationExpressionFromConstructorParameter(node.getName());
109111
}
110112
String fieldName = node.getName();
111-
collectTemplateLink(node, locationExpression, getTypeDeclaration(node), null, fieldName, false);
113+
collectTemplateLink(null, node, locationExpression, getTypeDeclaration(node), null, fieldName, false);
112114
}
113115
super.visitField(node);
114116
}
@@ -137,67 +139,39 @@ public void visitClass(PsiClass node) {
137139
// public static class Templates {
138140
// public static native TemplateInstance book(Book book);
139141
boolean ignoreFragments = isIgnoreFragments(annotation);
142+
String basePath = getBasePath(annotation);
140143
for(PsiMethod method : node.getMethods()) {
141-
collectTemplateLink(method, node, ignoreFragments );
144+
collectTemplateLink(basePath, method, node, ignoreFragments );
142145
}
143146
}
144147
}
145148
super.visitClass(node);
146149
levelTypeDecl--;
147150
}
148151

149-
/**
150-
* Returns true if @CheckedTemplate annotation declares that fragment must be
151-
* ignored and false otherwise.
152-
*
153-
* <code>
154-
* @CheckedTemplate(ignoreFragments=true)
155-
* </code>
156-
*
157-
* @param checkedTemplateAnnotation the CheckedTemplate annotation.
158-
*
159-
* @return true if @CheckedTemplate annotation declares that fragment must be
160-
* ignored and false otherwise.
161-
*/
162-
private static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation) {
163-
Boolean ignoreFragment = null;
164-
try {
165-
for(PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
166-
if (CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS.equalsIgnoreCase(pair.getAttributeName())) {
167-
ignoreFragment = AnnotationUtils.getValueAsBoolean(pair);
168-
}
169-
}
170-
} catch (IndexNotReadyException | ProcessCanceledException | CancellationException e) {
171-
throw e;
172-
} catch (Exception e) {
173-
// Do nothing
174-
}
175-
return ignoreFragment != null ? ignoreFragment.booleanValue() : false;
176-
}
177-
178152
private static PsiClass getTypeDeclaration(PsiElement node) {
179153
return PsiTreeUtil.getParentOfType(node, PsiClass.class);
180154
}
181155

182156

183-
private void collectTemplateLink(PsiMethod methodDeclaration, PsiClass type, boolean ignoreFragments) {
157+
private void collectTemplateLink(String basePath, PsiMethod methodDeclaration, PsiClass type, boolean ignoreFragments) {
184158
String className = null;
185159
boolean innerClass = levelTypeDecl > 1;
186160
if (innerClass) {
187161
className = PsiTypeUtils.getSimpleClassName(typeRoot.getName());
188162
}
189163
String methodName = methodDeclaration.getName();
190-
collectTemplateLink(methodDeclaration, null, type, className, methodName, ignoreFragments );
164+
collectTemplateLink(basePath, methodDeclaration, null, type, className, methodName, ignoreFragments );
191165
}
192166

193-
private void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
167+
private void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
194168
String fieldOrMethodName, boolean ignoreFragment ) {
195169
try {
196170
String location = locationAnnotation != null && locationAnnotation.getValue() instanceof String ? (String) locationAnnotation.getValue() : null;
197171
Module project = utils.getModule();
198172
TemplatePathInfo templatePathInfo = location != null
199-
? PsiQuteProjectUtils.getTemplatePath(null, location, ignoreFragment)
200-
: PsiQuteProjectUtils.getTemplatePath(className, fieldOrMethodName, ignoreFragment);
173+
? PsiQuteProjectUtils.getTemplatePath(basePath, null, location, ignoreFragment)
174+
: PsiQuteProjectUtils.getTemplatePath(basePath, className, fieldOrMethodName, ignoreFragment);
201175

202176
VirtualFile templateFile = null;
203177
if (location == null) {
@@ -208,7 +182,7 @@ private void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locat
208182
} else {
209183
templateFile = getVirtualFile(project, templatePathInfo.getTemplateUri(), "");
210184
}
211-
collectTemplateLink(fieldOrMethod, locationAnnotation, type, className, fieldOrMethodName, location,
185+
collectTemplateLink(basePath, fieldOrMethod, locationAnnotation, type, className, fieldOrMethodName, location,
212186
templateFile, templatePathInfo);
213187
} catch (IndexNotReadyException | ProcessCanceledException | CancellationException e) {
214188
throw e;
@@ -275,7 +249,7 @@ protected Range createRange(PsiElement fieldOrMethod) {
275249
return utils.toRange(typeRoot, tr.getStartOffset(), tr.getLength());
276250
}
277251

278-
protected abstract void collectTemplateLink(PsiElement node, PsiLiteralValue locationAnnotation, PsiClass type,
252+
protected abstract void collectTemplateLink(String basePath, PsiElement node, PsiLiteralValue locationAnnotation, PsiClass type,
279253
String className, String fieldOrMethodName, String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo);
280254

281255
private static boolean isTemplateType(PsiType type) {

src/main/java/com/redhat/devtools/intellij/qute/psi/internal/java/QuteJavaCodeLensCollector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public QuteJavaCodeLensCollector(PsiFile typeRoot, List<CodeLens> lenses, IPsiUt
5959
}
6060

6161
@Override
62-
protected void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className, String fieldOrMethodName,
62+
protected void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className, String fieldOrMethodName,
6363
String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo) {
6464
if (!templatePathInfo.isValid()) {
6565
// It is an empty fragment which is not valid, don't generate a codelens.

src/main/java/com/redhat/devtools/intellij/qute/psi/internal/java/QuteJavaDiagnosticsCollector.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626

2727
import java.util.List;
2828

29+
import static com.redhat.devtools.intellij.qute.psi.utils.PsiQuteProjectUtils.appendAndSlash;
30+
import static io.quarkus.qute.CheckedTemplate.DEFAULTED;
31+
2932
/**
3033
* Report diagnostics error for non existing Qute template for:
3134
*
@@ -49,13 +52,13 @@ public QuteJavaDiagnosticsCollector(PsiFile typeRoot, List<Diagnostic> diagnosti
4952
}
5053

5154
@Override
52-
protected void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
55+
protected void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
5356
String fieldOrMethodName, String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo) {
5457
QuteErrorCode error = getQuteErrorCode(templatePathInfo, templateFile);
5558
if (error == null) {
5659
return;
5760
}
58-
String path = createPath(className, fieldOrMethodName, location);
61+
String path = createPath(basePath, className, fieldOrMethodName, location);
5962
String fragmentId = templatePathInfo.getFragmentId();
6063
if (templatePathInfo.hasFragment() && path.endsWith(fragmentId)) {
6164
// Adjust path by removing fragment information
@@ -92,14 +95,17 @@ private static QuteErrorCode getQuteErrorCode(TemplatePathInfo templatePathInfo,
9295
return null;
9396
}
9497

95-
private static String createPath(String className, String fieldOrMethodName, String location) {
98+
private static String createPath(String basePath, String className, String fieldOrMethodName, String location) {
9699
if (location != null) {
97100
return location;
98101
}
99-
if (className == null) {
100-
return fieldOrMethodName;
102+
StringBuilder path = new StringBuilder();
103+
if (basePath != null && !DEFAULTED.equals(basePath)) {
104+
appendAndSlash(path, basePath);
105+
} else if (className != null){
106+
appendAndSlash(path, className);
101107
}
102-
return className + '/' + fieldOrMethodName;
108+
return path.append(fieldOrMethodName).toString();
103109
}
104110

105111
private static Diagnostic createDiagnostic(Range range, DiagnosticSeverity severity, IQuteErrorCode errorCode,

src/main/java/com/redhat/devtools/intellij/qute/psi/internal/java/QuteJavaDocumentLinkCollector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public QuteJavaDocumentLinkCollector(PsiFile typeRoot, List<DocumentLink> links,
5050
}
5151

5252
@Override
53-
protected void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
53+
protected void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
5454
String fieldOrMethodName, String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo) {
5555
if (!templatePathInfo.isValid()) {
5656
// It is an empty fragment which is not valid, don't generate a document link.

src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/datamodel/CheckedTemplateSupport.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ protected void processAnnotation(PsiElement javaElement, PsiAnnotation checkedTe
8080
if (javaElement instanceof PsiClass) {
8181
PsiClass type = (PsiClass) javaElement;
8282
boolean ignoreFragments = isIgnoreFragments(checkedTemplateAnnotation);
83-
collectDataModelTemplateForCheckedTemplate(type, ignoreFragments, context.getTypeResolver(type),
83+
String basePath = getBasePath(checkedTemplateAnnotation);
84+
collectDataModelTemplateForCheckedTemplate(type, basePath, ignoreFragments, context.getTypeResolver(type),
8485
context.getDataModelProject().getTemplates(), monitor);
8586
}
8687
}
@@ -96,7 +97,7 @@ protected void processAnnotation(PsiElement javaElement, PsiAnnotation checkedTe
9697
* ignored and false otherwise.
9798
* @CheckedTemplate(ignoreFragments=true) </code>
9899
*/
99-
private static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation) {
100+
public static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation) {
100101
Boolean ignoreFragment = null;
101102
try {
102103
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
@@ -112,15 +113,41 @@ private static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation
112113
return ignoreFragment != null ? ignoreFragment.booleanValue() : false;
113114
}
114115

116+
/**
117+
* Returns the <code>basePath</code> value declared in the @CheckedTemplate annotation, relative to the templates root, to search the templates from.
118+
*<code>
119+
* @CheckedTemplate(basePath="somewhere")
120+
*</code>
121+
*
122+
* @param checkedTemplateAnnotation the CheckedTemplate annotation.
123+
* @return the <code>basePath</code> value declared in the @CheckedTemplate annotation
124+
*/
125+
public static String getBasePath(PsiAnnotation checkedTemplateAnnotation) {
126+
String basePath = null;
127+
try {
128+
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
129+
if (CHECKED_TEMPLATE_ANNOTATION_BASE_PATH.equalsIgnoreCase(pair.getAttributeName())) {
130+
basePath = pair.getLiteralValue();
131+
}
132+
}
133+
} catch (IndexNotReadyException | ProcessCanceledException | CancellationException e) {
134+
throw e;
135+
} catch (Exception e) {
136+
// Do nothing
137+
}
138+
return basePath;
139+
}
140+
115141
/**
116142
* Collect data model template from @CheckedTemplate.
117143
*
118144
* @param type the Java type.
145+
* @param basePath the base path relative to the templates root
119146
* @param ignoreFragments true if fragments must be ignored and false otherwise.
120147
* @param templates the data model templates to update with collect of template.
121148
* @param monitor the progress monitor.
122149
*/
123-
private static void collectDataModelTemplateForCheckedTemplate(PsiClass type, boolean ignoreFragments, ITypeResolver typeResolver,
150+
private static void collectDataModelTemplateForCheckedTemplate(PsiClass type, String basePath, boolean ignoreFragments, ITypeResolver typeResolver,
124151
List<DataModelTemplate<DataModelParameter>> templates, ProgressIndicator monitor) {
125152
boolean innerClass = type.getContainingClass() != null;
126153
String className = !innerClass ? null
@@ -131,7 +158,7 @@ private static void collectDataModelTemplateForCheckedTemplate(PsiClass type, bo
131158
PsiMethod[] methods = type.getMethods();
132159
for (PsiMethod method : methods) {
133160
// src/main/resources/templates/${className}/${methodName}.qute.html
134-
TemplatePathInfo templatePathInfo = getTemplatePath(className, method.getName(), ignoreFragments);
161+
TemplatePathInfo templatePathInfo = getTemplatePath(basePath, className, method.getName(), ignoreFragments);
135162

136163
// Get or create template
137164
String templateUri = templatePathInfo.getTemplateUri();

src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/datamodel/TemplateFieldSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ private static DataModelTemplate<DataModelParameter> createTemplateDataModel(Psi
117117
String location = locationFromConstructorParameter != null ? locationFromConstructorParameter : getLocation(field);
118118
String fieldName = field.getName();
119119
// src/main/resources/templates/${methodName}.qute.html
120-
String templateUri = getTemplatePath(null, location != null ? location : fieldName, true).getTemplateUri();
120+
String templateUri = getTemplatePath(null,null, location != null ? location : fieldName, true).getTemplateUri();
121121

122122
// Create template data model with:
123123
// - template uri : Qute template file which must be bind with data model.

src/main/java/com/redhat/devtools/intellij/qute/psi/utils/PsiQuteProjectUtils.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils;
1818
import com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants;
1919
import com.redhat.qute.commons.ProjectInfo;
20+
import org.jetbrains.annotations.NotNull;
2021

2122
/**
2223
* JDT Qute utilities.
@@ -28,6 +29,11 @@ public class PsiQuteProjectUtils {
2829

2930
private static final String TEMPLATES_BASE_DIR = "src/main/resources/templates/";
3031

32+
/**
33+
* Value for Qute annotations indicating behaviour should be using the default
34+
*/
35+
private static final String DEFAULTED = "<<defaulted>>";
36+
3137
private PsiQuteProjectUtils() {
3238
}
3339

@@ -61,21 +67,23 @@ public static boolean hasQuteSupport(Module javaProject) {
6167
return PsiTypeUtils.findType(javaProject, QuteJavaConstants.ENGINE_BUILDER_CLASS) != null;
6268
}
6369

64-
public static String getTemplatePath(String className, String methodOrFieldName) {
70+
public static String getTemplatePath(String basePath, String className, String methodOrFieldName) {
6571
StringBuilder path = new StringBuilder(TEMPLATES_BASE_DIR);
66-
if (className != null) {
67-
path.append(className);
68-
path.append('/');
72+
if (basePath != null && !DEFAULTED.equals(basePath)) {
73+
appendAndSlash(path, basePath);
74+
} else if (className != null) {
75+
appendAndSlash(path, className);
6976
}
7077
return path.append(methodOrFieldName).toString();
7178
}
7279

73-
public static TemplatePathInfo getTemplatePath(String className, String methodOrFieldName, boolean ignoreFragments) {
80+
public static TemplatePathInfo getTemplatePath(String basePath, String className, String methodOrFieldName, boolean ignoreFragments) {
7481
String fragmentId = null;
7582
StringBuilder templateUri = new StringBuilder(TEMPLATES_BASE_DIR);
76-
if (className != null) {
77-
templateUri.append(className);
78-
templateUri.append('/');
83+
if (basePath != null && !DEFAULTED.equals(basePath)) {
84+
appendAndSlash(templateUri, basePath);
85+
} else if (className != null) {
86+
appendAndSlash(templateUri, className);
7987
}
8088
if (!ignoreFragments) {
8189
int fragmentIndex = methodOrFieldName != null ? methodOrFieldName.lastIndexOf('$') : -1;
@@ -87,4 +95,16 @@ public static TemplatePathInfo getTemplatePath(String className, String methodOr
8795
templateUri.append(methodOrFieldName);
8896
return new TemplatePathInfo(templateUri.toString(), fragmentId);
8997
}
98+
99+
/**
100+
* Appends a segment to a path, add trailing "/" if necessary
101+
* @param path the path to append to
102+
* @param segment the segment to append to the path
103+
*/
104+
public static void appendAndSlash(@NotNull StringBuilder path, @NotNull String segment) {
105+
path.append(segment);
106+
if (!segment.endsWith("/")) {
107+
path.append('/');
108+
}
109+
}
90110
}

0 commit comments

Comments
 (0)