Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class RequiredPluginsClasspathContainer {
private static final String JUNIT4_PLUGIN = "org.junit";
private static final VersionRange JUNIT_4_VERSION = new VersionRange("[4.0,5)"); //$NON-NLS-1$
@SuppressWarnings("nls")
private static final Set<String> JUNIT5_RUNTIME_PLUGINS = Set.of("org.junit", //
private static final List<String> JUNIT5_RUNTIME_PLUGINS = List.of("org.junit", //
"junit-platform-launcher",
"org.junit.platform.launcher",
"junit-jupiter-engine", // BSN of the bundle from Maven-Central
Expand Down Expand Up @@ -223,7 +223,7 @@ private List<IClasspathEntry> computePluginEntriesByModel() throws CoreException
return List.of();
}

Map<BundleDescription, List<Rule>> map = retrieveVisiblePackagesFromState(desc);
Map<BundleDescription, LinkedHashSet<Rule>> map = retrieveVisiblePackagesFromState(desc);

// Add any library entries contributed via classpath contributor
// extension (Bug 363733)
Expand Down Expand Up @@ -338,8 +338,8 @@ private static synchronized Stream<IClasspathContributor> getClasspathContributo
return Stream.concat(fClasspathContributors.stream(), PDECore.getDefault().getClasspathContributors());
}

private Map<BundleDescription, List<Rule>> retrieveVisiblePackagesFromState(BundleDescription desc) {
Map<BundleDescription, List<Rule>> visiblePackages = new HashMap<>();
private Map<BundleDescription, LinkedHashSet<Rule>> retrieveVisiblePackagesFromState(BundleDescription desc) {
Map<BundleDescription, LinkedHashSet<Rule>> visiblePackages = new HashMap<>();
StateHelper helper = BundleHelper.getPlatformAdmin().getStateHelper();
addVisiblePackagesFromState(helper, desc, visiblePackages);
if (desc.getHost() != null) {
Expand All @@ -349,7 +349,7 @@ private Map<BundleDescription, List<Rule>> retrieveVisiblePackagesFromState(Bund
}

private void addVisiblePackagesFromState(StateHelper helper, BundleDescription desc,
Map<BundleDescription, List<Rule>> visiblePackages) {
Map<BundleDescription, LinkedHashSet<Rule>> visiblePackages) {
if (desc == null) {
return;
}
Expand All @@ -359,11 +359,9 @@ private void addVisiblePackagesFromState(StateHelper helper, BundleDescription d
if (exporter == null) {
continue;
}
List<Rule> list = visiblePackages.computeIfAbsent(exporter, e -> new ArrayList<>());
LinkedHashSet<Rule> list = visiblePackages.computeIfAbsent(exporter, e -> new LinkedHashSet<>());
Rule rule = getRule(helper, desc, export);
if (!list.contains(rule)) {
list.add(rule);
}
list.add(rule);
}
}

Expand All @@ -375,7 +373,7 @@ private Rule getRule(StateHelper helper, BundleDescription desc, ExportPackageDe
}

protected void addDependencyViaImportPackage(BundleDescription desc, Set<BundleDescription> added,
Map<BundleDescription, List<Rule>> map, List<IClasspathEntry> entries) throws CoreException {
Map<BundleDescription, LinkedHashSet<Rule>> map, List<IClasspathEntry> entries) throws CoreException {
if (desc == null || !added.add(desc)) {
return;
}
Expand All @@ -393,12 +391,12 @@ protected void addDependencyViaImportPackage(BundleDescription desc, Set<BundleD
}

private void addDependency(BundleDescription desc, Set<BundleDescription> added,
Map<BundleDescription, List<Rule>> map, List<IClasspathEntry> entries) throws CoreException {
Map<BundleDescription, LinkedHashSet<Rule>> map, List<IClasspathEntry> entries) throws CoreException {
addDependency(desc, added, map, entries, true);
}

private void addDependency(BundleDescription desc, Set<BundleDescription> added,
Map<BundleDescription, List<Rule>> map, List<IClasspathEntry> entries, boolean useInclusion)
Map<BundleDescription, LinkedHashSet<Rule>> map, List<IClasspathEntry> entries, boolean useInclusion)
throws CoreException {
if (desc == null || !added.add(desc)) {
return;
Expand Down Expand Up @@ -441,7 +439,7 @@ private void addDependency(BundleDescription desc, Set<BundleDescription> added,
}
}

private boolean addPlugin(BundleDescription desc, boolean useInclusions, Map<BundleDescription, List<Rule>> map,
private boolean addPlugin(BundleDescription desc, boolean useInclusions, Map<BundleDescription, LinkedHashSet<Rule>> map,
List<IClasspathEntry> entries) throws CoreException {
IPluginModelBase model = PluginRegistry.findModel((Resource) desc);
if (model == null || !model.isEnabled()) {
Expand Down Expand Up @@ -469,7 +467,7 @@ private boolean addPlugin(BundleDescription desc, boolean useInclusions, Map<Bun
return true;
}

private List<Rule> getInclusions(Map<BundleDescription, List<Rule>> map, IPluginModelBase model) {
private List<Rule> getInclusions(Map<BundleDescription, LinkedHashSet<Rule>> map, IPluginModelBase model) {
BundleDescription desc = model.getBundleDescription();
if (desc == null || "false".equals(System.getProperty("pde.restriction")) //$NON-NLS-1$ //$NON-NLS-2$
|| !(fModel instanceof IBundlePluginModelBase) || TargetPlatformHelper.getTargetVersion() < 3.1) {
Expand All @@ -479,12 +477,12 @@ private List<Rule> getInclusions(Map<BundleDescription, List<Rule>> map, IPlugin
if (desc.getHost() != null) {
desc = (BundleDescription) desc.getHost().getSupplier();
}
List<Rule> rules = map.getOrDefault(desc, List.of());
return (rules.isEmpty() && !ClasspathUtilCore.hasBundleStructure(model)) ? null : rules;
LinkedHashSet<Rule> rules = map.getOrDefault(desc, new LinkedHashSet<>());
return (rules.isEmpty() && !ClasspathUtilCore.hasBundleStructure(model)) ? null : new ArrayList<>(rules);
}

private void addHostPlugin(HostSpecification hostSpec, Set<BundleDescription> added,
Map<BundleDescription, List<Rule>> map, List<IClasspathEntry> entries) throws CoreException {
Map<BundleDescription, LinkedHashSet<Rule>> map, List<IClasspathEntry> entries) throws CoreException {
BaseDescription desc = hostSpec.getSupplier();

if (desc instanceof BundleDescription host) {
Expand Down Expand Up @@ -628,7 +626,7 @@ private void addJunit5RuntimeDependencies(Set<BundleDescription> added, List<ICl
}

// add dependency with exclude all rule
Map<BundleDescription, List<Rule>> rules = Map.of(desc, List.of());
Map<BundleDescription, LinkedHashSet<Rule>> rules = Map.of(desc, new LinkedHashSet<>());
addPlugin(desc, true, rules, entries);
}
}
Expand Down Expand Up @@ -691,7 +689,7 @@ private void addTransitiveDependenciesWithForbiddenAccess(Set<BundleDescription>
while (transitiveDeps.hasNext()) {
BundleDescription desc = transitiveDeps.next();
if (added.add(desc)) {
Map<BundleDescription, List<Rule>> rules = Map.of(desc, List.of());
Map<BundleDescription, LinkedHashSet<Rule>> rules = Map.of(desc, new LinkedHashSet<>());
addPlugin(desc, true, rules, entries);
}
}
Expand All @@ -705,21 +703,24 @@ private void addExtraModel(BundleDescription desc, Set<BundleDescription> added,
if (added.contains(bundleDesc)) {
return;
}
Map<BundleDescription, List<Rule>> rules = new HashMap<>();
Map<BundleDescription, LinkedHashSet<Rule>> rules = new HashMap<>();
findExportedPackages(bundleDesc, desc, rules);
addDependency(bundleDesc, added, rules, entries, true);
}
}

protected final void findExportedPackages(BundleDescription desc, BundleDescription projectDesc,
Map<BundleDescription, List<Rule>> map) {
Map<BundleDescription, LinkedHashSet<Rule>> map) {
if (desc != null) {
Queue<BundleDescription> queue = new ArrayDeque<>();
queue.add(desc);
while (!queue.isEmpty()) {
BundleDescription bdesc = queue.remove();
if (map.containsKey(bdesc)) {
continue;
}
ExportPackageDescription[] expkgs = bdesc.getExportPackages();
List<Rule> rules = new ArrayList<>();
LinkedHashSet<Rule> rules = new LinkedHashSet<>();
for (ExportPackageDescription expkg : expkgs) {
boolean discouraged = restrictPackage(projectDesc, expkg);
IPath path = IPath.fromOSString(expkg.getName().replace('.', '/') + "/*"); //$NON-NLS-1$
Expand Down
1 change: 1 addition & 0 deletions ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Import-Package: jakarta.annotation;version="[2.1.0,3.0.0)",
org.eclipse.pde.internal.build,
org.hamcrest,
org.junit,
org.junit.jupiter.api;version="[5.8.1,6.0.0)",
org.junit.jupiter.api.function;version="[5.8.1,6.0.0)",
org.junit.jupiter.migrationsupport;version="[5.13.0,6.0.0)",
org.junit.platform.suite.api;version="[1.13.0,2.0.0)",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*******************************************************************************
* Copyright (c) 2025 vogella GmbH 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:
* Lars Vogel - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.core.tests.internal.classpath;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceDescription;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.core.plugin.PluginRegistry;
import org.eclipse.pde.core.project.IBundleProjectDescription;
import org.eclipse.pde.core.project.IBundleProjectService;
import org.eclipse.pde.core.project.IRequiredBundleDescription;
import org.eclipse.pde.core.target.ITargetDefinition;
import org.eclipse.pde.core.target.ITargetLocation;
import org.eclipse.pde.core.target.ITargetPlatformService;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.ui.wizards.tools.UpdateClasspathJob;
import org.eclipse.pde.ui.tests.runtime.TestUtils;
import org.eclipse.pde.ui.tests.util.ProjectUtils;
import org.eclipse.pde.ui.tests.util.TargetPlatformUtil;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.osgi.framework.Version;
import org.osgi.framework.VersionRange;

public class ChainedReexportPerformanceTest {

private static final String CHAIN_PREFIX = "Chain_";
private static final int PACKAGE_COUNT = 1000;
private static final int BUNDLE_CHAIN_DEPTH = 5;
private File targetDir;
private ITargetDefinition savedTarget;

@BeforeAll
public static void beforeAll() throws Exception {
ProjectUtils.deleteAllWorkspaceProjects();
}

@AfterAll
public static void afterAll() throws Exception {
ProjectUtils.deleteAllWorkspaceProjects();
}

@BeforeEach
public void setUp() throws Exception {
savedTarget = TargetPlatformUtil.TPS.getWorkspaceTargetDefinition();

IWorkspaceDescription desc = ResourcesPlugin.getWorkspace().getDescription();
desc.setAutoBuilding(false);
ResourcesPlugin.getWorkspace().setDescription(desc);

targetDir = Files.createTempDirectory("pde_chain_perf_target").toFile();
createChainedTargetPlatform();
}

@AfterEach
public void tearDown() throws Exception {
IWorkspaceDescription desc = ResourcesPlugin.getWorkspace().getDescription();
desc.setAutoBuilding(true);
ResourcesPlugin.getWorkspace().setDescription(desc);

if (targetDir != null && targetDir.exists()) {
Files.walk(targetDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
}

if (savedTarget != null && savedTarget != TargetPlatformUtil.TPS.getWorkspaceTargetDefinition()) {
TargetPlatformUtil.loadAndSetTarget(savedTarget);
}
}

private void createChainedTargetPlatform() throws Exception {
// Create a chain of bundles: B_0 -> B_1 -> ... -> B_N (all re-exporting)
for (int i = 0; i < BUNDLE_CHAIN_DEPTH; i++) {
String name = CHAIN_PREFIX + i;
String exports = createPackageExports(name);
String requires = (i > 0) ? (CHAIN_PREFIX + (i - 1) + ";visibility:=reexport") : null;
createBundle(targetDir, name, exports, requires);
}

ITargetPlatformService tps = PDECore.getDefault().acquireService(ITargetPlatformService.class);
ITargetDefinition target = tps.newTarget();
target.setTargetLocations(new ITargetLocation[] { tps.newDirectoryLocation(targetDir.getAbsolutePath()) });
TargetPlatformUtil.loadAndSetTarget(target);
}

private String createPackageExports(String bundleName) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < PACKAGE_COUNT; i++) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(bundleName).append(".pkg.").append(i);
}
return sb.toString();
}

private void createBundle(File dir, String name, String exports, String requires) throws IOException {
File jarFile = new File(dir, name + ".jar");
try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile))) {
Manifest manifest = new Manifest();
Attributes main = manifest.getMainAttributes();
main.put(Attributes.Name.MANIFEST_VERSION, "1.0");
main.put(new Attributes.Name("Bundle-ManifestVersion"), "2");
main.put(new Attributes.Name("Bundle-SymbolicName"), name);
main.put(new Attributes.Name("Bundle-Version"), "1.0.0");
if (exports != null) {
main.put(new Attributes.Name("Export-Package"), exports);
}
if (requires != null) {
main.put(new Attributes.Name("Require-Bundle"), requires);
}

ZipEntry entry = new ZipEntry("META-INF/MANIFEST.MF");
jos.putNextEntry(entry);
manifest.write(jos);
jos.closeEntry();
}
}

@Test
public void testChainedReexportPerformance() throws Exception {
IBundleProjectService service = PDECore.getDefault().acquireService(IBundleProjectService.class);

String consumerName = "ConsumerBundle";
IProject consumerProj = ResourcesPlugin.getWorkspace().getRoot().getProject(consumerName);
consumerProj.create(null);
consumerProj.open(null);

IBundleProjectDescription consumerDesc = service.getDescription(consumerProj);
consumerDesc.setSymbolicName(consumerName);
consumerDesc.setBundleVersion(new Version("1.0.0"));

// Require the last bundle in the chain with re-export
IRequiredBundleDescription mainReq = service.newRequiredBundle(CHAIN_PREFIX + (BUNDLE_CHAIN_DEPTH - 1),
(VersionRange) null, false, true);
consumerDesc.setRequiredBundles(new IRequiredBundleDescription[] { mainReq });

consumerDesc.setNatureIds(new String[] { JavaCore.NATURE_ID, IBundleProjectDescription.PLUGIN_NATURE });
consumerDesc.apply(null);

ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor());
TestUtils.waitForJobs("Init", 500, 5000);

IPluginModelBase consumerModel = PluginRegistry.findModel(consumerProj);
if (consumerModel == null) {
throw new IllegalStateException("Consumer model not found");
}

UpdateClasspathJob.scheduleFor(List.of(consumerModel), false);
boolean timedOut = TestUtils.waitForJobs("classpath", 100, 10000);
assertFalse(timedOut, "Performance regression: classpath computation timed out for chained re-exports");

IClasspathEntry[] resolvedClasspath = JavaCore.create(consumerProj).getRawClasspath();
assertTrue(resolvedClasspath.length > 0, "Classpath should not be empty");
}
}
Loading
Loading