Skip to content
Draft
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 @@ -29,6 +29,7 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
Expand All @@ -39,6 +40,9 @@
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.osgi.service.resolver.BundleDelta;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.HostSpecification;
Expand Down Expand Up @@ -129,6 +133,11 @@ public void removeModel(IPluginModelBase model) {
private ArrayList<IStateDeltaListener> fStateListeners; // a list of listeners interested in changes to the PDE/resolver State
private boolean fCancelled = false;

/**
* only access synchronized with fEntriesSynchronizer
**/
private Job fInitializationJob;

/**
* Initialize the workspace and external (target) model manager
* and add listeners to each one
Expand Down Expand Up @@ -402,6 +411,50 @@ public boolean isInitialized() {
}
}

/**
* Initializes the master table in a background job unless it is initialized
* already, and runs the given callback as soon as the table is available.
* <p>
* The calling thread is never used to resolve the target platform. The
* callback runs at most once, either directly on the calling thread when the
* table is already initialized, or on the job's thread.
* </p>
*/
public void initializeInBackground(Runnable whenInitialized) {
Job job;
synchronized (fEntriesSynchronizer) {
if (fEntries != null) {
job = null;
} else {
if (fInitializationJob == null) {
fInitializationJob = Job.create(PDECoreMessages.PluginModelManager_InitializingPluginModels,
this::initialize);
fInitializationJob.setPriority(Job.LONG);
}
job = fInitializationJob;
}
}
if (job == null) {
whenInitialized.run();
return;
}
AtomicBoolean notified = new AtomicBoolean();
job.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
event.getJob().removeJobChangeListener(this);
if (notified.compareAndSet(false, true)) {
whenInitialized.run();
}
}
});
job.schedule();
// the job may already have finished before the listener was attached
if (isInitialized() && notified.compareAndSet(false, true)) {
whenInitialized.run();
}
}

