1 /******************************************************************************* 2 * Copyright (c) 2009, 2018 Tasktop Technologies and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Tasktop Technologies - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.equinox.internal.p2.ui.discovery.wizards; 15 16 import java.lang.reflect.InvocationTargetException; 17 import java.util.*; 18 import java.util.List; 19 import java.util.regex.Pattern; 20 import org.eclipse.core.runtime.*; 21 import org.eclipse.equinox.internal.p2.discovery.Catalog; 22 import org.eclipse.equinox.internal.p2.discovery.compatibility.SiteVerifier; 23 import org.eclipse.equinox.internal.p2.discovery.model.*; 24 import org.eclipse.equinox.internal.p2.discovery.util.CatalogCategoryComparator; 25 import org.eclipse.equinox.internal.p2.discovery.util.CatalogItemComparator; 26 import org.eclipse.equinox.internal.p2.ui.ProvUI; 27 import org.eclipse.equinox.internal.p2.ui.discovery.DiscoveryUi; 28 import org.eclipse.equinox.internal.p2.ui.discovery.util.*; 29 import org.eclipse.equinox.p2.engine.IProfile; 30 import org.eclipse.equinox.p2.metadata.IInstallableUnit; 31 import org.eclipse.equinox.p2.query.QueryUtil; 32 import org.eclipse.equinox.p2.ui.ProvisioningUI; 33 import org.eclipse.jface.dialogs.MessageDialog; 34 import org.eclipse.jface.operation.IRunnableContext; 35 import org.eclipse.jface.viewers.*; 36 import org.eclipse.jface.window.IShellProvider; 37 import org.eclipse.osgi.util.NLS; 38 import org.eclipse.swt.SWT; 39 import org.eclipse.swt.events.SelectionEvent; 40 import org.eclipse.swt.events.SelectionListener; 41 import org.eclipse.swt.widgets.*; 42 import org.eclipse.ui.statushandlers.StatusManager; 43 44 /** 45 * The main wizard page that allows users to select connectors that they wish to install. 46 * 47 * @author David Green 48 * @author Steffen Pingel 49 */ 50 public class CatalogViewer extends FilteredViewer { 51 52 protected static class CatalogContentProvider implements ITreeContentProvider { 53 54 private Catalog catalog; 55 56 private boolean hasCategories; 57 hasCategories()58 public boolean hasCategories() { 59 return hasCategories; 60 } 61 setHasCategories(boolean hasCategories)62 public void setHasCategories(boolean hasCategories) { 63 this.hasCategories = hasCategories; 64 } 65 66 @Override dispose()67 public void dispose() { 68 catalog = null; 69 } 70 getCatalog()71 public Catalog getCatalog() { 72 return catalog; 73 } 74 75 @Override getChildren(Object parentElement)76 public Object[] getChildren(Object parentElement) { 77 if (parentElement instanceof CatalogCategory) { 78 return ((CatalogCategory) parentElement).getItems().toArray(); 79 } 80 return null; 81 } 82 83 @Override getElements(Object inputElement)84 public Object[] getElements(Object inputElement) { 85 if (catalog != null) { 86 List<Object> elements = new ArrayList<>(); 87 if (hasCategories()) { 88 elements.addAll(catalog.getCategories()); 89 } 90 elements.addAll(catalog.getItems()); 91 return elements.toArray(new Object[0]); 92 } 93 return new Object[0]; 94 } 95 96 @Override getParent(Object element)97 public Object getParent(Object element) { 98 if (element instanceof CatalogCategory) { 99 return catalog; 100 } 101 if (element instanceof CatalogItem) { 102 return ((CatalogItem) element).getCategory(); 103 } 104 return null; 105 } 106 107 @Override hasChildren(Object element)108 public boolean hasChildren(Object element) { 109 if (element instanceof CatalogCategory) { 110 return ((CatalogCategory) element).getItems().size() > 0; 111 } 112 return false; 113 } 114 115 @Override inputChanged(Viewer viewer, Object oldInput, Object newInput)116 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 117 this.catalog = (Catalog) newInput; 118 } 119 120 } 121 122 private class Filter extends ViewerFilter { 123 Filter()124 public Filter() { 125 // constructor 126 } 127 128 @Override select(Viewer filteredViewer, Object parentElement, Object element)129 public boolean select(Viewer filteredViewer, Object parentElement, Object element) { 130 if (element instanceof CatalogItem) { 131 return doFilter((CatalogItem) element); 132 } else if (element instanceof CatalogCategory) { 133 // only show categories if at least one child is visible 134 CatalogCategory category = (CatalogCategory) element; 135 for (CatalogItem item : category.getItems()) { 136 if (doFilter(item)) { 137 return true; 138 } 139 } 140 return false; 141 } 142 return true; 143 } 144 145 } 146 147 private class FindFilter extends PatternFilter { 148 FindFilter()149 public FindFilter() { 150 // constructor 151 } 152 filterMatches(String text)153 private boolean filterMatches(String text) { 154 return text != null && wordMatches(text); 155 } 156 157 @Override getChildren(Object element)158 protected Object[] getChildren(Object element) { 159 if (element instanceof CatalogCategory) { 160 return ((CatalogCategory) element).getItems().toArray(); 161 } 162 return super.getChildren(element); 163 } 164 165 @Override isLeafMatch(Viewer filteredViewer, Object element)166 protected boolean isLeafMatch(Viewer filteredViewer, Object element) { 167 if (element instanceof CatalogItem) { 168 CatalogItem descriptor = (CatalogItem) element; 169 if (!(filterMatches(descriptor.getName()) || filterMatches(descriptor.getDescription()) || filterMatches(descriptor.getProvider()) || filterMatches(descriptor.getLicense()))) { 170 return false; 171 } 172 return true; 173 } 174 return false; 175 } 176 177 } 178 179 // private class ConnectorBorderPaintListener implements PaintListener { 180 // public void paintControl(PaintEvent e) { 181 // Composite composite = (Composite) e.widget; 182 // Rectangle bounds = composite.getBounds(); 183 // GC gc = e.gc; 184 // gc.setLineStyle(SWT.LINE_DOT); 185 // gc.drawLine(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y); 186 // } 187 // } 188 189 private static final int DEFAULT_HEIGHT = 250; 190 191 final Catalog catalog; 192 193 private final List<CatalogItem> checkedItems = new ArrayList<>(); 194 195 private boolean complete; 196 197 private final CatalogConfiguration configuration; 198 199 protected final IRunnableContext context; 200 201 boolean ignoreUpdates; 202 203 Set<String> installedFeatures; 204 205 DiscoveryResources resources; 206 207 private final SelectionProviderAdapter selectionProvider; 208 209 protected final IShellProvider shellProvider; 210 211 boolean showInstalled; 212 213 Button showInstalledCheckbox; 214 215 Set<Tag> visibleTags; 216 217 private boolean showCategories; 218 219 private CatalogContentProvider contentProvider; 220 CatalogViewer(Catalog catalog, IShellProvider shellProvider, IRunnableContext context, CatalogConfiguration configuration)221 public CatalogViewer(Catalog catalog, IShellProvider shellProvider, IRunnableContext context, CatalogConfiguration configuration) { 222 Assert.isNotNull(catalog); 223 Assert.isNotNull(shellProvider); 224 Assert.isNotNull(context); 225 Assert.isNotNull(configuration); 226 this.catalog = catalog; 227 this.shellProvider = shellProvider; 228 this.context = context; 229 this.configuration = configuration; 230 this.selectionProvider = new SelectionProviderAdapter(); 231 this.showInstalled = configuration.isShowInstalled(); 232 this.showCategories = configuration.isShowCategories(); 233 if (configuration.getSelectedTags() != null) { 234 this.visibleTags = new HashSet<>(configuration.getSelectedTags()); 235 } else { 236 this.visibleTags = new HashSet<>(); 237 } 238 setMinimumHeight(DEFAULT_HEIGHT); 239 setComplete(false); 240 } 241 addSelectionChangedListener(ISelectionChangedListener listener)242 public void addSelectionChangedListener(ISelectionChangedListener listener) { 243 selectionProvider.addSelectionChangedListener(listener); 244 } 245 catalogUpdated(boolean wasCancelled, boolean wasError)246 protected void catalogUpdated(boolean wasCancelled, boolean wasError) { 247 if (catalog != null && !wasCancelled && !wasError) { 248 doCheckCatalog(); 249 } 250 viewer.setInput(catalog); 251 selectionProvider.setSelection(StructuredSelection.EMPTY); 252 } 253 doCheckCatalog()254 protected void doCheckCatalog() { 255 int categoryWithConnectorCount = 0; 256 for (CatalogCategory category : catalog.getCategories()) { 257 categoryWithConnectorCount += category.getItems().size(); 258 } 259 if (categoryWithConnectorCount == 0) { 260 // nothing was discovered: notify the user 261 MessageDialog.openWarning(getShell(), Messages.ConnectorDiscoveryWizardMainPage_noConnectorsFound, Messages.ConnectorDiscoveryWizardMainPage_noConnectorsFound_description); 262 } 263 } 264 computeStatus(InvocationTargetException e, String message)265 protected IStatus computeStatus(InvocationTargetException e, String message) { 266 Throwable cause = e.getCause(); 267 if (cause.getMessage() != null) { 268 message = NLS.bind(Messages.ConnectorDiscoveryWizardMainPage_message_with_cause, message, cause.getMessage()); 269 } 270 return new Status(IStatus.ERROR, DiscoveryUi.ID_PLUGIN, message, e); 271 } 272 createPattern(String filterText)273 protected Pattern createPattern(String filterText) { 274 if (filterText == null || filterText.length() == 0) { 275 return null; 276 } 277 String regex = filterText; 278 regex.replace("\\", "\\\\").replace("?", ".").replace("*", ".*?"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ 279 return Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); 280 } 281 282 @Override doCreateFilter()283 protected PatternFilter doCreateFilter() { 284 return new FindFilter(); 285 } 286 287 @Override doCreateHeaderControls(Composite parent)288 protected void doCreateHeaderControls(Composite parent) { 289 if (configuration.isShowInstalledFilter()) { 290 showInstalledCheckbox = new Button(parent, SWT.CHECK); 291 showInstalledCheckbox.setSelection(showInstalled); 292 showInstalledCheckbox.setText(Messages.DiscoveryViewer_Show_Installed); 293 showInstalledCheckbox.addSelectionListener(new SelectionListener() { 294 295 @Override 296 public void widgetDefaultSelected(SelectionEvent e) { 297 widgetSelected(e); 298 } 299 300 @Override 301 public void widgetSelected(SelectionEvent e) { 302 if (ignoreUpdates) { 303 return; 304 } 305 306 ignoreUpdates = true; 307 setShowInstalled(showInstalledCheckbox.getSelection()); 308 ignoreUpdates = false; 309 } 310 }); 311 } 312 if (configuration.isShowTagFilter()) { 313 for (final Tag tag : catalog.getTags()) { 314 final Button checkbox = new Button(parent, SWT.CHECK); 315 checkbox.setSelection(visibleTags.contains(tag)); 316 checkbox.setText(tag.getLabel()); 317 checkbox.addSelectionListener(new SelectionListener() { 318 @Override 319 public void widgetDefaultSelected(SelectionEvent e) { 320 widgetSelected(e); 321 } 322 323 @Override 324 public void widgetSelected(SelectionEvent e) { 325 boolean selection = checkbox.getSelection(); 326 if (selection) { 327 visibleTags.add(tag); 328 } else { 329 visibleTags.remove(tag); 330 } 331 refresh(); 332 } 333 }); 334 } 335 } 336 } 337 338 @Override doCreateViewer(Composite container)339 protected StructuredViewer doCreateViewer(Composite container) { 340 341 StructuredViewer _viewer = new ControlListViewer(container, SWT.BORDER) { 342 @Override 343 protected ControlListItem<?> doCreateItem(Composite parent, Object element) { 344 return doCreateViewerItem(parent, element); 345 } 346 }; 347 contentProvider = doCreateContentProvider(); 348 contentProvider.setHasCategories(isShowCategories()); 349 _viewer.setContentProvider(contentProvider); 350 _viewer.setComparator(new ViewerComparator() { 351 CatalogCategoryComparator categoryComparator = new CatalogCategoryComparator(); 352 353 CatalogItemComparator itemComparator = new CatalogItemComparator(); 354 355 @Override 356 public int compare(Viewer v, Object o1, Object o2) { 357 CatalogCategory cat1 = getCategory(o1); 358 CatalogCategory cat2 = getCategory(o2); 359 360 // FIXME filter uncategorized items? 361 if (cat1 == null) { 362 return (cat2 != null) ? 1 : 0; 363 } else if (cat2 == null) { 364 return 1; 365 } 366 367 int i = categoryComparator.compare(cat1, cat2); 368 if (i == 0) { 369 if (o1 instanceof CatalogCategory) { 370 return -1; 371 } 372 if (o2 instanceof CatalogCategory) { 373 return 1; 374 } 375 if (cat1 == cat2 && o1 instanceof CatalogItem && o2 instanceof CatalogItem) { 376 return itemComparator.compare((CatalogItem) o1, (CatalogItem) o2); 377 } 378 return super.compare(v, o1, o2); 379 } 380 return i; 381 } 382 383 private CatalogCategory getCategory(Object o) { 384 if (o instanceof CatalogCategory) { 385 return (CatalogCategory) o; 386 } 387 if (o instanceof CatalogItem) { 388 return ((CatalogItem) o).getCategory(); 389 } 390 return null; 391 } 392 }); 393 394 resources = new DiscoveryResources(container.getDisplay()); 395 _viewer.getControl().addDisposeListener(e -> { 396 resources.dispose(); 397 if (catalog != null) 398 catalog.dispose(); 399 }); 400 _viewer.addFilter(new Filter()); 401 return _viewer; 402 } 403 doCreateContentProvider()404 protected CatalogContentProvider doCreateContentProvider() { 405 return new CatalogContentProvider(); 406 } 407 408 @SuppressWarnings({"rawtypes", "unchecked"}) doCreateViewerItem(Composite parent, Object element)409 protected ControlListItem<?> doCreateViewerItem(Composite parent, Object element) { 410 if (element instanceof CatalogItem) { 411 return new DiscoveryItem(parent, SWT.NONE, resources, shellProvider, (CatalogItem) element, this); 412 } else if (element instanceof CatalogCategory) { 413 return new CategoryItem(parent, SWT.NONE, resources, (CatalogCategory) element); 414 } 415 return null; 416 } 417 doFilter(CatalogItem item)418 protected boolean doFilter(CatalogItem item) { 419 if (!showInstalled && item.isInstalled()) { 420 return false; 421 } 422 423 if (!isTagVisible(item)) { 424 return false; 425 } 426 427 for (CatalogFilter filter : configuration.getFilters()) { 428 if (!filter.select(item)) { 429 return false; 430 } 431 } 432 433 return true; 434 } 435 getCatalog()436 public Catalog getCatalog() { 437 return catalog; 438 } 439 getCheckedItems()440 public List<CatalogItem> getCheckedItems() { 441 return new ArrayList<>(checkedItems); 442 } 443 getConfiguration()444 public CatalogConfiguration getConfiguration() { 445 return configuration; 446 } 447 getInstalledFeatures(IProgressMonitor monitor)448 protected Set<String> getInstalledFeatures(IProgressMonitor monitor) { 449 Set<String> features = new HashSet<>(); 450 IProfile profile = ProvUI.getProfileRegistry(ProvisioningUI.getDefaultUI().getSession()).getProfile(ProvisioningUI.getDefaultUI().getProfileId()); 451 if (profile != null) { 452 for (IInstallableUnit unit : profile.available(QueryUtil.createIUGroupQuery(), monitor)) { 453 features.add(unit.getId()); 454 } 455 } 456 return features; 457 } 458 getResources()459 protected DiscoveryResources getResources() { 460 return resources; 461 } 462 getSelection()463 public IStructuredSelection getSelection() { 464 return (IStructuredSelection) selectionProvider.getSelection(); 465 } 466 getShell()467 private Shell getShell() { 468 return shellProvider.getShell(); 469 } 470 isComplete()471 public boolean isComplete() { 472 return complete; 473 } 474 isShowCategories()475 public boolean isShowCategories() { 476 return showCategories; 477 } 478 isShowInstalled()479 public boolean isShowInstalled() { 480 return showInstalled; 481 } 482 isTagVisible(CatalogItem item)483 private boolean isTagVisible(CatalogItem item) { 484 if (!configuration.isShowTagFilter()) { 485 return true; 486 } 487 for (Tag selectedTag : visibleTags) { 488 for (Tag tag : item.getTags()) { 489 if (tag.equals(selectedTag)) { 490 return true; 491 } 492 } 493 } 494 return false; 495 } 496 modifySelection(final CatalogItem connector, boolean selected)497 protected void modifySelection(final CatalogItem connector, boolean selected) { 498 modifySelectionInternal(connector, selected); 499 updateState(); 500 } 501 modifySelectionInternal(final CatalogItem connector, boolean selected)502 private void modifySelectionInternal(final CatalogItem connector, boolean selected) { 503 connector.setSelected(selected); 504 if (selected) { 505 checkedItems.add(connector); 506 } else { 507 checkedItems.remove(connector); 508 } 509 } 510 postDiscovery()511 protected void postDiscovery() { 512 for (CatalogItem connector : catalog.getItems()) { 513 connector.setInstalled(installedFeatures != null && installedFeatures.containsAll(connector.getInstallableUnits())); 514 } 515 } 516 refresh()517 public void refresh() { 518 if (viewer != null && !viewer.getControl().isDisposed()) { 519 viewer.refresh(); 520 } 521 } 522 removeSelectionChangedListener(ISelectionChangedListener listener)523 public void removeSelectionChangedListener(ISelectionChangedListener listener) { 524 selectionProvider.removeSelectionChangedListener(listener); 525 } 526 setComplete(boolean complete)527 public void setComplete(boolean complete) { 528 this.complete = complete; 529 } 530 setSelection(IStructuredSelection selection)531 public void setSelection(IStructuredSelection selection) { 532 Set<CatalogItem> selected = new HashSet<>(); 533 for (Object descriptor : selection.toArray()) { 534 if (descriptor instanceof CatalogItem) { 535 selected.add((CatalogItem) descriptor); 536 } 537 } 538 for (CatalogItem connector : catalog.getItems()) { 539 modifySelectionInternal(connector, selected.contains(connector)); 540 } 541 updateState(); 542 } 543 setShowInstalled(boolean showInstalled)544 public void setShowInstalled(boolean showInstalled) { 545 this.showInstalled = showInstalled; 546 showInstalledCheckbox.setSelection(showInstalled); 547 refresh(); 548 } 549 setShowCategories(boolean showCategories)550 public void setShowCategories(boolean showCategories) { 551 this.showCategories = showCategories; 552 if (contentProvider != null) { 553 contentProvider.setHasCategories(showCategories); 554 refresh(); 555 } 556 } 557 updateCatalog()558 public void updateCatalog() { 559 boolean wasCancelled = false; 560 boolean wasError = false; 561 try { 562 final IStatus[] result = new IStatus[1]; 563 context.run(true, true, monitor -> { 564 if (installedFeatures == null) { 565 installedFeatures = getInstalledFeatures(monitor); 566 } 567 568 result[0] = catalog.performDiscovery(monitor); 569 if (monitor.isCanceled()) { 570 throw new InterruptedException(); 571 } 572 573 postDiscovery(); 574 }); 575 576 if (result[0] != null && !result[0].isOK()) { 577 StatusManager.getManager().handle(result[0], StatusManager.SHOW | StatusManager.BLOCK | StatusManager.LOG); 578 wasError = true; 579 } 580 } catch (InvocationTargetException e) { 581 IStatus status = computeStatus(e, Messages.ConnectorDiscoveryWizardMainPage_unexpectedException); 582 StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.BLOCK | StatusManager.LOG); 583 wasError = true; 584 } catch (InterruptedException e) { 585 // cancelled by user so nothing to do here. 586 wasCancelled = true; 587 } 588 if (catalog != null) { 589 catalogUpdated(wasCancelled, wasError); 590 verifyUpdateSiteAvailability(); 591 } 592 // help UI tests 593 viewer.setData("discoveryComplete", "true"); //$NON-NLS-1$//$NON-NLS-2$ 594 } 595 verifyUpdateSiteAvailability()596 protected void verifyUpdateSiteAvailability() { 597 if (configuration.isVerifyUpdateSiteAvailability() && !catalog.getItems().isEmpty()) { 598 try { 599 context.run(true, true, monitor -> { 600 SiteVerifier verifier = new SiteVerifier(catalog); 601 verifier.verifySiteAvailability(monitor); 602 }); 603 } catch (InvocationTargetException e) { 604 IStatus status = computeStatus(e, Messages.ConnectorDiscoveryWizardMainPage_unexpectedException); 605 StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.BLOCK | StatusManager.LOG); 606 } catch (InterruptedException e) { 607 // cancelled by user so nothing to do here. 608 } 609 } 610 } 611 updateState()612 private void updateState() { 613 setComplete(!checkedItems.isEmpty()); 614 selectionProvider.setSelection(new StructuredSelection(getCheckedItems())); 615 } 616 617 } 618