diff --git a/gradle.properties b/gradle.properties index 552e933df..ee7e61c52 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,8 +19,8 @@ gradleVersion=8.6 channel=nightly quarkusVersion=3.15.1 lsp4mpVersion=0.13.0 -quarkusLsVersion=0.20.0 -quteLsVersion=0.20.0 +quarkusLsVersion=0.21.0-SNAPSHOT +quteLsVersion=0.21.0-SNAPSHOT # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib kotlin.stdlib.default.dependency=false # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguageSubstitutor.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguageSubstitutor.java index e2a3a1a6d..f9e13ab5c 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguageSubstitutor.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguageSubstitutor.java @@ -36,12 +36,13 @@ * */ public class QuteLanguageSubstitutor extends LanguageSubstitutor { - protected boolean isTemplate(VirtualFile file, Module module) { - return isQuteTemplate(file, module) && - ModuleRootManager.getInstance(module).getFileIndex().isInSourceContent(file); + + private static boolean isTemplate(VirtualFile file, Module module) { + return isQuteTemplate(file, module) /*&& + ModuleRootManager.getInstance(module).getFileIndex().isInSourceContent(file)*/; } - protected boolean isQuteModule(Module module) { + private static boolean isQuteModule(Module module) { OrderEnumerator libraries = ModuleRootManager.getInstance(module).orderEntries().librariesOnly(); return libraries.process(new RootPolicy() { @Override diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/QuteJavaConstants.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/QuteJavaConstants.java index da0680a08..84e3d1c0f 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/QuteJavaConstants.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/QuteJavaConstants.java @@ -11,6 +11,9 @@ *******************************************************************************/ package com.redhat.devtools.intellij.qute.psi.internal; +import java.util.Collection; +import java.util.List; + /** * Qute Java constants. * @@ -30,6 +33,8 @@ public class QuteJavaConstants { public static final String TEMPLATE_INSTANCE_INTERFACE = "io.quarkus.qute.TemplateInstance"; + public static Collection QUTE_MAVEN_COORDS = List.of("io.quarkus:quarkus-qute", "io.quarkus.qute:qute-core"); + public static final String ENGINE_BUILDER_CLASS = "io.quarkus.qute.EngineBuilder"; public static final String VALUE_ANNOTATION_NAME = "value"; @@ -64,6 +69,8 @@ public class QuteJavaConstants { public static final String TEMPLATE_EXTENSION_ANNOTATION_MATCH_NAME = "matchName"; + public static final String TEMPLATE_EXTENSION_ANNOTATION_MATCH_NAMES = "matchNames"; + // @TemplateData public static final String TEMPLATE_DATA_ANNOTATION = "io.quarkus.qute.TemplateData"; diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/DataMappingSupport.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/DataMappingSupport.java new file mode 100644 index 000000000..44c2a930c --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/DataMappingSupport.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.psi.internal.extensions.roq; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.redhat.devtools.intellij.qute.psi.template.datamodel.AbstractAnnotationTypeReferenceDataModelProvider; +import com.redhat.devtools.intellij.qute.psi.template.datamodel.SearchContext; +import com.redhat.devtools.intellij.qute.psi.utils.AnnotationUtils; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverInfo; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.logging.Logger; + +import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.VALUE_ANNOTATION_NAME; +import static com.redhat.devtools.intellij.qute.psi.internal.extensions.roq.RoqJavaConstants.DATA_MAPPING_ANNOTATION; + +/** + * Roq @DataMapping annotation support. + * + * @author Angelo ZERR + */ +public class DataMappingSupport extends AbstractAnnotationTypeReferenceDataModelProvider { + + private static final Logger LOGGER = Logger.getLogger(DataMappingSupport.class.getName()); + + private static final String INJECT_NAMESPACE = "inject"; + + private static final String[] ANNOTATION_NAMES = {DATA_MAPPING_ANNOTATION}; + + @Override + protected String[] getAnnotationNames() { + return ANNOTATION_NAMES; + } + + @Override + protected void processAnnotation(PsiElement javaElement, PsiAnnotation annotation, String annotationName, + SearchContext context, ProgressIndicator monitor) { + if (!(javaElement instanceof PsiClass)) { + return; + } + // @DataMapping(value = "events", parentArray = true) + // public record Events(List list) { + // becomes --> inject:events + + PsiClass type = (PsiClass) javaElement; + String value = getDataMappingAnnotationValue(type); + if (StringUtils.isNoneBlank(value)) { + collectResolversForInject(type, value, context.getDataModelProject().getValueResolvers()); + } + } + + @Nullable + private static String getDataMappingAnnotationValue(PsiClass javaElement) { + PsiAnnotation namedAnnotation = AnnotationUtils.getAnnotation(javaElement, + DATA_MAPPING_ANNOTATION); + if (namedAnnotation != null) { + return AnnotationUtils.getAnnotationMemberValue(namedAnnotation, VALUE_ANNOTATION_NAME); + } + return null; + } + + private static void collectResolversForInject(PsiClass type, String named, List resolvers) { + ValueResolverInfo resolver = new ValueResolverInfo(); + resolver.setNamed(named); + resolver.setSourceType(type.getQualifiedName()); + resolver.setSignature(type.getQualifiedName()); + resolver.setNamespace(INJECT_NAMESPACE); + resolver.setKind(ValueResolverKind.InjectedBean); + resolvers.add(resolver); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqDataModelProvider.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqDataModelProvider.java new file mode 100644 index 000000000..297b3c632 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqDataModelProvider.java @@ -0,0 +1,72 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.devtools.intellij.qute.psi.internal.extensions.roq; + +import java.util.Arrays; + + +import com.intellij.java.library.JavaLibraryUtil; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.util.Query; +import com.redhat.devtools.intellij.qute.psi.template.datamodel.AbstractDataModelProvider; +import com.redhat.devtools.intellij.qute.psi.template.datamodel.SearchContext; +import com.redhat.qute.commons.datamodel.DataModelParameter; +import com.redhat.qute.commons.datamodel.DataModelTemplate; +import com.redhat.qute.commons.datamodel.DataModelTemplateMatcher; + +/** + * Inject 'site' and 'page' as data model parameters for all Qute templates + * which belong to a Roq application. + */ +public class RoqDataModelProvider extends AbstractDataModelProvider { + + @Override + public void beginSearch(SearchContext context, ProgressIndicator monitor) { + if (!RoqUtils.isRoqProject(context.getJavaProject())) { + // It is not a Roq application, don't inject site and page. + return; + } + //quarkus-roq-frontmatter + + DataModelTemplate roqTemplate = new DataModelTemplate(); + roqTemplate.setTemplateMatcher(new DataModelTemplateMatcher(Arrays.asList("**/**"))); + + // site + DataModelParameter site = new DataModelParameter(); + site.setKey("site"); + site.setSourceType(RoqJavaConstants.SITE_CLASS); + roqTemplate.addParameter(site); + + // page + DataModelParameter page = new DataModelParameter(); + page.setKey("page"); + page.setSourceType(RoqJavaConstants.PAGE_CLASS); + roqTemplate.addParameter(page); + + context.getDataModelProject().getTemplates().add(roqTemplate); + } + + @Override + public void collectDataModel(Object match, SearchContext context, ProgressIndicator monitor) { + // Do nothing + } + + @Override + protected String[] getPatterns() { + return null; + } + + @Override + protected Query createSearchPattern(SearchContext context, String pattern) { + return null; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqJavaConstants.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqJavaConstants.java new file mode 100644 index 000000000..424a8dccd --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqJavaConstants.java @@ -0,0 +1,38 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.devtools.intellij.qute.psi.internal.extensions.roq; + +import java.util.Collection; +import java.util.List; + +/** + * Roq Java constants. + * + * @author Angelo ZERR + * + */ +public class RoqJavaConstants { + + private RoqJavaConstants() { + } + + public static final String ROQ_ARTIFACT_ID = "quarkus-roq-frontmatter"; + + public static final Collection ROQ_MAVEN_COORS = List.of("io.quarkiverse.roq:quarkus-roq-frontmatter"); + + public static final String DATA_MAPPING_ANNOTATION = "io.quarkiverse.roq.data.runtime.annotations.DataMapping"; + + public static final String SITE_CLASS = "io.quarkiverse.roq.frontmatter.runtime.model.Site"; + + public static final String PAGE_CLASS = "io.quarkiverse.roq.frontmatter.runtime.model.Page"; + +} \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqTemplateRootPathProvider.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqTemplateRootPathProvider.java new file mode 100644 index 000000000..28ca183b5 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqTemplateRootPathProvider.java @@ -0,0 +1,64 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat Inc. and others. +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License v. 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +* which is available at https://www.apache.org/licenses/LICENSE-2.0. +* +* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.devtools.intellij.qute.psi.internal.extensions.roq; + +import java.util.List; + + +import com.intellij.java.library.JavaLibraryUtil; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.vfs.VirtualFile; +import com.redhat.devtools.intellij.quarkus.QuarkusModuleUtil; +import com.redhat.devtools.intellij.qute.psi.template.rootpath.ITemplateRootPathProvider; +import com.redhat.devtools.intellij.qute.psi.utils.PsiQuteProjectUtils; +import com.redhat.devtools.intellij.qute.psi.utils.PsiTypeUtils; +import com.redhat.devtools.lsp4ij.LSPIJUtils; +import com.redhat.qute.commons.TemplateRootPath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Roq template root path provider for Roq project. + */ +public class RoqTemplateRootPathProvider implements ITemplateRootPathProvider { + + private static final String ORIGIN = "roq"; + + private static final String[] TEMPLATES_BASE_DIRS = { "templates/", "content/", "src/main/resources/content/" }; + + @Override + public boolean isApplicable(Module javaProject) { + return RoqUtils.isRoqProject(javaProject); + } + + @Override + public void collectTemplateRootPaths(Module javaProject, List rootPaths) { + VirtualFile moduleDir = QuarkusModuleUtil.getModuleDirPath(javaProject); + if (moduleDir != null) { + // templates + String templateBaseDir = LSPIJUtils.toUri(moduleDir).resolve("templates").toASCIIString(); + rootPaths.add(new TemplateRootPath(templateBaseDir, ORIGIN)); + // content + String contentBaseDir = LSPIJUtils.toUri(moduleDir).resolve("content").toASCIIString(); + rootPaths.add(new TemplateRootPath(contentBaseDir, ORIGIN)); + } + // src/main/resources/content + VirtualFile resourcesContentDir = PsiQuteProjectUtils.findBestResourcesDir(javaProject, "content"); + if (resourcesContentDir != null) { + String contentBaseDir = LSPIJUtils.toUri(resourcesContentDir).resolve("content").toASCIIString(); + rootPaths.add(new TemplateRootPath(contentBaseDir, ORIGIN)); + } + } + +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqUtils.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqUtils.java new file mode 100644 index 000000000..4fec8a2e9 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/roq/RoqUtils.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.psi.internal.extensions.roq; + +import com.intellij.java.library.JavaLibraryUtil; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.Set; + +/** + * Roq Utilities. + */ +public class RoqUtils { + + /** + * Returns true if the given module is a Roq project and false otherwise. + * + * @param module the module. + * @return true if the given module is a Roq project and false otherwise. + */ + public static boolean isRoqProject(@NotNull Module module) { + if (JavaLibraryUtil.hasAnyLibraryJar(module, RoqJavaConstants.ROQ_MAVEN_COORS)) { + return true; + } + + Set projectDependencies = new HashSet<>(); + ModuleUtilCore.getDependencies(module, projectDependencies); + return projectDependencies + .stream() + .anyMatch(m -> RoqJavaConstants.ROQ_ARTIFACT_ID.equals(m.getName())); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/webbundler/WebBundlerJavaConstants.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/webbundler/WebBundlerJavaConstants.java new file mode 100644 index 000000000..cfb2dc8f2 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/webbundler/WebBundlerJavaConstants.java @@ -0,0 +1,32 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.devtools.intellij.qute.psi.internal.extensions.webbundler; + +import java.util.Collection; +import java.util.List; + +/** + * Web Bundler Java constants. + * + * @author Angelo ZERR + * + */ +public class WebBundlerJavaConstants { + + private WebBundlerJavaConstants() { + } + + public static final Collection WEB_BUNDLER_MAVEN_COORS = List.of("io.quarkiverse.web-bundler:quarkus-roq-frontmatter"); + + public static final String BUNDLE_CLASS = "io.quarkiverse.web.bundler.runtime.Bundle"; + +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/webbundler/WebBundlerTemplateRootPathProvider.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/webbundler/WebBundlerTemplateRootPathProvider.java new file mode 100644 index 000000000..7f78aa77a --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/extensions/webbundler/WebBundlerTemplateRootPathProvider.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.psi.internal.extensions.webbundler; + +import com.intellij.java.library.JavaLibraryUtil; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.vfs.VirtualFile; +import com.redhat.devtools.intellij.quarkus.QuarkusModuleUtil; +import com.redhat.devtools.intellij.qute.psi.template.rootpath.ITemplateRootPathProvider; +import com.redhat.devtools.intellij.qute.psi.utils.PsiQuteProjectUtils; +import com.redhat.devtools.lsp4ij.LSPIJUtils; +import com.redhat.qute.commons.TemplateRootPath; + +import java.util.List; + + +/** + * Web Bundler template root path provider for Web Bundler project. + */ +public class WebBundlerTemplateRootPathProvider implements ITemplateRootPathProvider { + + private static final String ORIGIN = "web-bundler"; + + @Override + public boolean isApplicable(Module project) { + return JavaLibraryUtil.hasAnyLibraryJar(project, WebBundlerJavaConstants.WEB_BUNDLER_MAVEN_COORS); + } + + @Override + public void collectTemplateRootPaths(Module javaProject, List rootPaths) { + VirtualFile moduleDir = QuarkusModuleUtil.getModuleDirPath(javaProject); + if (moduleDir != null) { + // web/templates + String templateBaseDir = LSPIJUtils.toUri(moduleDir).resolve("web/templates").toASCIIString(); + rootPaths.add(new TemplateRootPath(templateBaseDir, ORIGIN)); + } + } + +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/datamodel/TemplateExtensionAnnotationSupport.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/datamodel/TemplateExtensionAnnotationSupport.java index bbe2df128..3db683e1d 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/datamodel/TemplateExtensionAnnotationSupport.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/datamodel/TemplateExtensionAnnotationSupport.java @@ -1,200 +1,197 @@ /******************************************************************************* -* Copyright (c) 2022 Red Hat Inc. and others. -* All rights reserved. This program and the accompanying materials -* which accompanies this distribution, and is available at -* http://www.eclipse.org/legal/epl-v20.html -* -* SPDX-License-Identifier: EPL-2.0 -* -* Contributors: -* Red Hat Inc. - initial API and implementation -*******************************************************************************/ + * Copyright (c) 2022 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ package com.redhat.devtools.intellij.qute.psi.internal.template.datamodel; -import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.TEMPLATE_EXTENSION_ANNOTATION; -import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.TEMPLATE_EXTENSION_ANNOTATION_MATCH_NAME; -import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.TEMPLATE_EXTENSION_ANNOTATION_NAMESPACE; - -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.logging.Level; -import java.util.logging.Logger; - import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.IndexNotReadyException; -import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiAnnotationOwner; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiCompiledElement; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiModifierListOwner; +import com.intellij.psi.*; import com.redhat.devtools.intellij.qute.psi.QuteSupportForTemplate; import com.redhat.devtools.intellij.qute.psi.internal.resolver.ITypeResolver; import com.redhat.devtools.intellij.qute.psi.template.datamodel.AbstractAnnotationTypeReferenceDataModelProvider; import com.redhat.devtools.intellij.qute.psi.template.datamodel.SearchContext; import com.redhat.devtools.intellij.qute.psi.utils.AnnotationUtils; import com.redhat.devtools.intellij.qute.psi.utils.PsiTypeUtils; +import com.redhat.qute.commons.datamodel.resolvers.ValueResolverInfo; import com.redhat.qute.commons.datamodel.resolvers.ValueResolverKind; import org.apache.commons.lang3.StringUtils; -import com.redhat.qute.commons.datamodel.resolvers.ValueResolverInfo; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants.*; /** * Template extension support. - * + * * @author Angelo ZERR - * * @see https://quarkus.io/guides/qute-reference#template_extension_methods - * */ public class TemplateExtensionAnnotationSupport extends AbstractAnnotationTypeReferenceDataModelProvider { - private static final Logger LOGGER = Logger.getLogger(TemplateExtensionAnnotationSupport.class.getName()); + private static final Logger LOGGER = Logger.getLogger(TemplateExtensionAnnotationSupport.class.getName()); + + private static final String[] ANNOTATION_NAMES = {TEMPLATE_EXTENSION_ANNOTATION}; - private static final String[] ANNOTATION_NAMES = { TEMPLATE_EXTENSION_ANNOTATION }; + @Override + protected String[] getAnnotationNames() { + return ANNOTATION_NAMES; + } - @Override - protected String[] getAnnotationNames() { - return ANNOTATION_NAMES; - } + @Override + protected void processAnnotation(PsiElement javaElement, PsiAnnotation annotation, String annotationName, + SearchContext context, ProgressIndicator monitor) { + if (!(javaElement instanceof PsiAnnotationOwner || javaElement instanceof PsiModifierListOwner)) { + return; + } + PsiAnnotation templateExtension = AnnotationUtils.getAnnotation(javaElement, + TEMPLATE_EXTENSION_ANNOTATION); + if (templateExtension == null) { + return; + } + if (javaElement instanceof PsiClass) { + PsiClass type = (PsiClass) javaElement; + collectResolversForTemplateExtension(type, templateExtension, + context.getDataModelProject().getValueResolvers(), context.getJavaProject(), monitor); + } else if (javaElement instanceof PsiMethod) { + PsiMethod method = (PsiMethod) javaElement; + collectResolversForTemplateExtension(method, templateExtension, + context.getDataModelProject().getValueResolvers(), context.getJavaProject(), monitor); + } + } - @Override - protected void processAnnotation(PsiElement javaElement, PsiAnnotation annotation, String annotationName, - SearchContext context, ProgressIndicator monitor) { - if (!(javaElement instanceof PsiAnnotationOwner || javaElement instanceof PsiModifierListOwner)) { - return; - } - PsiAnnotation templateExtension = AnnotationUtils.getAnnotation(javaElement, - TEMPLATE_EXTENSION_ANNOTATION); - if (templateExtension == null) { - return; - } - if (javaElement instanceof PsiClass) { - PsiClass type = (PsiClass) javaElement; - collectResolversForTemplateExtension(type, templateExtension, - context.getDataModelProject().getValueResolvers(), context.getJavaProject(), monitor); - } else if (javaElement instanceof PsiMethod) { - PsiMethod method = (PsiMethod) javaElement; - collectResolversForTemplateExtension(method, templateExtension, - context.getDataModelProject().getValueResolvers(), context.getJavaProject(), monitor); - } - } + private static void collectResolversForTemplateExtension(PsiClass type, PsiAnnotation templateExtension, + List resolvers, Module project, + ProgressIndicator monitor) { + try { + ITypeResolver typeResolver = QuteSupportForTemplate.createTypeResolver(type, project); + PsiMethod[] methods = type.getMethods(); + int resolversLengthPreAdd = resolvers.size(); + for (PsiMethod method : methods) { + if (isTemplateExtensionMethod(method)) { + PsiAnnotation methodTemplateExtension = AnnotationUtils.getAnnotation(method, + TEMPLATE_EXTENSION_ANNOTATION); + collectResolversForTemplateExtension(method, + methodTemplateExtension != null ? methodTemplateExtension : templateExtension, resolvers, + typeResolver, ValueResolverKind.TemplateExtensionOnMethod); + } + } + if (resolversLengthPreAdd == resolvers.size()) { + // Add a dummy ValueResolverInfo to indicate that this is a template extensions + // class + addDummyResolverForTemplateExtensionsClass(type, resolvers); + } + } catch (ProcessCanceledException e) { + //Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility + //TODO delete block when minimum required version is 2024.2 + throw e; + } catch (IndexNotReadyException | CancellationException e) { + throw e; + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Error while getting methods of '" + type.getName() + "'.", e); + } + } - private static void collectResolversForTemplateExtension(PsiClass type, PsiAnnotation templateExtension, - List resolvers, Module project, - ProgressIndicator monitor) { - try { - ITypeResolver typeResolver = QuteSupportForTemplate.createTypeResolver(type, project); - PsiMethod[] methods = type.getMethods(); - int resolversLengthPreAdd = resolvers.size(); - for (PsiMethod method : methods) { - if (isTemplateExtensionMethod(method)) { - PsiAnnotation methodTemplateExtension = AnnotationUtils.getAnnotation(method, - TEMPLATE_EXTENSION_ANNOTATION); - collectResolversForTemplateExtension(method, - methodTemplateExtension != null ? methodTemplateExtension : templateExtension, resolvers, - typeResolver, ValueResolverKind.TemplateExtensionOnMethod); - } - } - if (resolversLengthPreAdd == resolvers.size()) { - // Add a dummy ValueResolverInfo to indicate that this is a template extensions - // class - addDummyResolverForTemplateExtensionsClass(type, resolvers); - } - } catch (ProcessCanceledException e) { - //Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility - //TODO delete block when minimum required version is 2024.2 - throw e; - } catch (IndexNotReadyException | CancellationException e) { - throw e; - } catch (Exception e) { - LOGGER.log(Level.WARNING, "Error while getting methods of '" + type.getName() + "'.", e); - } - } + /** + * Returns true if the given method method is a template extension + * method and false otherwise. + *

