|
39 | 39 | import java.nio.file.Files; |
40 | 40 | import java.util.ArrayList; |
41 | 41 | import java.util.Arrays; |
| 42 | +import java.util.Collection; |
42 | 43 | import java.util.Collections; |
43 | 44 | import java.util.IdentityHashMap; |
44 | 45 | import java.util.Iterator; |
|
84 | 85 | import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; |
85 | 86 | import org.eclipse.jface.viewers.ISelection; |
86 | 87 | import org.eclipse.jface.viewers.IStructuredSelection; |
| 88 | +import org.eclipse.jface.viewers.TreePath; |
87 | 89 | import org.eclipse.jface.viewers.TreeViewer; |
88 | 90 | import org.eclipse.jface.viewers.Viewer; |
89 | 91 | import org.eclipse.jface.viewers.ViewerDropAdapter; |
|
151 | 153 | import aQute.bnd.build.Workspace; |
152 | 154 | import aQute.bnd.exceptions.Exceptions; |
153 | 155 | import aQute.bnd.http.HttpClient; |
| 156 | +import aQute.bnd.osgi.resource.FilterParser.PackageExpression; |
| 157 | +import aQute.bnd.osgi.resource.ResourceUtils; |
154 | 158 | import aQute.bnd.service.Actionable; |
155 | 159 | import aQute.bnd.service.Refreshable; |
156 | 160 | import aQute.bnd.service.Registry; |
@@ -198,6 +202,9 @@ public void workspaceOfflineChanged(boolean offline) { |
198 | 202 | private final IObservableValue<String> workspaceName = new WritableValue<>(); |
199 | 203 | private final IObservableValue<String> workspaceDescription = new WritableValue<>(); |
200 | 204 |
|
| 205 | + private Object[] lastExpandedElements; |
| 206 | + private TreePath[] lastExpandedPaths; |
| 207 | + |
201 | 208 | @Override |
202 | 209 | public void createPartControl(final Composite parent) { |
203 | 210 | // CREATE CONTROLS |
@@ -601,11 +608,28 @@ boolean addFilesToRepository(RepositoryPlugin repo, File[] files) { |
601 | 608 | } |
602 | 609 |
|
603 | 610 | private void updatedFilter(String filterString) { |
604 | | - contentProvider.setFilter(filterString); |
605 | | - viewer.refresh(); |
606 | | - if (filterString != null) { |
607 | | - viewer.expandToLevel(2); |
608 | | - } |
| 611 | + viewer.getTree() |
| 612 | + .setRedraw(false); |
| 613 | + |
| 614 | + try { |
| 615 | + if (filterString == null || filterString.isEmpty()) { |
| 616 | + // Restore previous state when clearing filter |
| 617 | + contentProvider.setFilter(null); |
| 618 | + viewer.refresh(); // Required to clear filter |
| 619 | + restoreExpansionState(); |
| 620 | + viewer.refresh(); |
| 621 | + } else { |
| 622 | + // Save state before applying new filter |
| 623 | + saveExpansionState(); |
| 624 | + contentProvider.setFilter(filterString); |
| 625 | + viewer.refresh(); |
| 626 | + viewer.expandToLevel(2); |
| 627 | + } |
| 628 | + |
| 629 | + } finally { |
| 630 | + viewer.getTree() |
| 631 | + .setRedraw(true); |
| 632 | + } |
609 | 633 | } |
610 | 634 |
|
611 | 635 | void createActions() { |
@@ -1209,10 +1233,78 @@ private void addCopyToClipboardSubMenueEntries(Actionable act, final RepositoryP |
1209 | 1233 |
|
1210 | 1234 | if ((act instanceof Repository) || (act instanceof RepositoryPlugin)) { |
1211 | 1235 | hmenu.add(createContextMenueCopyInfoRepo(act, rp, clipboard)); |
| 1236 | + hmenu.add(createContextMenueCopyBundlesWithSelfImports(act, rp, clipboard)); |
1212 | 1237 | } |
1213 | 1238 |
|
1214 | 1239 | } |
1215 | 1240 |
|
| 1241 | + |
| 1242 | + private HierarchicalLabel<Action> createContextMenueCopyBundlesWithSelfImports(Actionable act, final RepositoryPlugin rp, |
| 1243 | + final Clipboard clipboard) { |
| 1244 | + return new HierarchicalLabel<Action>("Copy to clipboard :: Bundles with substitution packages (self-imports)", |
| 1245 | + (label) -> createAction(label.getLeaf(), |
| 1246 | + "Add list of bundles containing packages which are imported and exported in their Manifest.", true, |
| 1247 | + false, rp, () -> { |
| 1248 | + |
| 1249 | + final StringBuilder sb = new StringBuilder( |
| 1250 | + "Shows list of bundles in the repository '" + rp.getName() |
| 1251 | + + "' containing substitution packages / self-imports (i.e. same package imported and exported) in their Manifest. \n" |
| 1252 | + + "Note: a missing version range can cause wiring / resolution problems.\n" |
| 1253 | + + "See https://docs.osgi.org/specification/osgi.core/8.0.0/framework.module.html#i3238802 " |
| 1254 | + + "and https://docs.osgi.org/specification/osgi.core/8.0.0/framework.module.html#framework.module-import.export.same.package " |
| 1255 | + + "for more information." |
| 1256 | + + "\n\n"); |
| 1257 | + |
| 1258 | + for (RepositoryBundleVersion rpv : contentProvider.allRepoBundleVersions(rp)) { |
| 1259 | + org.osgi.resource.Resource r = rpv.getResource(); |
| 1260 | + Collection<PackageExpression> selfImports = ResourceUtils |
| 1261 | + .getSubstitutionPackages(r); |
| 1262 | + |
| 1263 | + if (!selfImports.isEmpty()) { |
| 1264 | + long numWithoutRange = selfImports.stream() |
| 1265 | + .filter(pckExp -> pckExp.getRangeExpression() == null) |
| 1266 | + .count(); |
| 1267 | + |
| 1268 | + // Main package information |
| 1269 | + sb.append(r.toString()) |
| 1270 | + .append("\n"); |
| 1271 | + sb.append(" Substitution packages: ") |
| 1272 | + .append(selfImports.size()); |
| 1273 | + |
| 1274 | + // Additional information about packages without |
| 1275 | + // version range |
| 1276 | + if (numWithoutRange > 0) { |
| 1277 | + sb.append(" (") |
| 1278 | + .append(numWithoutRange) |
| 1279 | + .append(" without version range)"); |
| 1280 | + } |
| 1281 | + sb.append("\n"); |
| 1282 | + |
| 1283 | + // List of substitution packages |
| 1284 | + sb.append(" [\n"); |
| 1285 | + for (PackageExpression pckExp : selfImports) { |
| 1286 | + sb.append(" ") |
| 1287 | + .append(pckExp.toString()) |
| 1288 | + .append(",\n"); |
| 1289 | + } |
| 1290 | + // Remove the last comma and newline |
| 1291 | + if (!selfImports.isEmpty()) { |
| 1292 | + sb.setLength(sb.length() - 2); |
| 1293 | + } |
| 1294 | + sb.append("\n ]\n\n"); |
| 1295 | + } |
| 1296 | + |
| 1297 | + } |
| 1298 | + |
| 1299 | + if (sb.isEmpty()) { |
| 1300 | + clipboard.copy("-Empty-"); |
| 1301 | + } else { |
| 1302 | + clipboard.copy(sb.toString()); |
| 1303 | + } |
| 1304 | + |
| 1305 | + })); |
| 1306 | + } |
| 1307 | + |
1216 | 1308 | private HierarchicalLabel<Action> createContextMenueCopyInfoRepo(Actionable act, final RepositoryPlugin rp, |
1217 | 1309 | final Clipboard clipboard) { |
1218 | 1310 | return new HierarchicalLabel<Action>("Copy to clipboard :: Copy info", (label) -> createAction(label.getLeaf(), |
@@ -1330,6 +1422,21 @@ private void handleOpenAdvancedSearch(Event event) { |
1330 | 1422 | } |
1331 | 1423 | } |
1332 | 1424 |
|
| 1425 | + private void saveExpansionState() { |
| 1426 | + lastExpandedElements = viewer.getExpandedElements(); |
| 1427 | + lastExpandedPaths = viewer.getExpandedTreePaths(); |
| 1428 | + } |
| 1429 | + |
| 1430 | + private void restoreExpansionState() { |
| 1431 | + if (lastExpandedElements != null) { |
| 1432 | + viewer.setExpandedElements(lastExpandedElements); |
| 1433 | + } |
| 1434 | + if (lastExpandedPaths != null) { |
| 1435 | + viewer.setExpandedTreePaths(lastExpandedPaths); |
| 1436 | + } |
| 1437 | + } |
| 1438 | + |
| 1439 | + |
1333 | 1440 | @Override |
1334 | 1441 | public Workspace getWorkspace() { |
1335 | 1442 | return this.workspace; |
|
0 commit comments