Skip to content

Commit

Permalink
fix: respect basePath in @CheckedTemplate to find templates
Browse files Browse the repository at this point in the history
Signed-off-by: Fred Bricon <[email protected]>
  • Loading branch information
fbricon committed Sep 19, 2023
1 parent db1ab9b commit 92ee81c
Show file tree
Hide file tree
Showing 13 changed files with 290 additions and 75 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.acme.qute;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateExtension;
import io.quarkus.qute.TemplateInstance;

@Path("items3")
public class ItemResourceWithCustomBasePath {

@CheckedTemplate(basePath="ItemResourceWithFragment")
static class Templates {
static native TemplateInstance items(List<Item> items);
static native TemplateInstance items$id1(List<Item> items);
static native TemplateInstance items3$id2(List<Item> items);
static native TemplateInstance items3$(List<Item> items);
}

@GET
@Produces(MediaType.TEXT_HTML)
public TemplateInstance get() {
List<Item> items = new ArrayList<>();
items.add(new Item(new BigDecimal(10), "Apple"));
items.add(new Item(new BigDecimal(16), "Pear"));
items.add(new Item(new BigDecimal(30), "Orange"));
return Templates.items(items);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.acme.qute;

import java.util.List;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;

@CheckedTemplate(basePath="ItemResourceWithFragment")
public class ItemTemplatesCustomBasePath {

static native TemplateInstance items(List<Item> items);
static native TemplateInstance items$id1(List<Item> items);
static native TemplateInstance items3$id2(List<Item> items);
static native TemplateInstance items3$(List<Item> items);

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class QuteJavaConstants {
public static final String OLD_CHECKED_TEMPLATE_ANNOTATION = "io.quarkus.qute.api.CheckedTemplate";

public static final String CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS = "ignoreFragments";
public static final String CHECKED_TEMPLATE_ANNOTATION_BASE_PATH = "basePath";

// @TemplateExtension

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.OLD_CHECKED_TEMPLATE_ANNOTATION;
import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS;
import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.TEMPLATE_CLASS;
import static com.redhat.devtools.intellij.qute.psi.internal.template.datamodel.CheckedTemplateSupport.getBasePath;
import static com.redhat.devtools.intellij.qute.psi.internal.template.datamodel.CheckedTemplateSupport.isIgnoreFragments;

/**
* Abstract class which collects {@link PsiMethod} or
Expand Down Expand Up @@ -108,7 +110,7 @@ public void visitField(PsiField node) {
.getLocationExpressionFromConstructorParameter(node.getName());
}
String fieldName = node.getName();
collectTemplateLink(node, locationExpression, getTypeDeclaration(node), null, fieldName, false);
collectTemplateLink(null, node, locationExpression, getTypeDeclaration(node), null, fieldName, false);
}
super.visitField(node);
}
Expand Down Expand Up @@ -137,67 +139,39 @@ public void visitClass(PsiClass node) {
// public static class Templates {
// public static native TemplateInstance book(Book book);
boolean ignoreFragments = isIgnoreFragments(annotation);
String basePath = getBasePath(annotation);
for(PsiMethod method : node.getMethods()) {
collectTemplateLink(method, node, ignoreFragments );
collectTemplateLink(basePath, method, node, ignoreFragments );
}
}
}
super.visitClass(node);
levelTypeDecl--;
}

/**
* Returns true if @CheckedTemplate annotation declares that fragment must be
* ignored and false otherwise.
*
* <code>
* @CheckedTemplate(ignoreFragments=true)
* </code>
*
* @param checkedTemplateAnnotation the CheckedTemplate annotation.
*
* @return true if @CheckedTemplate annotation declares that fragment must be
* ignored and false otherwise.
*/
private static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation) {
Boolean ignoreFragment = null;
try {
for(PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
if (CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS.equalsIgnoreCase(pair.getAttributeName())) {
ignoreFragment = AnnotationUtils.getValueAsBoolean(pair);
}
}
} catch (IndexNotReadyException | ProcessCanceledException | CancellationException e) {
throw e;
} catch (Exception e) {
// Do nothing
}
return ignoreFragment != null ? ignoreFragment.booleanValue() : false;
}

private static PsiClass getTypeDeclaration(PsiElement node) {
return PsiTreeUtil.getParentOfType(node, PsiClass.class);
}


private void collectTemplateLink(PsiMethod methodDeclaration, PsiClass type, boolean ignoreFragments) {
private void collectTemplateLink(String basePath, PsiMethod methodDeclaration, PsiClass type, boolean ignoreFragments) {
String className = null;
boolean innerClass = levelTypeDecl > 1;
if (innerClass) {
className = PsiTypeUtils.getSimpleClassName(typeRoot.getName());
}
String methodName = methodDeclaration.getName();
collectTemplateLink(methodDeclaration, null, type, className, methodName, ignoreFragments );
collectTemplateLink(basePath, methodDeclaration, null, type, className, methodName, ignoreFragments );
}

private void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
private void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
String fieldOrMethodName, boolean ignoreFragment ) {
try {
String location = locationAnnotation != null && locationAnnotation.getValue() instanceof String ? (String) locationAnnotation.getValue() : null;
Module project = utils.getModule();
TemplatePathInfo templatePathInfo = location != null
? PsiQuteProjectUtils.getTemplatePath(null, location, ignoreFragment)
: PsiQuteProjectUtils.getTemplatePath(className, fieldOrMethodName, ignoreFragment);
? PsiQuteProjectUtils.getTemplatePath(basePath, null, location, ignoreFragment)
: PsiQuteProjectUtils.getTemplatePath(basePath, className, fieldOrMethodName, ignoreFragment);

VirtualFile templateFile = null;
if (location == null) {
Expand All @@ -208,7 +182,7 @@ private void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locat
} else {
templateFile = getVirtualFile(project, templatePathInfo.getTemplateUri(), "");
}
collectTemplateLink(fieldOrMethod, locationAnnotation, type, className, fieldOrMethodName, location,
collectTemplateLink(basePath, fieldOrMethod, locationAnnotation, type, className, fieldOrMethodName, location,
templateFile, templatePathInfo);
} catch (IndexNotReadyException | ProcessCanceledException | CancellationException e) {
throw e;
Expand Down Expand Up @@ -275,7 +249,7 @@ protected Range createRange(PsiElement fieldOrMethod) {
return utils.toRange(typeRoot, tr.getStartOffset(), tr.getLength());
}

protected abstract void collectTemplateLink(PsiElement node, PsiLiteralValue locationAnnotation, PsiClass type,
protected abstract void collectTemplateLink(String basePath, PsiElement node, PsiLiteralValue locationAnnotation, PsiClass type,
String className, String fieldOrMethodName, String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo);

private static boolean isTemplateType(PsiType type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public QuteJavaCodeLensCollector(PsiFile typeRoot, List<CodeLens> lenses, IPsiUt
}

@Override
protected void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className, String fieldOrMethodName,
protected void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className, String fieldOrMethodName,
String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo) {
if (!templatePathInfo.isValid()) {
// It is an empty fragment which is not valid, don't generate a codelens.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@

import java.util.List;

import static com.redhat.devtools.intellij.qute.psi.utils.PsiQuteProjectUtils.appendAndSlash;
import static io.quarkus.qute.CheckedTemplate.DEFAULTED;

/**
* Report diagnostics error for non existing Qute template for:
*
Expand All @@ -49,13 +52,13 @@ public QuteJavaDiagnosticsCollector(PsiFile typeRoot, List<Diagnostic> diagnosti
}

@Override
protected void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
protected void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
String fieldOrMethodName, String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo) {
QuteErrorCode error = getQuteErrorCode(templatePathInfo, templateFile);
if (error == null) {
return;
}
String path = createPath(className, fieldOrMethodName, location);
String path = createPath(basePath, className, fieldOrMethodName, location);
String fragmentId = templatePathInfo.getFragmentId();
if (templatePathInfo.hasFragment() && path.endsWith(fragmentId)) {
// Adjust path by removing fragment information
Expand Down Expand Up @@ -92,14 +95,17 @@ private static QuteErrorCode getQuteErrorCode(TemplatePathInfo templatePathInfo,
return null;
}

private static String createPath(String className, String fieldOrMethodName, String location) {
private static String createPath(String basePath, String className, String fieldOrMethodName, String location) {
if (location != null) {
return location;
}
if (className == null) {
return fieldOrMethodName;
StringBuilder path = new StringBuilder();
if (basePath != null && !DEFAULTED.equals(basePath)) {
appendAndSlash(path, basePath);
} else if (className != null){
appendAndSlash(path, className);
}
return className + '/' + fieldOrMethodName;
return path.append(fieldOrMethodName).toString();
}

private static Diagnostic createDiagnostic(Range range, DiagnosticSeverity severity, IQuteErrorCode errorCode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public QuteJavaDocumentLinkCollector(PsiFile typeRoot, List<DocumentLink> links,
}

@Override
protected void collectTemplateLink(PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
protected void collectTemplateLink(String basePath, PsiElement fieldOrMethod, PsiLiteralValue locationAnnotation, PsiClass type, String className,
String fieldOrMethodName, String location, VirtualFile templateFile, TemplatePathInfo templatePathInfo) {
if (!templatePathInfo.isValid()) {
// It is an empty fragment which is not valid, don't generate a document link.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ protected void processAnnotation(PsiElement javaElement, PsiAnnotation checkedTe
if (javaElement instanceof PsiClass) {
PsiClass type = (PsiClass) javaElement;
boolean ignoreFragments = isIgnoreFragments(checkedTemplateAnnotation);
collectDataModelTemplateForCheckedTemplate(type, ignoreFragments, context.getTypeResolver(type),
String basePath = getBasePath(checkedTemplateAnnotation);
collectDataModelTemplateForCheckedTemplate(type, basePath, ignoreFragments, context.getTypeResolver(type),
context.getDataModelProject().getTemplates(), monitor);
}
}
Expand All @@ -96,7 +97,7 @@ protected void processAnnotation(PsiElement javaElement, PsiAnnotation checkedTe
* ignored and false otherwise.
* @CheckedTemplate(ignoreFragments=true) </code>
*/
private static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation) {
public static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation) {
Boolean ignoreFragment = null;
try {
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
Expand All @@ -112,15 +113,32 @@ private static boolean isIgnoreFragments(PsiAnnotation checkedTemplateAnnotation
return ignoreFragment != null ? ignoreFragment.booleanValue() : false;
}

public static String getBasePath(PsiAnnotation checkedTemplateAnnotation) {
String basePath = null;
try {
for (PsiNameValuePair pair : checkedTemplateAnnotation.getParameterList().getAttributes()) {
if (CHECKED_TEMPLATE_ANNOTATION_BASE_PATH.equalsIgnoreCase(pair.getAttributeName())) {
basePath = pair.getLiteralValue();
}
}
} catch (IndexNotReadyException | ProcessCanceledException | CancellationException e) {
throw e;
} catch (Exception e) {
// Do nothing
}
return basePath;
}

/**
* Collect data model template from @CheckedTemplate.
*
* @param type the Java type.
* @param basePath the base path relative to the templates root
* @param ignoreFragments true if fragments must be ignored and false otherwise.
* @param templates the data model templates to update with collect of template.
* @param monitor the progress monitor.
*/
private static void collectDataModelTemplateForCheckedTemplate(PsiClass type, boolean ignoreFragments, ITypeResolver typeResolver,
private static void collectDataModelTemplateForCheckedTemplate(PsiClass type, String basePath, boolean ignoreFragments, ITypeResolver typeResolver,
List<DataModelTemplate<DataModelParameter>> templates, ProgressIndicator monitor) {
boolean innerClass = type.getContainingClass() != null;
String className = !innerClass ? null
Expand All @@ -131,7 +149,7 @@ private static void collectDataModelTemplateForCheckedTemplate(PsiClass type, bo
PsiMethod[] methods = type.getMethods();
for (PsiMethod method : methods) {
// src/main/resources/templates/${className}/${methodName}.qute.html
TemplatePathInfo templatePathInfo = getTemplatePath(className, method.getName(), ignoreFragments);
TemplatePathInfo templatePathInfo = getTemplatePath(basePath, className, method.getName(), ignoreFragments);

// Get or create template
String templateUri = templatePathInfo.getTemplateUri();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ private static DataModelTemplate<DataModelParameter> createTemplateDataModel(Psi
String location = locationFromConstructorParameter != null ? locationFromConstructorParameter : getLocation(field);
String fieldName = field.getName();
// src/main/resources/templates/${methodName}.qute.html
String templateUri = getTemplatePath(null, location != null ? location : fieldName, true).getTemplateUri();
String templateUri = getTemplatePath(null,null, location != null ? location : fieldName, true).getTemplateUri();

// Create template data model with:
// - template uri : Qute template file which must be bind with data model.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils;
import com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants;
import com.redhat.qute.commons.ProjectInfo;
import org.jetbrains.annotations.NotNull;

/**
* JDT Qute utilities.
Expand All @@ -28,6 +29,11 @@ public class PsiQuteProjectUtils {

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

/**
* Value for Qute annotations indicating behaviour should be using the default
*/
private static final String DEFAULTED = "<<defaulted>>";

private PsiQuteProjectUtils() {
}

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

public static String getTemplatePath(String className, String methodOrFieldName) {
public static String getTemplatePath(String basePath, String className, String methodOrFieldName) {
StringBuilder path = new StringBuilder(TEMPLATES_BASE_DIR);
if (className != null) {
path.append(className);
path.append('/');
if (basePath != null && !DEFAULTED.equals(basePath)) {
appendAndSlash(path, basePath);
} else if (className != null) {
appendAndSlash(path, className);
}
return path.append(methodOrFieldName).toString();
}

public static TemplatePathInfo getTemplatePath(String className, String methodOrFieldName, boolean ignoreFragments) {
public static TemplatePathInfo getTemplatePath(String basePath, String className, String methodOrFieldName, boolean ignoreFragments) {
String fragmentId = null;
StringBuilder templateUri = new StringBuilder(TEMPLATES_BASE_DIR);
if (className != null) {
templateUri.append(className);
templateUri.append('/');
if (basePath != null && !DEFAULTED.equals(basePath)) {
appendAndSlash(templateUri, basePath);
} else if (className != null) {
appendAndSlash(templateUri, className);
}
if (!ignoreFragments) {
int fragmentIndex = methodOrFieldName != null ? methodOrFieldName.lastIndexOf('$') : -1;
Expand All @@ -87,4 +95,16 @@ public static TemplatePathInfo getTemplatePath(String className, String methodOr
templateUri.append(methodOrFieldName);
return new TemplatePathInfo(templateUri.toString(), fragmentId);
}

/**
* Appends a segment to a path, add trailing "/" if necessary
* @param path the path to append to
* @param segment the segment to append to the path
*/
public static void appendAndSlash(@NotNull StringBuilder path, @NotNull String segment) {
path.append(segment);
if (!segment.endsWith("/")) {
path.append('/');
}
}
}
Loading

0 comments on commit 92ee81c

Please sign in to comment.