+ * A template extension method: + * + *

    + *
  • must not be private
  • + *
  • must be static,
  • + *
  • must not return void.
  • + *
+ * + * @param method the method to check. + * @return true if the given method method is a template extension + * method and false otherwise. + */ + private static boolean isTemplateExtensionMethod(PsiMethod method) { + try { + return !method.isConstructor() /* && Flags.isPublic(method.getFlags()) */ + && !PsiTypeUtils.isVoidReturnType(method); + } catch (ProcessCanceledException e) { + //Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility + //TODO delete block when minimum required version is 2024.2 + throw e; + } catch (IndexNotReadyException | CancellationException e) { + throw e; + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Error while getting method information of '" + method.getName() + "'.", e); + return false; + } + } - /** - * Returns true if the given method method is a template extension - * method and false otherwise. - * - * A template extension method: - * - *
    - *
  • must not be private
  • - *
  • must be static,
  • - *
  • must not return void.
  • - *
- * - * @param method the method to check. - * @return true if the given method method is a template extension - * method and false otherwise. - */ - private static boolean isTemplateExtensionMethod(PsiMethod method) { - try { - return !method.isConstructor() /* && Flags.isPublic(method.getFlags()) */ - && !PsiTypeUtils.isVoidReturnType(method); - } catch (ProcessCanceledException e) { - //Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility - //TODO delete block when minimum required version is 2024.2 - throw e; - } catch (IndexNotReadyException | CancellationException e) { - throw e; - } catch (Exception e) { - LOGGER.log(Level.WARNING, "Error while getting method information of '" + method.getName() + "'.", e); - return false; - } - } + public static void collectResolversForTemplateExtension(PsiMethod method, PsiAnnotation templateExtension, + List resolvers, Module project, ProgressIndicator monitor) { + if (isTemplateExtensionMethod(method)) { + ITypeResolver typeResolver = QuteSupportForTemplate.createTypeResolver(method, project); + collectResolversForTemplateExtension(method, templateExtension, resolvers, typeResolver, + ValueResolverKind.TemplateExtensionOnMethod); + } + } - public static void collectResolversForTemplateExtension(PsiMethod method, PsiAnnotation templateExtension, - List resolvers, Module project, ProgressIndicator monitor) { - if (isTemplateExtensionMethod(method)) { - ITypeResolver typeResolver = QuteSupportForTemplate.createTypeResolver(method, project); - collectResolversForTemplateExtension(method, templateExtension, resolvers, typeResolver, - ValueResolverKind.TemplateExtensionOnMethod); - } - } + private static void collectResolversForTemplateExtension(PsiMethod method, PsiAnnotation templateExtension, + List resolvers, + ITypeResolver typeResolver, ValueResolverKind kind) { + ValueResolverInfo resolver = new ValueResolverInfo(); + resolver.setSourceType(method.getContainingClass().getQualifiedName()); + resolver.setSignature(typeResolver.resolveMethodSignature(method)); + resolver.setKind(kind); + resolver.setBinary(method.getContainingClass() instanceof PsiCompiledElement); + try { + String namespace = AnnotationUtils.getAnnotationMemberValue(templateExtension, + TEMPLATE_EXTENSION_ANNOTATION_NAMESPACE); + resolver.setNamespace(namespace); + List matchNames = AnnotationUtils.getAnnotationMemberValueAsArray(templateExtension, + TEMPLATE_EXTENSION_ANNOTATION_MATCH_NAMES); + if (!matchNames.isEmpty()) { + resolver.setMatchNames(matchNames); + } else { + String matchName = AnnotationUtils.getAnnotationMemberValue(templateExtension, + TEMPLATE_EXTENSION_ANNOTATION_MATCH_NAME); + if (StringUtils.isNotEmpty(matchName)) { + resolver.setMatchNames(Arrays.asList(matchName)); + } + } + } catch (ProcessCanceledException e) { + //Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility + //TODO delete block when minimum required version is 2024.2 + throw e; + } catch (IndexNotReadyException | CancellationException e) { + throw e; + } catch (RuntimeException e) { + LOGGER.log(Level.WARNING, + "Error while getting annotation member value of '" + method.getName() + "'.", e); + } + if (!resolvers.contains(resolver)) { + resolvers.add(resolver); + } + } - private static void collectResolversForTemplateExtension(PsiMethod method, PsiAnnotation templateExtension, - List resolvers, - ITypeResolver typeResolver, ValueResolverKind kind) { - ValueResolverInfo resolver = new ValueResolverInfo(); - resolver.setSourceType(method.getContainingClass().getQualifiedName()); - resolver.setSignature(typeResolver.resolveMethodSignature(method)); - resolver.setKind(kind); - resolver.setBinary(method.getContainingClass() instanceof PsiCompiledElement); - try { - String namespace = AnnotationUtils.getAnnotationMemberValue(templateExtension, - TEMPLATE_EXTENSION_ANNOTATION_NAMESPACE); - String matchName = AnnotationUtils.getAnnotationMemberValue(templateExtension, - TEMPLATE_EXTENSION_ANNOTATION_MATCH_NAME); - resolver.setNamespace(namespace); - if (StringUtils.isNotEmpty(matchName)) { - resolver.setMatchName(matchName); - } - } catch (ProcessCanceledException e) { - //Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multicatch to keep backward compatibility - //TODO delete block when minimum required version is 2024.2 - throw e; - } catch (IndexNotReadyException | CancellationException e) { - throw e; - } catch (RuntimeException e) { - LOGGER.log(Level.WARNING, - "Error while getting annotation member value of '" + method.getName() + "'.", e); - } - if (!resolvers.contains(resolver)) { - resolvers.add(resolver); - } - } + private static void addDummyResolverForTemplateExtensionsClass(PsiClass type, List resolvers) { + ValueResolverInfo resolver = new ValueResolverInfo(); + resolver.setSourceType(type.getQualifiedName()); + resolver.setKind(ValueResolverKind.TemplateExtensionOnClass); + resolver.setBinary(type instanceof PsiCompiledElement); + // Needed to prevent NPE + resolver.setSignature(""); + resolvers.add(resolver); + } - private static void addDummyResolverForTemplateExtensionsClass(PsiClass type, List resolvers) { - ValueResolverInfo resolver = new ValueResolverInfo(); - resolver.setSourceType(type.getQualifiedName()); - resolver.setKind(ValueResolverKind.TemplateExtensionOnClass); - resolver.setBinary(type instanceof PsiCompiledElement); - // Needed to prevent NPE - resolver.setSignature(""); - resolvers.add(resolver); - } } diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/rootpath/TemplateRootPathProviderRegistry.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/rootpath/TemplateRootPathProviderRegistry.java new file mode 100644 index 000000000..6ad75a0f4 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/rootpath/TemplateRootPathProviderRegistry.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2022 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.psi.internal.template.rootpath; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.module.Module; +import com.intellij.util.KeyedLazyInstanceEP; +import com.redhat.devtools.intellij.qute.psi.internal.AbstractQuteExtensionPointRegistry; +import com.redhat.devtools.intellij.qute.psi.template.rootpath.ITemplateRootPathProvider; +import com.redhat.qute.commons.TemplateRootPath; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Registry to handle instances of {@link ITemplateRootPathProvider} + * + * @author Angelo ZERR + */ +public class TemplateRootPathProviderRegistry extends AbstractQuteExtensionPointRegistry { + + private static final Logger LOGGER = Logger.getLogger(TemplateRootPathProviderRegistry.class.getName()); + + private static final ExtensionPointName TEMPLATE_ROOT_PATH_PROVIDERS_EXTENSION_POINT_ID = ExtensionPointName.create("com.redhat.devtools.intellij.quarkus.qute.templateRootPathProvider"); + + public static class TemplateRootPathProviderBean extends KeyedLazyInstanceEP { + } + + + private static final TemplateRootPathProviderRegistry INSTANCE = new TemplateRootPathProviderRegistry(); + + private TemplateRootPathProviderRegistry() { + super(); + } + + public static TemplateRootPathProviderRegistry getInstance() { + return INSTANCE; + } + + @Override + public ExtensionPointName getProviderExtensionId() { + return TEMPLATE_ROOT_PATH_PROVIDERS_EXTENSION_POINT_ID; + } + + /** + * Returns the template root path list for the given java project. + * + * @param javaProject the java project. + * @return the template root path list for the given java project. + */ + public List getTemplateRootPaths(Module javaProject) { + List rootPaths = new ArrayList<>(); + for (ITemplateRootPathProvider provider : super.getProviders()) { + if (provider.isApplicable(javaProject)) { + try { + provider.collectTemplateRootPaths(javaProject, rootPaths); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error while collecting template root path with the provider '" + + provider.getClass().getName() + "'.", e); + } + } + } + return rootPaths; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/template/rootpath/DefaultTemplateRootPathProvider.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/template/rootpath/DefaultTemplateRootPathProvider.java new file mode 100644 index 000000000..ee65ea930 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/template/rootpath/DefaultTemplateRootPathProvider.java @@ -0,0 +1,41 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat Inc. and others. +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License v. 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +* which is available at https://www.apache.org/licenses/LICENSE-2.0. +* +* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.devtools.intellij.qute.psi.template.rootpath; + +import com.intellij.openapi.module.Module; +import com.redhat.devtools.intellij.qute.psi.utils.PsiQuteProjectUtils; +import com.redhat.qute.commons.TemplateRootPath; + +import java.util.List; + +/** + * Default template root path provider for Qute project (src/main/resources/templates) + */ +public class DefaultTemplateRootPathProvider implements ITemplateRootPathProvider{ + + private static final String ORIGIN = "core"; + public static final String TEMPLATES_FOLDER_NAME = "templates/"; + + @Override + public boolean isApplicable(Module project) { + return PsiQuteProjectUtils.hasQuteSupport(project); + } + + @Override + public void collectTemplateRootPaths(Module javaProject, List rootPaths) { + String templateBaseDir = PsiQuteProjectUtils.getTemplateBaseDir(javaProject, TEMPLATES_FOLDER_NAME); + rootPaths.add(new TemplateRootPath(templateBaseDir, ORIGIN)); + } + +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/template/rootpath/ITemplateRootPathProvider.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/template/rootpath/ITemplateRootPathProvider.java new file mode 100644 index 000000000..a590d83d0 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/template/rootpath/ITemplateRootPathProvider.java @@ -0,0 +1,45 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat Inc. and others. +* +* This program and the accompanying materials are made available under the +* terms of the Eclipse Public License v. 2.0 which is available at +* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +* which is available at https://www.apache.org/licenses/LICENSE-2.0. +* +* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package com.redhat.devtools.intellij.qute.psi.template.rootpath; + +import java.util.List; + +import com.intellij.openapi.module.Module; +import com.redhat.qute.commons.TemplateRootPath; + +/** + * Template root path provider API. + * + * @author Angelo ZERR + * + */ +public interface ITemplateRootPathProvider { + + /** + * Returns true if the given Java project can provide template root path and + * false otherwise. + * + * @param project the Java project. + * @return true if the given Java project can provide template root path and + * false otherwise. + */ + boolean isApplicable(Module project); + + /** + * Collect template root path for the given Java project. + * + * @param javaProject the Java project. + */ + void collectTemplateRootPaths(Module javaProject, List rootPaths); +} \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/AnnotationUtils.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/AnnotationUtils.java index d1ab805d5..deb1472c3 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/AnnotationUtils.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/AnnotationUtils.java @@ -18,7 +18,14 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiModifierListOwner; import com.intellij.psi.PsiNameValuePair; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Java annotations utilities. @@ -29,6 +36,7 @@ public class AnnotationUtils { private static final String ATTRIBUTE_VALUE = "value"; + private static final Logger log = LoggerFactory.getLogger(AnnotationUtils.class); public static boolean hasAnnotation(PsiElement annotatable, String... annotationNames) { return getAnnotation(annotatable, annotationNames) != null; @@ -129,10 +137,37 @@ private static boolean isMatchAnnotationFullyQualifiedName(PsiAnnotation annotat */ public static String getAnnotationMemberValue(PsiAnnotation annotation, String memberName) { PsiAnnotationMemberValue member = annotation.findDeclaredAttributeValue(memberName); + if (member == null) { + return null; + } return getValueAsString(member); } - private static String getValueAsString(PsiAnnotationMemberValue member) { + /** + * Returns the value array of the given member name of the given annotation. + * + * @param annotation the annotation. + * @param memberName the member name. + * @return the value array of the given member name of the given annotation. + */ + @NotNull + public static List getAnnotationMemberValueAsArray(PsiAnnotation annotation, String memberName) { + PsiAnnotationMemberValue member = annotation.findDeclaredAttributeValue(memberName); + if (member == null) { + return Collections.emptyList(); + } + List values = new ArrayList<>(); + PsiElement[] elements = member.getChildren(); + for (var element : elements) { + String value = getValueAsString(element); + if (value != null) { + values.add(value); + } + } + return values; + } + + private static String getValueAsString(PsiElement member) { String value = member != null && member.getText() != null ? member.getText() : null; if (value != null && value.length() > 1 && value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"') { value = value.substring(1, value.length() - 1); diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/PsiQuteProjectUtils.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/PsiQuteProjectUtils.java index 6bd83da60..fa073a7e1 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/PsiQuteProjectUtils.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/PsiQuteProjectUtils.java @@ -11,6 +11,7 @@ *******************************************************************************/ package com.redhat.devtools.intellij.qute.psi.utils; +import com.intellij.java.library.JavaLibraryUtil; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; @@ -18,14 +19,19 @@ import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.redhat.devtools.intellij.quarkus.QuarkusModuleUtil; +import com.redhat.devtools.intellij.qute.psi.internal.template.rootpath.TemplateRootPathProviderRegistry; import com.redhat.devtools.lsp4ij.LSPIJUtils; import com.redhat.devtools.intellij.qute.psi.internal.QuteJavaConstants; +import com.redhat.qute.commons.FileUtils; import com.redhat.qute.commons.ProjectInfo; +import com.redhat.qute.commons.TemplateRootPath; import io.quarkus.runtime.util.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.java.JavaResourceRootType; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; @@ -50,17 +56,33 @@ private PsiQuteProjectUtils() { public static ProjectInfo getProjectInfo(Module javaProject) { String projectUri = getProjectURI(javaProject); - String templateBaseDir = getTemplateBaseDir(javaProject); + // Project dependencies // Project dependencies Set projectDependencies = new HashSet<>(); ModuleUtilCore.getDependencies(javaProject, projectDependencies); - return new ProjectInfo(projectUri, projectDependencies + // Template root paths + List templateRootPaths = TemplateRootPathProviderRegistry.getInstance() + .getTemplateRootPaths(javaProject); + return new ProjectInfo(projectUri, projectDependencies .stream() .filter(projectDependency -> !javaProject.equals(projectDependency)) .map(LSPIJUtils::getProjectUri) - .collect(Collectors.toList()), templateBaseDir); + .collect(Collectors.toList()), templateRootPaths); } + /** + * Returns the full path of the Qute templates base dir '$base-dir-of-module/src/main/resources/templates' for the given module. + * + * @param javaProject the Java module project. + * @return the full path of the Qute templates base dir '$base-dir-of-module/src/main/resources/templates' for the given module. + */ + public static String getTemplateBaseDir(Module javaProject, String templateFolderName) { + VirtualFile resourcesDir = findBestResourcesDir(javaProject); + if (resourcesDir != null) { + return LSPIJUtils.toUri(resourcesDir).resolve(templateFolderName).toASCIIString(); + } + return LSPIJUtils.toUri(javaProject).resolve(RESOURCES_BASE_DIR).resolve(templateFolderName).toASCIIString(); + } /** * Returns the full path of the Qute templates base dir '$base-dir-of-module/src/main/resources/templates' for the given module. * @@ -94,20 +116,24 @@ private static String getTemplateBaseDir(Module javaProject) { return relativeResourcesPath + "/" + TEMPLATES_FOLDER_NAME + "/"; } + public static @Nullable VirtualFile findBestResourcesDir(@NotNull Module javaProject) { + return findBestResourcesDir(javaProject, TEMPLATES_FOLDER_NAME); + } + /** * Returns the best 'resources' directory for the given Java module project and null otherwise. * * @param javaProject the Java module project. * @return the best resources dir for the given Java module project. */ - public static @Nullable VirtualFile findBestResourcesDir(@NotNull Module javaProject) { + public static @Nullable VirtualFile findBestResourcesDir(@NotNull Module javaProject, String templatesFolderName) { List resourcesDirs = ModuleRootManager.getInstance(javaProject).getSourceRoots(JavaResourceRootType.RESOURCE); if (!resourcesDirs.isEmpty()) { QuarkusModuleUtil.sortRoot(resourcesDirs); // put root with smallest path first (eliminates generated sources roots) // The module configure 'Resources folder' // 1) loop for each configured resources dir and returns the first which contains 'templates' folder. for (var dir : resourcesDirs) { - var templatesDir = dir.findChild(TEMPLATES_FOLDER_NAME); + var templatesDir = dir.findChild(templatesFolderName); if (templatesDir != null && templatesDir.exists()) { return dir; } @@ -150,7 +176,7 @@ public static String getProjectURI(Project project) { } public static boolean hasQuteSupport(Module javaProject) { - return PsiTypeUtils.findType(javaProject, QuteJavaConstants.ENGINE_BUILDER_CLASS) != null; + return JavaLibraryUtil.hasAnyLibraryJar(javaProject, QuteJavaConstants.QUTE_MAVEN_COORDS); } public static TemplatePathInfo getTemplatePath(String templatesBaseDir, String basePath, String className, String methodOrFieldName, boolean ignoreFragments, TemplateNameStrategy templateNameStrategy) { @@ -211,7 +237,21 @@ public static void appendAndSlash(@NotNull StringBuilder path, @NotNull String s } public static boolean isQuteTemplate(VirtualFile file, Module module) { - return file.getPath().contains(TEMPLATES_FOLDER_NAME) && - ModuleRootManager.getInstance(module).getFileIndex().isInSourceContent(file); + String templateFileUri = file.getPath(); + if(file.getPath().contains(TEMPLATES_FOLDER_NAME) && + ModuleRootManager.getInstance(module).getFileIndex().isInSourceContent(file)) { + return true; + } + ProjectInfo projectInfo = PsiQuteProjectUtils.getProjectInfo(module); + if (projectInfo == null) { + return false; + } + Path templatePath = Paths.get(file.getPath()); + for (TemplateRootPath rootPath : projectInfo.getTemplateRootPaths()) { + if (rootPath.isIncluded(templatePath)) { + return true; + } + } + return false; } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f269824c1..ddd95a22d 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -459,6 +459,11 @@ + + + + + + - + + + + + diff --git a/src/test/java/com/redhat/devtools/intellij/qute/psi/QuteAssert.java b/src/test/java/com/redhat/devtools/intellij/qute/psi/QuteAssert.java index c64401a04..8527b8309 100644 --- a/src/test/java/com/redhat/devtools/intellij/qute/psi/QuteAssert.java +++ b/src/test/java/com/redhat/devtools/intellij/qute/psi/QuteAssert.java @@ -28,18 +28,28 @@ */ public class QuteAssert { - public static void assertValueResolver(String namespace, String signature, String sourceType, - List resolvers) { - assertValueResolver(namespace, signature, sourceType, null, resolvers); + public static ValueResolverInfo assertValueResolver(String namespace, String signature, String sourceType, + List resolvers) { + return assertValueResolver(namespace, signature, sourceType, null, null, false, resolvers); } - public static void assertValueResolver(String namespace, String signature, String sourceType, String named, - List resolvers) { - assertValueResolver(namespace, signature, sourceType, named, false, resolvers); + public static ValueResolverInfo assertValueResolver(String namespace, String signature, String sourceType, + String named, List resolvers) { + return assertValueResolver(namespace, signature, sourceType, named, null, false, resolvers); } - public static ValueResolverInfo assertValueResolver(String namespace, String signature, String sourceType, String named, - boolean globalVariable, List resolvers) { + public static ValueResolverInfo assertValueResolver(String namespace, String signature, String sourceType, + List matchNames, List resolvers) { + return assertValueResolver(namespace, signature, sourceType, null, matchNames, false, resolvers); + } + + public static ValueResolverInfo assertValueResolver(String namespace, String signature, String sourceType, + String named, boolean globalVariable, List resolvers) { + return assertValueResolver(namespace, signature, sourceType, named, null, globalVariable, resolvers); + } + + public static ValueResolverInfo assertValueResolver(String namespace, String signature, String sourceType, + String named, List matchNames, boolean globalVariable, List resolvers) { Optional result = resolvers.stream() .filter(r -> signature.equals(r.getSignature()) && Objects.equals(namespace, r.getNamespace())) .findFirst(); @@ -48,10 +58,17 @@ public static ValueResolverInfo assertValueResolver(String namespace, String sig Assert.assertEquals(namespace, resolver.getNamespace()); Assert.assertEquals(signature, resolver.getSignature()); Assert.assertEquals(sourceType, resolver.getSourceType()); + if (matchNames == null) { + Assert.assertNull(resolver.getMatchNames()); + } else { + Assert.assertArrayEquals(matchNames.toArray(), + resolver.getMatchNames() != null ? resolver.getMatchNames().toArray() : null); + } Assert.assertEquals(globalVariable, resolver.isGlobalVariable()); return resolver; } + public static void assertNotValueResolver(String namespace, String signature, String sourceType, String named, List resolvers) { assertNotValueResolver(namespace, signature, sourceType, named, false, resolvers); diff --git a/src/test/java/com/redhat/devtools/intellij/qute/psi/template/TemplateGetDataModelProjectTest.java b/src/test/java/com/redhat/devtools/intellij/qute/psi/template/TemplateGetDataModelProjectTest.java index 87a88e9fb..72f961656 100644 --- a/src/test/java/com/redhat/devtools/intellij/qute/psi/template/TemplateGetDataModelProjectTest.java +++ b/src/test/java/com/redhat/devtools/intellij/qute/psi/template/TemplateGetDataModelProjectTest.java @@ -24,6 +24,7 @@ import org.junit.Assert; import org.junit.Test; +import java.util.Arrays; import java.util.List; import static com.redhat.devtools.intellij.qute.psi.QuteAssert.*; @@ -216,11 +217,16 @@ private static void testValueResolversFromTemplateExtension(List, key : java.lang.Object) : V", "io.quarkus.qute.runtime.extensions.MapTemplateExtensions", resolvers);