diff --git a/src/main/java/io/openliberty/tools/intellij/LibertyExplorer.java b/src/main/java/io/openliberty/tools/intellij/LibertyExplorer.java index fbec073db..f52d4c7ec 100644 --- a/src/main/java/io/openliberty/tools/intellij/LibertyExplorer.java +++ b/src/main/java/io/openliberty/tools/intellij/LibertyExplorer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020, 2024 IBM Corporation. + * Copyright (c) 2020, 2025 IBM Corporation. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -20,7 +20,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.util.Computable; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.DoubleClickListener; import com.intellij.ui.PopupHandler; import com.intellij.ui.components.JBScrollPane; @@ -30,18 +29,15 @@ import io.openliberty.tools.intellij.actions.LibertyToolbarActionGroup; import io.openliberty.tools.intellij.util.*; import org.jetbrains.annotations.NotNull; -import org.xml.sax.SAXException; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreePath; -import javax.xml.parsers.ParserConfigurationException; import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -106,115 +102,40 @@ public static ActionToolbar buildActionToolbar(Tree tree) { * @return Tree object of all valid Liberty Gradle and Liberty Maven projects */ public static Tree buildTree(Project project, Color backgroundColor) { - LibertyModules libertyModules = LibertyModules.getInstance(); - // clear all stored Liberty modules for current project - libertyModules.removeForProject(project); - DefaultMutableTreeNode top = new DefaultMutableTreeNode("Root node"); - - ArrayList mavenBuildFiles; - ArrayList gradleBuildFiles; - HashMap> map = new HashMap<>(); - try { - mavenBuildFiles = LibertyProjectUtil.getMavenBuildFiles(project); - gradleBuildFiles = LibertyProjectUtil.getGradleBuildFiles(project); - } catch (IOException | SAXException | ParserConfigurationException e) { - LOGGER.warn("Could not find Liberty Maven or Gradle projects in workspace", - e); + LibertyModules libertyModules = LibertyModules.getInstance().scanLibertyModules(project); + // This singleton may contain entries from old projects if you close a project and open another + if (libertyModules.getLibertyModules(project).isEmpty()) { return null; } + DefaultMutableTreeNode top = new DefaultMutableTreeNode("Root node"); + HashMap> projectMap = new HashMap<>(); - if (mavenBuildFiles.isEmpty() && gradleBuildFiles.isEmpty()) { - return null; - } - - for (BuildFile buildFile : mavenBuildFiles) { - // create a new Liberty project - VirtualFile virtualFile = buildFile.getBuildFile(); - String projectName = null; - if (virtualFile == null) { - LOGGER.error(String.format("Could not resolve current Maven project %s", virtualFile)); - break; - } - LibertyModuleNode node; - try { - projectName = LibertyMavenUtil.getProjectNameFromPom(virtualFile); - } catch (Exception e) { - LOGGER.warn(String.format("Could not resolve project name from build file: %s", virtualFile), e); - } - if (projectName == null) { - if (virtualFile.getParent() != null) { - projectName = virtualFile.getParent().getName(); - } else { - projectName = project.getName(); - } - } - - boolean validContainerVersion = buildFile.isValidContainerVersion(); - LibertyModule module = libertyModules.addLibertyModule(new LibertyModule(project, virtualFile, projectName, Constants.LIBERTY_MAVEN_PROJECT, validContainerVersion)); - node = new LibertyModuleNode(module); + for (LibertyModule libertyModule : libertyModules.getLibertyModules(project)) { + LibertyModuleNode node = new LibertyModuleNode(libertyModule); top.add(node); ArrayList settings = new ArrayList(); - settings.add(virtualFile); - settings.add(Constants.LIBERTY_MAVEN_PROJECT); - map.put(projectName, settings); + settings.add(libertyModule.getBuildFile()); + settings.add(libertyModule.getProjectType()); + projectMap.put(libertyModule.getName(), settings); // ordered to align with IntelliJ's right-click menu - node.add(new LibertyActionNode(Constants.LIBERTY_DEV_START, module)); - // check if Liberty Maven Plugin is 3.3-M1+ + node.add(new LibertyActionNode(Constants.LIBERTY_DEV_START, libertyModule)); + // check if Liberty Maven Plugin is 3.3-M1+ or Liberty Gradle Plugin is 3.1-M1+ // if version is not specified in pom, assume latest version as downloaded from maven central + boolean validContainerVersion = libertyModule.isValidContainerVersion(); if (validContainerVersion) { - node.add(new LibertyActionNode(Constants.LIBERTY_DEV_START_CONTAINER, module)); + node.add(new LibertyActionNode(Constants.LIBERTY_DEV_START_CONTAINER, libertyModule)); } - node.add(new LibertyActionNode(Constants.LIBERTY_DEV_CUSTOM_START, module)); - node.add(new LibertyActionNode(Constants.LIBERTY_DEV_STOP, module)); - node.add(new LibertyActionNode(Constants.LIBERTY_DEV_TESTS, module)); - node.add(new LibertyActionNode(Constants.VIEW_INTEGRATION_TEST_REPORT, module)); - node.add(new LibertyActionNode(Constants.VIEW_UNIT_TEST_REPORT, module)); - } - - for (BuildFile buildFile : gradleBuildFiles) { - VirtualFile virtualFile = buildFile.getBuildFile(); - String projectName = null; - if (virtualFile == null) { - LOGGER.error(String.format("Could not resolve current Gradle project %s", buildFile)); - break; - } - LibertyModuleNode node; - try { - projectName = LibertyGradleUtil.getProjectName(virtualFile); - } catch (Exception e) { - LOGGER.warn(String.format("Could not resolve project name for project %s", virtualFile), e); - } - if (projectName == null) { - if (virtualFile.getParent() != null) { - projectName = virtualFile.getParent().getName(); - } else { - projectName = project.getName(); - } - } - - boolean validContainerVersion = buildFile.isValidContainerVersion(); - LibertyModule module = libertyModules.addLibertyModule(new LibertyModule(project, virtualFile, projectName, Constants.LIBERTY_GRADLE_PROJECT, validContainerVersion)); - node = new LibertyModuleNode(module); - - top.add(node); - ArrayList settings = new ArrayList(); - settings.add(virtualFile); - settings.add(Constants.LIBERTY_GRADLE_PROJECT); - map.put(projectName, settings); - - // ordered to align with IntelliJ's right-click menu - node.add(new LibertyActionNode(Constants.LIBERTY_DEV_START, module)); - // check if Liberty Gradle Plugin is 3.1-M1+ - // TODO: handle version specified in a gradle.settings file - if (buildFile.isValidContainerVersion()) { - node.add(new LibertyActionNode(Constants.LIBERTY_DEV_START_CONTAINER, module)); + node.add(new LibertyActionNode(Constants.LIBERTY_DEV_CUSTOM_START, libertyModule)); + node.add(new LibertyActionNode(Constants.LIBERTY_DEV_STOP, libertyModule)); + node.add(new LibertyActionNode(Constants.LIBERTY_DEV_TESTS, libertyModule)); + if (libertyModule.getProjectType().equals(Constants.LIBERTY_MAVEN_PROJECT)) { + node.add(new LibertyActionNode(Constants.VIEW_INTEGRATION_TEST_REPORT, libertyModule)); + node.add(new LibertyActionNode(Constants.VIEW_UNIT_TEST_REPORT, libertyModule)); + } else if (libertyModule.getProjectType().equals(Constants.LIBERTY_GRADLE_PROJECT)) { + node.add(new LibertyActionNode(Constants.VIEW_GRADLE_TEST_REPORT, libertyModule)); } - node.add(new LibertyActionNode(Constants.LIBERTY_DEV_CUSTOM_START, module)); - node.add(new LibertyActionNode(Constants.LIBERTY_DEV_STOP, module)); - node.add(new LibertyActionNode(Constants.LIBERTY_DEV_TESTS, module)); - node.add(new LibertyActionNode(Constants.VIEW_GRADLE_TEST_REPORT, module)); } Tree tree = new Tree(top); @@ -224,7 +145,7 @@ public static Tree buildTree(Project project, Color backgroundColor) { DataManager.registerDataProvider(tree, newDataProvider); TreeDataProvider treeDataProvider = (TreeDataProvider) DataManager.getDataProvider(tree); - treeDataProvider.setProjectMap(map); + treeDataProvider.setProjectMap(projectMap); tree.addTreeSelectionListener(e -> { Object node = e.getPath().getLastPathComponent(); diff --git a/src/main/java/io/openliberty/tools/intellij/LibertyModules.java b/src/main/java/io/openliberty/tools/intellij/LibertyModules.java index 3624bbe4b..13188c12c 100644 --- a/src/main/java/io/openliberty/tools/intellij/LibertyModules.java +++ b/src/main/java/io/openliberty/tools/intellij/LibertyModules.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 IBM Corporation. + * Copyright (c) 2022, 2025 IBM Corporation. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -10,10 +10,15 @@ package io.openliberty.tools.intellij; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; +import io.openliberty.tools.intellij.util.*; +import org.xml.sax.SAXException; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; import java.nio.file.Paths; import java.util.*; @@ -21,6 +26,7 @@ * Singleton to save the Liberty modules in the open project */ public class LibertyModules { + private final static Logger LOGGER = Logger.getInstance(LibertyModules.class); private static LibertyModules instance = null; @@ -38,6 +44,67 @@ public synchronized static LibertyModules getInstance() { return instance; } + /** + * Remove existing data and scan the project for the modules that are Liberty apps. + * @return this singleton, the list will be empty if there are no Liberty modules + */ + public LibertyModules scanLibertyModules(Project project) { + synchronized (libertyModules) { + removeForProject(project); // remove previous data, if any + return rescanLibertyModules(project); + } + } + + /** + * Scan the project for the modules that are Liberty apps and update any existing entries. + * @return this singleton, the list will be empty if there are no Liberty modules + */ + public LibertyModules rescanLibertyModules(Project project) { + synchronized (libertyModules) { + ArrayList buildFiles = new ArrayList<>(); + try { + buildFiles.addAll(LibertyProjectUtil.getMavenBuildFiles(project)); + } catch (IOException | SAXException | ParserConfigurationException e) { + LOGGER.error("I/O error or error parsing Liberty Maven projects in workspace", e); + } + try { // search for Gradle files even if Maven files experience error + buildFiles.addAll(LibertyProjectUtil.getGradleBuildFiles(project)); + } catch (IOException | SAXException | ParserConfigurationException e) { + LOGGER.error("I/O error or error parsing Liberty Gradle projects in workspace", e); + } + + for (BuildFile buildFile : buildFiles) { + // create a new Liberty Module object for this project + VirtualFile virtualFile = buildFile.getBuildFile(); + String projectName = null; + if (virtualFile == null) { + LOGGER.error(String.format("Could not resolve current project %s", virtualFile)); + break; + } + try { + if (buildFile.getProjectType().equals(Constants.LIBERTY_MAVEN_PROJECT)) { + projectName = LibertyMavenUtil.getProjectNameFromPom(virtualFile); + } else { + projectName = LibertyGradleUtil.getProjectName(virtualFile); + } + } catch (Exception e) { + LOGGER.warn(String.format("Could not resolve project name from build file: %s", virtualFile), e); + } + if (projectName == null) { + if (virtualFile.getParent() != null) { + projectName = virtualFile.getParent().getName(); + } else { + projectName = project.getName(); + } + } + + boolean validContainerVersion = buildFile.isValidContainerVersion(); + addLibertyModule(new LibertyModule(project, virtualFile, projectName, buildFile.getProjectType(), validContainerVersion)); + } + } + return getInstance(); + } + /** * Add tracked Liberty project to workspace, update project, * projectType, name and validContainerVersion if already tracked. @@ -101,6 +168,24 @@ public List getLibertyBuildFilesAsString(Project project) { return sBuildFiles; } + /** + * Returns all Liberty modules for the given project + * + * @param project + * @return Liberty modules for the given project + */ + public List getLibertyModules(Project project) { + ArrayList supportedLibertyModules = new ArrayList<>(); + synchronized (libertyModules) { + libertyModules.values().forEach(libertyModule -> { + if (project.equals(libertyModule.getProject())) { + supportedLibertyModules.add(libertyModule); + } + }); + } + return supportedLibertyModules; + } + /** * Returns all Liberty modules with the supported project type(s) for the given project * ex. all Liberty Maven projects diff --git a/src/main/java/io/openliberty/tools/intellij/runConfiguration/LibertyRunConfiguration.java b/src/main/java/io/openliberty/tools/intellij/runConfiguration/LibertyRunConfiguration.java index ae636f75b..a76cc411b 100644 --- a/src/main/java/io/openliberty/tools/intellij/runConfiguration/LibertyRunConfiguration.java +++ b/src/main/java/io/openliberty/tools/intellij/runConfiguration/LibertyRunConfiguration.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022, 2024 IBM Corporation. + * Copyright (c) 2022, 2025 IBM Corporation. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -39,13 +39,13 @@ public class LibertyRunConfiguration extends ModuleBasedConfiguration getConfigurationEditor() { @Nullable @Override public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) throws ExecutionException { + LibertyModule libertyModule; try { libertyModule = libertyModules.getLibertyProjectFromString(getBuildFile()); } catch (NullPointerException e) { diff --git a/src/main/java/io/openliberty/tools/intellij/util/BuildFile.java b/src/main/java/io/openliberty/tools/intellij/util/BuildFile.java index c23e667d6..9e3a3294d 100644 --- a/src/main/java/io/openliberty/tools/intellij/util/BuildFile.java +++ b/src/main/java/io/openliberty/tools/intellij/util/BuildFile.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020, 2024 IBM Corporation. + * Copyright (c) 2020, 2025 IBM Corporation. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -25,7 +25,14 @@ public String getProjectType() { return projectType; } + /** + * Liberty project type must be Gradle or Maven. + * @param projectType + */ public void setProjectType(String projectType) { + if (!Constants.LIBERTY_GRADLE_PROJECT.equals(projectType) && !Constants.LIBERTY_MAVEN_PROJECT.equals(projectType)) { + throw new IllegalArgumentException("Only Gradle and Maven project types are supported: " + projectType); + } this.projectType = projectType; } @@ -58,6 +65,4 @@ public String getProjectName() { public void setProjectName(String projectName) { this.projectName = projectName; } - - } diff --git a/src/main/java/io/openliberty/tools/intellij/util/LibertyProjectUtil.java b/src/main/java/io/openliberty/tools/intellij/util/LibertyProjectUtil.java index ee5148820..a61762964 100644 --- a/src/main/java/io/openliberty/tools/intellij/util/LibertyProjectUtil.java +++ b/src/main/java/io/openliberty/tools/intellij/util/LibertyProjectUtil.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020, 2024 IBM Corporation. + * Copyright (c) 2020, 2025 IBM Corporation. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -11,8 +11,10 @@ import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; import com.intellij.psi.search.FilenameIndex; @@ -33,6 +35,9 @@ import java.io.File; import java.io.IOException; import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.ExecutionException; public class LibertyProjectUtil { private static Logger LOGGER = Logger.getInstance(LibertyProjectUtil.class); @@ -182,36 +187,49 @@ public static void setFocusToWidget(Project project, ShellTerminalWidget widget) } } - // returns valid build files for the current project + // Search the filename index to find valid build files (Maven and Gradle) for the current project private static ArrayList getBuildFiles(Project project, String buildFileType, BuildFileFilter filter) throws ParserConfigurationException, SAXException, IOException { - ArrayList buildFiles = new ArrayList(); - + ArrayList collectedBuildFiles = new ArrayList(); + Collection indexedVFiles; if (buildFileType.equals(Constants.LIBERTY_MAVEN_PROJECT)) { - Collection mavenFiles = FilenameIndex.getVirtualFilesByName("pom.xml", GlobalSearchScope.projectScope(project)); - for (VirtualFile mavenFile : mavenFiles) { - BuildFile buildFile = LibertyMavenUtil.validPom(mavenFile); - // check if valid pom.xml, or if part of Liberty project - if (filter.matches(project, buildFile, mavenFile)) { - buildFile.setBuildFile(mavenFile); - buildFiles.add(buildFile); - } - } - } else if (buildFileType.equals(Constants.LIBERTY_GRADLE_PROJECT)) { - Collection gradleFiles = FilenameIndex.getVirtualFilesByName("build.gradle", GlobalSearchScope.projectScope(project)); - for (VirtualFile gradleFile : gradleFiles) { + indexedVFiles = readIndex(project, "pom.xml"); + } else { + indexedVFiles = readIndex(project, "build.gradle"); + } + if (indexedVFiles != null) { + for (VirtualFile vFile : indexedVFiles) { try { - BuildFile buildFile = LibertyGradleUtil.validBuildGradle(gradleFile); - // check if valid build.gradle, or if part of Liberty project - if (filter.matches(project, buildFile, gradleFile)) { - buildFile.setBuildFile(gradleFile); - buildFiles.add(buildFile); + BuildFile buildFile; + if (buildFileType.equals(Constants.LIBERTY_MAVEN_PROJECT)) { + buildFile = LibertyMavenUtil.validPom(vFile); + } else { + buildFile = LibertyGradleUtil.validBuildGradle(vFile); + } + // check if valid pom.xml or build.gradle, or if part of Liberty project + if (filter.matches(project, buildFile, vFile)) { + buildFile.setBuildFile(vFile); + buildFile.setProjectType(buildFileType); + collectedBuildFiles.add(buildFile); } } catch (Exception e) { - LOGGER.error(String.format("Error parsing build.gradle %s", gradleFile), e.getMessage()); + LOGGER.error(String.format("Error parsing build file %s", vFile), e.getMessage()); } } } - return buildFiles; + return collectedBuildFiles; + } + + // Wrap the search for files in a executeOnPooledThread() method to handle the slow operations on EDT issue + // and in a runReadAction() to handle the read action required problem. + private static Collection readIndex(Project project, String name) { + try { + Computable> virtualFilesComputation = () -> FilenameIndex.getVirtualFilesByName(name, GlobalSearchScope.projectScope(project)); + Callable> readAction = () -> ApplicationManager.getApplication().runReadAction(virtualFilesComputation); + Future> filesFuture = ApplicationManager.getApplication().executeOnPooledThread(readAction); + return filesFuture.get(); + } catch (ExecutionException | InterruptedException e) { + return null; + } } /** diff --git a/src/test/java/io/openliberty/tools/intellij/it/UIBotTestUtils.java b/src/test/java/io/openliberty/tools/intellij/it/UIBotTestUtils.java index 23bf08fce..51a4cc6f3 100644 --- a/src/test/java/io/openliberty/tools/intellij/it/UIBotTestUtils.java +++ b/src/test/java/io/openliberty/tools/intellij/it/UIBotTestUtils.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023, 2024 IBM Corporation. + * Copyright (c) 2023, 2025 IBM Corporation. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -437,6 +437,7 @@ public static void openAndValidateLibertyToolWindow(RemoteRobot remoteRobot, Str * @param remoteRobot The RemoteRobot instance. */ public static void openLibertyToolWindow(RemoteRobot remoteRobot) { + TestUtils.printTrace(TestUtils.TraceSevLevel.INFO, "UIBotTestUtils.openLibertyToolWindow Entry"); int maxRetries = 6; Exception error = null; for (int i = 0; i < maxRetries; i++) { @@ -466,6 +467,7 @@ public static void openLibertyToolWindow(RemoteRobot remoteRobot) { if (error != null) { throw new RuntimeException("Unable to open the Liberty tool window.", error); } + TestUtils.printTrace(TestUtils.TraceSevLevel.INFO, "UIBotTestUtils.openLibertyToolWindow Exit"); } /** @@ -2241,9 +2243,9 @@ public static void selectConfigUsingMenu(RemoteRobot remoteRobot, String cfgName // Exit loop if successful break; } catch (WaitForConditionTimeoutException e) { - System.err.println("Attempt " + (attempt + 1) + " failed: Timeout while trying to find or interact with menu items."); + TestUtils.printTrace(TestUtils.TraceSevLevel.INFO, "Attempt " + (attempt + 1) + " failed: Timeout while trying to find or interact with menu items. Retrying..."); } catch (Exception e) { - System.err.println("Attempt " + (attempt + 1) + " failed: " + e.getMessage()); + TestUtils.printTrace(TestUtils.TraceSevLevel.INFO, "Attempt " + (attempt + 1) + " failed: " + e.getMessage() + " Retrying..."); } if (attempt == 4) { // If the last attempt fails