This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch fix/private-api-remediation in repository https://gitbox.apache.org/repos/asf/struts-intellij-plugin.git
commit ba31f4ed0b5c287d2b508af041359e0cb6ea4d95 Author: Lukasz Lenart <[email protected]> AuthorDate: Tue Feb 10 18:03:36 2026 +0200 fix: remediate private and deprecated API usages for marketplace approval Replace internal and deprecated IntelliJ Platform APIs to pass JetBrains Marketplace verification for plugin version 253.x. Changes: - Replace IconManager.loadRasterizedIcon() with IconLoader.getIcon() - Replace WebFacet.getWebRoots(boolean) with getWebRoots() - Replace AnActionButton with DumbAwareAction in toolbar - Replace IdeFocusManager.doWhenFocusSettlesDown() with requestFocus() - Replace ResourceRegistrar.addStdResource(Class) with ClassLoader version - Replace FilenameIndex.getFilesByName() with getVirtualFilesByName() - Replace deprecated URL(String) constructor with URI.create().toURL() - Document GraphBuilder internal API usage (no public replacement) - Document EXTEND_CLASS_NAMES deprecation (no replacement available) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --- CLAUDE.md | 68 ++- src/main/gen/com/intellij/lang/ognl/OgnlIcons.java | 14 +- .../java/com/intellij/struts2/Struts2Icons.java | 24 +- .../intellij/struts2/Struts2ResourceProvider.java | 148 ++--- .../struts/impl/path/FileReferenceSetHelper.java | 126 ++-- .../struts2/facet/ui/FileSetConfigurationTab.java | 662 ++++++++++----------- .../graph/fileEditor/Struts2GraphComponent.java | 197 +++--- .../graph/fileEditor/Struts2GraphFileEditor.java | 155 ++--- .../inspection/HardcodedActionUrlInspection.java | 486 ++++++++------- .../contributor/ConstantValueClassConverter.java | 140 ++--- .../velocity/Struts2GlobalMacroProvider.java | 43 +- 11 files changed, 1052 insertions(+), 1011 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5b0707d..0752c42 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,6 +5,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Development Commands ### Build and Test + ```bash ./gradlew build # Build the plugin ./gradlew test -x rat # Run unit tests (excluding Apache RAT license checks) @@ -15,6 +16,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ``` ### Development and Debugging + ```bash ./gradlew runIde # Run IntelliJ IDEA with the plugin loaded ./gradlew runIdeForUiTests # Launch IDE with robot server on port 8082 @@ -23,6 +25,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ``` ### Code Quality + ```bash ./gradlew runInspections # Run Qodana inspections (requires Docker) ./gradlew rat # Run Apache RAT license check @@ -30,32 +33,38 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Architecture -This is an IntelliJ IDEA Ultimate plugin for Apache Struts 2 framework, providing IDE support for struts.xml configuration, OGNL expressions, validation files, and JSP tag libraries. +This is an IntelliJ IDEA Ultimate plugin for Apache Struts 2 framework, providing IDE support for struts.xml +configuration, OGNL expressions, validation files, and JSP tag libraries. ### Package Structure + - `com.intellij.struts2` - Core Struts 2 plugin functionality - `com.intellij.lang.ognl` - OGNL language support (lexer, parser, highlighting, completion) ### Key Architectural Components **DOM Model Layer** (`com.intellij.struts2.dom`) + - `struts/` - Struts XML configuration DOM (actions, results, interceptors, packages) - `validator/` - Validation XML DOM models - `params/` - Parameter handling and type conversion - DOM converters in `impl/` packages handle reference resolution **Framework Integration** (`com.intellij.struts2.facet`) + - `StrutsFacet` / `StrutsFacetType` - Framework facet configuration - `StrutsFrameworkDetector` - Auto-detection of Struts projects - `StrutsFrameworkInitializer` - Modern `ProjectActivity`-based initialization (replaces deprecated `StartupManager`) - `FileSetConfigurationTab` - UI for configuring struts.xml file sets **Reference Resolution** (`com.intellij.struts2.reference`) + - `StrutsReferenceContributor` - XML reference providers for struts.xml - `jsp/` - JSP tag library references and action link resolution - Reference contributors for various Struts tag libraries (jQuery, Bootstrap, etc.) **OGNL Language** (`com.intellij.lang.ognl`) + - `OgnlLanguage` / `OgnlFileType` - Language definition - `lexer/` - JFlex-based lexer (`ognl.flex`) - `parser/` - Grammar Kit parser (`OgnlParser.bnf`) @@ -64,23 +73,32 @@ This is an IntelliJ IDEA Ultimate plugin for Apache Struts 2 framework, providin - `completion/` - Code completion providers **Extension Points** (defined in `plugin.xml`) + - `struts2.constantContributor` - Add custom Struts constants - `struts2.resultContributor` - Custom result type path resolution - `struts2.classContributor` - Extend class resolution (actions, interceptors) - `struts2.paramNameCustomConverter` - Custom parameter name resolution ### Template Engine Integrations + Optional modules loaded via `<depends optional="true">`: + - `struts2-freemarker.xml` - FreeMarker template support - `struts2-velocity.xml` - Velocity template support - `struts2-spring.xml` - Spring integration - `struts2-groovy.xml` - Groovy annotations support ### Test Organization + - `src/test/java` - Test classes following IntelliJ Platform test patterns - `src/test/testData` - Test fixture files (XML, JSP, Java sources) - Test base classes extend IntelliJ Platform's `LightJavaCodeInsightFixtureTestCase` or similar +### Generated Code + +- `src/main/gen/` - Generated parser/lexer code (included in source set automatically) +- Source grammars: `src/main/grammar/ognl.bnf` (Grammar Kit), `_OgnlLexer.flex` (JFlex) + ## IntelliJ Platform Version Mapping | Branch | Platform Version | Build Range | @@ -89,51 +107,59 @@ Optional modules loaded via `<depends optional="true">`: | 243.x | 2024.3 | 243.* | | 251.x | 2025.1 | 251.* | | 252.x | 2025.2 | 252.* | +| 253.x | 2025.3 | 253.* | ### Plugin Version Format + `{BRANCH}.{BUILD}.{FIX}` (e.g., `252.18970.1`) - **BRANCH** - IntelliJ Platform branch (252 = 2025.2) - **BUILD** - Automatically calculated in GitHub Actions as `18969 + git rev-list --count HEAD` - **FIX** - Patch version (typically `1` for new builds) -The base value `18969` maintains historical continuity from when the plugin was donated by JetBrains to Apache Software Foundation, ensuring version numbers continue from the previous build sequence. +The base value `18969` maintains historical continuity from when the plugin was donated by JetBrains to Apache Software +Foundation, ensuring version numbers continue from the previous build sequence. ## Platform Upgrade Checklist When upgrading to a new IntelliJ Platform version: 1. **Update `gradle.properties`**: - - `platformVersion` - Target platform (e.g., `2025.2`) - - `pluginSinceBuild` / `pluginUntilBuild` - Build range (e.g., `251` to `252.*`) - - `pluginVersion` - Match branch prefix (e.g., `252.x.y`) - + - `platformVersion` - Target platform (e.g., `2025.2`) + - `pluginSinceBuild` / `pluginUntilBuild` - Build range (e.g., `251` to `252.*`) + - `pluginVersion` - Match branch prefix (e.g., `252.x.y`) 2. **Check API Compatibility**: - - Review https://jb.gg/intellij-api-changes for breaking changes - - Run `./gradlew runPluginVerifier` to detect issues - - Common changes: deprecated UI icons, removed internal APIs, changed test framework paths - + - Review https://jb.gg/intellij-api-changes for breaking changes + - Run `./gradlew runPluginVerifier` to detect issues + - Common changes: deprecated UI icons, removed internal APIs, changed test framework paths 3. **Update CI/Tooling** (if Java version changes): - - `.github/workflows/build.yml` - Java version in setup - - `qodana.yml` - Linter version and `projectJDK` - - `build.gradle.kts` - `jvmToolchain()` version - + - `.github/workflows/build.yml` - Java version in setup + - `qodana.yml` - Linter version and `projectJDK` + - `build.gradle.kts` - `jvmToolchain()` version 4. **Fix Test Path Issues**: - - IntelliJ Platform test frameworks sometimes change path resolution - - Override `getBasePath()` and `getTestDataPath()` if tests fail to find fixtures - - Use project-relative paths like `"src/test/testData/..."` - + - IntelliJ Platform test frameworks sometimes change path resolution + - Override `getBasePath()` and `getTestDataPath()` if tests fail to find fixtures + - Use project-relative paths like `"src/test/testData/..."` 5. **Update CHANGELOG.md** with dependency upgrades ## Known Platform Quirks -**Test Data Path Resolution**: IntelliJ 2024.2+ changed how `LexerTestCase` and `DomStubTest` resolve paths. If tests fail with "Cannot find source file", override path methods to use project-relative paths. +**Test Data Path Resolution**: IntelliJ 2024.2+ changed how `LexerTestCase` and `DomStubTest` resolve paths. If tests +fail with "Cannot find source file", override path methods to use project-relative paths. + +**Internal API Usage**: Some platform icons and utilities are internal. The plugin verifier will warn about these. +Prefer public APIs: -**Internal API Usage**: Some platform icons and utilities are internal. The plugin verifier will warn about these. Prefer public APIs: - Use `AllIcons.Nodes.*` instead of `PlatformIcons` - Use `Charset.availableCharsets()` instead of `CharsetToolkit.getAvailableCharsets()` -**JSP Reference Providers**: The `javaee.web.customServletReferenceProvider` extension point behavior changed in 2024.2. Action link references in JSP may need alternative registration approaches. +**JSP Reference Providers**: The `javaee.web.customServletReferenceProvider` extension point behavior changed in 2024.2. +Action link references in JSP may need alternative registration approaches. + +**Temporarily Disabled Tests**: Several tests are disabled pending 2025.3 compatibility fixes: +OgnlLexerTest, StrutsCompletionTest, StrutsHighlightingSpringTest, StrutsResultResolvingTest, +ActionLinkReferenceProviderTest. See CHANGELOG.md for full list - these should be re-enabled +when infrastructure issues are resolved. ## Claude Guidance diff --git a/src/main/gen/com/intellij/lang/ognl/OgnlIcons.java b/src/main/gen/com/intellij/lang/ognl/OgnlIcons.java index 449531f..1fc4f32 100644 --- a/src/main/gen/com/intellij/lang/ognl/OgnlIcons.java +++ b/src/main/gen/com/intellij/lang/ognl/OgnlIcons.java @@ -1,7 +1,7 @@ // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.lang.ognl; -import com.intellij.ui.IconManager; +import com.intellij.openapi.util.IconLoader; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -11,8 +11,12 @@ import javax.swing.*; * DO NOT EDIT IT BY HAND, run "Generate icon classes" configuration instead */ public final class OgnlIcons { - private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) { - return IconManager.getInstance().loadRasterizedIcon(path, OgnlIcons.class.getClassLoader(), cacheKey, flags); - } - /** 10x10 */ public static final @NotNull Icon Action_small = load("icons/action_small.svg", -592871696, 0); + private static @NotNull Icon load(@NotNull String path) { + return IconLoader.getIcon(path, OgnlIcons.class.getClassLoader()); + } + + /** + * 10x10 + */ + public static final @NotNull Icon Action_small = load("icons/action_small.svg"); } diff --git a/src/main/java/com/intellij/struts2/Struts2Icons.java b/src/main/java/com/intellij/struts2/Struts2Icons.java index 7322e7c..e6bffd5 100644 --- a/src/main/java/com/intellij/struts2/Struts2Icons.java +++ b/src/main/java/com/intellij/struts2/Struts2Icons.java @@ -16,7 +16,7 @@ */ package com.intellij.struts2; -import com.intellij.ui.IconManager; +import com.intellij.openapi.util.IconLoader; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -26,10 +26,20 @@ import javax.swing.*; * DO NOT EDIT IT BY HAND, run "Generate icon classes" configuration instead */ public final class Struts2Icons { - private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) { - return IconManager.getInstance().loadRasterizedIcon(path, Struts2Icons.class.getClassLoader(), cacheKey, flags); - } - /** 16x16 */ public static final @NotNull Icon Action = load("icons/action.svg", 1640284181, 0); - /** 10x10 */ public static final @NotNull Icon Action_small = load("icons/action_small.svg", -592871696, 0); - /** 10x10 */ public static final @NotNull Icon Edit_small = load("icons/edit_small.svg", 1635832574, 0); + private static @NotNull Icon load(@NotNull String path) { + return IconLoader.getIcon(path, Struts2Icons.class.getClassLoader()); + } + + /** + * 16x16 + */ + public static final @NotNull Icon Action = load("icons/action.svg"); + /** + * 10x10 + */ + public static final @NotNull Icon Action_small = load("icons/action_small.svg"); + /** + * 10x10 + */ + public static final @NotNull Icon Edit_small = load("icons/edit_small.svg"); } diff --git a/src/main/java/com/intellij/struts2/Struts2ResourceProvider.java b/src/main/java/com/intellij/struts2/Struts2ResourceProvider.java index 82f399f..96f1d75 100644 --- a/src/main/java/com/intellij/struts2/Struts2ResourceProvider.java +++ b/src/main/java/com/intellij/struts2/Struts2ResourceProvider.java @@ -25,79 +25,79 @@ import org.jetbrains.annotations.NonNls; * @author Dmitry Avdeev */ final class Struts2ResourceProvider implements StandardResourceProvider { - @NonNls - private static final String DTD_PATH = "/resources/dtds/"; - - @Override - public void registerResources(final ResourceRegistrar registrar) { - addDTDResource(StrutsConstants.STRUTS_2_0_DTD_URI, - StrutsConstants.STRUTS_2_0_DTD_ID, - "struts-2.0.dtd", registrar); - - addDTDResource(StrutsConstants.STRUTS_2_1_DTD_URI, - StrutsConstants.STRUTS_2_1_DTD_ID, - "struts-2.1.dtd", registrar); - - addDTDResource(StrutsConstants.STRUTS_2_1_7_DTD_URI, - StrutsConstants.STRUTS_2_1_7_DTD_ID, - "struts-2.1.7.dtd", registrar); - - addDTDResource(StrutsConstants.STRUTS_2_3_DTD_URI, - StrutsConstants.STRUTS_2_3_DTD_ID, - "struts-2.3.dtd", registrar); - - addDTDResource(StrutsConstants.STRUTS_2_5_DTD_URI, - StrutsConstants.STRUTS_2_5_DTD_ID, - "struts-2.5.dtd", registrar); - - addDTDResource(StrutsConstants.STRUTS_6_0_DTD_URI, - StrutsConstants.STRUTS_6_0_DTD_ID, - "struts-6.0.dtd", registrar); - - - addDTDResource(StrutsConstants.VALIDATOR_1_00_DTD_URI, - StrutsConstants.VALIDATOR_1_00_DTD_ID, - "xwork-validator-1.0.dtd", registrar); - addDTDResource(StrutsConstants.VALIDATOR_1_00_STRUTS_DTD_URI, - StrutsConstants.VALIDATOR_1_00_STRUTS_DTD_ID, - "xwork-validator-struts-1.0.dtd", registrar); - - addDTDResource(StrutsConstants.VALIDATOR_1_02_DTD_URI, - StrutsConstants.VALIDATOR_1_02_DTD_ID, - "xwork-validator-1.0.2.dtd", registrar); - addDTDResource(StrutsConstants.VALIDATOR_1_02_STRUTS_DTD_URI, - StrutsConstants.VALIDATOR_1_02_STRUTS_DTD_ID, - "xwork-validator-struts-1.0.2.dtd", registrar); - - addDTDResource(StrutsConstants.VALIDATOR_1_03_DTD_URI, - StrutsConstants.VALIDATOR_1_03_DTD_ID, - "xwork-validator-1.0.3.dtd", registrar); - - addDTDResource(StrutsConstants.VALIDATOR_CONFIG_DTD_URI, - StrutsConstants.VALIDATOR_CONFIG_DTD_ID, - "xwork-validator-config-1.0.dtd", registrar); - addDTDResource(StrutsConstants.VALIDATOR_CONFIG_STRUTS_DTD_URI, - StrutsConstants.VALIDATOR_CONFIG_STRUTS_DTD_ID, - "xwork-validator-config-struts-1.0.dtd", registrar); - addDTDResource(StrutsConstants.VALIDATOR_DEFINITION_DTD_URI, - StrutsConstants.VALIDATOR_DEFINITION_DTD_ID, - "xwork-validator-definition-1.0.dtd", registrar); - } - - /** - * Adds a DTD resource from local DTD resource path. - * - * @param uri Resource URI. - * @param id Resource ID. - * @param localFile DTD filename. - * @param registrar Resource registrar. - */ - private static void addDTDResource(@NonNls final String uri, - @NonNls final String id, - @NonNls final String localFile, - final ResourceRegistrar registrar) { - registrar.addStdResource(uri, DTD_PATH + localFile, Struts2ResourceProvider.class); - registrar.addStdResource(id, DTD_PATH + localFile, Struts2ResourceProvider.class); - } + @NonNls + private static final String DTD_PATH = "/resources/dtds/"; + + @Override + public void registerResources(final ResourceRegistrar registrar) { + addDTDResource(StrutsConstants.STRUTS_2_0_DTD_URI, + StrutsConstants.STRUTS_2_0_DTD_ID, + "struts-2.0.dtd", registrar); + + addDTDResource(StrutsConstants.STRUTS_2_1_DTD_URI, + StrutsConstants.STRUTS_2_1_DTD_ID, + "struts-2.1.dtd", registrar); + + addDTDResource(StrutsConstants.STRUTS_2_1_7_DTD_URI, + StrutsConstants.STRUTS_2_1_7_DTD_ID, + "struts-2.1.7.dtd", registrar); + + addDTDResource(StrutsConstants.STRUTS_2_3_DTD_URI, + StrutsConstants.STRUTS_2_3_DTD_ID, + "struts-2.3.dtd", registrar); + + addDTDResource(StrutsConstants.STRUTS_2_5_DTD_URI, + StrutsConstants.STRUTS_2_5_DTD_ID, + "struts-2.5.dtd", registrar); + + addDTDResource(StrutsConstants.STRUTS_6_0_DTD_URI, + StrutsConstants.STRUTS_6_0_DTD_ID, + "struts-6.0.dtd", registrar); + + + addDTDResource(StrutsConstants.VALIDATOR_1_00_DTD_URI, + StrutsConstants.VALIDATOR_1_00_DTD_ID, + "xwork-validator-1.0.dtd", registrar); + addDTDResource(StrutsConstants.VALIDATOR_1_00_STRUTS_DTD_URI, + StrutsConstants.VALIDATOR_1_00_STRUTS_DTD_ID, + "xwork-validator-struts-1.0.dtd", registrar); + + addDTDResource(StrutsConstants.VALIDATOR_1_02_DTD_URI, + StrutsConstants.VALIDATOR_1_02_DTD_ID, + "xwork-validator-1.0.2.dtd", registrar); + addDTDResource(StrutsConstants.VALIDATOR_1_02_STRUTS_DTD_URI, + StrutsConstants.VALIDATOR_1_02_STRUTS_DTD_ID, + "xwork-validator-struts-1.0.2.dtd", registrar); + + addDTDResource(StrutsConstants.VALIDATOR_1_03_DTD_URI, + StrutsConstants.VALIDATOR_1_03_DTD_ID, + "xwork-validator-1.0.3.dtd", registrar); + + addDTDResource(StrutsConstants.VALIDATOR_CONFIG_DTD_URI, + StrutsConstants.VALIDATOR_CONFIG_DTD_ID, + "xwork-validator-config-1.0.dtd", registrar); + addDTDResource(StrutsConstants.VALIDATOR_CONFIG_STRUTS_DTD_URI, + StrutsConstants.VALIDATOR_CONFIG_STRUTS_DTD_ID, + "xwork-validator-config-struts-1.0.dtd", registrar); + addDTDResource(StrutsConstants.VALIDATOR_DEFINITION_DTD_URI, + StrutsConstants.VALIDATOR_DEFINITION_DTD_ID, + "xwork-validator-definition-1.0.dtd", registrar); + } + + /** + * Adds a DTD resource from local DTD resource path. + * + * @param uri Resource URI. + * @param id Resource ID. + * @param localFile DTD filename. + * @param registrar Resource registrar. + */ + private static void addDTDResource(@NonNls final String uri, + @NonNls final String id, + @NonNls final String localFile, + final ResourceRegistrar registrar) { + registrar.addStdResource(uri, DTD_PATH + localFile, Struts2ResourceProvider.class.getClassLoader()); + registrar.addStdResource(id, DTD_PATH + localFile, Struts2ResourceProvider.class.getClassLoader()); + } } \ No newline at end of file diff --git a/src/main/java/com/intellij/struts2/dom/struts/impl/path/FileReferenceSetHelper.java b/src/main/java/com/intellij/struts2/dom/struts/impl/path/FileReferenceSetHelper.java index 63ab97b..cb92169 100644 --- a/src/main/java/com/intellij/struts2/dom/struts/impl/path/FileReferenceSetHelper.java +++ b/src/main/java/com/intellij/struts2/dom/struts/impl/path/FileReferenceSetHelper.java @@ -42,76 +42,76 @@ import java.util.Objects; */ public final class FileReferenceSetHelper { - private FileReferenceSetHelper() { - } + private FileReferenceSetHelper() { + } - /** - * Creates a new FileReferenceSet allowing references only to (web-)directories and given FileType. - * - * @param psiElement Current element. - * @param allowedFileType Allowed filetype for resolving. - * @return Instance. - */ - public static FileReferenceSet createRestrictedByFileType(final PsiElement psiElement, - @NotNull FileType allowedFileType) { - return new FileReferenceSet(psiElement) { + /** + * Creates a new FileReferenceSet allowing references only to (web-)directories and given FileType. + * + * @param psiElement Current element. + * @param allowedFileType Allowed filetype for resolving. + * @return Instance. + */ + public static FileReferenceSet createRestrictedByFileType(final PsiElement psiElement, + @NotNull FileType allowedFileType) { + return new FileReferenceSet(psiElement) { - @Override - protected boolean isSoft() { - return true; - } + @Override + protected boolean isSoft() { + return true; + } - @Override - protected Condition<PsiFileSystemItem> getReferenceCompletionFilter() { - return psiFileSystemItem -> { - if (psiFileSystemItem instanceof PsiDirectory || - psiFileSystemItem instanceof WebDirectoryElement) { - return true; - } + @Override + protected Condition<PsiFileSystemItem> getReferenceCompletionFilter() { + return psiFileSystemItem -> { + if (psiFileSystemItem instanceof PsiDirectory || + psiFileSystemItem instanceof WebDirectoryElement) { + return true; + } - final VirtualFile virtualFile = psiFileSystemItem.getVirtualFile(); - return virtualFile != null && FileTypeRegistry.getInstance().isFileOfType(virtualFile, allowedFileType); + final VirtualFile virtualFile = psiFileSystemItem.getVirtualFile(); + return virtualFile != null && FileTypeRegistry.getInstance().isFileOfType(virtualFile, allowedFileType); + }; + } }; - } - }; - } + } - /** - * Adds all {@link WebDirectoryElement}s as well as web-directory with name of given current namespace - * (if not "root" namespace) as possible content roots. - * - * @param psiElement Current element. - * @param namespace Current namespace. - * @param webFacet Module. - * @param set FRS to patch. - */ - public static void addWebDirectoryAndCurrentNamespaceAsRoots(final PsiElement psiElement, - final String namespace, - final WebFacet webFacet, - final FileReferenceSet set) { - final WebDirectoryUtil directoryUtil = WebDirectoryUtil.getWebDirectoryUtil(psiElement.getProject()); - set.addCustomization( - FileReferenceSet.DEFAULT_PATH_EVALUATOR_OPTION, - file -> { - final List<PsiFileSystemItem> basePathRoots = new ArrayList<>(); + /** + * Adds all {@link WebDirectoryElement}s as well as web-directory with name of given current namespace + * (if not "root" namespace) as possible content roots. + * + * @param psiElement Current element. + * @param namespace Current namespace. + * @param webFacet Module. + * @param set FRS to patch. + */ + public static void addWebDirectoryAndCurrentNamespaceAsRoots(final PsiElement psiElement, + final String namespace, + final WebFacet webFacet, + final FileReferenceSet set) { + final WebDirectoryUtil directoryUtil = WebDirectoryUtil.getWebDirectoryUtil(psiElement.getProject()); + set.addCustomization( + FileReferenceSet.DEFAULT_PATH_EVALUATOR_OPTION, + file -> { + final List<PsiFileSystemItem> basePathRoots = new ArrayList<>(); - // 1. add all configured web root mappings - final List<WebRoot> webRoots = webFacet.getWebRoots(true); - for (final WebRoot webRoot : webRoots) { - final String webRootPath = webRoot.getRelativePath(); - final WebDirectoryElement webRootBase = - directoryUtil.findWebDirectoryElementByPath(webRootPath, webFacet); - ContainerUtil.addIfNotNull(basePathRoots, webRootBase); - } + // 1. add all configured web root mappings + final List<WebRoot> webRoots = webFacet.getWebRoots(); + for (final WebRoot webRoot : webRoots) { + final String webRootPath = webRoot.getRelativePath(); + final WebDirectoryElement webRootBase = + directoryUtil.findWebDirectoryElementByPath(webRootPath, webFacet); + ContainerUtil.addIfNotNull(basePathRoots, webRootBase); + } - // 2. add parent <package> "namespace" as result prefix directory path if not ROOT - if (!Objects.equals(namespace, StrutsPackage.DEFAULT_NAMESPACE)) { - final WebDirectoryElement packageBase = - directoryUtil.findWebDirectoryElementByPath(namespace, webFacet); - ContainerUtil.addIfNotNull(basePathRoots, packageBase); - } + // 2. add parent <package> "namespace" as result prefix directory path if not ROOT + if (!Objects.equals(namespace, StrutsPackage.DEFAULT_NAMESPACE)) { + final WebDirectoryElement packageBase = + directoryUtil.findWebDirectoryElementByPath(namespace, webFacet); + ContainerUtil.addIfNotNull(basePathRoots, packageBase); + } - return basePathRoots; - }); - } + return basePathRoots; + }); + } } \ No newline at end of file diff --git a/src/main/java/com/intellij/struts2/facet/ui/FileSetConfigurationTab.java b/src/main/java/com/intellij/struts2/facet/ui/FileSetConfigurationTab.java index 415e103..939ee02 100644 --- a/src/main/java/com/intellij/struts2/facet/ui/FileSetConfigurationTab.java +++ b/src/main/java/com/intellij/struts2/facet/ui/FileSetConfigurationTab.java @@ -26,6 +26,7 @@ import com.intellij.ide.projectView.PresentationData; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ActionUpdateThread; import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.ui.DialogWrapper; @@ -68,378 +69,373 @@ import java.util.Set; */ public class FileSetConfigurationTab extends FacetEditorTab implements Disposable { - private final StructureTreeModel<SimpleTreeStructure> myModel; - // GUI components ----------------------- - private JPanel myPanel; - - private final SimpleTree myTree; - private final AnActionButton myRemoveButton; - private final AnActionButton myEditButton; - private JPanel myTreePanel; - - // GUI helpers - private final SimpleNode myRootNode = new SimpleNode() { - @Override - public SimpleNode @NotNull [] getChildren() { - final List<SimpleNode> nodes = new ArrayList<>(myBuffer.size()); - for (final StrutsFileSet entry : myBuffer) { - if (!entry.isRemoved()) { - final FileSetNode setNode = new FileSetNode(entry); - nodes.add(setNode); + private final StructureTreeModel<SimpleTreeStructure> myModel; + // GUI components ----------------------- + private JPanel myPanel; + + private final SimpleTree myTree; + private final AnActionButton myRemoveButton; + private final AnActionButton myEditButton; + private JPanel myTreePanel; + + // GUI helpers + private final SimpleNode myRootNode = new SimpleNode() { + @Override + public SimpleNode @NotNull [] getChildren() { + final List<SimpleNode> nodes = new ArrayList<>(myBuffer.size()); + for (final StrutsFileSet entry : myBuffer) { + if (!entry.isRemoved()) { + final FileSetNode setNode = new FileSetNode(entry); + nodes.add(setNode); + } + } + return nodes.toArray(new SimpleNode[0]); } - } - return nodes.toArray(new SimpleNode[0]); - } - - @Override - public boolean isAutoExpandNode() { - return true; - } - }; - private final TreeExpander myTreeExpander; + @Override + public boolean isAutoExpandNode() { + return true; + } + }; - private final StrutsConfigsSearcher myConfigsSearcher; + private final TreeExpander myTreeExpander; - // original config - private final StrutsFacetConfiguration originalConfiguration; - private final Module module; + private final StrutsConfigsSearcher myConfigsSearcher; - // local config - private final Set<StrutsFileSet> myBuffer = new LinkedHashSet<>(); - private boolean myModified; + // original config + private final StrutsFacetConfiguration originalConfiguration; + private final Module module; - public FileSetConfigurationTab(@NotNull final StrutsFacetConfiguration strutsFacetConfiguration, - @NotNull final FacetEditorContext facetEditorContext) { - originalConfiguration = strutsFacetConfiguration; - module = facetEditorContext.getModule(); - myConfigsSearcher = new StrutsConfigsSearcher(module); + // local config + private final Set<StrutsFileSet> myBuffer = new LinkedHashSet<>(); + private boolean myModified; - // init tree - final SimpleTreeStructure structure = new SimpleTreeStructure() { - @NotNull - @Override - public Object getRootElement() { - return myRootNode; - } - }; + public FileSetConfigurationTab(@NotNull final StrutsFacetConfiguration strutsFacetConfiguration, + @NotNull final FacetEditorContext facetEditorContext) { + originalConfiguration = strutsFacetConfiguration; + module = facetEditorContext.getModule(); + myConfigsSearcher = new StrutsConfigsSearcher(module); - myTree = new SimpleTree(); - myTree.setRootVisible(false); - myTree.setShowsRootHandles(true); // show expand/collapse handles - myTree.getEmptyText().setText(StrutsBundle.message("facet.fileset.no.filesets.defined"), SimpleTextAttributes.ERROR_ATTRIBUTES); - myTreeExpander = new DefaultTreeExpander(myTree); - - myModel = new StructureTreeModel<>(structure, this); - myTree.setModel(new AsyncTreeModel(myModel, this)); - - final DumbService dumbService = DumbService.getInstance(facetEditorContext.getProject()); - myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { - @Override - public void valueChanged(final TreeSelectionEvent e) { - final StrutsFileSet fileSet = getCurrentFileSet(); - myEditButton.setEnabled(fileSet != null && !dumbService.isDumb()); - myRemoveButton.setEnabled(fileSet != null); - } - }); - - final CommonActionsManager actionManager = CommonActionsManager.getInstance(); - myTreePanel.add( - ToolbarDecorator.createDecorator(myTree) - .setAddAction(new AnActionButtonRunnable() { - @Override - public void run(AnActionButton button) { - final StrutsFileSet fileSet = - new StrutsFileSet(StrutsFileSet.getUniqueId(myBuffer), - StrutsFileSet.getUniqueName(StrutsBundle.message("facet.fileset.my.fileset"), myBuffer), - originalConfiguration) { - @Override - public boolean isNew() { - return true; - } - }; - - final FileSetEditor editor = new FileSetEditor(myPanel, - fileSet, - facetEditorContext, - myConfigsSearcher); - editor.show(); - if (editor.getExitCode() == DialogWrapper.OK_EXIT_CODE) { - final StrutsFileSet editedFileSet = editor.getEditedFileSet(); - Disposer.register(strutsFacetConfiguration, editedFileSet); - myBuffer.add(editedFileSet); - myModified = true; - myModel.invalidateAsync().thenRun(() -> selectFileSet(editedFileSet)); + // init tree + final SimpleTreeStructure structure = new SimpleTreeStructure() { + @NotNull + @Override + public Object getRootElement() { + return myRootNode; } - IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myTree, true)); - } - }) - .setRemoveAction(new AnActionButtonRunnable() { - @Override - public void run(AnActionButton button) { - remove(); - myModified = true; - myModel.invalidateAsync(); - IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myTree, true)); - } - }) - .setEditAction(new AnActionButtonRunnable() { - @Override - public void run(AnActionButton button) { - final StrutsFileSet fileSet = getCurrentFileSet(); - if (fileSet != null) { - final FileSetEditor editor = new FileSetEditor(myPanel, - fileSet, - facetEditorContext, - myConfigsSearcher); - editor.show(); - if (editor.getExitCode() == DialogWrapper.OK_EXIT_CODE) { - myModified = true; - myBuffer.remove(fileSet); - final StrutsFileSet edited = editor.getEditedFileSet(); - Disposer.register(strutsFacetConfiguration, edited); - myBuffer.add(edited); - edited.setAutodetected(false); - myModel.invalidateAsync(); - selectFileSet(edited); - } - IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myTree, true)); + }; + + myTree = new SimpleTree(); + myTree.setRootVisible(false); + myTree.setShowsRootHandles(true); // show expand/collapse handles + myTree.getEmptyText().setText(StrutsBundle.message("facet.fileset.no.filesets.defined"), SimpleTextAttributes.ERROR_ATTRIBUTES); + myTreeExpander = new DefaultTreeExpander(myTree); + + myModel = new StructureTreeModel<>(structure, this); + myTree.setModel(new AsyncTreeModel(myModel, this)); + + final DumbService dumbService = DumbService.getInstance(facetEditorContext.getProject()); + myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { + @Override + public void valueChanged(final TreeSelectionEvent e) { + final StrutsFileSet fileSet = getCurrentFileSet(); + myEditButton.setEnabled(fileSet != null && !dumbService.isDumb()); + myRemoveButton.setEnabled(fileSet != null); } - } - }) - .addExtraAction(actionManager.createExpandAllAction(myTreeExpander, myTree)) - .addExtraAction(actionManager.createCollapseAllAction(myTreeExpander, myTree)) - .addExtraAction(new AnActionButton(StrutsBundle.messagePointer("action.AnActionButton.text.open.struts.2.plugin.documentation"), - AllIcons.Actions.Help) { - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - BrowserUtil.browse("https://confluence.jetbrains.com/pages/viewpage.action?pageId=35367"); - } - - @Override - public @NotNull ActionUpdateThread getActionUpdateThread() { - return ActionUpdateThread.EDT; - } - }) - - .disableUpDownActions() - .createPanel()); - - myEditButton = ToolbarDecorator.findEditButton(myTreePanel); - myRemoveButton = ToolbarDecorator.findRemoveButton(myTreePanel); - - // Note: makeDumbAware API may have changed in IntelliJ Platform 2025.2 - // Removing these calls as they caused compilation errors - // Component dumb awareness should still work through DumbAware interface - } - - @Nullable - private StrutsFileSet getCurrentFileSet() { - final FileSetNode currentFileSetNode = getCurrentFileSetNode(); - return currentFileSetNode == null ? null : currentFileSetNode.mySet; - } - - @Nullable - private FileSetNode getCurrentFileSetNode() { - final SimpleNode selectedNode = myTree.getSelectedNode(); - if (selectedNode == null) { - return null; + }); + + final CommonActionsManager actionManager = CommonActionsManager.getInstance(); + myTreePanel.add( + ToolbarDecorator.createDecorator(myTree) + .setAddAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + final StrutsFileSet fileSet = + new StrutsFileSet(StrutsFileSet.getUniqueId(myBuffer), + StrutsFileSet.getUniqueName(StrutsBundle.message("facet.fileset.my.fileset"), myBuffer), + originalConfiguration) { + @Override + public boolean isNew() { + return true; + } + }; + + final FileSetEditor editor = new FileSetEditor(myPanel, + fileSet, + facetEditorContext, + myConfigsSearcher); + editor.show(); + if (editor.getExitCode() == DialogWrapper.OK_EXIT_CODE) { + final StrutsFileSet editedFileSet = editor.getEditedFileSet(); + Disposer.register(strutsFacetConfiguration, editedFileSet); + myBuffer.add(editedFileSet); + myModified = true; + myModel.invalidateAsync().thenRun(() -> selectFileSet(editedFileSet)); + } + IdeFocusManager.getGlobalInstance().requestFocus(myTree, true); + } + }) + .setRemoveAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + remove(); + myModified = true; + myModel.invalidateAsync(); + IdeFocusManager.getGlobalInstance().requestFocus(myTree, true); + } + }) + .setEditAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + final StrutsFileSet fileSet = getCurrentFileSet(); + if (fileSet != null) { + final FileSetEditor editor = new FileSetEditor(myPanel, + fileSet, + facetEditorContext, + myConfigsSearcher); + editor.show(); + if (editor.getExitCode() == DialogWrapper.OK_EXIT_CODE) { + myModified = true; + myBuffer.remove(fileSet); + final StrutsFileSet edited = editor.getEditedFileSet(); + Disposer.register(strutsFacetConfiguration, edited); + myBuffer.add(edited); + edited.setAutodetected(false); + myModel.invalidateAsync(); + selectFileSet(edited); + } + IdeFocusManager.getGlobalInstance().requestFocus(myTree, true); + } + } + }) + .addExtraAction(actionManager.createExpandAllAction(myTreeExpander, myTree)) + .addExtraAction(actionManager.createCollapseAllAction(myTreeExpander, myTree)) + .addExtraAction(new DumbAwareAction(StrutsBundle.messagePointer("action.AnActionButton.text.open.struts.2.plugin.documentation"), + AllIcons.Actions.Help) { + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + BrowserUtil.browse("https://confluence.jetbrains.com/pages/viewpage.action?pageId=35367"); + } + + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.EDT; + } + }) + + .disableUpDownActions() + .createPanel()); + + myEditButton = ToolbarDecorator.findEditButton(myTreePanel); + myRemoveButton = ToolbarDecorator.findRemoveButton(myTreePanel); + + // Note: makeDumbAware API may have changed in IntelliJ Platform 2025.2 + // Removing these calls as they caused compilation errors + // Component dumb awareness should still work through DumbAware interface } - if (selectedNode instanceof FileSetNode) { - return (FileSetNode)selectedNode; + + @Nullable + private StrutsFileSet getCurrentFileSet() { + final FileSetNode currentFileSetNode = getCurrentFileSetNode(); + return currentFileSetNode == null ? null : currentFileSetNode.mySet; } - else if (selectedNode.getParent() instanceof FileSetNode) { - return (FileSetNode)selectedNode.getParent(); + + @Nullable + private FileSetNode getCurrentFileSetNode() { + final SimpleNode selectedNode = myTree.getSelectedNode(); + if (selectedNode == null) { + return null; + } + if (selectedNode instanceof FileSetNode) { + return (FileSetNode) selectedNode; + } else if (selectedNode.getParent() instanceof FileSetNode) { + return (FileSetNode) selectedNode.getParent(); + } else { + final SimpleNode parent = selectedNode.getParent(); + if (parent != null && parent.getParent() instanceof FileSetNode) { + return (FileSetNode) selectedNode.getParent().getParent(); + } + } + return null; } - else { - final SimpleNode parent = selectedNode.getParent(); - if (parent != null && parent.getParent() instanceof FileSetNode) { - return (FileSetNode)selectedNode.getParent().getParent(); - } + + private void selectFileSet(final StrutsFileSet fileSet) { + SimpleNode simpleNode = ContainerUtil.find(myRootNode.getChildren(), node -> ((FileSetNode) node).mySet == fileSet); + assert simpleNode != null; + myModel.select(simpleNode, myTree, path -> { + }); } - return null; - } - - private void selectFileSet(final StrutsFileSet fileSet) { - SimpleNode simpleNode = ContainerUtil.find(myRootNode.getChildren(), node -> ((FileSetNode)node).mySet == fileSet); - assert simpleNode != null; - myModel.select(simpleNode, myTree, path -> {}); - } - - private void remove() { - final SimpleNode[] nodes = myTree.getSelectedNodesIfUniform(); - for (final SimpleNode node : nodes) { - - if (node instanceof FileSetNode) { - final StrutsFileSet fileSet = ((FileSetNode)node).mySet; - if (fileSet.getFiles().isEmpty()) { - myBuffer.remove(fileSet); - return; - } - final int result = Messages.showYesNoDialog(myPanel, - StrutsBundle.message("facet.fileset.remove.fileset.question", - fileSet.getName()), - StrutsBundle.message("facet.fileset.remove.fileset.title"), - Messages.getQuestionIcon()); - if (result == Messages.YES) { - if (fileSet.isAutodetected()) { - fileSet.setRemoved(true); - myBuffer.add(fileSet); - } - else { - myBuffer.remove(fileSet); - } + private void remove() { + final SimpleNode[] nodes = myTree.getSelectedNodesIfUniform(); + for (final SimpleNode node : nodes) { + + if (node instanceof FileSetNode) { + final StrutsFileSet fileSet = ((FileSetNode) node).mySet; + if (fileSet.getFiles().isEmpty()) { + myBuffer.remove(fileSet); + return; + } + + final int result = Messages.showYesNoDialog(myPanel, + StrutsBundle.message("facet.fileset.remove.fileset.question", + fileSet.getName()), + StrutsBundle.message("facet.fileset.remove.fileset.title"), + Messages.getQuestionIcon()); + if (result == Messages.YES) { + if (fileSet.isAutodetected()) { + fileSet.setRemoved(true); + myBuffer.add(fileSet); + } else { + myBuffer.remove(fileSet); + } + } + } else if (node instanceof ConfigFileNode) { + final VirtualFilePointer filePointer = ((ConfigFileNode) node).myFilePointer; + final StrutsFileSet fileSet = ((FileSetNode) node.getParent()).mySet; + fileSet.removeFile(filePointer); + } } - } - else if (node instanceof ConfigFileNode) { - final VirtualFilePointer filePointer = ((ConfigFileNode)node).myFilePointer; - final StrutsFileSet fileSet = ((FileSetNode)node.getParent()).mySet; - fileSet.removeFile(filePointer); - } } - } - - @Override - @Nls - public String getDisplayName() { - return StrutsBundle.message("facet.fileset.title"); - } - - @Override - @NotNull - public JComponent createComponent() { - return myPanel; - } - - @Override - public boolean isModified() { - return myModified; - } - - @Override - public void apply() { - final Set<StrutsFileSet> fileSets = originalConfiguration.getFileSets(); - fileSets.clear(); - for (final StrutsFileSet fileSet : myBuffer) { - if (!fileSet.isAutodetected() || fileSet.isRemoved()) { - fileSets.add(fileSet); - } + + @Override + @Nls + public String getDisplayName() { + return StrutsBundle.message("facet.fileset.title"); } - originalConfiguration.setModified(); - myModified = false; - } - - @Override - public void reset() { - myBuffer.clear(); - final Set<StrutsFileSet> sets = StrutsManager.getInstance(module.getProject()).getAllConfigFileSets(module); - /*new StrutsFileSet(fileSet)*/ - myBuffer.addAll(sets); - - myModel.invalidateAsync(); - myTree.setSelectionRow(0); - } - - @Override - public void disposeUIResources() { - Disposer.dispose(this); - } - - @Override - public void dispose() { - } - - private class FileSetNode extends SimpleNode { - - protected final StrutsFileSet mySet; - - FileSetNode(final StrutsFileSet fileSet) { - super(myRootNode); - mySet = fileSet; - - final PresentationData presentationData = getPresentation(); - final String name = mySet.getName(); //NON-NLS - - if (fileSet.getFiles().isEmpty()) { - presentationData.addText(name, getErrorAttributes()); - presentationData.setTooltip(StrutsBundle.message("facet.fileset.no.files.attached")); - } - else { - presentationData.addText(name, getPlainAttributes()); - presentationData.setLocationString(Integer.toString(fileSet.getFiles().size())); - } + + @Override + @NotNull + public JComponent createComponent() { + return myPanel; } @Override - public SimpleNode @NotNull [] getChildren() { - final List<SimpleNode> nodes = new ArrayList<>(); + public boolean isModified() { + return myModified; + } - for (final VirtualFilePointer file : mySet.getFiles()) { - nodes.add(new ConfigFileNode(file, this)); - } - return nodes.toArray(new SimpleNode[0]); + @Override + public void apply() { + final Set<StrutsFileSet> fileSets = originalConfiguration.getFileSets(); + fileSets.clear(); + for (final StrutsFileSet fileSet : myBuffer) { + if (!fileSet.isAutodetected() || fileSet.isRemoved()) { + fileSets.add(fileSet); + } + } + originalConfiguration.setModified(); + myModified = false; } @Override - public boolean isAutoExpandNode() { - return true; + public void reset() { + myBuffer.clear(); + final Set<StrutsFileSet> sets = StrutsManager.getInstance(module.getProject()).getAllConfigFileSets(module); + /*new StrutsFileSet(fileSet)*/ + myBuffer.addAll(sets); + + myModel.invalidateAsync(); + myTree.setSelectionRow(0); } @Override - public Object @NotNull [] getEqualityObjects() { - return new Object[]{mySet, mySet.getName(), mySet.getFiles()}; + public void disposeUIResources() { + Disposer.dispose(this); } - } + @Override + public void dispose() { + } - private static final class ConfigFileNode extends SimpleNode { + private class FileSetNode extends SimpleNode { - private final VirtualFilePointer myFilePointer; + protected final StrutsFileSet mySet; - ConfigFileNode(final VirtualFilePointer name, final SimpleNode parent) { - super(parent); - myFilePointer = name; - getTemplatePresentation().setIcon(StrutsIcons.STRUTS_CONFIG_FILE); - } + FileSetNode(final StrutsFileSet fileSet) { + super(myRootNode); + mySet = fileSet; - @Override - public boolean isAlwaysLeaf() { - return true; - } + final PresentationData presentationData = getPresentation(); + final String name = mySet.getName(); //NON-NLS - @Override - protected void doUpdate(@NotNull PresentationData presentation) { - final VirtualFile file = myFilePointer.getFile(); - if (file != null) { - renderFile(presentation, file, getPlainAttributes(), null); - } - else { - renderFile(presentation, null, getErrorAttributes(), StrutsBundle.message("facet.fileset.file.not.found")); - } + if (fileSet.getFiles().isEmpty()) { + presentationData.addText(name, getErrorAttributes()); + presentationData.setTooltip(StrutsBundle.message("facet.fileset.no.files.attached")); + } else { + presentationData.addText(name, getPlainAttributes()); + presentationData.setLocationString(Integer.toString(fileSet.getFiles().size())); + } + } + + @Override + public SimpleNode @NotNull [] getChildren() { + final List<SimpleNode> nodes = new ArrayList<>(); + + for (final VirtualFilePointer file : mySet.getFiles()) { + nodes.add(new ConfigFileNode(file, this)); + } + return nodes.toArray(new SimpleNode[0]); + } + + @Override + public boolean isAutoExpandNode() { + return true; + } + + @Override + public Object @NotNull [] getEqualityObjects() { + return new Object[]{mySet, mySet.getName(), mySet.getFiles()}; + } } - private void renderFile(@NotNull PresentationData presentation, - final VirtualFile file, - final SimpleTextAttributes textAttributes, - @NlsContexts.Tooltip @Nullable final String toolTip) { - presentation.setTooltip(toolTip); - presentation.clearText(); - presentation.addText(myFilePointer.getFileName(), textAttributes); //NON-NLS - - if (file != null) { - presentation.setLocationString(file.getPath()); - } + + private static final class ConfigFileNode extends SimpleNode { + + private final VirtualFilePointer myFilePointer; + + ConfigFileNode(final VirtualFilePointer name, final SimpleNode parent) { + super(parent); + myFilePointer = name; + getTemplatePresentation().setIcon(StrutsIcons.STRUTS_CONFIG_FILE); + } + + @Override + public boolean isAlwaysLeaf() { + return true; + } + + @Override + protected void doUpdate(@NotNull PresentationData presentation) { + final VirtualFile file = myFilePointer.getFile(); + if (file != null) { + renderFile(presentation, file, getPlainAttributes(), null); + } else { + renderFile(presentation, null, getErrorAttributes(), StrutsBundle.message("facet.fileset.file.not.found")); + } + } + + private void renderFile(@NotNull PresentationData presentation, + final VirtualFile file, + final SimpleTextAttributes textAttributes, + @NlsContexts.Tooltip @Nullable final String toolTip) { + presentation.setTooltip(toolTip); + presentation.clearText(); + presentation.addText(myFilePointer.getFileName(), textAttributes); //NON-NLS + + if (file != null) { + presentation.setLocationString(file.getPath()); + } + } + + @Override + public SimpleNode @NotNull [] getChildren() { + return NO_CHILDREN; + } } @Override - public SimpleNode @NotNull [] getChildren() { - return NO_CHILDREN; + public String getHelpTopic() { + return "reference.settings.project.structure.facets.struts2.facet"; } - } - - @Override - public String getHelpTopic() { - return "reference.settings.project.structure.facets.struts2.facet"; - } } diff --git a/src/main/java/com/intellij/struts2/graph/fileEditor/Struts2GraphComponent.java b/src/main/java/com/intellij/struts2/graph/fileEditor/Struts2GraphComponent.java index 6f84ea4..85a666d 100644 --- a/src/main/java/com/intellij/struts2/graph/fileEditor/Struts2GraphComponent.java +++ b/src/main/java/com/intellij/struts2/graph/fileEditor/Struts2GraphComponent.java @@ -52,110 +52,113 @@ import java.util.Objects; * @author Yann Cébron */ public class Struts2GraphComponent extends JPanel implements DataProvider, Disposable { - @NonNls - private static final String STRUTS2_DESIGNER_COMPONENT = "STRUTS2_DESIGNER_COMPONENT"; - - private final GraphBuilder<BasicStrutsNode, BasicStrutsEdge> myBuilder; - - public Struts2GraphComponent(final XmlFile xmlFile) { - final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); - - progress.setText("Initializing..."); - final Project project = xmlFile.getProject(); - final Graph2D graph = GraphManager.getGraphManager().createGraph2D(); - final Graph2DView view = GraphManager.getGraphManager().createGraph2DView(); - - progress.setText("Building model..."); - final StrutsDataModel myDataModel = new StrutsDataModel(xmlFile); - final StrutsPresentationModel presentationModel = new StrutsPresentationModel(graph); + @NonNls + private static final String STRUTS2_DESIGNER_COMPONENT = "STRUTS2_DESIGNER_COMPONENT"; + + private final GraphBuilder<BasicStrutsNode, BasicStrutsEdge> myBuilder; + + public Struts2GraphComponent(final XmlFile xmlFile) { + final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + + progress.setText("Initializing..."); + final Project project = xmlFile.getProject(); + final Graph2D graph = GraphManager.getGraphManager().createGraph2D(); + final Graph2DView view = GraphManager.getGraphManager().createGraph2DView(); + + progress.setText("Building model..."); + final StrutsDataModel myDataModel = new StrutsDataModel(xmlFile); + final StrutsPresentationModel presentationModel = new StrutsPresentationModel(graph); + + progress.setText("Setup graph..."); + myBuilder = GraphBuilderFactory.getInstance(project).createGraphBuilder(graph, + view, + myDataModel, + presentationModel); + Disposer.register(this, myBuilder); + + JComponent graphComponent = myBuilder.getView().getJComponent(); + setLayout(new BorderLayout()); + + ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar( + ActionPlaces.TOOLBAR, AbstractGraphAction.getCommonToolbarActions(), true); + toolbar.setTargetComponent(graphComponent); + + add(toolbar.getComponent(), BorderLayout.NORTH); + add(graphComponent, BorderLayout.CENTER); + + // TODO: GraphBuilder.initialize() is @Internal API with no public replacement. + // Consider migrating to Diagram API when available. + myBuilder.initialize(); + + DomManager.getDomManager(myBuilder.getProject()).addDomEventListener(new DomEventListener() { + @Override + public void eventOccured(@NotNull final DomEvent event) { + if (isShowing()) { + // TODO: GraphBuilder.queueUpdate() is deprecated with no public replacement. + myBuilder.queueUpdate(); + } + } + }, this); + } - progress.setText("Setup graph..."); - myBuilder = GraphBuilderFactory.getInstance(project).createGraphBuilder(graph, - view, - myDataModel, - presentationModel); - Disposer.register(this, myBuilder); + public List<DomElement> getSelectedDomElements() { + final var selected = new ArrayList<DomElement>(); + GraphSelectionService.getInstance().forEachSelectedNode(myBuilder.getGraph(), node -> { + final var nodeObject = myBuilder.getNodeObject(node); + if (nodeObject != null) { + ContainerUtil.addIfNotNull(selected, nodeObject.getIdentifyingElement()); + } + }); + return selected; + } - JComponent graphComponent = myBuilder.getView().getJComponent(); - setLayout(new BorderLayout()); + public void setSelectedDomElement(final DomElement domElement) { + // TODO + //if (domElement == null) return; + // + //final SeamPagesDomElement pageflowDomElement = domElement.getParentOfType(SeamPagesDomElement.class, false); + //if (pageflowDomElement == null) return; + // + //final Node selectedNode = myBuilder.getNode(pageflowDomElement); + // + //if (selectedNode != null) { + // final Graph2D graph = myBuilder.getGraph(); + // + // for (Node n : graph.getNodeArray()) { + // final boolean selected = n.equals(selectedNode); + // graph.setSelected(n, selected); + // if (selected) { + // final YRectangle yRectangle = graph.getRectangle(n); + // if (!myBuilder.getView().getVisibleRect().contains( + // new Rectangle((int)yRectangle.getX(), (int)yRectangle.getY(), (int)yRectangle.getWidth(), (int)yRectangle.getHeight()))) { + // myBuilder.getView().setCenter(graph.getX(n), graph.getY(n)); + // } + // } + // } + //} + //myBuilder.getView().updateView(); + } - ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar( - ActionPlaces.TOOLBAR, AbstractGraphAction.getCommonToolbarActions(), true); - toolbar.setTargetComponent(graphComponent); + public GraphBuilder getBuilder() { + return myBuilder; + } - add(toolbar.getComponent(), BorderLayout.NORTH); - add(graphComponent, BorderLayout.CENTER); + public Overview getOverview() { + return GraphManager.getGraphManager().createOverview(myBuilder.getView()); + } - myBuilder.initialize(); + @Override + public void dispose() { + } - DomManager.getDomManager(myBuilder.getProject()).addDomEventListener(new DomEventListener() { - @Override - public void eventOccured(@NotNull final DomEvent event) { - if (isShowing()) { - myBuilder.queueUpdate(); + @Override + @Nullable + public Object getData(@NotNull @NonNls final String dataId) { + if (Objects.equals(dataId, STRUTS2_DESIGNER_COMPONENT)) { + return this; } - } - }, this); - } - - public List<DomElement> getSelectedDomElements() { - final var selected = new ArrayList<DomElement>(); - GraphSelectionService.getInstance().forEachSelectedNode(myBuilder.getGraph(), node -> { - final var nodeObject = myBuilder.getNodeObject(node); - if (nodeObject != null) { - ContainerUtil.addIfNotNull(selected, nodeObject.getIdentifyingElement()); - } - }); - return selected; - } - - public void setSelectedDomElement(final DomElement domElement) { - // TODO - //if (domElement == null) return; - // - //final SeamPagesDomElement pageflowDomElement = domElement.getParentOfType(SeamPagesDomElement.class, false); - //if (pageflowDomElement == null) return; - // - //final Node selectedNode = myBuilder.getNode(pageflowDomElement); - // - //if (selectedNode != null) { - // final Graph2D graph = myBuilder.getGraph(); - // - // for (Node n : graph.getNodeArray()) { - // final boolean selected = n.equals(selectedNode); - // graph.setSelected(n, selected); - // if (selected) { - // final YRectangle yRectangle = graph.getRectangle(n); - // if (!myBuilder.getView().getVisibleRect().contains( - // new Rectangle((int)yRectangle.getX(), (int)yRectangle.getY(), (int)yRectangle.getWidth(), (int)yRectangle.getHeight()))) { - // myBuilder.getView().setCenter(graph.getX(n), graph.getY(n)); - // } - // } - // } - //} - //myBuilder.getView().updateView(); - } - - public GraphBuilder getBuilder() { - return myBuilder; - } - - public Overview getOverview() { - return GraphManager.getGraphManager().createOverview(myBuilder.getView()); - } - - @Override - public void dispose() { - } - - @Override - @Nullable - public Object getData(@NotNull @NonNls final String dataId) { - if (Objects.equals(dataId, STRUTS2_DESIGNER_COMPONENT)) { - return this; - } - return null; - } + return null; + } } \ No newline at end of file diff --git a/src/main/java/com/intellij/struts2/graph/fileEditor/Struts2GraphFileEditor.java b/src/main/java/com/intellij/struts2/graph/fileEditor/Struts2GraphFileEditor.java index 1438362..bc720b5 100644 --- a/src/main/java/com/intellij/struts2/graph/fileEditor/Struts2GraphFileEditor.java +++ b/src/main/java/com/intellij/struts2/graph/fileEditor/Struts2GraphFileEditor.java @@ -38,86 +38,87 @@ import java.util.List; */ public class Struts2GraphFileEditor extends PerspectiveFileEditor { - private Struts2GraphComponent myComponent; - private final XmlFile myXmlFile; - - private final @NotNull NotNullLazyValue<StructureViewBuilder> myStructureViewBuilder = - NotNullLazyValue.atomicLazy(() -> GraphStructureViewBuilderSetup.setupFor(getStruts2GraphComponent().getBuilder(), null)); - - public Struts2GraphFileEditor(final Project project, final VirtualFile file) { - super(project, file); - - final PsiFile psiFile = getPsiFile(); - assert psiFile instanceof XmlFile; - - myXmlFile = (XmlFile)psiFile; - } - - @Override - @Nullable - protected DomElement getSelectedDomElement() { - final List<DomElement> selectedDomElements = getStruts2GraphComponent().getSelectedDomElements(); - - return selectedDomElements.size() > 0 ? selectedDomElements.get(0) : null; - } - - @Override - protected void setSelectedDomElement(final DomElement domElement) { - getStruts2GraphComponent().setSelectedDomElement(domElement); - } - - @Override - @NotNull - protected JComponent createCustomComponent() { - return getStruts2GraphComponent(); - } - - @Override - @Nullable - public JComponent getPreferredFocusedComponent() { - return getStruts2GraphComponent().getBuilder().getView().getJComponent(); - } - - @Override - public void commit() { - } - - @Override - public void reset() { - getStruts2GraphComponent().getBuilder().queueUpdate(); - } - - @Override - @NotNull - public String getName() { - return "Graph"; - } - - @Override - public StructureViewBuilder getStructureViewBuilder() { - return myStructureViewBuilder.getValue(); - } - - private Struts2GraphComponent getStruts2GraphComponent() { - if (myComponent == null) { - myComponent = createGraphComponent(); - Disposer.register(this, myComponent); + private Struts2GraphComponent myComponent; + private final XmlFile myXmlFile; + + private final @NotNull NotNullLazyValue<StructureViewBuilder> myStructureViewBuilder = + NotNullLazyValue.atomicLazy(() -> GraphStructureViewBuilderSetup.setupFor(getStruts2GraphComponent().getBuilder(), null)); + + public Struts2GraphFileEditor(final Project project, final VirtualFile file) { + super(project, file); + + final PsiFile psiFile = getPsiFile(); + assert psiFile instanceof XmlFile; + + myXmlFile = (XmlFile) psiFile; + } + + @Override + @Nullable + protected DomElement getSelectedDomElement() { + final List<DomElement> selectedDomElements = getStruts2GraphComponent().getSelectedDomElements(); + + return selectedDomElements.size() > 0 ? selectedDomElements.get(0) : null; + } + + @Override + protected void setSelectedDomElement(final DomElement domElement) { + getStruts2GraphComponent().setSelectedDomElement(domElement); + } + + @Override + @NotNull + protected JComponent createCustomComponent() { + return getStruts2GraphComponent(); + } + + @Override + @Nullable + public JComponent getPreferredFocusedComponent() { + return getStruts2GraphComponent().getBuilder().getView().getJComponent(); + } + + @Override + public void commit() { + } + + @Override + public void reset() { + // TODO: GraphBuilder.queueUpdate() is deprecated with no public replacement. + getStruts2GraphComponent().getBuilder().queueUpdate(); + } + + @Override + @NotNull + public String getName() { + return "Graph"; + } + + @Override + public StructureViewBuilder getStructureViewBuilder() { + return myStructureViewBuilder.getValue(); + } + + private Struts2GraphComponent getStruts2GraphComponent() { + if (myComponent == null) { + myComponent = createGraphComponent(); + Disposer.register(this, myComponent); + } + return myComponent; } - return myComponent; - } - /** - * Creates graph component while showing modal wait dialog. - * - * @return new instance. - */ - private Struts2GraphComponent createGraphComponent() { - final Struts2GraphComponent[] graphComponent = {null}; - ProgressManager.getInstance().runProcessWithProgressSynchronously( - (Runnable)() -> graphComponent[0] = ReadAction.compute(() -> new Struts2GraphComponent(myXmlFile)), "Generating Graph", false, myXmlFile.getProject()); + /** + * Creates graph component while showing modal wait dialog. + * + * @return new instance. + */ + private Struts2GraphComponent createGraphComponent() { + final Struts2GraphComponent[] graphComponent = {null}; + ProgressManager.getInstance().runProcessWithProgressSynchronously( + (Runnable) () -> graphComponent[0] = ReadAction.compute(() -> new Struts2GraphComponent(myXmlFile)), "Generating Graph", false, myXmlFile.getProject()); - return graphComponent[0]; - } + return graphComponent[0]; + } } \ No newline at end of file diff --git a/src/main/java/com/intellij/struts2/jsp/inspection/HardcodedActionUrlInspection.java b/src/main/java/com/intellij/struts2/jsp/inspection/HardcodedActionUrlInspection.java index 0ed336e..7af9d1a 100644 --- a/src/main/java/com/intellij/struts2/jsp/inspection/HardcodedActionUrlInspection.java +++ b/src/main/java/com/intellij/struts2/jsp/inspection/HardcodedActionUrlInspection.java @@ -41,6 +41,7 @@ import com.intellij.xml.XmlNamespaceHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.net.URI; import java.net.URL; import java.util.Collections; @@ -50,290 +51,285 @@ import java.util.Collections; */ public class HardcodedActionUrlInspection extends XmlSuppressableInspectionTool { - @NotNull - @Override - public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { - final boolean isJspFileWithStrutsSupport = - JspPsiUtil.getJspFile(holder.getFile()) != null && - StrutsFacet.getInstance(holder.getFile()) != null; + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + final boolean isJspFileWithStrutsSupport = + JspPsiUtil.getJspFile(holder.getFile()) != null && + StrutsFacet.getInstance(holder.getFile()) != null; + + @Nullable final String actionExtension; + if (isJspFileWithStrutsSupport) { + actionExtension = ContainerUtil.getFirstItem(StrutsConstantHelper.getActionExtensions(holder.getFile())); + } else { + actionExtension = null; + } + + return new XmlElementVisitor() { + + @Override + public void visitXmlAttributeValue(@NotNull XmlAttributeValue value) { + if (!isJspFileWithStrutsSupport || + actionExtension == null) { + return; + } + + XmlTag tag = PsiTreeUtil.getParentOfType(value, XmlTag.class); + if (tag == null) return; + + URL parsedURL = parseURL(value, actionExtension); + if (parsedURL == null) return; - @Nullable final String actionExtension; - if (isJspFileWithStrutsSupport) { - actionExtension = ContainerUtil.getFirstItem(StrutsConstantHelper.getActionExtensions(holder.getFile())); + if (buildTag("", parsedURL, "", false, actionExtension) == null) return; + + TextRange range = ElementManipulators.getValueTextRange(value); + holder.registerProblem(value, range, "Use Struts <url> tag instead of hardcoded URL", new WrapWithSUrl(actionExtension)); + } + }; } - else { - actionExtension = null; + + @Override + public String @NotNull [] getGroupPath() { + return new String[]{StrutsBundle.message("inspections.group.path.name"), getGroupDisplayName()}; } - return new XmlElementVisitor() { - @Override - public void visitXmlAttributeValue(@NotNull XmlAttributeValue value) { - if (!isJspFileWithStrutsSupport || - actionExtension == null) { - return; - } + private static final class WrapWithSUrl implements LocalQuickFix { - XmlTag tag = PsiTreeUtil.getParentOfType(value, XmlTag.class); - if (tag == null) return; + private final String myActionExtension; - URL parsedURL = parseURL(value, actionExtension); - if (parsedURL == null) return; + private WrapWithSUrl(String actionExtension) { + myActionExtension = actionExtension; + } - if (buildTag("", parsedURL, "", false, actionExtension) == null) return; + @NotNull + @Override + public String getFamilyName() { + return "Wrap with Struts <url> tag"; + } - TextRange range = ElementManipulators.getValueTextRange(value); - holder.registerProblem(value, range, "Use Struts <url> tag instead of hardcoded URL", new WrapWithSUrl(actionExtension)); - } - }; - } + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + if (element instanceof XmlAttributeValue value) { + XmlTag tag = PsiTreeUtil.getParentOfType(value, XmlTag.class, false); - @Override - public String @NotNull [] getGroupPath() { - return new String[]{StrutsBundle.message("inspections.group.path.name"), getGroupDisplayName()}; - } + final boolean inline = tag instanceof HtmlTag; + final URL url = parseURL(value, myActionExtension); + if (url == null) { + return; + } - private static final class WrapWithSUrl implements LocalQuickFix { + final JspFile jspFile = JspPsiUtil.getJspFile(value); + assert jspFile != null; + + XmlTag rootTag = jspFile.getRootTag(); + String prefix = rootTag.getPrefixByNamespace(StrutsConstants.TAGLIB_STRUTS_UI_URI); + + if (StringUtil.isEmpty(prefix)) { + prefix = "s"; // Use default Struts prefix + + // Insert taglib declaration after existing taglibs or comments + Document document = PsiDocumentManager.getInstance(project).getDocument(jspFile); + if (document != null) { + String text = document.getText(); + int insertionPoint = 0; + + // Look for last existing taglib declaration + int lastTaglibEnd = -1; + int searchPos = 0; + while (true) { + int taglibStart = text.indexOf("<%@ taglib", searchPos); + if (taglibStart == -1) break; + + int taglibEnd = text.indexOf("%>", taglibStart); + if (taglibEnd != -1) { + lastTaglibEnd = taglibEnd + 2; + searchPos = lastTaglibEnd; + } else { + break; + } + } + + if (lastTaglibEnd != -1) { + // Insert after last existing taglib + insertionPoint = lastTaglibEnd; + // Skip to end of line + while (insertionPoint < text.length() && text.charAt(insertionPoint) != '\n') { + insertionPoint++; + } + if (insertionPoint < text.length()) insertionPoint++; // Skip the newline + } else { + // No existing taglibs, insert after comment block + int commentEnd = text.indexOf("-->"); + if (commentEnd != -1) { + insertionPoint = commentEnd + 3; + // Skip whitespace/newlines after comment + while (insertionPoint < text.length() && Character.isWhitespace(text.charAt(insertionPoint))) { + insertionPoint++; + } + } + } + + String taglibDeclaration = "<%@ taglib prefix=\"" + prefix + "\" uri=\"" + StrutsConstants.TAGLIB_STRUTS_UI_URI + "\" %>\n"; + document.insertString(insertionPoint, taglibDeclaration); + PsiDocumentManager.getInstance(project).commitDocument(document); + } + + wrapValue(prefix, value, url, inline); + } else { + wrapValue(prefix, value, url, inline); + } + } + } - private final String myActionExtension; + private void wrapValue(String prefix, XmlAttributeValue value, URL url, boolean inline) { + final JspFile jspFile = JspPsiUtil.getJspFile(value); + assert jspFile != null; - private WrapWithSUrl(String actionExtension) { - myActionExtension = actionExtension; - } + Project project = jspFile.getProject(); + TextRange range = value.getValueTextRange(); + Document document = PsiDocumentManager.getInstance(project).getDocument(jspFile); + assert document != null; + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document); - @NotNull - @Override - public String getFamilyName() { - return "Wrap with Struts <url> tag"; - } + int start = range.getStartOffset(); + int lineStart = document.getLineStartOffset(document.getLineNumber(start)); + String linePrefix = document.getCharsSequence().subSequence(lineStart, start).toString(); + linePrefix = linePrefix.substring(0, linePrefix.length() - linePrefix.trim().length()); - @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - PsiElement element = descriptor.getPsiElement(); - if (element instanceof XmlAttributeValue value) { - XmlTag tag = PsiTreeUtil.getParentOfType(value, XmlTag.class, false); + String indent = linePrefix; + while (indent.length() < start - lineStart) indent += " "; - final boolean inline = tag instanceof HtmlTag; + Pair<String, String> tag_var = buildTag(prefix, url, indent, inline, myActionExtension); + String tag = tag_var.getFirst(); + String var = tag_var.getSecond(); - final URL url = parseURL(value, myActionExtension); - if (url == null) { - return; - } + int end = range.getEndOffset(); - final JspFile jspFile = JspPsiUtil.getJspFile(value); - assert jspFile != null; - - XmlTag rootTag = jspFile.getRootTag(); - String prefix = rootTag.getPrefixByNamespace(StrutsConstants.TAGLIB_STRUTS_UI_URI); - - if (StringUtil.isEmpty(prefix)) { - prefix = "s"; // Use default Struts prefix - - // Insert taglib declaration after existing taglibs or comments - Document document = PsiDocumentManager.getInstance(project).getDocument(jspFile); - if (document != null) { - String text = document.getText(); - int insertionPoint = 0; - - // Look for last existing taglib declaration - int lastTaglibEnd = -1; - int searchPos = 0; - while (true) { - int taglibStart = text.indexOf("<%@ taglib", searchPos); - if (taglibStart == -1) break; - - int taglibEnd = text.indexOf("%>", taglibStart); - if (taglibEnd != -1) { - lastTaglibEnd = taglibEnd + 2; - searchPos = lastTaglibEnd; - } else { - break; - } - } - - if (lastTaglibEnd != -1) { - // Insert after last existing taglib - insertionPoint = lastTaglibEnd; - // Skip to end of line - while (insertionPoint < text.length() && text.charAt(insertionPoint) != '\n') { - insertionPoint++; - } - if (insertionPoint < text.length()) insertionPoint++; // Skip the newline + int formattingStart; + int formattingEnd; + + if (inline) { + document.replaceString(start, end, tag); + formattingStart = start; + formattingEnd = start + tag.length(); } else { - // No existing taglibs, insert after comment block - int commentEnd = text.indexOf("-->"); - if (commentEnd != -1) { - insertionPoint = commentEnd + 3; - // Skip whitespace/newlines after comment - while (insertionPoint < text.length() && Character.isWhitespace(text.charAt(insertionPoint))) { - insertionPoint++; - } - } + document.replaceString(start, end, "${" + var + "}"); + XmlTag containingTag = PsiTreeUtil.getParentOfType(value, XmlTag.class, false); + assert containingTag != null; + int startOffset = containingTag.getTextRange().getStartOffset(); + document.insertString(startOffset, "\n"); + document.insertString(startOffset, tag); + + formattingStart = startOffset; + formattingEnd = startOffset + tag.length() + 2; } - - String taglibDeclaration = "<%@ taglib prefix=\"" + prefix + "\" uri=\"" + StrutsConstants.TAGLIB_STRUTS_UI_URI + "\" %>\n"; - document.insertString(insertionPoint, taglibDeclaration); + PsiDocumentManager.getInstance(project).commitDocument(document); - } - - wrapValue(prefix, value, url, inline); - } - else { - wrapValue(prefix, value, url, inline); - } - } - } - private void wrapValue(String prefix, XmlAttributeValue value, URL url, boolean inline) { - final JspFile jspFile = JspPsiUtil.getJspFile(value); - assert jspFile != null; - - Project project = jspFile.getProject(); - TextRange range = value.getValueTextRange(); - Document document = PsiDocumentManager.getInstance(project).getDocument(jspFile); - assert document != null; - PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document); - - int start = range.getStartOffset(); - int lineStart = document.getLineStartOffset(document.getLineNumber(start)); - String linePrefix = document.getCharsSequence().subSequence(lineStart, start).toString(); - linePrefix = linePrefix.substring(0, linePrefix.length() - linePrefix.trim().length()); - - String indent = linePrefix; - while (indent.length() < start - lineStart) indent += " "; - - Pair<String, String> tag_var = buildTag(prefix, url, indent, inline, myActionExtension); - String tag = tag_var.getFirst(); - String var = tag_var.getSecond(); - - int end = range.getEndOffset(); - - int formattingStart; - int formattingEnd; - - if (inline) { - document.replaceString(start, end, tag); - formattingStart = start; - formattingEnd = start + tag.length(); - } - else { - document.replaceString(start, end, "${" + var + "}"); - XmlTag containingTag = PsiTreeUtil.getParentOfType(value, XmlTag.class, false); - assert containingTag != null; - int startOffset = containingTag.getTextRange().getStartOffset(); - document.insertString(startOffset, "\n"); - document.insertString(startOffset, tag); - - formattingStart = startOffset; - formattingEnd = startOffset + tag.length() + 2; - } - - PsiDocumentManager.getInstance(project).commitDocument(document); - - CodeStyleManager.getInstance(project).reformatText(jspFile, formattingStart, formattingEnd); + CodeStyleManager.getInstance(project).reformatText(jspFile, formattingStart, formattingEnd); + } } - } - private static Pair<String, String> buildTag(String prefix, URL url, String indent, boolean inline, String actionExtension) { - String path = url.getPath(); - int slash = path.lastIndexOf('/'); - String namespace = slash > 0 ? path.substring(0, slash) : null; - String action = slash != -1 ? path.substring(slash + 1) : path; + private static Pair<String, String> buildTag(String prefix, URL url, String indent, boolean inline, String actionExtension) { + String path = url.getPath(); + int slash = path.lastIndexOf('/'); + String namespace = slash > 0 ? path.substring(0, slash) : null; + String action = slash != -1 ? path.substring(slash + 1) : path; - action = StringUtil.trimEnd(action, actionExtension); + action = StringUtil.trimEnd(action, actionExtension); - int exclamationIdx = action.indexOf('!'); - String method = null; - if (exclamationIdx > 0) { - method = action.substring(exclamationIdx + 1); - action = action.substring(0, exclamationIdx); - } + int exclamationIdx = action.indexOf('!'); + String method = null; + if (exclamationIdx > 0) { + method = action.substring(exclamationIdx + 1); + action = action.substring(0, exclamationIdx); + } - StringBuilder sb = new StringBuilder(); - sb.append('<').append(prefix).append(":url"); + StringBuilder sb = new StringBuilder(); + sb.append('<').append(prefix).append(":url"); - String var; - if (inline) { - var = null; - } - else { - var = action + "_url"; - sb.append(" var=\"").append(var).append("\""); - } + String var; + if (inline) { + var = null; + } else { + var = action + "_url"; + sb.append(" var=\"").append(var).append("\""); + } - if (namespace != null) { - sb.append(" namespace=\"").append(namespace).append("\""); - } + if (namespace != null) { + sb.append(" namespace=\"").append(namespace).append("\""); + } - sb.append(" action=\"").append(action).append("\""); - if (method != null) { - sb.append(" method=\"").append(method).append("\""); - } + sb.append(" action=\"").append(action).append("\""); + if (method != null) { + sb.append(" method=\"").append(method).append("\""); + } - String query = url.getQuery(); - if (StringUtil.isEmpty(query)) { - sb.append("/>"); - } - else { - sb.append(">"); - - for (String escapedArg : StringUtil.split(query, "&")) { - for (String arg : StringUtil.split(escapedArg, "&")) { - int eq = arg.indexOf('='); - String name = eq > 0 ? arg.substring(0, eq) : arg; - String value = eq > 0 ? arg.substring(eq + 1) : ""; - - if (name.contains("[") || name.contains("$")) return null; // This will not work if arg name is actually an expression - - sb.append("\n").append(indent).append(" <") - .append(prefix) - .append(":param name=\"") - .append(name).append("\">") - .append(value) - .append("</") - .append(prefix) - .append(":param>"); + String query = url.getQuery(); + if (StringUtil.isEmpty(query)) { + sb.append("/>"); + } else { + sb.append(">"); + + for (String escapedArg : StringUtil.split(query, "&")) { + for (String arg : StringUtil.split(escapedArg, "&")) { + int eq = arg.indexOf('='); + String name = eq > 0 ? arg.substring(0, eq) : arg; + String value = eq > 0 ? arg.substring(eq + 1) : ""; + + if (name.contains("[") || name.contains("$")) + return null; // This will not work if arg name is actually an expression + + sb.append("\n").append(indent).append(" <") + .append(prefix) + .append(":param name=\"") + .append(name).append("\">") + .append(value) + .append("</") + .append(prefix) + .append(":param>"); + } + } + sb.append('\n').append(indent); + sb.append("</").append(prefix).append(":url>"); } - } - sb.append('\n').append(indent); - sb.append("</").append(prefix).append(":url>"); + + return Pair.create(sb.toString(), var); } - return Pair.create(sb.toString(), var); - } + @Nullable + private static URL parseURL(XmlAttributeValue value, String actionExtension) { + String rawUrl = value.getValue(); + if (rawUrl.startsWith("http://") || + rawUrl.startsWith("https://")) { + return null; + } - @Nullable - private static URL parseURL(XmlAttributeValue value, String actionExtension) { - String rawUrl = value.getValue(); - if (rawUrl.startsWith("http://") || - rawUrl.startsWith("https://")) { - return null; - } + URL parsedURL; + try { + parsedURL = URI.create("http://" + rawUrl).toURL(); + } catch (Exception e) { + return null; + } - URL parsedURL; - try { - parsedURL = new URL("http://" + rawUrl); - } - catch (Exception e) { - return null; - } + String host = parsedURL.getHost(); + if (!StringUtil.isEmpty(host) && + !(host.startsWith("${") && host.endsWith("}"))) { + return null; + } - String host = parsedURL.getHost(); - if (!StringUtil.isEmpty(host) && - !(host.startsWith("${") && host.endsWith("}"))) { - return null; - } + String path = parsedURL.getPath(); + if (!path.endsWith(actionExtension)) { + return null; + } - String path = parsedURL.getPath(); - if (!path.endsWith(actionExtension)) { - return null; - } + if (path.contains("${")) { + return null; // Dynamic action paths cannot be converted. + } - if (path.contains("${")) { - return null; // Dynamic action paths cannot be converted. + return parsedURL; } - - return parsedURL; - } } diff --git a/src/main/java/com/intellij/struts2/model/constant/contributor/ConstantValueClassConverter.java b/src/main/java/com/intellij/struts2/model/constant/contributor/ConstantValueClassConverter.java index 8ab013d..16b6aee 100644 --- a/src/main/java/com/intellij/struts2/model/constant/contributor/ConstantValueClassConverter.java +++ b/src/main/java/com/intellij/struts2/model/constant/contributor/ConstantValueClassConverter.java @@ -40,81 +40,83 @@ import java.util.Set; */ class ConstantValueClassConverter extends ResolvingConverter<PsiClass> implements CustomReferenceConverter { - private final JavaClassReferenceProvider javaClassReferenceProvider = new JavaClassReferenceProvider(); - - private final Map<String, String> shortCutToPsiClassMap; - private final boolean hasShortCuts; - - ConstantValueClassConverter(@NonNls @NotNull String baseClass, - final Map<String, String> shortCutToPsiClassMap) { - this.shortCutToPsiClassMap = shortCutToPsiClassMap; - this.hasShortCuts = !shortCutToPsiClassMap.isEmpty(); - - javaClassReferenceProvider.setSoft(true); - javaClassReferenceProvider.setAllowEmpty(false); - javaClassReferenceProvider.setOption(JavaClassReferenceProvider.CONCRETE, Boolean.TRUE); - javaClassReferenceProvider.setOption(JavaClassReferenceProvider.NOT_INTERFACE, Boolean.TRUE); - javaClassReferenceProvider.setOption(JavaClassReferenceProvider.EXTEND_CLASS_NAMES, new String[]{baseClass}); - } - - @NotNull - @Override - public Collection<? extends PsiClass> getVariants(ConvertContext context) { - return Collections.emptyList(); - } - - @Override - public PsiClass fromString(@Nullable @NonNls final String s, final ConvertContext convertContext) { - if (s == null) { - return null; + private final JavaClassReferenceProvider javaClassReferenceProvider = new JavaClassReferenceProvider(); + + private final Map<String, String> shortCutToPsiClassMap; + private final boolean hasShortCuts; + + ConstantValueClassConverter(@NonNls @NotNull String baseClass, + final Map<String, String> shortCutToPsiClassMap) { + this.shortCutToPsiClassMap = shortCutToPsiClassMap; + this.hasShortCuts = !shortCutToPsiClassMap.isEmpty(); + + javaClassReferenceProvider.setSoft(true); + javaClassReferenceProvider.setAllowEmpty(false); + javaClassReferenceProvider.setOption(JavaClassReferenceProvider.CONCRETE, Boolean.TRUE); + javaClassReferenceProvider.setOption(JavaClassReferenceProvider.NOT_INTERFACE, Boolean.TRUE); + // TODO: EXTEND_CLASS_NAMES is deprecated but no replacement is documented. + //noinspection deprecation + javaClassReferenceProvider.setOption(JavaClassReferenceProvider.EXTEND_CLASS_NAMES, new String[]{baseClass}); } - // 1. via shortcut - if (hasShortCuts) { - final String shortCutClassName = shortCutToPsiClassMap.get(s); + @NotNull + @Override + public Collection<? extends PsiClass> getVariants(ConvertContext context) { + return Collections.emptyList(); + } + + @Override + public PsiClass fromString(@Nullable @NonNls final String s, final ConvertContext convertContext) { + if (s == null) { + return null; + } + + // 1. via shortcut + if (hasShortCuts) { + final String shortCutClassName = shortCutToPsiClassMap.get(s); + + if (StringUtil.isNotEmpty(shortCutClassName)) { + return DomJavaUtil.findClass(shortCutClassName, convertContext.getInvocationElement()); + } + } + + // 2. first non-null result from extension point contributor (currently only Spring) + for (final ConstantValueConverterClassContributor converterClassContributor : + ConstantValueConverterClassContributor.EP_NAME.getExtensionList()) { + final PsiClass contributorClass = converterClassContributor.fromString(s, convertContext); + if (contributorClass != null) { + return contributorClass; + } + } + + // 3. via JAVA-class + final PsiClass psiClass = DomJavaUtil.findClass(s, convertContext.getInvocationElement()); + if (psiClass == null) { + return null; + } + return !psiClass.isInterface() && !psiClass.hasModifierProperty(PsiModifier.ABSTRACT) ? psiClass : null; + } + + @Override + public String toString(@Nullable PsiClass aClass, ConvertContext context) { + return aClass == null ? null : aClass.getName(); + } - if (StringUtil.isNotEmpty(shortCutClassName)) { - return DomJavaUtil.findClass(shortCutClassName, convertContext.getInvocationElement()); - } + @Override + @NotNull + public Set<String> getAdditionalVariants(@NotNull final ConvertContext context) { + return shortCutToPsiClassMap.keySet(); } - // 2. first non-null result from extension point contributor (currently only Spring) - for (final ConstantValueConverterClassContributor converterClassContributor : - ConstantValueConverterClassContributor.EP_NAME.getExtensionList()) { - final PsiClass contributorClass = converterClassContributor.fromString(s, convertContext); - if (contributorClass != null) { - return contributorClass; - } + @Override + public PsiReference @NotNull [] createReferences(GenericDomValue value, PsiElement element, ConvertContext context) { + final PsiReference[] references = javaClassReferenceProvider.getReferencesByElement(element); + //noinspection unchecked + return ArrayUtil.append(references, new GenericDomValueReference(value), PsiReference.ARRAY_FACTORY); } - // 3. via JAVA-class - final PsiClass psiClass = DomJavaUtil.findClass(s, convertContext.getInvocationElement()); - if (psiClass == null) { - return null; + @Override + public String getErrorMessage(@Nullable final String s, final ConvertContext context) { + return CodeInsightBundle.message("error.cannot.resolve.class", s); } - return !psiClass.isInterface() && !psiClass.hasModifierProperty(PsiModifier.ABSTRACT) ? psiClass : null; - } - - @Override - public String toString(@Nullable PsiClass aClass, ConvertContext context) { - return aClass == null ? null : aClass.getName(); - } - - @Override - @NotNull - public Set<String> getAdditionalVariants(@NotNull final ConvertContext context) { - return shortCutToPsiClassMap.keySet(); - } - - @Override - public PsiReference @NotNull [] createReferences(GenericDomValue value, PsiElement element, ConvertContext context) { - final PsiReference[] references = javaClassReferenceProvider.getReferencesByElement(element); - //noinspection unchecked - return ArrayUtil.append(references, new GenericDomValueReference(value), PsiReference.ARRAY_FACTORY); - } - - @Override - public String getErrorMessage(@Nullable final String s, final ConvertContext context) { - return CodeInsightBundle.message("error.cannot.resolve.class", s); - } } diff --git a/src/main/java/com/intellij/struts2/velocity/Struts2GlobalMacroProvider.java b/src/main/java/com/intellij/struts2/velocity/Struts2GlobalMacroProvider.java index 8b9c5eb..0491431 100644 --- a/src/main/java/com/intellij/struts2/velocity/Struts2GlobalMacroProvider.java +++ b/src/main/java/com/intellij/struts2/velocity/Struts2GlobalMacroProvider.java @@ -16,7 +16,9 @@ package com.intellij.struts2.velocity; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.velocity.VtlGlobalMacroProvider; @@ -34,27 +36,28 @@ import java.util.Collections; * @author Yann Cébron */ final class Struts2GlobalMacroProvider extends VtlGlobalMacroProvider { - @NonNls - private static final String STRUTS_MACROS_FILENAME = "struts.vm"; + @NonNls + private static final String STRUTS_MACROS_FILENAME = "struts.vm"; - @NotNull - @Override - public Collection<VtlMacro> getGlobalMacros(@NotNull final VtlFile vtlFile) { - final Module module = ModuleUtilCore.findModuleForPsiElement(vtlFile); - if (module == null) { - return Collections.emptySet(); - } + @NotNull + @Override + public Collection<VtlMacro> getGlobalMacros(@NotNull final VtlFile vtlFile) { + final Module module = ModuleUtilCore.findModuleForPsiElement(vtlFile); + if (module == null) { + return Collections.emptySet(); + } - final PsiFile[] filesByName = FilenameIndex.getFilesByName(vtlFile.getProject(), - STRUTS_MACROS_FILENAME, - GlobalSearchScope.moduleRuntimeScope(module, false)); - if (filesByName.length == 1) { - final PsiFile psiFile = filesByName[0]; - if (psiFile instanceof VtlFile) { - return ((VtlFile) psiFile).getDefinedMacros(); - } - } + final Collection<VirtualFile> virtualFiles = FilenameIndex.getVirtualFilesByName( + STRUTS_MACROS_FILENAME, + GlobalSearchScope.moduleRuntimeScope(module, false)); + if (virtualFiles.size() == 1) { + final VirtualFile virtualFile = virtualFiles.iterator().next(); + final PsiFile psiFile = PsiManager.getInstance(vtlFile.getProject()).findFile(virtualFile); + if (psiFile instanceof VtlFile) { + return ((VtlFile) psiFile).getDefinedMacros(); + } + } - return Collections.emptySet(); - } + return Collections.emptySet(); + } } \ No newline at end of file
