From 16f2691598aad8cda7af4f925b74aa13bcc3fa10 Mon Sep 17 00:00:00 2001 From: azerr Date: Fri, 12 Jul 2024 10:42:40 +0200 Subject: [PATCH] qute(NoMatchingTemplate) if using @CheckedTemplate with basePath Fixes https://github.com/redhat-developer/vscode-quarkus/issues/787 Signed-off-by: azerr --- .../QuarkusConfigMappingProvider.java | 6 +- .../QuarkusConfigPropertiesProvider.java | 4 +- .../properties/QuarkusConfigRootProvider.java | 6 +- .../qute/ItemResourceWithCustomBasePath.java | 39 +++++++++ .../qute/ItemTemplatesCustomBasePath.java | 15 ++++ .../qute/jdt/java/JavaCodeLensTest.java | 86 +++++++++++++++++++ .../qute/jdt/java/JavaDiagnosticsTest.java | 35 ++++++++ .../qute/jdt/java/JavaDocumentLinkTest.java | 36 ++++++++ .../qute/jdt/internal/QuteJavaConstants.java | 1 + .../AbstractQuteTemplateLinkCollector.java | 51 ++++++++--- .../java/QuteJavaCodeLensCollector.java | 7 +- .../java/QuteJavaDiagnosticsCollector.java | 30 ++++--- .../java/QuteJavaDocumentLinkCollector.java | 7 +- .../datamodel/CheckedTemplateSupport.java | 35 +++++++- .../datamodel/TemplateFieldSupport.java | 2 +- .../datamodel/TemplateRecordsSupport.java | 2 +- .../qute/jdt/utils/AnnotationUtils.java | 11 +++ .../qute/jdt/utils/JDTQuteProjectUtils.java | 39 ++++++--- 18 files changed, 356 insertions(+), 56 deletions(-) create mode 100644 qute.jdt/com.redhat.qute.jdt.test/projects/maven/qute-quickstart/src/main/java/org/acme/qute/ItemResourceWithCustomBasePath.java create mode 100644 qute.jdt/com.redhat.qute.jdt.test/projects/maven/qute-quickstart/src/main/java/org/acme/qute/ItemTemplatesCustomBasePath.java diff --git a/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigMappingProvider.java b/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigMappingProvider.java index d27a5747f..a0c819feb 100644 --- a/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigMappingProvider.java +++ b/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigMappingProvider.java @@ -23,7 +23,7 @@ import static io.quarkus.runtime.util.StringUtil.hyphenate; import static io.quarkus.runtime.util.StringUtil.join; import static io.quarkus.runtime.util.StringUtil.lowerCase; -import static org.eclipse.lsp4mp.jdt.core.utils.AnnotationUtils.getAnnotation; +import static org.eclipse.lsp4mp.jdt.core.utils.AnnotationUtils.getFirstAnnotation; import static org.eclipse.lsp4mp.jdt.core.utils.AnnotationUtils.getAnnotationMemberValue; import static org.eclipse.lsp4mp.jdt.core.utils.AnnotationUtils.hasAnnotation; import static org.eclipse.lsp4mp.jdt.core.utils.JDTTypeUtils.findType; @@ -254,7 +254,7 @@ private static String convertName(IMember member, IAnnotation configMappingAnnot // @WithName("name") // String host(); // --> See https://quarkus.io/guides/config-mappings#withname - IAnnotation withNameAnnotation = getAnnotation((IAnnotatable) member, WITH_NAME_ANNOTATION); + IAnnotation withNameAnnotation = getFirstAnnotation((IAnnotatable) member, WITH_NAME_ANNOTATION); if (withNameAnnotation != null) { String name = getAnnotationMemberValue(withNameAnnotation, WITH_NAME_ANNOTATION_VALUE); if (StringUtils.isNotEmpty(name)) { @@ -304,7 +304,7 @@ private static String convertName(IMember member, IAnnotation configMappingAnnot */ private static String getWithDefault(IMember member) { try { - IAnnotation withDefaultAnnotation = getAnnotation((IAnnotatable) member, WITH_DEFAULT_ANNOTATION); + IAnnotation withDefaultAnnotation = getFirstAnnotation((IAnnotatable) member, WITH_DEFAULT_ANNOTATION); if (withDefaultAnnotation != null) { String defaultValue = getAnnotationMemberValue(withDefaultAnnotation, WITH_DEFAULT_ANNOTATION_VALUE); if (StringUtils.isNotEmpty(defaultValue)) { diff --git a/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java b/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java index 191a9de24..161b76f95 100644 --- a/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java +++ b/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java @@ -14,7 +14,7 @@ import static io.quarkus.runtime.util.StringUtil.join; import static io.quarkus.runtime.util.StringUtil.lowerCase; import static io.quarkus.runtime.util.StringUtil.withoutSuffix; -import static org.eclipse.lsp4mp.jdt.core.utils.AnnotationUtils.getAnnotation; +import static org.eclipse.lsp4mp.jdt.core.utils.AnnotationUtils.getFirstAnnotation; import static org.eclipse.lsp4mp.jdt.core.utils.AnnotationUtils.getAnnotationMemberValue; import static org.eclipse.lsp4mp.jdt.core.utils.JDTTypeUtils.findType; import static org.eclipse.lsp4mp.jdt.core.utils.JDTTypeUtils.getEnclosedType; @@ -189,7 +189,7 @@ private void processConfigProperties(IJavaElement javaElement, IAnnotation confi } String name = null; String defaultValue = null; - IAnnotation configPropertyAnnotation = getAnnotation(method, + IAnnotation configPropertyAnnotation = getFirstAnnotation(method, MicroProfileConfigConstants.CONFIG_PROPERTY_ANNOTATION); if (configPropertyAnnotation != null) { name = getAnnotationMemberValue(configPropertyAnnotation, diff --git a/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigRootProvider.java b/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigRootProvider.java index dae467bf9..8ebc34e87 100644 --- a/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigRootProvider.java +++ b/quarkus.jdt.ext/com.redhat.microprofile.jdt.quarkus/src/main/java/com/redhat/microprofile/jdt/internal/quarkus/core/properties/QuarkusConfigRootProvider.java @@ -15,7 +15,7 @@ import static io.quarkus.runtime.util.StringUtil.lowerCaseFirst; import static io.quarkus.runtime.util.StringUtil.toList; import static io.quarkus.runtime.util.StringUtil.withoutSuffix; -import static org.eclipse.lsp4mp.jdt.core.utils.AnnotationUtils.getAnnotation; +import static org.eclipse.lsp4mp.jdt.core.utils.AnnotationUtils.getFirstAnnotation; import static org.eclipse.lsp4mp.jdt.core.utils.AnnotationUtils.getAnnotationMemberValue; import static org.eclipse.lsp4mp.jdt.core.utils.JDTTypeUtils.findType; import static org.eclipse.lsp4mp.jdt.core.utils.JDTTypeUtils.getEnclosedType; @@ -413,7 +413,7 @@ private void processConfigGroup(String extensionName, IJavaElement javaElement, continue; } - final IAnnotation configItemAnnotation = getAnnotation((IAnnotatable) field, + final IAnnotation configItemAnnotation = getFirstAnnotation((IAnnotatable) field, QuarkusConstants.CONFIG_ITEM_ANNOTATION); String name = configItemAnnotation == null ? hyphenate(field.getElementName()) : getAnnotationMemberValue(configItemAnnotation, @@ -437,7 +437,7 @@ private void processConfigGroup(String extensionName, IJavaElement javaElement, String fieldTypeName = getResolvedTypeName(field); IType fieldClass = findType(field.getJavaProject(), fieldTypeName); - final IAnnotation configGroupAnnotation = getAnnotation((IAnnotatable) fieldClass, + final IAnnotation configGroupAnnotation = getFirstAnnotation((IAnnotatable) fieldClass, QuarkusConstants.CONFIG_GROUP_ANNOTATION); if (configGroupAnnotation != null) { processConfigGroup(extensionName, fieldClass, subKey, configPhase, javadocCache, quarkusContext, diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/qute-quickstart/src/main/java/org/acme/qute/ItemResourceWithCustomBasePath.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/qute-quickstart/src/main/java/org/acme/qute/ItemResourceWithCustomBasePath.java new file mode 100644 index 000000000..56d1cafad --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/qute-quickstart/src/main/java/org/acme/qute/ItemResourceWithCustomBasePath.java @@ -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 items); + static native TemplateInstance items$id1(List items); + static native TemplateInstance items3$id2(List items); + static native TemplateInstance items3$(List items); + } + + @GET + @Produces(MediaType.TEXT_HTML) + public TemplateInstance get() { + List 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); + } + + +} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/projects/maven/qute-quickstart/src/main/java/org/acme/qute/ItemTemplatesCustomBasePath.java b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/qute-quickstart/src/main/java/org/acme/qute/ItemTemplatesCustomBasePath.java new file mode 100644 index 000000000..8cfebe581 --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt.test/projects/maven/qute-quickstart/src/main/java/org/acme/qute/ItemTemplatesCustomBasePath.java @@ -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 items); + static native TemplateInstance items$id1(List items); + static native TemplateInstance items3$id2(List items); + static native TemplateInstance items3$(List items); + +} \ No newline at end of file diff --git a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaCodeLensTest.java b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaCodeLensTest.java index c45ac871c..cb6e477fd 100644 --- a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaCodeLensTest.java +++ b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaCodeLensTest.java @@ -198,6 +198,92 @@ public void checkedTemplateInInnerClass() throws CoreException, Exception { "qute.command.generate.template.file", Arrays.asList(items2Uri))); } + @Test + public void checkedTemplateWithCustomBasePath() throws Exception { + + // @CheckedTemplate(basePath="ItemResourceWithFragment") + // public class ItemTemplatesCustomBasePath { + // + // static native TemplateInstance items(List items); + // static native TemplateInstance items$id1(List items); + // static native TemplateInstance items3$id2(List items); + // static native TemplateInstance items3$(List items); + // } + + IJavaProject javaProject = loadMavenProject(QuteMavenProjectName.qute_quickstart); + + QuteJavaCodeLensParams params = new QuteJavaCodeLensParams(); + IFile javaFile = javaProject.getProject() + .getFile(new Path("src/main/java/org/acme/qute/ItemTemplatesCustomBasePath.java")); + params.setUri(javaFile.getLocation().toFile().toURI().toString()); + + List lenses = QuteSupportForJava.getInstance().codeLens(params, getJDTUtils(), + new NullProgressMonitor()); + assertEquals(3, lenses.size()); + + String itemsUri = javaProject.getProject() + .getFile("src/main/resources/templates/ItemResourceWithFragment/items.html").getLocationURI() + .toString(); + String items3Uri = javaProject.getProject() + .getFile("src/main/resources/templates/ItemResourceWithFragment/items3.html").getLocationURI() + .toString(); + + assertCodeLens(lenses, // + cl(r(9, 1, 9, 56), // + "Open `src/main/resources/templates/ItemResourceWithFragment/items.html`", // + "qute.command.open.uri", Arrays.asList(itemsUri)), // + cl(r(10, 1, 10, 60), // + "Open `id1` fragment of `src/main/resources/templates/ItemResourceWithFragment/items.html`", // + "qute.command.open.uri", Arrays.asList(itemsUri, "id1")), // + cl(r(11, 1, 11, 61), // + "Create `src/main/resources/templates/ItemResourceWithFragment/items3.html`", // + "qute.command.generate.template.file", Arrays.asList(items3Uri))); + + } + + @Test + public void checkedTemplateInInnerClassWithCustomBasePath() throws Exception { + + // @CheckedTemplate(basePath="ItemResourceWithFragment") + // public class ItemTemplatesCustomBasePath { + // + // static native TemplateInstance items(List items); + // static native TemplateInstance items$id1(List items); + // static native TemplateInstance items3$id2(List items); + // static native TemplateInstance items3$(List items); + // } + + IJavaProject javaProject = loadMavenProject(QuteMavenProjectName.qute_quickstart); + + QuteJavaCodeLensParams params = new QuteJavaCodeLensParams(); + IFile javaFile = javaProject.getProject() + .getFile(new Path("src/main/java/org/acme/qute/ItemTemplatesCustomBasePath.java")); + params.setUri(javaFile.getLocation().toFile().toURI().toString()); + + List lenses = QuteSupportForJava.getInstance().codeLens(params, getJDTUtils(), + new NullProgressMonitor()); + assertEquals(3, lenses.size()); + + String itemsUri = javaProject.getProject() + .getFile("src/main/resources/templates/ItemResourceWithFragment/items.html").getLocationURI() + .toString(); + String items3Uri = javaProject.getProject() + .getFile("src/main/resources/templates/ItemResourceWithFragment/items3.html").getLocationURI() + .toString(); + + assertCodeLens(lenses, // + cl(r(9, 1, 9, 56), // + "Open `src/main/resources/templates/ItemResourceWithFragment/items.html`", // + "qute.command.open.uri", Arrays.asList(itemsUri)), // + cl(r(10, 1, 10, 60), // + "Open `id1` fragment of `src/main/resources/templates/ItemResourceWithFragment/items.html`", // + "qute.command.open.uri", Arrays.asList(itemsUri, "id1")), // + cl(r(11, 1, 11, 61), // + "Create `src/main/resources/templates/ItemResourceWithFragment/items3.html`", // + "qute.command.generate.template.file", Arrays.asList(items3Uri))); + + } + @Test public void checkedTemplateWithFragment() throws CoreException, Exception { diff --git a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaDiagnosticsTest.java b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaDiagnosticsTest.java index 30e0e0918..400228f70 100644 --- a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaDiagnosticsTest.java +++ b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaDiagnosticsTest.java @@ -195,6 +195,41 @@ public void checkedTemplateInWithFragment() throws CoreException, Exception { DiagnosticSeverity.Error, "qute", QuteErrorCode.NoMatchingTemplate.name())); } + @Test + public void checkedTemplateWithCustomBasePath() throws Exception { + + // @CheckedTemplate(basePath="ItemResourceWithFragment") + // public class ItemTemplatesCustomBasePath { + // + // static native TemplateInstance items(List items); + // static native TemplateInstance items$id1(List items); + // static native TemplateInstance items3$id2(List items); + // static native TemplateInstance items3$(List items); + // } + + IJavaProject javaProject = loadMavenProject(QuteMavenProjectName.qute_quickstart); + + QuteJavaDiagnosticsParams params = new QuteJavaDiagnosticsParams(); + IFile javaFile = javaProject.getProject() + .getFile(new Path("src/main/java/org/acme/qute/ItemTemplatesCustomBasePath.java")); + params.setUris(Arrays.asList(javaFile.getLocation().toFile().toURI().toString())); + + List publishDiagnostics = QuteSupportForJava.getInstance().diagnostics(params, + getJDTUtils(), new NullProgressMonitor()); + assertEquals(1, publishDiagnostics.size()); + + List diagnostics = publishDiagnostics.get(0).getDiagnostics(); + assertEquals(2, diagnostics.size()); + + assertDiagnostic(diagnostics, // + new Diagnostic(r(11, 32, 11, 42), + "No template matching the path ItemResourceWithFragment/items3 could be found for: org.acme.qute.ItemTemplatesCustomBasePath", + DiagnosticSeverity.Error, "qute", QuteErrorCode.NoMatchingTemplate.name()), // + new Diagnostic(r(12, 32, 12, 39), + "Fragment [] not defined in template ItemResourceWithFragment/items3$", + DiagnosticSeverity.Error, "qute", QuteErrorCode.FragmentNotDefined.name())); + } + public static Range r(int line, int startChar, int endChar) { return r(line, startChar, line, endChar); } diff --git a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaDocumentLinkTest.java b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaDocumentLinkTest.java index 4ca8c9f06..63daa7565 100644 --- a/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaDocumentLinkTest.java +++ b/qute.jdt/com.redhat.qute.jdt.test/src/main/java/com/redhat/qute/jdt/java/JavaDocumentLinkTest.java @@ -200,6 +200,42 @@ public void checkedTemplateWithFragment() throws CoreException, Exception { "Open `src/main/resources/templates/ItemResourceWithFragment/items2$id2.html`")); } + @Test + public void checkedTemplateWithCustomBasePath() throws Exception { + + // @CheckedTemplate(basePath="ItemResourceWithFragment") + // public class ItemTemplatesCustomBasePath { + // + // static native TemplateInstance items(List items); + // static native TemplateInstance items$id1(List items); + // static native TemplateInstance items3$id2(List items); + // static native TemplateInstance items3$(List items); + // } + + IJavaProject javaProject = loadMavenProject(QuteMavenProjectName.qute_quickstart); + + QuteJavaDocumentLinkParams params = new QuteJavaDocumentLinkParams(); + IFile javaFile = javaProject.getProject() + .getFile(new Path("src/main/java/org/acme/qute/ItemTemplatesCustomBasePath.java")); + params.setUri(javaFile.getLocation().toFile().toURI().toString()); + + List links = QuteSupportForJava.getInstance().documentLink(params, getJDTUtils(), + new NullProgressMonitor()); + assertEquals(3, links.size()); + + String templateFileUri = javaProject.getProject() + .getFile("src/main/resources/templates/ItemResourceWithFragment/items.html").getLocationURI() + .toString(); + + assertDocumentLink(links, // + dl(r(9, 32, 9, 37), // + templateFileUri, "Open `src/main/resources/templates/ItemResourceWithFragment/items.html`"), // + dl(r(10, 32, 10, 41), // + templateFileUri, "Open `src/main/resources/templates/ItemResourceWithFragment/items.html`"), // + dl(r(11, 32, 11, 42), // + templateFileUri, "Create `src/main/resources/templates/ItemResourceWithFragment/items3.html`")); + } + public static Range r(int line, int startChar, int endChar) { return r(line, startChar, line, endChar); } diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java index bcbf0368f..41ae37f2a 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java @@ -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 diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/AbstractQuteTemplateLinkCollector.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/AbstractQuteTemplateLinkCollector.java index fb8e99635..2e53fc0e1 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/AbstractQuteTemplateLinkCollector.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/AbstractQuteTemplateLinkCollector.java @@ -12,6 +12,7 @@ package com.redhat.qute.jdt.internal.java; import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION; +import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_BASE_PATH; import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS; import static com.redhat.qute.jdt.internal.QuteJavaConstants.OLD_CHECKED_TEMPLATE_ANNOTATION; import static com.redhat.qute.jdt.internal.QuteJavaConstants.TEMPLATE_CLASS; @@ -135,7 +136,7 @@ public boolean visit(FieldDeclaration node) { .getLocationExpressionFromConstructorParameter(variable.getName().getIdentifier()); } String fieldName = variable.getName().getIdentifier(); - collectTemplateLink(node, locationExpression, getTypeDeclaration(node), null, fieldName, false); + collectTemplateLink(null, node, locationExpression, getTypeDeclaration(node), null, fieldName, false); } } return super.visit(node); @@ -182,10 +183,11 @@ public boolean visit(TypeDeclaration node) { // public static class Templates { // public static native TemplateInstance book(Book book); boolean ignoreFragments = isIgnoreFragments(annotation); + String basePath = getBasePath(annotation); List body = node.bodyDeclarations(); for (Object declaration : body) { if (declaration instanceof MethodDeclaration) { - collectTemplateLink((MethodDeclaration) declaration, node, ignoreFragments); + collectTemplateLink(basePath, (MethodDeclaration) declaration, node, ignoreFragments); } } } @@ -204,7 +206,7 @@ public boolean visit(TypeDeclaration node) { @Override public boolean visit(RecordDeclaration node) { String recordName = node.getName().getIdentifier(); - collectTemplateLink(node, null, node, null, recordName, false); + collectTemplateLink(null, node, null, node, null, recordName, false); return super.visit(node); } @@ -233,6 +235,29 @@ private static boolean isIgnoreFragments(Annotation checkedTemplateAnnotation) { return ignoreFragment != null ? ignoreFragment.booleanValue() : false; } + /** + * Returns the basePath value declared in the @CheckedTemplate + * annotation, relative to the templates root, to search the templates from. + * + * @CheckedTemplate(basePath="somewhere") + * + * + * @param checkedTemplateAnnotation the CheckedTemplate annotation. + * @return the basePath value declared in the @CheckedTemplate + * annotation + */ + public static String getBasePath(Annotation checkedTemplateAnnotation) { + String basePath = null; + try { + Expression ignoreFragmentExpr = AnnotationUtils.getAnnotationMemberValueExpression( + checkedTemplateAnnotation, CHECKED_TEMPLATE_ANNOTATION_BASE_PATH); + basePath = AnnotationUtils.getString(ignoreFragmentExpr); + } catch (Exception e) { + // Do nothing + } + return basePath; + } + @Override public void endVisit(TypeDeclaration node) { levelTypeDecl--; @@ -247,7 +272,7 @@ private static TypeDeclaration getTypeDeclaration(ASTNode node) { return parent != null && parent.getNodeType() == ASTNode.TYPE_DECLARATION ? (TypeDeclaration) parent : null; } - private void collectTemplateLink(MethodDeclaration methodDeclaration, TypeDeclaration type, + private void collectTemplateLink(String basePath, MethodDeclaration methodDeclaration, TypeDeclaration type, boolean ignoreFragment) { String className = null; boolean innerClass = levelTypeDecl > 1; @@ -255,17 +280,17 @@ private void collectTemplateLink(MethodDeclaration methodDeclaration, TypeDeclar className = JDTTypeUtils.getSimpleClassName(typeRoot.getElementName()); } String methodName = methodDeclaration.getName().getIdentifier(); - collectTemplateLink(methodDeclaration, null, type, className, methodName, ignoreFragment); + collectTemplateLink(basePath, methodDeclaration, null, type, className, methodName, ignoreFragment); } - private void collectTemplateLink(ASTNode fieldOrMethod, StringLiteral locationAnnotation, + private void collectTemplateLink(String basePath, ASTNode fieldOrMethod, StringLiteral locationAnnotation, AbstractTypeDeclaration type, String className, String fieldOrMethodName, boolean ignoreFragment) { try { String location = locationAnnotation != null ? locationAnnotation.getLiteralValue() : null; IProject project = typeRoot.getJavaProject().getProject(); TemplatePathInfo templatePathInfo = location != null - ? JDTQuteProjectUtils.getTemplatePath(null, location, ignoreFragment) - : JDTQuteProjectUtils.getTemplatePath(className, fieldOrMethodName, ignoreFragment); + ? JDTQuteProjectUtils.getTemplatePath(basePath, null, location, ignoreFragment) + : JDTQuteProjectUtils.getTemplatePath(basePath, className, fieldOrMethodName, ignoreFragment); IFile templateFile = null; if (location == null) { templateFile = getTemplateFile(project, templatePathInfo.getTemplateUri()); @@ -275,8 +300,8 @@ private void collectTemplateLink(ASTNode fieldOrMethod, StringLiteral locationAn } else { templateFile = project.getFile(templatePathInfo.getTemplateUri()); } - collectTemplateLink(fieldOrMethod, locationAnnotation, type, className, fieldOrMethodName, location, - templateFile, templatePathInfo); + collectTemplateLink(basePath, fieldOrMethod, locationAnnotation, type, className, fieldOrMethodName, + location, templateFile, templatePathInfo); } catch (JavaModelException e) { LOGGER.log(Level.SEVERE, "Error while creating Qute CodeLens for Java file.", e); } @@ -307,9 +332,9 @@ protected Range createRange(ASTNode fieldOrMethod) throws JavaModelException { } } - protected abstract void collectTemplateLink(ASTNode node, ASTNode locationAnnotation, AbstractTypeDeclaration type, - String className, String fieldOrMethodName, String location, IFile templateFile, - TemplatePathInfo templatePathInfo) throws JavaModelException; + protected abstract void collectTemplateLink(String basePath, ASTNode node, ASTNode locationAnnotation, + AbstractTypeDeclaration type, String className, String fieldOrMethodName, String location, + IFile templateFile, TemplatePathInfo templatePathInfo) throws JavaModelException; private static IFile getTemplateFile(IProject project, String templateFilePathWithoutExtension) { for (String suffix : suffixes) { diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaCodeLensCollector.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaCodeLensCollector.java index 08b0fed07..3a002e894 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaCodeLensCollector.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaCodeLensCollector.java @@ -27,7 +27,6 @@ import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Type; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.Range; @@ -67,9 +66,9 @@ public QuteJavaCodeLensCollector(ITypeRoot typeRoot, List lenses, IJDT } @Override - protected void collectTemplateLink(ASTNode fieldOrMethod, ASTNode locationAnnotation, AbstractTypeDeclaration type, - String className, String fieldOrMethodName, String location, IFile templateFile, - TemplatePathInfo templatePathInfo) throws JavaModelException { + protected void collectTemplateLink(String basePath, ASTNode fieldOrMethod, ASTNode locationAnnotation, + AbstractTypeDeclaration type, String className, String fieldOrMethodName, String location, + IFile templateFile, TemplatePathInfo templatePathInfo) throws JavaModelException { if (!templatePathInfo.isValid()) { // It is an empty fragment which is not valid, don't generate a codelens. return; diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaDiagnosticsCollector.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaDiagnosticsCollector.java index bea9771b9..de2db5c03 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaDiagnosticsCollector.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaDiagnosticsCollector.java @@ -11,6 +11,10 @@ *******************************************************************************/ package com.redhat.qute.jdt.internal.java; +import static com.redhat.qute.jdt.utils.JDTQuteProjectUtils.appendAndSlash; + +import static com.redhat.qute.jdt.utils.JDTQuteProjectUtils.DEFAULTED; + import java.util.List; import org.eclipse.core.resources.IFile; @@ -21,7 +25,6 @@ import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.Range; @@ -53,7 +56,7 @@ public QuteJavaDiagnosticsCollector(ITypeRoot typeRoot, List diagnos } @Override - protected void collectTemplateLink(ASTNode fieldOrMethod, ASTNode locationAnnotation, AbstractTypeDeclaration type, + protected void collectTemplateLink(String basePath, ASTNode fieldOrMethod, ASTNode locationAnnotation, AbstractTypeDeclaration type, String className, String fieldOrMethodName, String location, IFile templateFile, TemplatePathInfo templatePathInfo) throws JavaModelException { QuteErrorCode error = getQuteErrorCode(templatePathInfo, templateFile); @@ -61,7 +64,7 @@ protected void collectTemplateLink(ASTNode fieldOrMethod, ASTNode locationAnnota 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 @@ -99,15 +102,18 @@ private static QuteErrorCode getQuteErrorCode(TemplatePathInfo templatePathInfo, return null; } - private static String createPath(String className, String fieldOrMethodName, String location) { - if (location != null) { - return location; - } - if (className == null) { - return fieldOrMethodName; - } - return className + '/' + fieldOrMethodName; - } + private static String createPath(String basePath, String className, String fieldOrMethodName, String location) { + if (location != null) { + return location; + } + StringBuilder path = new StringBuilder(); + if (basePath != null && !DEFAULTED.equals(basePath)) { + appendAndSlash(path, basePath); + } else if (className != null){ + appendAndSlash(path, className); + } + return path.append(fieldOrMethodName).toString(); + } private static Diagnostic createDiagnostic(Range range, DiagnosticSeverity severity, IQuteErrorCode errorCode, Object... arguments) { diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaDocumentLinkCollector.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaDocumentLinkCollector.java index 509c565af..ae96a00d8 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaDocumentLinkCollector.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/QuteJavaDocumentLinkCollector.java @@ -20,7 +20,6 @@ import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.DocumentLink; import org.eclipse.lsp4j.Range; @@ -53,9 +52,9 @@ public QuteJavaDocumentLinkCollector(ITypeRoot typeRoot, List link } @Override - protected void collectTemplateLink(ASTNode fieldOrMethod, ASTNode locationAnnotation, AbstractTypeDeclaration type, - String className, String fieldOrMethodName, String location, IFile templateFile, - TemplatePathInfo templatePathInfo) throws JavaModelException { + protected void collectTemplateLink(String basePath, ASTNode fieldOrMethod, ASTNode locationAnnotation, + AbstractTypeDeclaration type, String className, String fieldOrMethodName, String location, + IFile templateFile, TemplatePathInfo templatePathInfo) throws JavaModelException { if (!templatePathInfo.isValid()) { // It is an empty fragment which is not valid, don't generate a document link. return; diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/CheckedTemplateSupport.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/CheckedTemplateSupport.java index 05c6c7779..409eada31 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/CheckedTemplateSupport.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/CheckedTemplateSupport.java @@ -12,6 +12,7 @@ package com.redhat.qute.jdt.internal.template.datamodel; import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION; +import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_BASE_PATH; import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS; import static com.redhat.qute.jdt.internal.QuteJavaConstants.OLD_CHECKED_TEMPLATE_ANNOTATION; import static com.redhat.qute.jdt.utils.JDTQuteProjectUtils.getTemplatePath; @@ -27,6 +28,7 @@ import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.ILocalVariable; +import org.eclipse.jdt.core.IMemberValuePair; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; @@ -88,7 +90,8 @@ protected void processAnnotation(IJavaElement javaElement, IAnnotation checkedTe if (javaElement instanceof IType) { IType type = (IType) 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); } } @@ -117,10 +120,36 @@ private static boolean isIgnoreFragments(IAnnotation checkedTemplateAnnotation) } } + /** + * Returns the basePath value declared in the @CheckedTemplate + * annotation, relative to the templates root, to search the templates from. + * + * @CheckedTemplate(basePath="somewhere") + * + * + * @param checkedTemplateAnnotation the CheckedTemplate annotation. + * @return the basePath value declared in the @CheckedTemplate + * annotation + */ + private static String getBasePath(IAnnotation checkedTemplateAnnotation) { + String basePath = null; + try { + for (IMemberValuePair pair : checkedTemplateAnnotation.getMemberValuePairs()) { + if (CHECKED_TEMPLATE_ANNOTATION_BASE_PATH.equalsIgnoreCase(pair.getMemberName())) { + basePath = AnnotationUtils.getValueAsString(pair); + } + } + } 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 typeResolver the Java type resolver. * @param templates the data model templates to update with collect of @@ -128,7 +157,7 @@ private static boolean isIgnoreFragments(IAnnotation checkedTemplateAnnotation) * @param monitor the progress monitor. * @throws JavaModelException */ - private static void collectDataModelTemplateForCheckedTemplate(IType type, boolean ignoreFragments, + private static void collectDataModelTemplateForCheckedTemplate(IType type, String basePath, boolean ignoreFragments, ITypeResolver typeResolver, List> templates, IProgressMonitor monitor) throws JavaModelException { boolean innerClass = type.getParent() != null && type.getParent().getElementType() == IJavaElement.TYPE; @@ -143,7 +172,7 @@ private static void collectDataModelTemplateForCheckedTemplate(IType type, boole for (IMethod method : methods) { // src/main/resources/templates/${className}/${methodName}.qute.html - TemplatePathInfo templatePathInfo = getTemplatePath(className, method.getElementName(), ignoreFragments); + TemplatePathInfo templatePathInfo = getTemplatePath(basePath, className, method.getElementName(), ignoreFragments); // Get or create template String templateUri = templatePathInfo.getTemplateUri(); diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateFieldSupport.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateFieldSupport.java index cf4b06f95..9c63bb4cd 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateFieldSupport.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateFieldSupport.java @@ -123,7 +123,7 @@ private static DataModelTemplate createTemplateDataModel(IFi : getLocation(field); String fieldName = field.getElementName(); // 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. diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateRecordsSupport.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateRecordsSupport.java index 0f9c05b42..c263a772c 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateRecordsSupport.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateRecordsSupport.java @@ -83,7 +83,7 @@ private static DataModelTemplate createTemplateDataModel(ITy String recordName = type.getElementName(); // src/main/resources/templates/${recordName}.qute.html - String templateUri = getTemplatePath(null, recordName, true).getTemplateUri(); + String templateUri = getTemplatePath(null, null, recordName, true).getTemplateUri(); // Create template data model with: // - template uri : Qute template file which must be bind with data model. diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/AnnotationUtils.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/AnnotationUtils.java index fcfb3980a..d36eaedd1 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/AnnotationUtils.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/AnnotationUtils.java @@ -277,4 +277,15 @@ public static Boolean getBoolean(Expression expression) { } return null; } + + public static String getString(Expression expression) { + if (expression == null) { + return null; + } + Object expressionValue = expression.resolveConstantExpressionValue(); + if (expressionValue instanceof String) { + return (String) expressionValue; + } + return null; + } } diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/JDTQuteProjectUtils.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/JDTQuteProjectUtils.java index b33760809..65ffa37fc 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/JDTQuteProjectUtils.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/JDTQuteProjectUtils.java @@ -41,6 +41,11 @@ public class JDTQuteProjectUtils { private static final String TEMPLATES_BASE_DIR = "src/main/resources/templates/"; + /** + * Value for Qute annotations indicating behaviour should be using the default + */ + public static final String DEFAULTED = "<>"; + private JDTQuteProjectUtils() { } @@ -106,25 +111,39 @@ public static boolean hasQuteSupport(IJavaProject javaProject) { return JDTTypeUtils.findType(javaProject, QuteJavaConstants.ENGINE_BUILDER_CLASS) != null; } - public static TemplatePathInfo getTemplatePath(String className, String methodOrFieldName, + 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 (!ignoreFragments) { - int fragmentIndex = methodOrFieldName != null ? methodOrFieldName.lastIndexOf('$') : -1; - if (fragmentIndex != -1) { - fragmentId = methodOrFieldName.substring(fragmentIndex + 1, methodOrFieldName.length()); - methodOrFieldName = methodOrFieldName.substring(0, fragmentIndex); - } + 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; + if (fragmentIndex != -1) { + fragmentId = methodOrFieldName.substring(fragmentIndex + 1, methodOrFieldName.length()); + methodOrFieldName = methodOrFieldName.substring(0, fragmentIndex); } } 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(StringBuilder path, String segment) { + path.append(segment); + if (!segment.endsWith("/")) { + path.append('/'); + } + } + public static CompilationUnit getASTRoot(ITypeRoot typeRoot) { return ASTResolving.createQuickFixAST((ICompilationUnit) typeRoot, null); }