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 @@ -25,7 +25,8 @@
TargetDefinitionResolutionTests.class, //
TargetDefinitionFeatureResolutionTests.class, //
IUBundleContainerTests.class, //
ProfileContainerTests.class })
ProfileContainerTests.class, //
StyledBundleLabelProviderTests.class })
public class AllTargetTests {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*******************************************************************************
* 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
*******************************************************************************/
package org.eclipse.pde.ui.tests.target;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.nio.file.Path;
import java.util.Arrays;

import org.eclipse.pde.core.target.ITargetDefinition;
import org.eclipse.pde.core.target.ITargetLocation;
import org.eclipse.pde.core.target.TargetBundle;
import org.eclipse.pde.internal.ui.shared.target.StyledBundleLabelProvider;
import org.junit.Test;

/**
* Tests for {@link StyledBundleLabelProvider}, in particular the optional
* inline source-location suffix that is appended to {@link TargetBundle}
* labels when a target context is set.
*/
public class StyledBundleLabelProviderTests extends AbstractTargetTest {

private static final String SEPARATOR = " - "; //$NON-NLS-1$

/**
* Without a target context the label must not carry any source-location
* suffix.
*/
@Test
public void testBundleLabelWithoutTargetContextOmitsSource() throws Exception {
Path dirPath = extractAbcdePlugins().resolve("plugins");
ITargetDefinition definition = getNewTarget();
ITargetLocation container = getTargetService().newDirectoryLocation(dirPath.toString());
definition.setTargetLocations(new ITargetLocation[] { container });
definition.resolve(null);

TargetBundle[] bundles = definition.getAllBundles();
assertNotNull("Expected resolved bundles", bundles);
assertTrue("Expected at least one bundle", bundles.length > 0);

StyledBundleLabelProvider provider = new StyledBundleLabelProvider(true, false);
try {
for (TargetBundle bundle : bundles) {
String text = provider.getText(bundle);
assertFalse("Label must not contain source suffix without target context: " + text,
text.contains(SEPARATOR + dirPath.toString()));
}
} finally {
provider.dispose();
}
}

/**
* With a target context set, every bundle label must be suffixed with its
* originating location so duplicates can be distinguished.
*/
@Test
public void testBundleLabelWithTargetContextAppendsSource() throws Exception {
Path dirPath = extractAbcdePlugins().resolve("plugins");
ITargetDefinition definition = getNewTarget();
ITargetLocation container = getTargetService().newDirectoryLocation(dirPath.toString());
definition.setTargetLocations(new ITargetLocation[] { container });
definition.resolve(null);

TargetBundle[] bundles = definition.getAllBundles();
assertNotNull("Expected resolved bundles", bundles);
assertTrue("Expected at least one bundle", bundles.length > 0);

StyledBundleLabelProvider provider = new StyledBundleLabelProvider(true, false);
try {
provider.setTargetContext(definition);
for (TargetBundle bundle : bundles) {
String text = provider.getText(bundle);
assertTrue("Label should end with source location suffix but was: " + text,
text.endsWith(SEPARATOR + dirPath.toString()));
}
} finally {
provider.dispose();
}
}

/**
* When a bundle exists in two different locations, each resolved
* {@link TargetBundle} must carry its owning location's path so the user
* can tell duplicates apart — the scenario that motivated this feature.
*/
@Test
public void testBundleLabelDistinguishesDuplicatesFromDifferentLocations() throws Exception {
Path abcdePath = extractAbcdePlugins().resolve("plugins");
Path multiPath = extractMultiVersionPlugins();

ITargetDefinition definition = getNewTarget();
ITargetLocation abcdeLocation = getTargetService().newDirectoryLocation(abcdePath.toString());
ITargetLocation multiLocation = getTargetService().newDirectoryLocation(multiPath.toString());
definition.setTargetLocations(new ITargetLocation[] { abcdeLocation, multiLocation });
definition.resolve(null);

TargetBundle[] abcdeBundles = abcdeLocation.getBundles();
TargetBundle[] multiBundles = multiLocation.getBundles();
assertNotNull("Expected abcde bundles", abcdeBundles);
assertNotNull("Expected multi-version bundles", multiBundles);
assertTrue("Expected abcde location to contribute bundles", abcdeBundles.length > 0);
assertTrue("Expected multi-version location to contribute bundles", multiBundles.length > 0);

StyledBundleLabelProvider provider = new StyledBundleLabelProvider(true, false);
try {
provider.setTargetContext(definition);
for (TargetBundle bundle : abcdeBundles) {
String text = provider.getText(bundle);
assertTrue("abcde bundle label should carry abcde path but was: " + text,
text.endsWith(SEPARATOR + abcdePath.toString()));
}
for (TargetBundle bundle : multiBundles) {
String text = provider.getText(bundle);
assertTrue("multi-version bundle label should carry multi path but was: " + text,
text.endsWith(SEPARATOR + multiPath.toString()));
}
} finally {
provider.dispose();
}
}

/**
* Clearing the target context restores the unsuffixed label, so the
* provider can be reused across editors.
*/
@Test
public void testClearingTargetContextRemovesSource() throws Exception {
Path dirPath = extractAbcdePlugins().resolve("plugins");
ITargetDefinition definition = getNewTarget();
ITargetLocation container = getTargetService().newDirectoryLocation(dirPath.toString());
definition.setTargetLocations(new ITargetLocation[] { container });
definition.resolve(null);

TargetBundle bundle = definition.getAllBundles()[0];
StyledBundleLabelProvider provider = new StyledBundleLabelProvider(true, false);
try {
provider.setTargetContext(definition);
String withContext = provider.getText(bundle);
assertTrue("Expected source suffix when context is set: " + withContext,
withContext.endsWith(SEPARATOR + dirPath.toString()));

provider.setTargetContext(null);
String withoutContext = provider.getText(bundle);
assertFalse("Expected no source suffix after context cleared: " + withoutContext,
withoutContext.contains(SEPARATOR + dirPath.toString()));
} finally {
provider.dispose();
}
}

/**
* The styled string for a resolved bundle without target context should
* still start with the bundle's symbolic name — a sanity check that the
* new code does not disturb the base label.
*/
@Test
public void testBundleLabelStartsWithSymbolicName() throws Exception {
Path dirPath = extractAbcdePlugins().resolve("plugins");
ITargetDefinition definition = getNewTarget();
ITargetLocation container = getTargetService().newDirectoryLocation(dirPath.toString());
definition.setTargetLocations(new ITargetLocation[] { container });
definition.resolve(null);

TargetBundle[] bundles = definition.getAllBundles();
assertTrue("Expected resolved abcde bundles", bundles.length > 0);

StyledBundleLabelProvider provider = new StyledBundleLabelProvider(true, false);
try {
TargetBundle a = Arrays.stream(bundles)
.filter(b -> "bundle.a".equals(b.getBundleInfo().getSymbolicName())).findFirst()
.orElse(bundles[0]);
String text = provider.getText(a);
assertEquals(a.getBundleInfo().getSymbolicName(), text.split(" ")[0]);
} finally {
provider.dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.pde.core.target.ITargetDefinition;
import org.eclipse.pde.core.target.ITargetLocation;
import org.eclipse.pde.core.target.NameVersionDescriptor;
import org.eclipse.pde.core.target.TargetBundle;
Expand Down Expand Up @@ -60,6 +61,7 @@ public class StyledBundleLabelProvider extends StyledCellLabelProvider implement

private boolean fShowVersion = true;
private boolean fAppendResolvedVariables = false;
private ITargetDefinition fTargetContext;
@SuppressWarnings("restriction")
private final org.eclipse.equinox.internal.p2.metadata.TranslationSupport fTranslations = org.eclipse.equinox.internal.p2.metadata.TranslationSupport
.getInstance();
Expand All @@ -79,6 +81,20 @@ public StyledBundleLabelProvider(boolean showVersion, boolean appendResolvedVari
fAppendResolvedVariables = appendResolvedVariables;
}

/**
* Sets the target definition used to resolve the originating
* {@link ITargetLocation} for a {@link TargetBundle}. When set, bundle
* labels are suffixed with a short description of the source location so
* that duplicates from different sources can be distinguished.
*
* @param context
* target definition providing the locations to search, or
* <code>null</code> to disable the source suffix
*/
public void setTargetContext(ITargetDefinition context) {
fTargetContext = context;
}

@Override
public void dispose() {
PDEPlugin.getDefault().getLabelProvider().disconnect(this);
Expand Down Expand Up @@ -134,10 +150,12 @@ private StyledString getInternalStyledString(Object element) {
IStatus status = bundle.getStatus();
if (status.isOK()) {
appendBundleInfo(styledString, bundle.getBundleInfo());
appendSourceLocation(styledString, bundle);
} else {
BundleInfo bundleInfo = bundle.getBundleInfo();
if (bundleInfo != null && bundleInfo.getSymbolicName() != null) {
appendBundleInfo(styledString, bundleInfo);
appendSourceLocation(styledString, bundle);
styledString.append(' ');
}
styledString.append(status.getMessage());
Expand Down Expand Up @@ -259,6 +277,68 @@ private void appendLocation(StyledString styledString, ITargetLocation container
}
}

/**
* Appends a short description of the {@link ITargetLocation} the given
* bundle was resolved from, if a target context has been set via
* {@link #setTargetContext(ITargetDefinition)} and the bundle's originating
* location can be found.
*/
private void appendSourceLocation(StyledString styledString, TargetBundle bundle) {
ITargetLocation source = findSourceLocation(bundle);
if (source == null) {
return;
}
String label = getSourceLocationLabel(source);
if (label == null || label.isEmpty()) {
return;
}
styledString.append(" - ", StyledString.DECORATIONS_STYLER); //$NON-NLS-1$
styledString.append(label, StyledString.DECORATIONS_STYLER);
}

private ITargetLocation findSourceLocation(TargetBundle bundle) {
if (fTargetContext == null) {
return null;
}
ITargetLocation[] locations = fTargetContext.getTargetLocations();
if (locations == null) {
return null;
}
for (ITargetLocation location : locations) {
TargetBundle[] bundles = location.getBundles();
if (bundles == null) {
continue;
}
for (TargetBundle b : bundles) {
if (b == bundle) {
return location;
}
}
}
return null;
}

private String getSourceLocationLabel(ITargetLocation container) {
try {
if (container instanceof FeatureBundleContainer feature) {
String id = feature.getFeatureId();
String version = feature.getFeatureVersion();
return version != null ? id + " [" + version + "]" : id; //$NON-NLS-1$ //$NON-NLS-2$
}
if (container instanceof IUBundleContainer iu) {
List<URI> repos = iu.getRepositories();
return repos.isEmpty() ? Messages.BundleContainerTable_8 : repos.get(0).toString();
}
String location = container.getLocation(false);
if (location != null) {
return location;
}
} catch (CoreException e) {
// fall through to type
}
return container.getType();
}

/**
* Appends a label describing the number of bundles included (ex. 5 of 10
* plug-ins).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ public class TargetContentsGroup {

private ViewerFilter fSourceFilter;
private ViewerFilter fPluginFilter;
private StyledBundleLabelProvider fLabelProvider;

private TargetDefinition fTargetDefinition;
/**
Expand Down Expand Up @@ -266,7 +267,8 @@ private TreeViewer createTree(Composite parent, FormToolkit toolkit) {
fTree.getControl().setFont(parent.getFont());
fTree.setUseHashlookup(true);
fTree.setContentProvider(new TreeContentProvider());
fTree.setLabelProvider(new StyledBundleLabelProvider(true, false));
fLabelProvider = new StyledBundleLabelProvider(true, false);
fTree.setLabelProvider(fLabelProvider);
fTree.addDoubleClickListener(event -> {
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
Object first = selection.getFirstElement();
Expand Down Expand Up @@ -912,6 +914,10 @@ public void setInput(ITargetDefinition input) {
return;
}

if (fLabelProvider != null) {
fLabelProvider.setTargetContext(fTargetDefinition);
}

if (!input.isResolved()) {
fTree.setInput(Messages.TargetContentsGroup_10);
setEnabled(false);
Expand Down Expand Up @@ -974,6 +980,9 @@ private void updateCheckState() {
*/
public void setCancelled() {
fTargetDefinition = null;
if (fLabelProvider != null) {
fLabelProvider.setTargetContext(null);
}
fTree.setInput(Messages.TargetContentsGroup_resolveCancelled);
setEnabled(false);
}
Expand Down
Loading