diff --git a/ui/org.eclipse.pde.bnd.ui/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.bnd.ui/META-INF/MANIFEST.MF index 013432c91f3..fae4e9c5091 100644 --- a/ui/org.eclipse.pde.bnd.ui/META-INF/MANIFEST.MF +++ b/ui/org.eclipse.pde.bnd.ui/META-INF/MANIFEST.MF @@ -28,7 +28,12 @@ Import-Package: aQute.bnd.build;version="4.5.0", aQute.bnd.service.clipboard;version="[1.0.0,2.0.0)", aQute.bnd.service.progress;version="[1.3.0,2.0.0)", aQute.bnd.service.repository;version="[1.6.0,2.0.0)", + aQute.bnd.service.resource;version="[1.1.0,2.0.0)", + aQute.bnd.unmodifiable;version="[2.1.0,3.0.0)", aQute.bnd.version;version="[2.3.0,3.0.0)", + aQute.lib.io;version="[4.5.0,5.0.0)", + aQute.lib.strings;version="[1.12.0,2.0.0)", + aQute.libg.glob;version="[1.6.0,2.0.0)", aQute.service.reporter;version="[1.2.0,2.0.0)", jakarta.xml.bind;version="[4.0.0,5.0.0)", jakarta.xml.bind.annotation;version="[4.0.0,5.0.0)", diff --git a/ui/org.eclipse.pde.bnd.ui/icons/brkpi_obj.svg b/ui/org.eclipse.pde.bnd.ui/icons/brkpi_obj.svg new file mode 100644 index 00000000000..b56f918486d --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/brkpi_obj.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/bundle-importer-exporter.svg b/ui/org.eclipse.pde.bnd.ui/icons/bundle-importer-exporter.svg new file mode 100644 index 00000000000..b135cce3ade --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/bundle-importer-exporter.svg @@ -0,0 +1,485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/class_obj.svg b/ui/org.eclipse.pde.bnd.ui/icons/class_obj.svg new file mode 100644 index 00000000000..12fdd7ed1b3 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/class_obj.svg @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/connections.png b/ui/org.eclipse.pde.bnd.ui/icons/connections.png new file mode 100644 index 00000000000..388022bd0cc Binary files /dev/null and b/ui/org.eclipse.pde.bnd.ui/icons/connections.png differ diff --git a/ui/org.eclipse.pde.bnd.ui/icons/excludeMode_filter.svg b/ui/org.eclipse.pde.bnd.ui/icons/excludeMode_filter.svg new file mode 100644 index 00000000000..f6a4a1bb5c0 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/excludeMode_filter.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/help.gif b/ui/org.eclipse.pde.bnd.ui/icons/help.gif deleted file mode 100644 index 9d70301dae3..00000000000 Binary files a/ui/org.eclipse.pde.bnd.ui/icons/help.gif and /dev/null differ diff --git a/ui/org.eclipse.pde.bnd.ui/icons/help.svg b/ui/org.eclipse.pde.bnd.ui/icons/help.svg new file mode 100644 index 00000000000..cfe92203561 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/help.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/jar_obj.svg b/ui/org.eclipse.pde.bnd.ui/icons/jar_obj.svg new file mode 100644 index 00000000000..5e37a58bc86 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/jar_obj.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/jcu_obj.svg b/ui/org.eclipse.pde.bnd.ui/icons/jcu_obj.svg new file mode 100644 index 00000000000..2f6738bc744 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/jcu_obj.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/lock.png b/ui/org.eclipse.pde.bnd.ui/icons/lock.png new file mode 100644 index 00000000000..e9ab2231967 Binary files /dev/null and b/ui/org.eclipse.pde.bnd.ui/icons/lock.png differ diff --git a/ui/org.eclipse.pde.bnd.ui/icons/lockedstate.svg b/ui/org.eclipse.pde.bnd.ui/icons/lockedstate.svg new file mode 100644 index 00000000000..7767244e411 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/lockedstate.svg @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/package.gif b/ui/org.eclipse.pde.bnd.ui/icons/package.gif deleted file mode 100644 index 131c28da405..00000000000 Binary files a/ui/org.eclipse.pde.bnd.ui/icons/package.gif and /dev/null differ diff --git a/ui/org.eclipse.pde.bnd.ui/icons/package_obj.svg b/ui/org.eclipse.pde.bnd.ui/icons/package_obj.svg new file mode 100644 index 00000000000..b70d20b4500 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/package_obj.svg @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/repositories.svg b/ui/org.eclipse.pde.bnd.ui/icons/repositories.svg new file mode 100644 index 00000000000..b6239bf9843 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/repositories.svg @@ -0,0 +1,280 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/signed_yes_tbl.svg b/ui/org.eclipse.pde.bnd.ui/icons/signed_yes_tbl.svg new file mode 100644 index 00000000000..0fca5f284fc --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/signed_yes_tbl.svg @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/tricks.svg b/ui/org.eclipse.pde.bnd.ui/icons/tricks.svg new file mode 100644 index 00000000000..b45ba974def --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/tricks.svg @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/warning_obj.gif b/ui/org.eclipse.pde.bnd.ui/icons/warning_obj.gif deleted file mode 100644 index 2b2e50fe7cb..00000000000 Binary files a/ui/org.eclipse.pde.bnd.ui/icons/warning_obj.gif and /dev/null differ diff --git a/ui/org.eclipse.pde.bnd.ui/icons/warning_obj.svg b/ui/org.eclipse.pde.bnd.ui/icons/warning_obj.svg new file mode 100644 index 00000000000..d96c6a2f93a --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/warning_obj.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/webresources16.svg b/ui/org.eclipse.pde.bnd.ui/icons/webresources16.svg new file mode 100644 index 00000000000..7524d977f84 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/icons/webresources16.svg @@ -0,0 +1,374 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/ui/org.eclipse.pde.bnd.ui/icons/whiteboard.png b/ui/org.eclipse.pde.bnd.ui/icons/whiteboard.png new file mode 100644 index 00000000000..e56900d7283 Binary files /dev/null and b/ui/org.eclipse.pde.bnd.ui/icons/whiteboard.png differ diff --git a/ui/org.eclipse.pde.bnd.ui/plugin.xml b/ui/org.eclipse.pde.bnd.ui/plugin.xml index f2ffd41f427..8b94fec7c63 100644 --- a/ui/org.eclipse.pde.bnd.ui/plugin.xml +++ b/ui/org.eclipse.pde.bnd.ui/plugin.xml @@ -35,5 +35,11 @@ icon="icons/database.png" id="pde.bnd.ui.repositoriesView" name="Bundle Repositories" restorable="true"> + + + diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/EditorUtils.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/EditorUtils.java index 6678152ec2d..765be5e31d1 100644 --- a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/EditorUtils.java +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/EditorUtils.java @@ -74,7 +74,7 @@ public void run() { }; btn.setEnabled(true); btn.setToolTipText(tooltipText); - btn.setImageDescriptor(Resources.getImageDescriptor("help.gif")); + btn.setImageDescriptor(Resources.getImageDescriptor("help.svg")); return btn; } diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/ResourceUtils.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/ResourceUtils.java index 6f316300cb7..13acd196893 100644 --- a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/ResourceUtils.java +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/ResourceUtils.java @@ -129,9 +129,9 @@ public static Capability getContentCapability(Resource resource) throws IllegalA String urlString = String.valueOf(url); try { - new URL(urlString); + new URI(urlString).toURL(); return c; - } catch (MalformedURLException mue) { + } catch (MalformedURLException | URISyntaxException mue) { // Oh well, just try the next one } } @@ -155,9 +155,9 @@ public static URI getURI(Capability contentCapability) { return ((URL) uriObj).toURI(); } - if (uriObj instanceof String) { + if (uriObj instanceof String uriStr) { try { - URL url = new URL((String) uriObj); + URL url = new URI(uriStr).toURL(); return url.toURI(); } catch (MalformedURLException mfue) { // Ignore diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/Resources.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/Resources.java index 6953225c92f..b2e53d71946 100644 --- a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/Resources.java +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/Resources.java @@ -65,6 +65,10 @@ private static URL getImageUrl(String key) { if (resource != null) { return resource; } + resource = Resources.class.getResource("/icons/" + key + ".svg"); + if (resource != null) { + return resource; + } resource = Resources.class.getResource("/icons/" + key + ".gif"); if (resource != null) { return resource; diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/internal/FileUtils.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/internal/FileUtils.java new file mode 100644 index 00000000000..e0f670274f9 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/internal/FileUtils.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2013, 2021 bndtools project 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: + * Neil Bartlett - initial API and implementation + * Ferry Huberts - ongoing enhancements + * BJ Hargrave - ongoing enhancements + * Fr Jeremy Krieg - ongoing enhancements + * Jürgen Albert - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.internal; + +import java.io.File; +import java.util.Arrays; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; + +public class FileUtils { + + + public static IFile[] getWorkspaceFiles(File javaFile) { + IWorkspaceRoot root = ResourcesPlugin.getWorkspace() + .getRoot(); + IFile[] candidates = root.findFilesForLocationURI(javaFile.toURI()); + Arrays.sort(candidates, (a, b) -> Integer.compare(a.getFullPath() + .segmentCount(), + b.getFullPath() + .segmentCount())); + return candidates; + } +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/internal/ItalicStyler.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/internal/ItalicStyler.java new file mode 100644 index 00000000000..b221e321341 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/internal/ItalicStyler.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2016, 2019 bndtools project 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: + * Neil Bartlett - initial API and implementation + * BJ Hargrave - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.internal; + +import org.eclipse.jface.preference.JFacePreferences; +import org.eclipse.jface.resource.ColorRegistry; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.StyledString.Styler; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.TextStyle; + +public class ItalicStyler extends Styler { + + public static final Styler INSTANCE_DEFAULT = new ItalicStyler(JFaceResources.DEFAULT_FONT, null, null); + public static final Styler INSTANCE_QUALIFIER = new ItalicStyler(JFaceResources.DEFAULT_FONT, + JFacePreferences.QUALIFIER_COLOR, null); + public static final Styler INSTANCE_ERROR = new ItalicStyler(JFaceResources.DEFAULT_FONT, + JFacePreferences.ERROR_COLOR, null); + + private final String fontName; + private final String fForegroundColorName; + private final String fBackgroundColorName; + + public ItalicStyler(String fontName, String foregroundColorName, String backgroundColorName) { + this.fontName = fontName; + fForegroundColorName = foregroundColorName; + fBackgroundColorName = backgroundColorName; + } + + @Override + public void applyStyles(TextStyle textStyle) { + ColorRegistry colorRegistry = JFaceResources.getColorRegistry(); + Font font = JFaceResources.getFontRegistry() + .getItalic(fontName); + if (fForegroundColorName != null) { + textStyle.foreground = colorRegistry.get(fForegroundColorName); + } + if (fBackgroundColorName != null) { + textStyle.background = colorRegistry.get(fBackgroundColorName); + } + textStyle.font = font; + } +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/internal/PartAdapter.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/internal/PartAdapter.java new file mode 100644 index 00000000000..d8fc15e526c --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/internal/PartAdapter.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2010, 2019 bndtools project 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: + * Neil Bartlett - initial API and implementation + * BJ Hargrave - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.internal; + +import org.eclipse.ui.IPartListener; +import org.eclipse.ui.IWorkbenchPart; + +public abstract class PartAdapter implements IPartListener { + + @Override + public void partActivated(IWorkbenchPart part) {} + + @Override + public void partBroughtToTop(IWorkbenchPart part) {} + + @Override + public void partClosed(IWorkbenchPart part) {} + + @Override + public void partDeactivated(IWorkbenchPart part) {} + + @Override + public void partOpened(IWorkbenchPart part) {} +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/CapReqComparator.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/CapReqComparator.java new file mode 100644 index 00000000000..8b21f648254 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/CapReqComparator.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2015, 2023 bndtools project 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: + * Neil Bartlett - initial API and implementation + * BJ Hargrave - ongoing enhancements + * Peter Kriens - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.model.resolution; + +import java.util.Collection; +import java.util.Comparator; + +import org.eclipse.pde.bnd.ui.model.resource.R5LabelFormatter; +import org.osgi.framework.Version; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; + +import aQute.bnd.osgi.resource.ResourceUtils; + +public class CapReqComparator implements Comparator { + + @Override + public int compare(Object o1, Object o2) { + if (o1 instanceof Requirement) + return compareReqToObj((Requirement) o1, o2); + + if (o1 instanceof RequirementWrapper) + return compareReqToObj(((RequirementWrapper) o1).requirement, o2); + + if (o1 instanceof Capability) + return compareCapToObj((Capability) o1, o2); + + return 0; + } + + private int compareReqToObj(Requirement r1, Object o2) { + if (o2 instanceof Requirement) + return compareReqToReq(r1, (Requirement) o2); + + if (o2 instanceof RequirementWrapper) + return compareReqToReq(r1, ((RequirementWrapper) o2).requirement); + + // requirements sort before other things + return -1; + } + + private int compareCapToObj(Capability c1, Object o2) { + if (o2 instanceof Capability) + return compareCapToCap(c1, (Capability) o2); + + // capabilities sort after other things + return 1; + } + + private int compareCapToCap(Capability c1, Capability c2) { + // Compare namespaces + String ns1 = c1.getNamespace(); + String ns2 = c2.getNamespace(); + int nsDiff = ns1.compareTo(ns2); + if (nsDiff != 0) + return nsDiff; + + // Compare the main attribute + String attribName = R5LabelFormatter.getMainAttributeName(ns1); + Object attrib1 = c1.getAttributes() + .get(attribName); + Object attrib2 = c2.getAttributes() + .get(attribName); + + if (attrib1 != null && attrib2 != null) { + int attribDiff = attrib1.toString() + .compareTo(attrib2.toString()); + if (attribDiff != 0) + return attribDiff; + } + + // Compare the versions + String versionAttribName = R5LabelFormatter.getVersionAttributeName(ns1); + if (versionAttribName == null) + return 0; + + Version v1 = highestVersion(c1.getAttributes().get(versionAttribName)); + Version v2 = highestVersion(c2.getAttributes().get(versionAttribName)); + + return v1.compareTo(v2); + } + + private static Version highestVersion(Object attr) { + if (attr instanceof Version v) { + return v; + } + if (attr instanceof Collection col) { + // e.g. namespace 'osgi.ee' can contain List + // see https://osgi.github.io/osgi/core/framework.namespaces.html#framework.namespaces-ee.namespace + // so we compare the highest versions + return col.stream() + .filter(Version.class::isInstance) + .map(Version.class::cast) + .max(Version::compareTo) + .orElse(Version.emptyVersion); + } + return Version.emptyVersion; // null or wrong type + } + + private int compareReqToReq(Requirement r1, Requirement r2) { + return ResourceUtils.REQUIREMENT_COMPARATOR.compare(r1, r2); + } +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/CapReqMapContentProvider.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/CapReqMapContentProvider.java new file mode 100644 index 00000000000..fcf36648b2f --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/CapReqMapContentProvider.java @@ -0,0 +1,190 @@ +/******************************************************************************* + * Copyright (c) 2014, 2023 bndtools project 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: + * Neil Bartlett - initial API and implementation + * BJ Hargrave - ongoing enhancements + * Christoph Rueger - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.model.resolution; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; +import org.osgi.framework.namespace.BundleNamespace; +import org.osgi.framework.namespace.HostNamespace; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.resource.Capability; + +import aQute.bnd.unmodifiable.Sets; +import aQute.libg.glob.Glob; + +public class CapReqMapContentProvider implements ITreeContentProvider { + + private static final Object[] EMPTY = new Object[0]; + + private static final Set NAMESPACES = Sets.of(BundleNamespace.BUNDLE_NAMESPACE, + IdentityNamespace.IDENTITY_NAMESPACE, HostNamespace.HOST_NAMESPACE, PackageNamespace.PACKAGE_NAMESPACE); + + private final Comparator comparator = new CapReqComparator(); + + private String wildcardFilter = null; + + @Override + public void dispose() {} + + @Override + public void inputChanged(Viewer viewer, Object oldValue, Object newValue) {} + + @Override + public Object[] getElements(Object input) { + List arrays = new LinkedList<>(); + + @SuppressWarnings("unchecked") + Map> map = (Map>) input; + + // Add entries for our preferred ordering of namespaces + for (String namespace : NAMESPACES) { + List listForNs = map.get(namespace); + if (listForNs != null) { + Object[] array = listForNs.toArray(); + Arrays.sort(array, comparator); + arrays.add(array); + } + } + + // Now the rest in any order + for (Entry> entry : map.entrySet()) { + // Skip if namespace is a member of the namespaces we have already + // added. + if (NAMESPACES.contains(entry.getKey())) + continue; + + List listForNs = entry.getValue(); + Object[] array = listForNs.toArray(); + Arrays.sort(array, comparator); + arrays.add(array); + } + + return filter(flatten(arrays)); + } + + private Object[] flatten(List arrays) { + // Iterate over once to count the lengths + int length = 0; + for (Object[] array : arrays) { + length += array.length; + } + Object[] result = new Object[length]; + + // Iterate again to flatten out the arrays + int position = 0; + for (Object[] array : arrays) { + System.arraycopy(array, 0, result, position, array.length); + position += array.length; + } + return result; + } + + @Override + public Object getParent(Object object) { + return null; + } + + @Override + public boolean hasChildren(Object object) { + boolean children = false; + + if (object instanceof RequirementWrapper) { + RequirementWrapper rw = (RequirementWrapper) object; + children = rw.requirers != null && !rw.requirers.isEmpty(); + } + + return children; + } + + @Override + public Object[] getChildren(Object parent) { + Object[] result = EMPTY; + if (parent instanceof RequirementWrapper) { + Collection requirers = ((RequirementWrapper) parent).requirers; + if (requirers != null) + result = requirers.toArray(); + } + return result; + } + + public void setFilter(String filterString) { + if (filterString == null || filterString.length() == 0 || filterString.trim() + .equals("*")) + wildcardFilter = null; + else + wildcardFilter = "*" + filterString.trim() + "*"; + + } + + private Object[] filter(Object[] array) { + List filteredResults = new ArrayList<>(); + if (wildcardFilter == null || wildcardFilter.equals("*") || wildcardFilter.equals("")) { + return array; + } else { + String[] split = wildcardFilter.split("\\s+"); + Glob globs[] = new Glob[split.length]; + for (int i = 0; i < split.length; i++) { + globs[i] = new Glob(split[i].toLowerCase()); + } + + // parallel search + Arrays.stream(array).parallel().forEach(obj -> { + + if (obj instanceof RequirementWrapper rw) { + + for (Glob g : globs) { + if (g.matcher(RequirementWrapperLabelProvider.tooltipText(rw) + .toLowerCase()) + .find()) { + filteredResults.add(obj); + return; + } + } + } + else if (obj instanceof Capability cap) { + + for (Glob g : globs) { + if (g.matcher(CapabilityLabelProvider.tooltipText(cap) + .toLowerCase()) + .find()) { + filteredResults.add(obj); + return; + } + } + } + + }); + + } + + // sort because parallel search causes random ordering + filteredResults.sort(comparator); + + return filteredResults.toArray(); + } + +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/CapabilityLabelProvider.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/CapabilityLabelProvider.java new file mode 100644 index 00000000000..1c3aa1e5e8a --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/CapabilityLabelProvider.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2014, 2023 bndtools project 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: + * Neil Bartlett - initial API and implementation + * Sean Bright - ongoing enhancements + * BJ Hargrave - ongoing enhancements + * Peter Kriens - ongoing enhancements + * Christoph Rueger - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.model.resolution; + +import java.util.Map.Entry; + +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.pde.bnd.ui.Resources; +import org.eclipse.pde.bnd.ui.model.resource.R5LabelFormatter; +import org.eclipse.swt.graphics.Image; +import org.osgi.resource.Capability; +import org.osgi.resource.Resource; + +import aQute.bnd.service.resource.SupportingResource; + +public class CapabilityLabelProvider extends StyledCellLabelProvider { + + private final boolean shortenNamespaces; + + public CapabilityLabelProvider() { + this(false); + } + + public CapabilityLabelProvider(boolean shortenNamespaces) { + this.shortenNamespaces = shortenNamespaces; + } + + @Override + public void update(ViewerCell cell) { + Capability cap = (Capability) cell.getElement(); + + StyledString label = new StyledString(); + R5LabelFormatter.appendCapability(label, cap, shortenNamespaces); + cell.setText(label.toString()); + cell.setStyleRanges(label.getStyleRanges()); + + // Get the icon from the capability namespace + Image icon = Resources.getImage(R5LabelFormatter.getNamespaceImagePath(cap.getNamespace())); + cell.setImage(icon); + } + + @Override + public String getToolTipText(Object element) { + if (element instanceof Capability cap) { + return tooltipText(cap); + } + + return null; + } + + static String tooltipText(Capability cap) { + // caps tooltips become quite large because of the bnd.hashes and uses + StringBuilder buf = new StringBuilder(400); + + Resource r = cap.getResource(); + + buf.append("FROM: ") + .append(r) + .append("\n"); + + buf.append(cap.getNamespace()); + + if (r instanceof SupportingResource sr) { + int index = sr.getSupportingIndex(); + if (index >= 0) { + buf.append("Capability from a supporting resource ") + .append(index) + .append(" part of ") + .append(sr.getParent()) + .append("\n"); + } + } + + for (Entry attribute : cap.getAttributes() + .entrySet()) + buf.append(";\n\t") + .append(attribute.getKey()) + .append(" = ") + .append(attribute.getValue()); + + for (Entry directive : cap.getDirectives() + .entrySet()) + buf.append(";\n\t") + .append(directive.getKey()) + .append(" := ") + .append(directive.getValue()); + + return buf.toString(); + } + +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/RequirementWrapper.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/RequirementWrapper.java new file mode 100644 index 00000000000..a643df0e8ae --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/RequirementWrapper.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2014, 2023 bndtools project 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: + * Neil Bartlett - initial API and implementation + * BJ Hargrave - ongoing enhancements + * Peter Kriens - ongoing enhancements + * Christoph Rueger - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.model.resolution; + +import java.util.Collection; +import java.util.Objects; + +import org.osgi.resource.Requirement; + +import aQute.bnd.osgi.Constants; + +public class RequirementWrapper { + + public final Requirement requirement; + public boolean resolved; + public boolean java; + public Collection requirers; + + public RequirementWrapper(Requirement requirement) { + this.requirement = requirement; + } + + public boolean isOptional() { + + String resolution = requirement.getDirectives() + .get(Constants.RESOLUTION); + + if (resolution == null) { + return false; + } + + return Constants.OPTIONAL.equals(resolution); + } + + @Override + public int hashCode() { + return Objects.hash(java, requirement, requirers, resolved); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RequirementWrapper other = (RequirementWrapper) obj; + return java == other.java && Objects.equals(requirement, other.requirement) + && Objects.equals(requirers, other.requirers) && resolved == other.resolved; + } + + @Override + public String toString() { + return "RequirementWrapper [resolved=" + resolved + ", java=" + java + ", requirement=" + requirement + "]"; + } + +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/RequirementWrapperLabelProvider.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/RequirementWrapperLabelProvider.java new file mode 100644 index 00000000000..f0aec01d5b5 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resolution/RequirementWrapperLabelProvider.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2014, 2023 bndtools project 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: + * Neil Bartlett - initial API and implementation + * Sean Bright - ongoing enhancements + * BJ Hargrave - ongoing enhancements + * Peter Kriens - ongoing enhancements + * Christoph Rueger - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.model.resolution; + +import java.util.Map.Entry; + +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.StyledString.Styler; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.pde.bnd.ui.Resources; +import org.eclipse.pde.bnd.ui.model.resource.R5LabelFormatter; +import org.eclipse.pde.bnd.ui.model.resource.RequirementLabelProvider; +import org.eclipse.swt.graphics.Image; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; + +import aQute.bnd.osgi.Clazz; +import aQute.bnd.service.resource.SupportingResource; + +public class RequirementWrapperLabelProvider extends RequirementLabelProvider { + + private final Styler resolved = StyledString.QUALIFIER_STYLER; + + public RequirementWrapperLabelProvider(boolean shortenNamespaces) { + super(shortenNamespaces); + } + + @Override + public void update(ViewerCell cell) { + Object element = cell.getElement(); + if (element instanceof RequirementWrapper) { + RequirementWrapper rw = (RequirementWrapper) element; + + Image icon = Resources.getImage(R5LabelFormatter.getNamespaceImagePath(rw.requirement.getNamespace())); + cell.setImage(icon); + + StyledString label = getLabel(rw.requirement); + if (rw.resolved || rw.java) + label.setStyle(0, label.length(), resolved); + + cell.setText(label.getString()); + cell.setStyleRanges(label.getStyleRanges()); + } else if (element instanceof Clazz) { + cell.setImage(Resources.getImage("class_obj")); + + String pkg; + String className; + + String fqn = ((Clazz) element).getFQN(); + int dot = fqn.lastIndexOf('.'); + if (dot >= 0) { + pkg = fqn.substring(0, dot); + className = fqn.substring(dot + 1); + } else { + pkg = ""; + className = fqn; + } + + StyledString label = new StyledString(className); + label.append(" - " + pkg, StyledString.QUALIFIER_STYLER); + + cell.setText(label.getString()); + cell.setStyleRanges(label.getStyleRanges()); + } + } + + @Override + public String getToolTipText(Object element) { + if (element instanceof RequirementWrapper rw) { + return tooltipText(rw); + } + + return null; + } + + + static String tooltipText(RequirementWrapper rw) { + Requirement req = rw.requirement; + + StringBuilder buf = new StringBuilder(300); + if (rw.resolved) + buf.append("RESOLVED:\n"); + if (rw.java) + buf.append("JAVA:\n"); + + Resource r = req.getResource(); + + buf.append("FROM: ") + .append(r) + .append("\n"); + + if (r instanceof SupportingResource sr) { + int index = sr.getSupportingIndex(); + if (index >= 0) { + buf.append("Requirement from a supporting resource ") + .append(index) + .append(" part of ") + .append(sr.getParent()) + .append("\n"); + } + } + buf.append(req.getNamespace()); + + for (Entry attr : req.getAttributes() + .entrySet()) + buf.append(";\n\t") + .append(attr.getKey()) + .append(" = ") + .append(attr.getValue()); + + for (Entry directive : req.getDirectives() + .entrySet()) + buf.append(";\n\t") + .append(directive.getKey()) + .append(" := ") + .append(directive.getValue()); + + return buf.toString(); + } + +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resource/R5LabelFormatter.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resource/R5LabelFormatter.java new file mode 100644 index 00000000000..11cd9fa9d43 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resource/R5LabelFormatter.java @@ -0,0 +1,368 @@ +/******************************************************************************* + * Copyright (c) 2013, 2023 bndtools project 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: + * Neil Bartlett - initial API and implementation + * Ferry Huberts - ongoing enhancements + * Peter Kriens - ongoing enhancements + * Sean Bright - ongoing enhancements + * Carter Smithhart - ongoing enhancements + * BJ Hargrave - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.model.resource; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.pde.bnd.ui.BoldStyler; +import org.eclipse.pde.bnd.ui.internal.ItalicStyler; +import org.osgi.framework.Version; +import org.osgi.framework.namespace.AbstractWiringNamespace; +import org.osgi.framework.namespace.BundleNamespace; +import org.osgi.framework.namespace.ExecutionEnvironmentNamespace; +import org.osgi.framework.namespace.HostNamespace; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.namespace.contract.ContractNamespace; +import org.osgi.namespace.extender.ExtenderNamespace; +import org.osgi.namespace.service.ServiceNamespace; +import org.osgi.resource.Capability; +import org.osgi.resource.Namespace; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; +import org.osgi.service.repository.ContentNamespace; + +import aQute.bnd.osgi.resource.CapReqBuilder; +import aQute.bnd.osgi.resource.FilterParser; +import aQute.bnd.osgi.resource.FilterParser.Expression; +import aQute.bnd.osgi.resource.FilterParser.Op; +import aQute.bnd.osgi.resource.FilterParser.RangeExpression; +import aQute.bnd.osgi.resource.FilterParser.SimpleExpression; +import aQute.bnd.osgi.resource.FilterParser.WithRangeExpression; +import aQute.bnd.osgi.resource.ResourceUtils; +import aQute.bnd.service.resource.SupportingResource; +import aQute.bnd.unmodifiable.Maps; + +public class R5LabelFormatter { + + private final static Pattern EE_PATTERN = Pattern.compile("osgi.ee=([^)]*).*version=([^)]*)"); + + private static final Map FILTER_PATTERNS = Maps.of( + ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, EE_PATTERN, PackageNamespace.PACKAGE_NAMESPACE, + Pattern.compile("osgi\\.wiring\\.package=([^)]*)")); + + public static final String PLUGIN_ID = "bndtools.core"; + + public static String getVersionAttributeName(String ns) { + String r; + + if (ns == null) + r = null; + else if (ns.equals(IdentityNamespace.IDENTITY_NAMESPACE)) + r = IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE; + else if (ns.equals(ContentNamespace.CONTENT_NAMESPACE)) + r = null; + else if (ns.equals(BundleNamespace.BUNDLE_NAMESPACE)) + r = AbstractWiringNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE; + else if (ns.equals(HostNamespace.HOST_NAMESPACE)) + r = AbstractWiringNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE; + else if (ns.equals(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE)) + r = ExecutionEnvironmentNamespace.CAPABILITY_VERSION_ATTRIBUTE; + else if (ns.equals(PackageNamespace.PACKAGE_NAMESPACE)) + r = PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE; + else if (ns.equals(ExtenderNamespace.EXTENDER_NAMESPACE)) + r = ExtenderNamespace.CAPABILITY_VERSION_ATTRIBUTE; + else if (ns.equals(ContractNamespace.CONTRACT_NAMESPACE)) + r = ContractNamespace.CAPABILITY_VERSION_ATTRIBUTE; + else if (ns.equals(ServiceNamespace.SERVICE_NAMESPACE)) + r = null; + else + r = null; + + return r; + } + + public static Pattern getFilterPattern(String ns) { + return FILTER_PATTERNS.get(ns); + } + + /* + * Most namespaces have a "main" attribute that is the same as the + * namespace. For example, namespace osgi.wiring.package has an attribute + * "osgi.wiring.package" that specifies the package name. The main exception + * to this rule is the osgi.service namespace, which uses "objectClass". + */ + public static String getMainAttributeName(String ns) { + if (ServiceNamespace.SERVICE_NAMESPACE.equals(ns)) + return ServiceNamespace.CAPABILITY_OBJECTCLASS_ATTRIBUTE; + + return ns; + } + + public static String getNamespaceImagePath(String ns) { + String r = "brkpi_obj.svg"; // generic green dot + + if (BundleNamespace.BUNDLE_NAMESPACE.equals(ns) || HostNamespace.HOST_NAMESPACE.equals(ns)) + r = "bundle.png"; + else if (ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(ns)) + r = "jcu_obj.svg"; + else if (PackageNamespace.PACKAGE_NAMESPACE.equals(ns)) + r = "package_obj.svg"; + else if (ServiceNamespace.SERVICE_NAMESPACE.equals(ns)) + r = "service.png"; + else if (ExtenderNamespace.EXTENDER_NAMESPACE.equals(ns)) + r = "tricks.svg"; + else if (ContractNamespace.CONTRACT_NAMESPACE.equals(ns)) + r = "signed_yes_tbl.svg"; + else if ("osgi.whiteboard".equals(ns)) + r = "whiteboard.png"; + else if ("bnd.multirelease".equals(ns)) + r = "jar_obj.svg"; + else if ("osgi.unresolvable".equalsIgnoreCase(ns) || "osgi.missing".equalsIgnoreCase(ns) + || "donotresolve".equalsIgnoreCase(ns) || "compile-only".equalsIgnoreCase(ns)) + r = "excludeMode_filter.svg"; + + return r; + } + + public static void appendNamespaceWithValue(StyledString label, String ns, String value, boolean shorten) { + String prefix = ns; + if (shorten) { + if (IdentityNamespace.IDENTITY_NAMESPACE.equals(ns)) + prefix = "id"; + else if (BundleNamespace.BUNDLE_NAMESPACE.equals(ns)) + prefix = ""; + else if (HostNamespace.HOST_NAMESPACE.equals(ns)) + prefix = "host"; + else if (ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(ns)) + prefix = ""; + else if (PackageNamespace.PACKAGE_NAMESPACE.equals(ns)) + prefix = ""; + else if (ServiceNamespace.SERVICE_NAMESPACE.equals(ns)) + prefix = ""; + else if (ContractNamespace.CONTRACT_NAMESPACE.equals(ns)) + prefix = ""; + else if ("osgi.whiteboard".equals(ns)) + prefix = ""; + } + + if (prefix.length() > 0) + label.append(prefix + "=", StyledString.QUALIFIER_STYLER); + label.append(value, BoldStyler.INSTANCE_DEFAULT); + } + + public static void appendCapability(StyledString label, Capability cap, boolean shorten) { + String ns = cap.getNamespace(); + + Resource r = cap.getResource(); + if (r instanceof SupportingResource sr) { + int index = sr.getSupportingIndex(); + if (index >= 0) { + label.append("[" + index + "] "); + } + } + + Object nsValue = cap.getAttributes() + .get(getMainAttributeName(ns)); + String versionAttributeName = getVersionAttributeName(ns); + if (nsValue != null) { + appendNamespaceWithValue(label, ns, nsValue.toString(), shorten); + + if (versionAttributeName != null) { + Object version = cap.getAttributes() + .get(versionAttributeName); + if (version != null) { + label.append(", " + versionAttributeName, StyledString.QUALIFIER_STYLER); + label.append(" " + version.toString(), BoldStyler.INSTANCE_COUNTER); + } + } + } else { + label.append(ns, BoldStyler.INSTANCE_DEFAULT); + } + label.append(" ", StyledString.QUALIFIER_STYLER); + + if (!cap.getAttributes() + .isEmpty()) { + boolean first = true; + for (Entry entry : cap.getAttributes() + .entrySet()) { + String key = entry.getKey(); + if (!key.equals(ns) && !key.equals(versionAttributeName)) { + if (first) + label.append("[", StyledString.QUALIFIER_STYLER); + else + label.append(", ", StyledString.QUALIFIER_STYLER); + + first = false; + label.append(key + "=", StyledString.QUALIFIER_STYLER); + label.append(entry.getValue() != null ? entry.getValue() + .toString() : "", StyledString.QUALIFIER_STYLER); + } + } + if (!first) + label.append("]", StyledString.QUALIFIER_STYLER); + } + + if (!cap.getDirectives() + .isEmpty()) { + label.append(" "); + boolean first = true; + for (Entry directive : cap.getDirectives() + .entrySet()) { + label.append(directive.getKey() + ":=" + directive.getValue(), StyledString.QUALIFIER_STYLER); + if (!first) + label.append(", ", StyledString.QUALIFIER_STYLER); + } + } + + } + + public static void appendResourceLabel(StyledString label, Resource resource) { + Capability identity = ResourceUtils.getIdentityCapability(resource); + String name = ResourceUtils.getIdentity(identity); + if (name == null) { + if (resource != null) { + name = resource.toString(); + } else { + name = ""; + } + } + label.append(name, BoldStyler.INSTANCE_DEFAULT); + + Version version = org.eclipse.pde.bnd.ui.ResourceUtils.getVersion(identity); + if (version != null) + label.append(" " + version, StyledString.COUNTER_STYLER); + } + + public static void appendRequirementLabel(StyledString label, Requirement requirement, boolean shorten) { + try { + requirement = CapReqBuilder.unalias(requirement); + } catch (Exception e) { + ILog.get() + .log(new Status(IStatus.WARNING, PLUGIN_ID, 0, + "Error parsing aliased requirement, using original", e)); + } + + String namespace = requirement.getNamespace(); + String filter = requirement.getDirectives() + .get(Namespace.REQUIREMENT_FILTER_DIRECTIVE); + + boolean optional = Namespace.RESOLUTION_OPTIONAL.equals(requirement.getDirectives() + .get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE)); + + Resource r = requirement.getResource(); + if (r instanceof SupportingResource sr) { + int index = sr.getSupportingIndex(); + if (index >= 0) + label.append("[" + index + "] "); + } + + FilterParser fp = new FilterParser(); + if (filter == null) { + if (namespace.contains("$")) { + Pattern pattern = Pattern.compile("\\{(.*?)\\}"); + Matcher matcher = pattern.matcher(namespace); + label.append(namespace); + while (matcher.find()) { + int begin = matcher.start(1); + int end = matcher.end(1); + label.setStyle(begin, end - begin, BoldStyler.INSTANCE_DEFAULT); + } + } else { + label.append(namespace + ": ", ItalicStyler.INSTANCE_ERROR); + } + } else { + try { + Expression exp = fp.parse(filter); + if (exp instanceof WithRangeExpression) { + appendNamespaceWithValue(label, namespace, ((WithRangeExpression) exp).printExcludingRange(), + shorten); + RangeExpression range = ((WithRangeExpression) exp).getRangeExpression(); + if (range != null) + label.append(" ") + .append(formatRangeString(range), StyledString.COUNTER_STYLER); + } else if (ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(namespace)) { + Matcher matcher = EE_PATTERN.matcher(filter); + if (matcher.find()) { + String eename = matcher.group(1); + String version = matcher.group(2); + appendNamespaceWithValue(label, namespace, eename, true); + label.append(" ") + .append(version, StyledString.COUNTER_STYLER); + } else { + appendNamespaceWithValue(label, namespace, filter, true); + } + } else { + appendNamespaceWithValue(label, namespace, filter, true); + } + } catch (Exception e) { + label.append(namespace + ": ", StyledString.QUALIFIER_STYLER); + label.append("", ItalicStyler.INSTANCE_ERROR); + } + } + + boolean first = true; + for (Entry directive : requirement.getDirectives() + .entrySet()) { + if (Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE.equals(directive.getKey()) + || Namespace.REQUIREMENT_FILTER_DIRECTIVE.equals(directive.getKey())) + continue; // deal with the filter: and resolution: directives + // separately + StringBuilder buf = new StringBuilder(); + buf.append(first ? " " : ", "); + buf.append(directive.getKey()) + .append(":=") + .append(directive.getValue()); + label.append(buf.toString(), StyledString.QUALIFIER_STYLER); + first = false; + } + + if (optional) { + label.setStyle(0, label.length(), StyledString.QUALIFIER_STYLER); + label.append(" ", ItalicStyler.INSTANCE_DEFAULT); + } + } + + public static String formatRangeString(RangeExpression range) { + StringBuilder sb = new StringBuilder(); + + SimpleExpression low = range.getLow(); + if (low == null) { + sb.append("[0"); + } else { + if (low.getOp() == Op.GREATER) + sb.append("("); + else + sb.append("["); + sb.append(low.getValue()); + } + + sb.append(", "); + + SimpleExpression high = range.getHigh(); + if (high == null) { + sb.append("\u221e]"); // INFINITY Unicode: U+221E, UTF-8: E2 88 9E + } else { + sb.append(high.getValue()); + if (high.getOp() == Op.LESS) + sb.append(")"); + else + sb.append("]"); + } + return sb.toString(); + } + +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resource/RequirementLabelProvider.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resource/RequirementLabelProvider.java new file mode 100644 index 00000000000..dc64cc0bb62 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/model/resource/RequirementLabelProvider.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2012, 2021 bndtools project 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: + * Neil Bartlett - initial API and implementation + * Peter Kriens - ongoing enhancements + * BJ Hargrave - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.model.resource; + +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.pde.bnd.ui.Resources; +import org.eclipse.swt.graphics.Image; +import org.osgi.resource.Requirement; + +public class RequirementLabelProvider extends StyledCellLabelProvider { + + protected final boolean shortenNamespaces; + + public RequirementLabelProvider() { + this(true); + } + + public RequirementLabelProvider(boolean shortenNamespaces) { + this.shortenNamespaces = shortenNamespaces; + } + + @Override + public void update(ViewerCell cell) { + Object element = cell.getElement(); + if (element instanceof Requirement) { + Requirement requirement = (Requirement) element; + + StyledString label = getLabel(requirement); + + cell.setText(label.getString()); + cell.setStyleRanges(label.getStyleRanges()); + + Image icon = Resources.getImage(R5LabelFormatter.getNamespaceImagePath(requirement.getNamespace())); + cell.setImage(icon); + } + } + + protected StyledString getLabel(Requirement requirement) { + StyledString label = new StyledString(); + return getLabel(label, requirement); + } + + protected StyledString getLabel(StyledString label, Requirement requirement) { + R5LabelFormatter.appendRequirementLabel(label, requirement, shortenNamespaces); + return label; + } +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/AnalyseBundleResolutionJob.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/AnalyseBundleResolutionJob.java new file mode 100644 index 00000000000..289311ee694 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/AnalyseBundleResolutionJob.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (c) 2010, 2023 bndtools project 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: + * Neil Bartlett - initial API and implementation + * PK Søreide - ongoing enhancements + * Ferry Huberts - ongoing enhancements + * Peter Kriens - ongoing enhancements + * BJ Hargrave - ongoing enhancements + * Sean Bright - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.tasks; + +import static java.util.Collections.emptyList; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Predicate; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.pde.bnd.ui.model.resolution.RequirementWrapper; +import org.osgi.resource.Capability; +import org.osgi.resource.Namespace; + +import aQute.bnd.build.model.EE; +import aQute.bnd.osgi.resource.ResourceUtils; + +public class AnalyseBundleResolutionJob extends Job { + + private final Set loaders; + + private Map> requirements; + private Map> capabilities; + private EE ee; + + public AnalyseBundleResolutionJob(String name, Set loaders) { + this(name, loaders, null); + } + + public AnalyseBundleResolutionJob(String name, Set loaders, EE ee) { + super(name); + this.loaders = loaders; + this.ee = ee; + } + + private static void mergeMaps(Map> from, Map> into) { + for (Entry> entry : from.entrySet()) { + K key = entry.getKey(); + + List list = into.get(key); + if (list == null) { + list = new ArrayList<>(); + into.put(key, list); + } + + list.addAll(entry.getValue()); + } + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + + + // Load all the capabilities and requirements + Map> allCaps = new HashMap<>(); + Map> allReqs = new HashMap<>(); + for (CapReqLoader loader : loaders) { + try (loader){ + Map> caps = loader.loadCapabilities(); + mergeMaps(caps, allCaps); + + Map> reqs = loader.loadRequirements(); + mergeMaps(reqs, allReqs); + } catch (Exception e) { + ILog.get().error("Error in Bnd resolution analysis.", e); + } + } + + // Check for resolved requirements + for (String namespace : allReqs.keySet()) { + List rws = allReqs.getOrDefault(namespace, emptyList()); + List candidates = allCaps.getOrDefault(namespace, emptyList()); + + List javaCandidates = ee == null ? emptyList() + : ee.getResource() + .getCapabilities(namespace); + + outer: for (RequirementWrapper rw : rws) { + String filterDirective = rw.requirement.getDirectives() + .get(Namespace.REQUIREMENT_FILTER_DIRECTIVE); + if (filterDirective == null) { + continue; + } + Predicate predicate = ResourceUtils.filterMatcher(rw.requirement); + for (Capability cand : candidates) { + if (predicate.test(cand)) { + rw.resolved = true; + continue outer; + } + } + for (Capability cand : javaCandidates) { + if (predicate.test(cand)) { + rw.java = true; + continue outer; + } + } + } + } + + // Generate the final results + // Set resultFiles = builderMap.keySet(); + // resultFileArray = resultFiles.toArray(new File[0]); + + this.requirements = allReqs; + this.capabilities = allCaps; + + // showResults(resultFileArray, importResults, exportResults); + return Status.OK_STATUS; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public Map> getRequirements() { + return Collections.unmodifiableMap(requirements); + } + + public Map> getCapabilities() { + return Collections.unmodifiableMap(capabilities); + } +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/BndBuilderCapReqLoader.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/BndBuilderCapReqLoader.java new file mode 100644 index 00000000000..d1a51295a3e --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/BndBuilderCapReqLoader.java @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (c) 2015, 2023 bndtools project 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: + * Neil Bartlett - initial API and implementation + * BJ Hargrave - ongoing enhancements + * Peter Kriens - ongoing enhancements + * Christoph Rueger - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.tasks; + +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.pde.bnd.ui.model.resolution.RequirementWrapper; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; + +import aQute.bnd.exceptions.Exceptions; +import aQute.bnd.osgi.Builder; +import aQute.bnd.osgi.Clazz; +import aQute.bnd.osgi.Jar; +import aQute.bnd.osgi.resource.ResourceBuilder; +import aQute.bnd.service.resource.SupportingResource; + +public abstract class BndBuilderCapReqLoader implements CapReqLoader { + + protected final File file; + private Map> loadCapabilities; + private Map> loadRequirements; + + public BndBuilderCapReqLoader(File file) { + this.file = file; + } + + @Override + public String getShortLabel() { + return file.getName(); + } + + @Override + public String getLongLabel() { + return file.getName() + " - " + file.getParentFile() + .getAbsolutePath(); + } + + protected abstract Builder getBuilder() throws Exception; + + private void load() throws Exception { + if ((loadCapabilities != null) && (loadRequirements != null)) { + return; + } + + Builder builder = getBuilder(); + if (builder == null) { + loadCapabilities = Collections.emptyMap(); + loadRequirements = Collections.emptyMap(); + return; + } + + Jar jar = builder.getJar(); + if (jar == null) { + loadCapabilities = Collections.emptyMap(); + loadRequirements = Collections.emptyMap(); + return; + } + + ResourceBuilder rb = new ResourceBuilder(); + rb.addJar(jar); + SupportingResource sr = rb.build(); + List capabilities = new ArrayList<>(); + List requirements = new ArrayList<>(); + + for (Resource resource : sr.all()) { + capabilities.addAll(resource.getCapabilities(null)); + requirements.addAll(resource.getRequirements(null)); + } + loadRequirements = requirements.stream() + .collect(groupingBy(Requirement::getNamespace, mapping(this::toRequirementWrapper, toList()))); + loadCapabilities = capabilities.stream() + .collect(groupingBy(Capability::getNamespace, toList())); + } + + @Override + public Map> loadCapabilities() throws Exception { + load(); + return loadCapabilities; + } + + @Override + public Map> loadRequirements() throws Exception { + load(); + return loadRequirements; + } + + private RequirementWrapper toRequirementWrapper(Requirement req) { + RequirementWrapper rw = new RequirementWrapper(req); + if (req.getNamespace() + .equals(PackageNamespace.PACKAGE_NAMESPACE)) { + String pkgName = (String) req.getAttributes() + .get(PackageNamespace.PACKAGE_NAMESPACE); + try { + rw.requirers = findImportingClasses(pkgName); + } catch (Exception e) { + throw Exceptions.duck(e); + } + } + return rw; + } + + private List findImportingClasses(String pkgName) throws Exception { + List classes = new LinkedList<>(); + Collection importers = getBuilder().getClasses("", "IMPORTING", pkgName); + + // Remove *this* package + for (Clazz clazz : importers) { + String fqn = clazz.getFQN(); + int dot = fqn.lastIndexOf('.'); + if (dot >= 0) { + String pkg = fqn.substring(0, dot); + if (!pkgName.equals(pkg)) + classes.add(clazz); + } + } + return classes; + } + + public File getFile() { + return file; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((file == null) ? 0 : file.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BndBuilderCapReqLoader other = (BndBuilderCapReqLoader) obj; + return Objects.equals(file, other.file); + } + +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/BndFileCapReqLoader.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/BndFileCapReqLoader.java new file mode 100644 index 00000000000..78b435b4a26 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/BndFileCapReqLoader.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2015, 2019 bndtools project 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: + * Neil Bartlett - initial API and implementation + * Sean Bright - ongoing enhancements + * BJ Hargrave - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.tasks; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.pde.bnd.ui.Central; +import org.eclipse.pde.bnd.ui.Workspaces; +import org.eclipse.pde.bnd.ui.internal.FileUtils; + +import aQute.bnd.build.Project; +import aQute.bnd.build.ProjectBuilder; +import aQute.bnd.build.Workspace; +import aQute.bnd.osgi.Builder; + +public class BndFileCapReqLoader extends BndBuilderCapReqLoader { + + private Builder builder; + + public BndFileCapReqLoader(File bndFile) { + super(bndFile); + } + + @Override + protected synchronized Builder getBuilder() throws Exception { + if (builder == null) { + Builder b; + + IFile[] wsfiles = FileUtils.getWorkspaceFiles(file); + if (wsfiles == null || wsfiles.length == 0) + throw new Exception("Unable to determine project owner for Bnd file: " + file.getAbsolutePath()); + + IProject project = wsfiles[0].getProject(); + + // Calculate the manifest + Workspace ws = Workspaces.getWorkspace(project).orElse(null); + Project bndProject = Central.getProject(ws, project); + if (bndProject == null) + return null; + if (file.getName() + .equals(Project.BNDFILE)) { + ProjectBuilder pb = bndProject.getBuilder(null); + boolean close = true; + try { + b = pb.getSubBuilders() + .get(0); + if (b == pb) { + close = false; + } else { + pb.removeClose(b); + } + } finally { + if (close) { + pb.close(); + } + } + } else { + b = bndProject.getSubBuilder(file); + } + + if (b == null) { + b = new Builder(); + b.setProperties(file); + } + b.build(); + + builder = b; + } + return builder; + } + + @Override + public synchronized void close() throws IOException { + if (builder != null) + builder.close(); + builder = null; + } + +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/CapReqLoader.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/CapReqLoader.java new file mode 100644 index 00000000000..af9b3085ab1 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/CapReqLoader.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2015, 2019 bndtools project 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: + * Neil Bartlett - initial API and implementation + * BJ Hargrave - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.tasks; + +import java.io.Closeable; +import java.util.List; +import java.util.Map; + +import org.eclipse.pde.bnd.ui.model.resolution.RequirementWrapper; +import org.osgi.resource.Capability; + +public interface CapReqLoader extends Closeable { + + String getShortLabel(); + + String getLongLabel(); + + Map> loadCapabilities() throws Exception; + + Map> loadRequirements() throws Exception; + +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/JarFileCapReqLoader.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/JarFileCapReqLoader.java new file mode 100644 index 00000000000..0713b7280e4 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/JarFileCapReqLoader.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2015, 2019 bndtools project 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: + * Neil Bartlett - initial API and implementation + * BJ Hargrave - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.tasks; + +import java.io.File; +import java.io.IOException; + +import aQute.bnd.osgi.Builder; +import aQute.bnd.osgi.Jar; + +public class JarFileCapReqLoader extends BndBuilderCapReqLoader { + + private Builder builder; + + public JarFileCapReqLoader(File jarFile) { + super(jarFile); + } + + @Override + protected synchronized Builder getBuilder() throws Exception { + if (builder == null) { + Builder b = new Builder(); + Jar jar = new Jar(file); + b.setJar(jar); + b.analyze(); + + builder = b; + } + return builder; + } + + @Override + public synchronized void close() throws IOException { + if (builder != null) + builder.close(); + builder = null; + } + +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/ResourceCapReqLoader.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/ResourceCapReqLoader.java new file mode 100644 index 00000000000..9f28c325c8f --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/tasks/ResourceCapReqLoader.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2015, 2023 bndtools project 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: + * Neil Bartlett - initial API and implementation + * BJ Hargrave - ongoing enhancements + * Peter Kriens - ongoing enhancements + * Christoph Rueger - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.tasks; + +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.pde.bnd.ui.ResourceUtils; +import org.eclipse.pde.bnd.ui.model.resolution.RequirementWrapper; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; + +import aQute.bnd.service.resource.SupportingResource; + +public class ResourceCapReqLoader implements CapReqLoader { + + private final Resource resource; + private final String name; + private final URI uri; + + public ResourceCapReqLoader(Resource resource) { + this.resource = resource; + this.name = ResourceUtils.getIdentity(resource); + URI uri = null; + try { + uri = ResourceUtils.getURI(ResourceUtils.getContentCapability(resource)); + } catch (Exception e) { + + } + this.uri = uri; + } + + @Override + public String getShortLabel() { + return name; + } + + @Override + public String getLongLabel() { + return name + "[" + uri + "]"; + } + + @Override + public Map> loadCapabilities() throws Exception { + Map> result = new HashMap<>(); + + List caps = new ArrayList<>(resource.getCapabilities(null)); + if (resource instanceof SupportingResource sr) { + for (Resource r : sr.getSupportingResources()) { + caps.addAll(r.getCapabilities(null)); + } + } + for (Capability cap : caps) { + String ns = cap.getNamespace(); + List listForNamespace = result.get(ns); + if (listForNamespace == null) { + listForNamespace = new LinkedList<>(); + result.put(ns, listForNamespace); + } + listForNamespace.add(cap); + } + + return result; + } + + @Override + public Map> loadRequirements() throws Exception { + Map> result = new HashMap<>(); + + List reqs = new ArrayList<>(resource.getRequirements(null)); + if (resource instanceof SupportingResource sr) { + for (Resource r : sr.getSupportingResources()) { + reqs.addAll(r.getRequirements(null)); + } + } + for (Requirement req : reqs) { + String ns = req.getNamespace(); + List listForNamespace = result.get(ns); + if (listForNamespace == null) { + listForNamespace = new LinkedList<>(); + result.put(ns, listForNamespace); + } + RequirementWrapper wrapper = new RequirementWrapper(req); + listForNamespace.add(wrapper); + } + + return result; + } + + @Override + public void close() throws IOException { + // no-op + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((resource == null) ? 0 : resource.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ResourceCapReqLoader other = (ResourceCapReqLoader) obj; + return Objects.equals(resource, other.resource); + } + +} diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/views/repository/PackageSearchPanel.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/views/repository/PackageSearchPanel.java index 2dc27c1c174..5eac0407571 100644 --- a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/views/repository/PackageSearchPanel.java +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/views/repository/PackageSearchPanel.java @@ -113,7 +113,7 @@ public void setFocus() { @Override public Image createImage(Device device) { - return Resources.getImage("package"); + return Resources.getImage("package_obj.svg"); } @Override diff --git a/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/views/resolution/ResolutionView.java b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/views/resolution/ResolutionView.java new file mode 100644 index 00000000000..f87c564e447 --- /dev/null +++ b/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/views/resolution/ResolutionView.java @@ -0,0 +1,988 @@ +/******************************************************************************* + * Copyright (c) 2015, 2025 bndtools project 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: + * Neil Bartlett - initial API and implementation + * BJ Hargrave - ongoing enhancements + * Peter Kriens - ongoing enhancements + * Christoph Rueger - ongoing enhancements +*******************************************************************************/ +package org.eclipse.pde.bnd.ui.views.resolution; + +import java.io.File; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Adapters; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.e4.core.services.events.IEventBroker; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.ui.JavaUI; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.util.LocalSelectionTransfer; +import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.OpenEvent; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.pde.bnd.ui.FilterPanelPart; +import org.eclipse.pde.bnd.ui.HelpButtons; +import org.eclipse.pde.bnd.ui.Resources; +import org.eclipse.pde.bnd.ui.internal.PartAdapter; +import org.eclipse.pde.bnd.ui.model.repo.RepositoryResourceElement; +import org.eclipse.pde.bnd.ui.model.resolution.CapReqMapContentProvider; +import org.eclipse.pde.bnd.ui.model.resolution.CapabilityLabelProvider; +import org.eclipse.pde.bnd.ui.model.resolution.RequirementWrapper; +import org.eclipse.pde.bnd.ui.model.resolution.RequirementWrapperLabelProvider; +import org.eclipse.pde.bnd.ui.tasks.AnalyseBundleResolutionJob; +import org.eclipse.pde.bnd.ui.tasks.BndBuilderCapReqLoader; +import org.eclipse.pde.bnd.ui.tasks.BndFileCapReqLoader; +import org.eclipse.pde.bnd.ui.tasks.CapReqLoader; +import org.eclipse.pde.bnd.ui.tasks.JarFileCapReqLoader; +import org.eclipse.pde.bnd.ui.tasks.ResourceCapReqLoader; +import org.eclipse.pde.bnd.ui.views.ViewEventTopics; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DragSourceEvent; +import org.eclipse.swt.dnd.DragSourceListener; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IPartListener; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.ResourceUtil; +import org.eclipse.ui.part.ViewPart; +import org.osgi.framework.namespace.HostNamespace; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; +import org.osgi.resource.Resource; +import org.osgi.service.repository.Repository; + +import aQute.bnd.build.model.EE; +import aQute.bnd.osgi.Clazz; +import aQute.bnd.osgi.resource.CapReqBuilder; +import aQute.bnd.osgi.resource.ResourceUtils; +import aQute.bnd.unmodifiable.Sets; +import aQute.lib.io.IO; +import aQute.lib.strings.Strings; + +public class ResolutionView extends ViewPart implements ISelectionListener, IResourceChangeListener { + + public static final String PLUGIN_ID = "bndtools.core"; + + private final List ees = Arrays.asList(EE.values()); + private Display display = null; + + private Tree reqsTree = null; + private Table capsTable = null; + + private TreeViewer reqsViewer; + private TableViewer capsViewer; + + private Label reqsLabel; + private Label capsLabel; + + private ViewerFilter hideSelfImportsFilter; + private ViewerFilter hideOptionalRequirements; + private ViewerFilter filterShowCapsProblems; + + private boolean inputLocked = false; + private boolean outOfDate = false; + Set loaders; + private Job analysisJob; + private int currentEE = 4; + + private final Set filteredCapabilityNamespaces; + private Set duplicateCapabilitiesWithDifferentHashes = new HashSet<>(); + + private final FilterPanelPart reqsFilterPart = new FilterPanelPart(Resources.getScheduler()); + private final FilterPanelPart capsFilterPart = new FilterPanelPart(Resources.getScheduler()); + + private static final String SEARCHSTRING_HINT = "Enter search string (Space to separate terms; '*' for partial matches)"; + + private CapReqMapContentProvider reqsContentProvider; + private CapReqMapContentProvider capsContentProvider; + + private final IEventBroker eventBroker = PlatformUI.getWorkbench() + .getService(IEventBroker.class); + + public ResolutionView() { + filteredCapabilityNamespaces = Sets.of(IdentityNamespace.IDENTITY_NAMESPACE, HostNamespace.HOST_NAMESPACE); + loaders = Collections.emptySet(); + } + + private final IPartListener partAdapter = new PartAdapter() { + @Override + public void partActivated(IWorkbenchPart part) { + if (part == ResolutionView.this) { + if (outOfDate) { + executeAnalysis(); + } + } else if (part instanceof IEditorPart) { + IEditorInput editorInput = ((IEditorPart) part).getEditorInput(); + IFile file = ResourceUtil.getFile(editorInput); + + if (file != null) { + IPath location = file.getLocation(); + + if (location != null) { + CapReqLoader loader = getLoaderForFile(location.toFile()); + + if (loader != null) { + setLoaders(Collections.singleton(loader)); + + if (getSite().getPage() + .isPartVisible(ResolutionView.this)) { + executeAnalysis(); + } else { + outOfDate = true; + } + } + } + } + } + } + }; + + + + + private boolean setLoaders(Set newLoaders) { + Set oldLoaders = loaders; + boolean swap = !oldLoaders.equals(newLoaders); + if (swap) { + loaders = newLoaders; + } + for (CapReqLoader l : swap ? oldLoaders : newLoaders) { + IO.close(l); + } + return swap; + } + + private CapReqLoader getLoaderForFile(File file) { + CapReqLoader loader; + if (Strings.endsWithIgnoreCase(file.getName(), ".bnd")) { + loader = new BndFileCapReqLoader(file); + } else if (Strings.endsWithIgnoreCase(file.getName(), ".jar")) { + loader = new JarFileCapReqLoader(file); + } else { + loader = null; + } + return loader; + } + + @Override + public void createPartControl(Composite parent) { + this.display = parent.getDisplay(); + + SashForm splitPanel = new SashForm(parent, SWT.HORIZONTAL); + splitPanel.setLayout(new FillLayout()); + + Composite reqsPanel = new Composite(splitPanel, SWT.NONE); + reqsPanel.setBackground(parent.getBackground()); + + GridLayout reqsLayout = new GridLayout(1, false); + reqsLayout.marginWidth = 0; + reqsLayout.marginHeight = 0; + reqsLayout.verticalSpacing = 2; + reqsPanel.setLayout(reqsLayout); + reqsFilterPart.createControl(reqsPanel, 5, 5); + reqsFilterPart.setHint(SEARCHSTRING_HINT); + reqsFilterPart.addPropertyChangeListener(event -> { + String filter = (String) event.getNewValue(); + updateReqsFilter(filter); + }); + + reqsLabel = new Label(reqsPanel, SWT.NONE); + reqsLabel.setText("Requirements:"); + setContentDescription( + "Click on one or multiple resources (bnd.bnd file, .jar file, repository bundle or repository) to see their requirements and capabilities."); + reqsTree = new Tree(reqsPanel, SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER); + reqsTree.setHeaderVisible(false); + reqsTree.setLinesVisible(false); + reqsTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + reqsViewer = new TreeViewer(reqsTree); + ColumnViewerToolTipSupport.enableFor(reqsViewer); + reqsViewer.setLabelProvider(new RequirementWrapperLabelProvider(true)); + reqsContentProvider = new CapReqMapContentProvider(); + reqsViewer.setContentProvider(reqsContentProvider); + reqsViewer.addDoubleClickListener(event -> handleReqsViewerDoubleClickEvent(event)); + + reqsViewer.getControl() + .addKeyListener(createCopyToClipboardAdapter(reqsViewer, + (IStructuredSelection selection, StringBuilder clipboardContent) -> reqsCopyToClipboard(selection, + (RequirementWrapperLabelProvider) reqsViewer.getLabelProvider(), clipboardContent))); + + Composite capsPanel = new Composite(splitPanel, SWT.NONE); + capsPanel.setBackground(parent.getBackground()); + + GridLayout capsLayout = new GridLayout(1, false); + capsLayout.marginWidth = 0; + capsLayout.marginHeight = 0; + capsLayout.verticalSpacing = 2; + capsPanel.setLayout(capsLayout); + capsFilterPart.createControl(capsPanel, 5, 5); + capsFilterPart.setHint(SEARCHSTRING_HINT); + capsFilterPart.addPropertyChangeListener(event -> { + String filter = (String) event.getNewValue(); + updateCapsFilter(filter); + }); + capsLabel = new Label(capsPanel, SWT.NONE); + capsLabel.setText("Capabilities:"); + capsTable = new Table(capsPanel, SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER); + capsTable.setHeaderVisible(false); + capsTable.setLinesVisible(false); + capsTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + capsViewer = new TableViewer(capsTable); + ColumnViewerToolTipSupport.enableFor(capsViewer); + capsViewer.setLabelProvider(new CapabilityLabelProvider(true)); + capsContentProvider = new CapReqMapContentProvider(); + capsViewer.setContentProvider(capsContentProvider); + capsViewer.setFilters(new ViewerFilter() { + @Override + public boolean select(Viewer viewer, Object parent, Object element) { + return !filteredCapabilityNamespaces.contains(((Capability) element).getNamespace()); + } + }); + + filterShowCapsProblems = new ViewerFilter() { + + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (element instanceof Capability cap) { + return duplicateCapabilitiesWithDifferentHashes.contains((cap)); + } + return false; + } + }; + + capsViewer.addDoubleClickListener(event -> handleCapsViewerDoubleClickEvent(event)); + + capsViewer.getTable() + .addKeyListener(createCopyToClipboardAdapter(capsViewer, + (IStructuredSelection selection1, StringBuilder clipboardContent1) -> capsCopyToClipboard(selection1, + (CapabilityLabelProvider) capsViewer.getLabelProvider(), clipboardContent1))); + + hideSelfImportsFilter = new ViewerFilter() { + + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (element instanceof RequirementWrapper rw) { + boolean resolved = rw.resolved | rw.java; + return !resolved; + } + return true; + } + }; + + hideOptionalRequirements = new ViewerFilter() { + + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (element instanceof RequirementWrapper rw) { + return !rw.isOptional(); + } + return true; + } + }; + + + reqsViewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY, new Transfer[] { + LocalSelectionTransfer.getTransfer() + }, new LocalTransferDragListener(reqsViewer)); + + capsViewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY, new Transfer[] { + LocalSelectionTransfer.getTransfer() + }, new LocalTransferDragListener(capsViewer)); + + reqsViewer.addOpenListener(this::openEditor); + + fillActionBars(); + + getSite().getPage() + .addPostSelectionListener(this); + getSite().getPage() + .addPartListener(partAdapter); + ResourcesPlugin.getWorkspace() + .addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); + + // Current selection & part + IWorkbenchPart activePart = getSite().getPage() + .getActivePart(); + ISelection activeSelection = getSite().getWorkbenchWindow() + .getSelectionService() + .getSelection(); + selectionChanged(activePart, activeSelection); + } + + + + + + + private void openEditor(OpenEvent event) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + for (Iterator iter = selection.iterator(); iter.hasNext();) { + Object item = iter.next(); + if (item instanceof Clazz) { + Clazz clazz = (Clazz) item; + String className = clazz.getFQN(); + IType type = null; + if (!loaders.isEmpty()) { + IWorkspaceRoot wsroot = ResourcesPlugin.getWorkspace() + .getRoot(); + for (CapReqLoader loader : loaders) { + if (loader instanceof BndBuilderCapReqLoader) { + File loaderFile = ((BndBuilderCapReqLoader) loader).getFile(); + IFile[] wsfiles = wsroot.findFilesForLocationURI(loaderFile.toURI()); + for (IFile wsfile : wsfiles) { + IJavaProject javaProject = JavaCore.create(wsfile.getProject()); + try { + type = javaProject.findType(className); + if (type != null) + break; + } catch (JavaModelException e1) { + ErrorDialog.openError(getSite().getShell(), "Error", "", + new Status(IStatus.ERROR, PLUGIN_ID, 0, + MessageFormat.format("Error opening Java class '{0}'.", className), e1)); + } + } + } + + } + } + try { + if (type != null) + JavaUI.openInEditor(type, true, true); + } catch (PartInitException e2) { + ErrorDialog.openError(getSite().getShell(), "Error", "", new Status(IStatus.ERROR, PLUGIN_ID, + 0, MessageFormat.format("Error opening Java editor for class '{0}'.", className), e2)); + } catch (JavaModelException e3) { + ErrorDialog.openError(getSite().getShell(), "Error", "", new Status(IStatus.ERROR, PLUGIN_ID, + 0, MessageFormat.format("Error opening Java class '{0}'.", className), e3)); + } + } + } + } + + void fillActionBars() { + IToolBarManager toolBarManager = getViewSite().getActionBars() + .getToolBarManager(); + + // Reqs Buttons + toolBarManager.add(createToggleHideSelfImportsButton()); + toolBarManager.add(createToggleHideOptionalReqsFilterButton()); + toolBarManager.add(new Separator()); + + // Caps Buttons + toolBarManager.add(createShowProblemCapsAction()); + toolBarManager.add(new Separator()); + + // Other Buttons + toolBarManager.add(createToggleLockInputButton()); + doEEActionMenu(toolBarManager); + toolBarManager.add(HelpButtons.HELP_BTN_RESOLUTION_VIEW); + + } + + + + + + private void doEEActionMenu(IToolBarManager toolBarManager) { + MenuManager menuManager = new MenuManager("Java", "resolutionview.java.menu"); + + Action showMenuAction = new Action("Java") { + @Override + public void runWithEvent(Event event) { + Menu menu = menuManager.createContextMenu(getViewSite().getShell()); + MenuItem[] items = menu.getItems(); + if (items != null && items.length == ees.size()) { + menu.setDefaultItem(items[currentEE]); + } + Point location = getViewSite().getShell() + .getDisplay() + .getCursorLocation(); + menu.setLocation(location.x, location.y); + menu.setVisible(true); + } + + @Override + public ImageDescriptor getImageDescriptor() { + return Resources.getImageDescriptor("jcu_obj"); + } + }; + for (int n = 0; n < ees.size(); n++) { + int nn = n; + EE ee = ees.get(n); + if (ee.getRelease() == 9) { + currentEE = n; + } + String name = getEEName(ee); + Action action = new Action(name) { + int index = nn; + + @Override + public void run() { + setEE(index); + showMenuAction.setToolTipText(getEEName(ees.get(currentEE))); + } + }; + menuManager.add(action); + } + showMenuAction.setToolTipText(getEEName(ees.get(currentEE))); + + toolBarManager.add(showMenuAction); + } + + private String getEEName(EE ee) { + return ee == EE.UNKNOWN ? "unknown" : ee.getEEName(); + } + + protected void setEE(int ee) { + currentEE = ee; + executeAnalysis(); + } + + @Override + public void setFocus() {} + + @Override + public void dispose() { + getSite().getPage() + .removeSelectionListener(this); + ResourcesPlugin.getWorkspace() + .removeResourceChangeListener(this); + getSite().getPage() + .removePartListener(partAdapter); + setLoaders(Collections. emptySet()); + duplicateCapabilitiesWithDifferentHashes.clear(); + super.dispose(); + } + + public void setInput(Set sourceLoaders, Map> capabilities, + Map> requirements) { + setLoaders(sourceLoaders); + sourceLoaders = loaders; + if (reqsTree != null && !reqsTree.isDisposed() && capsTable != null && !capsTable.isDisposed()) { + reqsViewer.setInput(requirements); + capsViewer.setInput(capabilities); + + String label; + if (!sourceLoaders.isEmpty()) { + StringBuilder builder = new StringBuilder(); + String delim = ""; + boolean shortLabel = sourceLoaders.size() > 1; + for (CapReqLoader l : sourceLoaders) { + builder.append(delim); + builder.append(shortLabel ? l.getShortLabel() : l.getLongLabel()); + delim = ", "; + } + label = builder.toString(); + } else { + label = ""; + } + setContentDescription(label); + + updateReqsLabel(); + + List caps = capabilities.values() + .stream() + .flatMap(List::stream) + .toList(); + + duplicateCapabilitiesWithDifferentHashes = new HashSet( + ResourceUtils.detectDuplicateCapabilitiesWithDifferentHashes("osgi.wiring.package", caps)); + + updateCapsLabel(); + + } + } + + @Override + public void selectionChanged(IWorkbenchPart part, ISelection selection) { + if (selection == null || !(selection instanceof IStructuredSelection)) + return; + + Set loaders = getLoadersFromSelection((IStructuredSelection) selection); + if (setLoaders(loaders)) { + IWorkbenchPage page = getSite().getPage(); + if (page != null && page.isPartVisible(this)) { + executeAnalysis(); + } else { + outOfDate = true; + } + } + + } + + private void updateReqsLabel() { + reqsLabel.setText("Requirements: " + reqsViewer.getTree() + .getItemCount()); + reqsLabel.getParent() + .layout(); + } + + private void updateCapsLabel() { + String problemAddition = ""; + if (!duplicateCapabilitiesWithDifferentHashes.isEmpty()) { + + int problemCount = 0; + TableItem[] items = capsViewer.getTable() + .getItems(); + for (int i = 0; i < items.length; i++) { + TableItem tableItem = items[i]; + Object data = tableItem.getData(); + if (data instanceof Capability cap) { + if (duplicateCapabilitiesWithDifferentHashes.contains(cap)) { + problemCount++; + } + } + } + + if (problemCount > 0) { + problemAddition = " Problems: " + problemCount; + } + } + capsLabel.setText("Capabilities: " + capsViewer.getTable() + .getItemCount() + problemAddition); + + capsLabel.getParent() + .layout(); + } + + private Set getLoadersFromSelection(IStructuredSelection structSel) { + Set result = new LinkedHashSet<>(); + Iterator iter = structSel.iterator(); + while (iter.hasNext()) { + + Object element = iter.next(); + CapReqLoader loader = null; + + File file = Adapters.adapt(element, File.class); + if (file != null) { + loader = getLoaderForFile(file); + } else { + IResource eresource = Adapters.adapt(element, IResource.class); + if (eresource != null) { + IPath location = eresource.getLocation(); + if (location != null) { + loader = getLoaderForFile(location.toFile()); + } + } else if (element instanceof Repository repo) { + ResourceUtils.getAllResources(repo) + .stream() + .filter(r -> { + try { + return ResourceUtils.getContentCapabilities(r) != null; + } catch (Exception e) { + return false; + } + }) + .map(ResourceCapReqLoader::new) + .forEach(result::add); + } else if (element instanceof RepositoryResourceElement) { + Resource resource = ((RepositoryResourceElement) element).getResource(); + loader = new ResourceCapReqLoader(resource); + } + } + + if (loader != null) + result.add(loader); + } + + return result; + } + + void executeAnalysis() { + if (inputLocked) + return; + + outOfDate = false; + synchronized (this) { + Job oldJob = analysisJob; + if (oldJob != null && oldJob.getState() != Job.NONE) + oldJob.cancel(); + + if (!loaders.isEmpty()) { + final AnalyseBundleResolutionJob job = new AnalyseBundleResolutionJob("importExportAnalysis", loaders, + ees.get(currentEE)); + job.setSystem(true); + + job.addJobChangeListener(new JobChangeAdapter() { + @Override + public void aboutToRun(IJobChangeEvent event) { + if (display != null && !display.isDisposed()) { + Runnable update = () -> setContentDescription("Working..."); + if (display.getThread() == Thread.currentThread()) + update.run(); + else + display.asyncExec(update); + } + } + + @Override + public void done(IJobChangeEvent event) { + IStatus result = job.getResult(); + if (result != null && result.isOK()) { + if (display != null && !display.isDisposed()) + display + .asyncExec(() -> setInput(loaders, job.getCapabilities(), job.getRequirements())); + } + } + }); + + analysisJob = job; + analysisJob.schedule(500); + } else { + analysisJob = null; + } + } + } + + @Override + public void resourceChanged(IResourceChangeEvent event) { + if (!loaders.isEmpty()) { + IWorkspaceRoot wsroot = ResourcesPlugin.getWorkspace() + .getRoot(); + for (CapReqLoader loader : loaders) { + if (loader instanceof BndBuilderCapReqLoader) { + File file = ((BndBuilderCapReqLoader) loader).getFile(); + IFile[] wsfiles = wsroot.findFilesForLocationURI(file.toURI()); + for (IFile wsfile : wsfiles) { + if (event.getDelta() + .findMember(wsfile.getFullPath()) != null) { + executeAnalysis(); + break; + } + } + } + } + } + } + + static class LocalTransferDragListener implements DragSourceListener { + + private final Viewer viewer; + + public LocalTransferDragListener(Viewer viewer) { + this.viewer = viewer; + } + + @Override + public void dragStart(DragSourceEvent event) {} + + @Override + public void dragSetData(DragSourceEvent event) { + LocalSelectionTransfer transfer = LocalSelectionTransfer.getTransfer(); + if (transfer.isSupportedType(event.dataType)) + transfer.setSelection(viewer.getSelection()); + } + + @Override + public void dragFinished(DragSourceEvent event) {} + } + + private void handleReqsViewerDoubleClickEvent(DoubleClickEvent event) { + if (!event.getSelection() + .isEmpty()) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + final Object element = selection.getFirstElement(); + + if (element instanceof RequirementWrapper rw) { + + // Open AdvanvedSearch of RepositoriesView + Requirement req = rw.requirement; + eventBroker.post(ViewEventTopics.REPOSITORIESVIEW_OPEN_ADVANCED_SEARCH.topic(), req); + } + + } + } + + private void handleCapsViewerDoubleClickEvent(DoubleClickEvent event) { + if (!event.getSelection() + .isEmpty()) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + final Object element = selection.getFirstElement(); + + if (element instanceof Capability cap) { + + // convert the capability to a requirement (without + // bundle-attributes and bnd.hashes) for better results in the + // advanced search e.g. + // (&(osgi.wiring.package=my.package.foo)(version>=1.7.23)) + Requirement req = CapReqBuilder.createRequirementFromCapability(cap, (name) -> { + if (name.equals("bundle-symbolic-name") || name.equals("bundle-version") + || name.equals("bnd.hashes")) { + return false; + } + + return true; + }) + .buildSyntheticRequirement(); + // Open AdvanvedSearch of RepositoriesView + eventBroker.post(ViewEventTopics.REPOSITORIESVIEW_OPEN_ADVANCED_SEARCH.topic(), req); + } + + } + } + + /** + * Generic copy to clipboard handling via Ctrl+C or MacOS: Cmd+C + * + * @param viewer the viewer + * @param clipboardContentExtractor handler to extract content from the + * selected items. + * @return a KeyAdapter copying content of the selected items to clipboard + */ + private KeyAdapter createCopyToClipboardAdapter(StructuredViewer viewer, + BiConsumer clipboardContentExtractor) { + return new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + // Check if Ctrl+C or MacOS: Cmd+C was pressed + if ((e.stateMask & SWT.MOD1) == SWT.MOD1 && e.keyCode == 'c') { + IStructuredSelection selection = viewer.getStructuredSelection(); + StringBuilder clipboardString = new StringBuilder(); + + clipboardContentExtractor.accept(selection, clipboardString); + + if (clipboardString.length() > 0) { + Clipboard clipboard = new Clipboard(Display.getCurrent()); + TextTransfer textTransfer = TextTransfer.getInstance(); + clipboard.setContents(new Object[] { + clipboardString.toString() + }, new Transfer[] { + textTransfer + }); + clipboard.dispose(); + } + } + } + + }; + } + + + private void reqsCopyToClipboard(IStructuredSelection selection, RequirementWrapperLabelProvider lp, + StringBuilder clipboardContent) { + + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + Object element = iterator.next(); + + if (element instanceof RequirementWrapper reqWrapper) { + + clipboardContent.append(lp.getToolTipText(reqWrapper)); + + if (iterator.hasNext()) { + clipboardContent.append(System.lineSeparator()); + } + + } else { + clipboardContent.append(element.toString()); + + if (iterator.hasNext()) { + clipboardContent.append(System.lineSeparator()); + } + + } + } + } + + + + private void capsCopyToClipboard(IStructuredSelection selection, CapabilityLabelProvider lp, + StringBuilder clipboardContent) { + + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + Object element = iterator.next(); + + if (element instanceof Capability cap) { + + clipboardContent.append(lp.getToolTipText(cap)); + + if (iterator.hasNext()) { + clipboardContent.append(System.lineSeparator()); + } + + } else { + clipboardContent.append(element.toString()); + } + + if (iterator.hasNext()) { + clipboardContent.append(System.lineSeparator()); + } + } + } + + private void updateReqsFilter(String filterString) { + reqsContentProvider.setFilter(filterString); + reqsViewer.refresh(); + updateReqsLabel(); + if (filterString != null) + reqsViewer.expandToLevel(1); + } + + private void updateCapsFilter(String filterString) { + capsContentProvider.setFilter(filterString); + capsViewer.refresh(); + updateCapsLabel(); + } + + private IAction createShowProblemCapsAction() { + String tooltipTextUnlocked = "Click to detect capabilities containing packages that have the same name but differ in the contained classes."; + + IAction toggleShowProblemCaps = new Action("showProblemCaps", IAction.AS_CHECK_BOX) { + @Override + public void runWithEvent(Event event) { + if (isChecked()) { + capsViewer.addFilter(filterShowCapsProblems); + this.setToolTipText( + "Showing capabilities containing packages that have the same name but differ in the contained classes."); + } else { + capsViewer.removeFilter(filterShowCapsProblems); + this.setToolTipText(tooltipTextUnlocked); + } + + updateCapsLabel(); + } + }; + toggleShowProblemCaps.setChecked(false); + toggleShowProblemCaps.setImageDescriptor(Resources.getImageDescriptor("/icons/warning_obj.svg")); + toggleShowProblemCaps + .setToolTipText(tooltipTextUnlocked); + return toggleShowProblemCaps; + } + + private IAction createToggleLockInputButton() { + String toolTipTextUnchecked = "Lock to current selection"; + + IAction toggleLockInput = new Action("lockInput", IAction.AS_CHECK_BOX) { + @Override + public void runWithEvent(Event event) { + inputLocked = isChecked(); + if (!inputLocked) { + this.setToolTipText(toolTipTextUnchecked); + executeAnalysis(); + } else { + this.setToolTipText("Current selection is locked"); + } + } + }; + toggleLockInput.setChecked(false); + toggleLockInput.setImageDescriptor(Resources.getImageDescriptor("lockedstate.svg")); + toggleLockInput.setToolTipText(toolTipTextUnchecked); + return toggleLockInput; + } + + private IAction createToggleHideSelfImportsButton() { + String toolTipTextShowAll = "Showing all requirements."; + String toolTipTextHideSelfImports = "Hiding resolved (including self-imported) requirements.\n\n" + + "Requirements that are resolved (exported and imported) within the set of selected bundles are hidden. Click to show all requirements."; + + IAction toggleShowSelfImports = new Action("showSelfImports", IAction.AS_CHECK_BOX) { + @Override + public void runWithEvent(Event event) { + if (isChecked()) { + reqsViewer.addFilter(hideSelfImportsFilter); + this.setToolTipText(toolTipTextHideSelfImports); + } else { + reqsViewer.removeFilter(hideSelfImportsFilter); + this.setToolTipText(toolTipTextShowAll); + } + updateReqsLabel(); + } + }; + toggleShowSelfImports.setChecked(false); + toggleShowSelfImports.setImageDescriptor(Resources.getImageDescriptor("/icons/bundle-importer-exporter.svg")); + toggleShowSelfImports.setToolTipText(toolTipTextShowAll); + return toggleShowSelfImports; + } + + private IAction createToggleHideOptionalReqsFilterButton() { + String toggleShowShowUnresolvedReqsFilterUnchecked = "Optional requirements are included. Click to hide optional requirements."; + + final IAction toggleShowShowUnresolvedReqsFilter = new Action("hideOptionalReqs", IAction.AS_CHECK_BOX) { + @Override + public void runWithEvent(Event event) { + if (isChecked()) { + reqsViewer.addFilter(hideOptionalRequirements); + this.setToolTipText("Optional requirements are now hidden"); + } else { + reqsViewer.removeFilter(hideOptionalRequirements); + this.setToolTipText(toggleShowShowUnresolvedReqsFilterUnchecked); + } + updateReqsLabel(); + } + }; + toggleShowShowUnresolvedReqsFilter.setChecked(false); + toggleShowShowUnresolvedReqsFilter.setImageDescriptor(Resources.getImageDescriptor("/icons/excludeMode_filter.svg")); + toggleShowShowUnresolvedReqsFilter.setToolTipText(toggleShowShowUnresolvedReqsFilterUnchecked); + return toggleShowShowUnresolvedReqsFilter; + } +}