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