diff --git a/biz.aQute.bndlib.tests/test/aQute/bnd/osgi/resource/CapReqBuilderTest.java b/biz.aQute.bndlib.tests/test/aQute/bnd/osgi/resource/CapReqBuilderTest.java
index cc9d44616d..7238f1272c 100644
--- a/biz.aQute.bndlib.tests/test/aQute/bnd/osgi/resource/CapReqBuilderTest.java
+++ b/biz.aQute.bndlib.tests/test/aQute/bnd/osgi/resource/CapReqBuilderTest.java
@@ -8,6 +8,7 @@
import java.util.Map;
import org.junit.jupiter.api.Test;
+import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import aQute.bnd.header.Attrs;
@@ -18,6 +19,7 @@
public class CapReqBuilderTest {
+
@Test
public void testSimple() throws Exception {
CapabilityBuilder cb = new CapabilityBuilder("test");
@@ -173,4 +175,41 @@ public void testNonAliasedRequirementUnchanged() throws Exception {
assertTrue(original == unaliased, "unaliasing a normal requirement should return the original object");
}
+ @Test
+ public void testCapabilityToRequirementWithFilter() throws Exception {
+ CapReqBuilder cr = new CapReqBuilder("osgi.wiring.package");
+ Attrs attrs = new Attrs();
+ attrs.putTyped("bundle-symbolic-name", "org.example");
+ attrs.putTyped("bundle-version", "1.7.23");
+ attrs.putTyped("osgi.wiring.package", "org.example.foo");
+ attrs.putTyped("version", "1.7.23");
+ attrs.putTyped("bnd.hashes", "123, 456, 789");
+ cr.addAttributes(attrs);
+
+ Capability cap = cr.buildSyntheticCapability();
+
+ Requirement req = CapReqBuilder.createRequirementFromCapability(cap)
+ .buildSyntheticRequirement();
+
+ assertEquals("osgi.wiring.package", req.getNamespace());
+ assertEquals(
+ "(&(bundle-symbolic-name=org.example)(bundle-version>=1.7.23)(osgi.wiring.package=org.example.foo)(version>=1.7.23)(bnd.hashes=123, 456, 789))",
+ req.getDirectives()
+ .get("filter"));
+
+ Requirement reqFiltered = CapReqBuilder.createRequirementFromCapability(cap, (name) -> {
+ if (name.equals("bundle-symbolic-name") || name.equals("bundle-version") || name.equals("bnd.hashes")) {
+ return false;
+ }
+
+ return true;
+ })
+ .buildSyntheticRequirement();
+
+ assertEquals("osgi.wiring.package", reqFiltered.getNamespace());
+ assertEquals("(&(osgi.wiring.package=org.example.foo)(version>=1.7.23))",
+ reqFiltered.getDirectives()
+ .get("filter"));
+ }
+
}
diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReqBuilder.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReqBuilder.java
index 8491924591..a0fb415010 100644
--- a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReqBuilder.java
+++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/CapReqBuilder.java
@@ -12,6 +12,7 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
+import java.util.function.Predicate;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
@@ -709,6 +710,18 @@ static Object toVersions(Object value) {
}
public static RequirementBuilder createRequirementFromCapability(Capability capability) {
+ return createRequirementFromCapability(capability, null);
+ }
+
+ /**
+ * @param capability the capability to convert
+ * @param includeAttributesFilter predicate to control from the caller which
+ * attributes to include. if null
all attributes are
+ * included.
+ * @return a RequirementBuilder from the capability
+ */
+ public static RequirementBuilder createRequirementFromCapability(Capability capability,
+ Predicate includeAttributesFilter) {
final String namespace = capability.getNamespace();
RequirementBuilder builder = new RequirementBuilder(namespace);
final String versionAttrName = Optional.ofNullable(ResourceUtils.getVersionAttributeForNamespace(namespace))
@@ -720,6 +733,12 @@ public static RequirementBuilder createRequirementFromCapability(Capability capa
.append('&');
}
capAttributes.forEach((name, v) -> {
+
+ if (includeAttributesFilter != null && !includeAttributesFilter.test(name)) {
+ // skip this attribute
+ return;
+ }
+
if (v instanceof Version || name.equals(versionAttrName)
|| (namespace.equals(PackageNamespace.PACKAGE_NAMESPACE)
&& name.equals(AbstractWiringNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE))) {
diff --git a/bndtools.core/bnd.bnd b/bndtools.core/bnd.bnd
index 3d82451ca8..b6d32b7b2f 100644
--- a/bndtools.core/bnd.bnd
+++ b/bndtools.core/bnd.bnd
@@ -109,7 +109,9 @@ Export-Package: org.osgi.service.metatype.annotations
bndtools.utils;version=project;packages='*',\
slf4j.api,\
org.eclipse.ui.ide.application;version='1.3',\
- org.eclipse.ui.console
+ org.eclipse.ui.console,\
+ org.eclipse.e4.core.services,\
+ org.osgi.service.event
-testpath: \
slf4j.api,\
diff --git a/bndtools.core/src/bndtools/views/ViewEventTopics.java b/bndtools.core/src/bndtools/views/ViewEventTopics.java
new file mode 100644
index 0000000000..cfa4aec95c
--- /dev/null
+++ b/bndtools.core/src/bndtools/views/ViewEventTopics.java
@@ -0,0 +1,30 @@
+package bndtools.views;
+
+/**
+ * Topics for the EventBroker which is used for communication between different
+ * views. For example if View1 sends an event to View2 wants to open a dialog in
+ * the other view. .
+ */
+public enum ViewEventTopics {
+
+ /**
+ * Event to open the advances search of the repositories view.
+ */
+ REPOSITORIESVIEW_OPEN_ADVANCED_SEARCH("EVENT/RepositoriesView/openAdvancedSearch");
+
+ private String eventtype;
+
+ ViewEventTopics(String eventtype) {
+ this.eventtype = eventtype;
+ }
+
+ public String topic() {
+ return eventtype;
+ }
+
+ @Override
+ public String toString() {
+ return eventtype;
+ }
+
+}
diff --git a/bndtools.core/src/bndtools/views/repository/AdvancedSearchDialog.java b/bndtools.core/src/bndtools/views/repository/AdvancedSearchDialog.java
index d3c0c55198..e2e8eeeed8 100644
--- a/bndtools.core/src/bndtools/views/repository/AdvancedSearchDialog.java
+++ b/bndtools.core/src/bndtools/views/repository/AdvancedSearchDialog.java
@@ -22,6 +22,7 @@
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPersistable;
+import org.eclipse.ui.XMLMemento;
import org.osgi.resource.Requirement;
public class AdvancedSearchDialog extends TitleAreaDialog implements IPersistable {
@@ -156,4 +157,19 @@ public void restoreState(IMemento memento) {
}
}
+ /**
+ * @param req a requirement
+ * @return a state object for the given requirement to open advancedSearch
+ * prefilled in the "Other" tab.
+ */
+ public static IMemento toNamespaceSearchPanelMemento(Requirement req) {
+ XMLMemento memento = XMLMemento.createWriteRoot("search");
+ memento.putInteger("tabIndex", 2);
+ IMemento other = memento.createChild("tab", "Other");
+ other.putString("namespace", req.getNamespace());
+ other.putString("filter", req.getDirectives()
+ .get("filter"));
+ return memento;
+ }
+
}
diff --git a/bndtools.core/src/bndtools/views/repository/RepositoriesView.java b/bndtools.core/src/bndtools/views/repository/RepositoriesView.java
index 7582809db9..41530690fd 100644
--- a/bndtools.core/src/bndtools/views/repository/RepositoriesView.java
+++ b/bndtools.core/src/bndtools/views/repository/RepositoriesView.java
@@ -39,6 +39,7 @@
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.jface.action.Action;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IAction;
@@ -89,6 +90,7 @@
import org.eclipse.ui.part.ResourceTransfer;
import org.eclipse.ui.part.ViewPart;
import org.osgi.resource.Requirement;
+import org.osgi.service.event.Event;
import org.osgi.service.repository.Repository;
import aQute.bnd.build.Workspace;
@@ -118,6 +120,7 @@
import bndtools.utils.HierarchicalLabel;
import bndtools.utils.HierarchicalMenu;
import bndtools.utils.SelectionDragAdapter;
+import bndtools.views.ViewEventTopics;
import bndtools.wizards.workspace.AddFilesToRepositoryWizard;
import bndtools.wizards.workspace.WorkspaceSetupWizard;
@@ -143,6 +146,9 @@ public class RepositoriesView extends ViewPart implements RepositoriesViewRefres
private Action downloadAction;
private String advancedSearchState;
private Action offlineAction;
+ private final IEventBroker eventBroker = PlatformUI.getWorkbench()
+ .getService(IEventBroker.class);
+
private final BndPreferences prefs = new BndPreferences();
@@ -467,6 +473,9 @@ public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
IActionBars actionBars = getViewSite().getActionBars();
actionBars.setGlobalActionHandler(ActionFactory.REFRESH.getId(), refreshAction);
+ // Event subscription
+ eventBroker.subscribe(ViewEventTopics.REPOSITORIESVIEW_OPEN_ADVANCED_SEARCH.topic(),
+ event -> handleOpenAdvancedSearch(event));
}
private void configureOfflineAction() {
@@ -1221,4 +1230,22 @@ private HierarchicalLabel createContextMenueBsn(final RepositoryPlugin r
}));
}
+ private void handleOpenAdvancedSearch(Event event) {
+
+ if (event == null) {
+ return;
+ }
+
+ // Handle the event, open the dialog
+ if (event.getProperty(IEventBroker.DATA) instanceof Requirement req) {
+
+ // fill and open advanced search
+ advancedSearchState = AdvancedSearchDialog.toNamespaceSearchPanelMemento(req)
+ .toString();
+ advancedSearchAction.setChecked(true);
+ advancedSearchAction.run();
+
+ }
+ }
+
}
diff --git a/bndtools.core/src/bndtools/views/resolution/ResolutionView.java b/bndtools.core/src/bndtools/views/resolution/ResolutionView.java
index 9f18af264e..21d9f72f84 100644
--- a/bndtools.core/src/bndtools/views/resolution/ResolutionView.java
+++ b/bndtools.core/src/bndtools/views/resolution/ResolutionView.java
@@ -9,6 +9,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiConsumer;
import org.bndtools.core.ui.icons.Icons;
import org.eclipse.core.resources.IFile;
@@ -23,6 +24,7 @@
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;
@@ -36,19 +38,25 @@
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.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;
@@ -68,16 +76,19 @@
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;
@@ -97,6 +108,7 @@
import bndtools.tasks.ResourceCapReqLoader;
import bndtools.utils.PartAdapter;
import bndtools.utils.SelectionUtils;
+import bndtools.views.ViewEventTopics;
public class ResolutionView extends ViewPart implements ISelectionListener, IResourceChangeListener {
@@ -119,6 +131,9 @@ public class ResolutionView extends ViewPart implements ISelectionListener, IRes
private final Set filteredCapabilityNamespaces;
+ private final IEventBroker eventBroker = PlatformUI.getWorkbench()
+ .getService(IEventBroker.class);
+
public ResolutionView() {
filteredCapabilityNamespaces = Sets.of(IdentityNamespace.IDENTITY_NAMESPACE, HostNamespace.HOST_NAMESPACE);
loaders = Collections.emptySet();
@@ -200,6 +215,12 @@ public void createPartControl(Composite parent) {
ColumnViewerToolTipSupport.enableFor(reqsViewer);
reqsViewer.setLabelProvider(new RequirementWrapperLabelProvider(true));
reqsViewer.setContentProvider(new CapReqMapContentProvider());
+ 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());
@@ -226,6 +247,13 @@ public boolean select(Viewer viewer, Object parent, Object element) {
}
});
+ capsViewer.addDoubleClickListener(event -> handleCapsViewerDoubleClickEvent(event));
+
+ capsViewer.getTable()
+ .addKeyListener(createCopyToClipboardAdapter(capsViewer,
+ (IStructuredSelection selection1, StringBuilder clipboardContent1) -> capsCopyToClipboard(selection1,
+ (CapabilityLabelProvider) capsViewer.getLabelProvider(), clipboardContent1)));
+
hideSelfImportsFilter = new ViewerFilter() {
@Override
@@ -268,6 +296,11 @@ public boolean select(Viewer viewer, Object parentElement, Object element) {
selectionChanged(activePart, activeSelection);
}
+
+
+
+
+
private void openEditor(OpenEvent event) {
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
for (Iterator> iter = selection.iterator(); iter.hasNext();) {
@@ -597,4 +630,135 @@ public void dragSetData(DragSourceEvent event) {
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());
+ }
+ }
+ }
}