/**
* Returns whether the model initialization was cancelled by the user.
* Other initializations, such as FeatureModelManager should use this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.eclipse.pde.ui.tests.runtime.AllPDERuntimeTests;
import org.eclipse.pde.ui.tests.search.dependencies.GatherUnusedDependenciesOperationTest;
import org.eclipse.pde.ui.tests.target.AllTargetTests;
import org.eclipse.pde.ui.tests.util.PDELabelProviderTest;
import org.eclipse.pde.ui.tests.views.log.AllLogViewTests;
import org.eclipse.pde.ui.tests.wizards.AllNewProjectTests;
import org.eclipse.ui.tests.smartimport.ProjectSmartImportTest;
Expand Down Expand Up @@ -74,6 +75,7 @@
AllPDECoreTests.class, //
ProjectSmartImportTest.class, //
GatherUnusedDependenciesOperationTest.class, //
PDELabelProviderTest.class, //
})
public class AllPDETests {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*******************************************************************************
* Copyright (c) 2026 Lars Vogel 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.ui.tests.util;

import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import org.eclipse.pde.core.plugin.PluginRegistry;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.plugin.ImportObject;
import org.eclipse.pde.internal.core.plugin.PluginImport;
import org.eclipse.pde.internal.core.plugin.WorkspacePluginModel;
import org.eclipse.pde.internal.ui.PDELabelProvider;
import org.eclipse.pde.internal.ui.PDEPluginImages;
import org.eclipse.swt.graphics.Image;
import org.junit.Before;
import org.junit.Test;

/**
* Verifies that {@link PDELabelProvider} does not resolve the target platform
* while decorating labels.
*/
public class PDELabelProviderTest {

private static final String UNRESOLVED_ID = "org.eclipse.pde.tests.does.not.exist"; //$NON-NLS-1$

private ImportObject fUnresolvedImport;

@Before
public void setUp() {
WorkspacePluginModel model = new WorkspacePluginModel(null, false);
fUnresolvedImport = new ImportObject(new PluginImport(model, UNRESOLVED_ID));
}

/**
* Once the models are available an unresolved import must still be decorated
* with the error overlay.
*/
@Test
public void testUnresolvedImportIsDecoratedWhenModelsAreAvailable() {
PluginRegistry.findEntry(UNRESOLVED_ID);
assertTrue("plug-in models should be initialized", //$NON-NLS-1$
PDECore.getDefault().getModelManager().isInitialized());

PDELabelProvider labelProvider = new PDELabelProvider();
try {
Image plain = labelProvider.get(PDEPluginImages.DESC_REQ_PLUGIN_OBJ);
assertNotSame("unresolved import should be decorated", plain, //$NON-NLS-1$
labelProvider.getImage(fUnresolvedImport));
} finally {
labelProvider.dispose();
}
}

/**
* As long as the models are not available the plain image must be returned
* instead of querying the plug-in registry, which would resolve the target
* platform on the calling thread.
*/
@Test
public void testImportIsUndecoratedWhileModelsAreUnavailable() {
PDELabelProvider labelProvider = new PDELabelProvider() {
@Override
protected boolean arePluginModelsAvailable() {
return false;
}
};
try {
Image plain = labelProvider.get(PDEPluginImages.DESC_REQ_PLUGIN_OBJ);
assertSame("import must not be decorated before the models are known", plain, //$NON-NLS-1$
labelProvider.getImage(fUnresolvedImport));
} finally {
labelProvider.dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.eclipse.pde.internal.ui;

import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
Expand All @@ -25,6 +26,7 @@
import org.eclipse.jdt.ui.ISharedImages;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.ResolverError;
import org.eclipse.osgi.util.NLS;
Expand All @@ -46,6 +48,7 @@
import org.eclipse.pde.core.plugin.VersionMatchRule;
import org.eclipse.pde.internal.core.ICoreConstants;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.PluginModelManager;
import org.eclipse.pde.internal.core.TargetPlatformHelper;
import org.eclipse.pde.internal.core.WorkspaceModelManager;
import org.eclipse.pde.internal.core.builders.CompilerFlags;
Expand Down Expand Up @@ -90,16 +93,51 @@
import org.eclipse.pde.internal.ui.util.SWTUtil;
import org.eclipse.pde.internal.ui.util.SharedLabelProvider;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.osgi.framework.Version;
import org.osgi.resource.Resource;

public class PDELabelProvider extends SharedLabelProvider {
private static final String SYSTEM_BUNDLE = "system.bundle"; //$NON-NLS-1$

private final AtomicBoolean fInitializationScheduled = new AtomicBoolean();

public PDELabelProvider() {
}

/**
* Returns whether the plug-in models can be queried without resolving the
* target platform, and schedules a background initialization if they cannot.
* <p>
* Every lookup in the plug-in registry has to be guarded by this check:
* resolving the target platform can take minutes and label providers are
* called on the UI thread. Labels are refreshed once initialization has
* finished.
* </p>
*/
protected boolean arePluginModelsAvailable() {
PluginModelManager manager = PDECore.getDefault().getModelManager();
if (manager.isInitialized()) {
return true;
}
if (fInitializationScheduled.compareAndSet(false, true)) {
manager.initializeInBackground(this::refreshLabels);
}
return false;
}

private void refreshLabels() {
fInitializationScheduled.set(false);
if (!PlatformUI.isWorkbenchRunning()) {
return;
}
Display display = PlatformUI.getWorkbench().getDisplay();
if (!display.isDisposed()) {
display.asyncExec(() -> fireLabelProviderChanged(new LabelProviderChangedEvent(this)));
}
}

@Override
public String getText(Object obj) {
if (obj instanceof IPluginModelBase) {
Expand Down Expand Up @@ -209,8 +247,14 @@ public String getObjectText(IPluginBase pluginBase) {
}

private String getSystemBundleInfo() {
IPluginBase systemBundle = PluginRegistry.findModel(SYSTEM_BUNDLE).getPluginBase();
return NLS.bind(" [{0}]", systemBundle.getId()); //$NON-NLS-1$
if (!arePluginModelsAvailable()) {
return ""; //$NON-NLS-1$
}
IPluginModelBase model = PluginRegistry.findModel(SYSTEM_BUNDLE);
if (model == null) {
return ""; //$NON-NLS-1$
}
return NLS.bind(" [{0}]", model.getPluginBase().getId()); //$NON-NLS-1$
}

private String preventNull(String text) {
Expand Down Expand Up @@ -253,7 +297,7 @@ public String getObjectText(IProductPlugin obj) {

public String getObjectText(BundleDescription bundle) {
String id = bundle.getSymbolicName();
if (isFullNameModeEnabled()) {
if (isFullNameModeEnabled() && arePluginModelsAvailable()) {
IPluginModelBase model = PluginRegistry.findModel((Resource) bundle);
if (model != null) {
return model.getPluginBase().getTranslatedName();
Expand All @@ -264,7 +308,7 @@ public String getObjectText(BundleDescription bundle) {
}

public String getObjectText(IPluginImport obj) {
if (isFullNameModeEnabled()) {
if (isFullNameModeEnabled() && arePluginModelsAvailable()) {
String id = obj.getId();
IPluginModelBase model = PluginRegistry.findModel(obj.getId());
if (model != null) {
Expand Down Expand Up @@ -314,7 +358,7 @@ private String getObjectText(Locale obj) {
}

public String getObjectText(FeaturePlugin obj) {
String name = isFullNameModeEnabled() ? obj.getLabel() : obj.getId();
String name = isFullNameModeEnabled() && arePluginModelsAvailable() ? obj.getLabel() : obj.getId();
String version = obj.getVersion();

String text;
Expand All @@ -330,7 +374,7 @@ public String getObjectText(FeaturePlugin obj) {
public String getObjectText(FeatureImport obj) {
int type = obj.getType();
if (type == IFeatureImport.PLUGIN) {
IPlugin plugin = obj.getPlugin();
IPlugin plugin = arePluginModelsAvailable() ? obj.getPlugin() : null;
if (plugin != null && isFullNameModeEnabled()) {
return preventNull(plugin.getTranslatedName());
}
Expand Down Expand Up @@ -397,10 +441,12 @@ public String getObjectText(ISiteFeature obj) {
}

public String getObjectText(ISiteBundle obj) {
IPluginModelBase modelBase = PluginRegistry.findModel(obj.getId(), obj.getVersion(),
VersionMatchRule.COMPATIBLE);
if (modelBase != null) {
return getObjectText(modelBase.getPluginBase());
if (arePluginModelsAvailable()) {
IPluginModelBase modelBase = PluginRegistry.findModel(obj.getId(), obj.getVersion(),
VersionMatchRule.COMPATIBLE);
if (modelBase != null) {
return getObjectText(modelBase.getPluginBase());
}
}
return preventNull(obj.getId());
}
Expand Down Expand Up @@ -644,15 +690,17 @@ public Image getObjectImage(IFragment fragment, boolean checkEnabled, boolean ja
private Image getObjectImage(ImportObject iobj) {
int flags = 0;
IPluginImport iimport = iobj.getImport();
if (!iobj.isResolved()) {
// The resolved state is only known once the target platform is available
boolean modelsAvailable = arePluginModelsAvailable();
if (modelsAvailable && !iobj.isResolved()) {
flags = iimport.isOptional() ? F_WARNING : F_ERROR;
} else if (iimport.isReexported()) {
flags = F_EXPORT;
}
if (iimport.isOptional()) {
flags |= F_OPTIONAL;
}
IPlugin plugin = iobj.getPlugin();
IPlugin plugin = modelsAvailable ? iobj.getPlugin() : null;
if (plugin != null) {
IPluginModelBase model = plugin.getPluginModel();
flags |= getModelFlags(model);
Expand Down Expand Up @@ -712,6 +760,9 @@ private Image getObjectImage(IPluginImport obj) {
}

private Image getObjectImage(IProductPlugin obj) {
if (!arePluginModelsAvailable()) {
return get(PDEPluginImages.DESC_PLUGIN_OBJ);
}
Version version = (obj.getVersion() != null && obj.getVersion().length() > 0 && !obj.getVersion().equals(ICoreConstants.DEFAULT_VERSION)) ? Version.parseVersion(obj.getVersion()) : null;
BundleDescription desc = TargetPlatformHelper.getState().getBundle(obj.getId(), version);
if (desc != null) {
Expand Down Expand Up @@ -781,7 +832,7 @@ private Image getObjectImage(IFeatureURLElement url) {

private Image getObjectImage(IFeaturePlugin plugin) {
int flags = 0;
if (((FeaturePlugin) plugin).getPluginBase() == null) {
if (arePluginModelsAvailable() && ((FeaturePlugin) plugin).getPluginBase() == null) {
int cflag = CompilerFlags.getFlag(null, CompilerFlags.F_UNRESOLVED_PLUGINS);
if (cflag == CompilerFlags.ERROR) {
flags = F_ERROR;
Expand Down Expand Up @@ -841,8 +892,7 @@ private Image getObjectImage(IFeatureImport obj) {
}
} else {
base = PDEPluginImages.DESC_REQ_PLUGIN_OBJ;
IPlugin plugin = iimport.getPlugin();
if (plugin == null) {
if (arePluginModelsAvailable() && iimport.getPlugin() == null) {
flags = F_ERROR;
}
}
Expand Down Expand Up @@ -907,7 +957,8 @@ public Image getObjectImage(PackageObject obj) {
if (importPackageObject.isOptional()) {
flags |= F_OPTIONAL;
}
if (!importPackageObject.isResolved()) {
// The resolved state is only known once the target platform is available
if (arePluginModelsAvailable() && !importPackageObject.isResolved()) {
flags |= importPackageObject.isOptional() ? F_WARNING : F_ERROR;
}
}
Expand Down
Loading