From 2b3ddd143903c42535ac7f4aca4b66e452ed556c Mon Sep 17 00:00:00 2001 From: Simeon Andreev Date: Mon, 23 Mar 2026 22:21:56 +0200 Subject: [PATCH] Add Java projects to classpath container org.eclipse.pde.core.externalJavaSearch The classpath container org.eclipse.pde.core.externalJavaSearch skips adding plug-ins if a respective project is found in the workspace. This can result in unexpected behavior when browsing code from the container, such as wrong syntax highlighting, navigation and search results. This change adjusts the classpath container to contain also projects found in the workspace. A test is also added for the initial bug fix, due to which workspace projects are skipped: https://bugs.eclipse.org/bugs/show_bug.cgi?id=197817 Fixes: #2269 --- .../core/SearchablePluginsManager.java | 10 +- ...rnalJavaSearchClasspathContainerTests.java | 138 ++++++++++++++++++ .../org/eclipse/pde/ui/tests/AllPDETests.java | 2 + 3 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/classpath/ExternalJavaSearchClasspathContainerTests.java diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/SearchablePluginsManager.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/SearchablePluginsManager.java index b81010429ff..ae7ea62b1c3 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/SearchablePluginsManager.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/SearchablePluginsManager.java @@ -183,10 +183,16 @@ public IClasspathEntry[] computeContainerClasspathEntries() { for (String id : plugins) { ModelEntry entry = PluginRegistry.findEntry(id); if (entry != null) { - boolean addModel = Arrays.stream(entry.getWorkspaceModels()) + List javaProjects = Arrays.stream(entry.getWorkspaceModels()) .map(IPluginModelBase::getUnderlyingResource).map(IResource::getProject) - .noneMatch(PluginProject::isJavaProject); + .filter(PluginProject::isJavaProject).collect(Collectors.toList()); + boolean addModel = javaProjects.isEmpty(); if (!addModel) { + for (IProject project : javaProjects) { + IPath path = project.getFullPath(); + IClasspathEntry projectEntry = JavaCore.newProjectEntry(path); + result.add(projectEntry); + } continue; } IPluginModelBase[] models = entry.getExternalModels(); diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/classpath/ExternalJavaSearchClasspathContainerTests.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/classpath/ExternalJavaSearchClasspathContainerTests.java new file mode 100644 index 00000000000..378e52dbed8 --- /dev/null +++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/classpath/ExternalJavaSearchClasspathContainerTests.java @@ -0,0 +1,138 @@ +/******************************************************************************* + * Copyright (c) 2026 Simeon Andreev and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Simeon Andreev - initial API and implementation + *******************************************************************************/ +package org.eclipse.pde.core.tests.internal.classpath; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.TypeNameRequestor; +import org.eclipse.jdt.internal.core.JavaModelManager; +import org.eclipse.pde.core.plugin.IPluginModelBase; +import org.eclipse.pde.core.plugin.PluginRegistry; +import org.eclipse.pde.internal.core.SearchablePluginsManager; +import org.eclipse.pde.internal.ui.wizards.imports.PluginImportOperation; +import org.eclipse.pde.internal.ui.wizards.imports.PluginImportWizard; +import org.eclipse.pde.ui.tests.runtime.TestUtils; +import org.eclipse.pde.ui.tests.util.ProjectUtils; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.IHandlerService; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.rules.TestRule; + +/** + * Test that the External Plug-in Libraries project doesn't find duplicated + * types, when a project is imported into the workspace. + */ +public class ExternalJavaSearchClasspathContainerTests { + + public static final String ADD_PLUGINS_TO_SEARCH_COMMAND_ID = "org.eclipse.pde.ui.addAllPluginsToJavaSearch"; + @ClassRule + public static final TestRule CLEAR_WORKSPACE = ProjectUtils.DELETE_ALL_WORKSPACE_PROJECTS_BEFORE_AND_AFTER; + @Rule + public final TestRule deleteCreatedTestProjectsAfter = ProjectUtils.DELETE_CREATED_WORKSPACE_PROJECTS_AFTER; + @Rule + public final TestName name = new TestName(); + + @Test + public void testSearchWithImportedProject() throws Exception { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + IHandlerService handlerService = window.getService(IHandlerService.class); + handlerService.executeCommand(ADD_PLUGINS_TO_SEARCH_COMMAND_ID, null); + TestUtils.waitForJobs(name.getMethodName(), 100, 10000); + JavaModelManager.getIndexManager().waitForIndex(false, null); + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IProject proxyProject = root.getProject(SearchablePluginsManager.PROXY_PROJECT_NAME); + assertNotNull("Adding " + SearchablePluginsManager.PROXY_PROJECT_NAME + " failed", proxyProject); + IJavaProject javaProject = JavaCore.create(proxyProject); + + String pluginId = "org.eclipse.core.expressions"; + String fqn = "org.eclipse.core.expressions.AndExpression"; + // expect a match like this: + // .../plugins/org.eclipse.equinox.common_3.20.300.v20251111-0312.jar|org/eclipse/core/runtime/IProgressMonitor.class + String expected = ".*.jar\\|" + fqn.replace('.', '/') + ".class"; + + List matches = performSearch(javaProject, fqn); + assertSingleMatch(expected, matches); + + IPluginModelBase plugin = PluginRegistry.findModel(pluginId); + IPluginModelBase[] plugins = { plugin }; + // import as binary so we don't have to compile, compiling will likely fail + PluginImportWizard.doImportOperation(PluginImportOperation.IMPORT_BINARY, plugins, true, false, null, null); + TestUtils.waitForJobs(name.getMethodName(), 100, 10000); + JavaModelManager.getIndexManager().waitForIndex(false, null); + IProject pluginProject = root.getProject(pluginId); + assertNotNull("Importing " + pluginId + " failed", pluginProject); + + // expect a match like this: + // /org.eclipse.core.expressions/org.eclipse.core.expressions_3.9.500.v20250608-0434.jar|org/eclipse/core/expressions/AndExpression.class + expected = pluginProject.getFullPath() + ".*.jar\\|" + fqn.replace('.', '/') + ".class"; + matches = performSearch(javaProject, fqn); + assertSingleMatch(expected, matches); + } + + private static void assertSingleMatch(String regexp, List matches) { + assertEquals("Expected only one search match, but found: " + matches, 1, matches.size()); + Pattern pattern = Pattern.compile(regexp); + Matcher matcher = pattern.matcher(matches.get(0)); + assertTrue("Unexpected search matches: " + matches + ", should match regexp: " + regexp, matcher.matches()); + } + + private static List performSearch(IJavaProject javaProject, String fqn) throws JavaModelException { + TestRequestor requestor = new TestRequestor(); + SearchEngine searchEngine = new SearchEngine(); + searchEngine.searchAllTypeNames( + fqn.substring(0, fqn.lastIndexOf('.')).toCharArray(), + SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, + fqn.substring(fqn.lastIndexOf('.') + 1).toCharArray(), + SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, + IJavaSearchConstants.TYPE, + SearchEngine.createJavaSearchScope(new IJavaElement[] { javaProject }), + requestor, + IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, + new NullProgressMonitor()); + List matches = requestor.matches; + return matches; + } + + private static class TestRequestor extends TypeNameRequestor { + + private final List matches = new ArrayList<>(); + + @Override + public void acceptType(int modifiers, char[] packageName, char[] simpleTypeName, char[][] enclosingTypeNames, String path) { + matches.add(path); + } + } +} diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDETests.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDETests.java index 8316577644e..434bd07c7f5 100644 --- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDETests.java +++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDETests.java @@ -16,6 +16,7 @@ import org.eclipse.pde.core.tests.internal.AllPDECoreTests; import org.eclipse.pde.core.tests.internal.classpath.ClasspathResolutionTest; import org.eclipse.pde.core.tests.internal.classpath.ClasspathResolutionTest2; +import org.eclipse.pde.core.tests.internal.classpath.ExternalJavaSearchClasspathContainerTests; import org.eclipse.pde.core.tests.internal.core.builders.BundleErrorReporterTest; import org.eclipse.pde.core.tests.internal.util.PDESchemaHelperTest; import org.eclipse.pde.ui.tests.build.properties.AllValidatorTests; @@ -65,6 +66,7 @@ DynamicPluginProjectReferencesTest.class, // ClasspathResolutionTest.class, // ClasspathResolutionTest2.class, // + ExternalJavaSearchClasspathContainerTests.class, // BundleErrorReporterTest.class, // AllPDECoreTests.class, // ProjectSmartImportTest.class, //