/******************************************************************************* * Copyright (c) 2017 OPCoach. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * OPCoach - initial API and implementation * Jérôme FALLEVOZ (Tech Advantage): export csv file *******************************************************************************/ package com.opcoach.e34tools.views; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.StringTokenizer; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.Platform; import org.eclipse.e4.core.di.annotations.Optional; import org.eclipse.e4.ui.di.Focus; import org.eclipse.e4.ui.services.IServiceConstants; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.osgi.service.resolver.BundleDescription; import org.eclipse.pde.core.plugin.IPluginModelBase; import org.eclipse.pde.internal.core.PDECore; import org.eclipse.pde.internal.core.ifeature.IFeatureModel; import org.eclipse.pde.internal.core.ifeature.IFeaturePlugin; import org.eclipse.pde.internal.core.ischema.ISchemaElement; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; 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.FileDialog; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import com.opcoach.e34tools.Migration34Activator; import com.opcoach.e34tools.io.CvsExport; import com.opcoach.e34tools.model.CustomExtensionPoint; @SuppressWarnings("restriction") public class MigrationStatsE4View { private static final String COUNT_COLUMN = "Count"; private static final String HELP_TXT = "This window displays statistics regarding an E4 migration." + "\n\nUSAGE\n-------" + "\nSelect one plugin or several plugins in your package explorer and get statistics." + "\nYOU MUST IMPORT THE latest org.eclipse.ui plugin (version 4.X) in your workspace" + "\n\nCONTENTS\n----------" + "\nThe first column contains the list of org.eclipse.ui extension points." + "\nMiddle columns contains sums of extensions point occurency" + "\nLast column displays the sum." + "\nDeprecated extension points or elements are displayed in red." + "\nThe upper dashboards summarizes information" + "\nThe more red you have, the more difficult will be your migration." + "\n\nFILTERS\n-------" + "\nThe filter buttons can filter lines that have a nul total count, or deprecated elements"; private MigrationDataComparator comparator; private final Map<IPluginModelBase, TreeViewerColumn> columnsCache = new HashMap<IPluginModelBase, TreeViewerColumn>(); private TreeViewerColumn countCol = null; /** * List of prefix to be removed in column name : with 'org.eclipse' it will * display only : emf.ecore in the column name */ private Collection<String> prefixFilters = new ArrayList<String>(); private String prefixFiltersString = ""; private final Map<String, Label> countLabels = new HashMap<String, Label>(); private PluginDataProvider provider; private CountDataProvider countProvider; // selected plugins must not appear twice ! (Fix issue #8) private HashSet<IPluginModelBase> currentSelectedPlugins; private Collection<IPluginModelBase> displayedPlugins = Collections.emptyList(); private TreeViewer tv; private FilterStats filter; private Group maindashboard; private Group deprdashboard; @Inject public MigrationStatsE4View(Composite parent) { parent.setLayout(new GridLayout(2, false)); provider = new PluginDataProvider(); createDashBoard(parent); createDeprecatedDashBoard(parent); updateDashboard(); createToolBar(parent); tv = new TreeViewer(parent); tv.setContentProvider(provider); tv.setLabelProvider(provider); tv.setInput(Platform.getExtensionRegistry()); final Tree cTree = tv.getTree(); cTree.setHeaderVisible(true); cTree.setLinesVisible(true); cTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); // hspan=2 tv.setInput("Foo"); // getElements starts alone // Create the first column, containing extension points TreeViewerColumn epCol = new TreeViewerColumn(tv, SWT.NONE); epCol.getColumn().setWidth(300); epCol.getColumn().setText("Extension Points"); PluginDataProvider labelProvider = new PluginDataProvider(); epCol.setLabelProvider(labelProvider); epCol.getColumn().setToolTipText("Extension points defined in org.eclipse.ui to be migrated"); epCol.getColumn().addSelectionListener(getHeaderSelectionAdapter(tv, epCol.getColumn(), 0, labelProvider)); comparator = new MigrationDataComparator(0, labelProvider); tv.setComparator(comparator); // Set the filters. filter = new FilterStats(); tv.setFilters(new ViewerFilter[] { filter }); // Open all the tree tv.expandAll(); ColumnViewerToolTipSupport.enableFor(tv); parent.layout(); } private void createToolBar(Composite parent) { ToolBar tb = new ToolBar(parent, SWT.FLAT | SWT.LEFT); ToolItem export = new ToolItem(tb, SWT.PUSH); export.setImage(Migration34Activator.getDefault().getImageRegistry().get(Migration34Activator.IMG_EXPORT)); export.setToolTipText("Export"); export.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { export(tv); } }); ToolItem expandAll = new ToolItem(tb, SWT.PUSH); expandAll.setImage(Migration34Activator.getDefault().getImageRegistry().get(Migration34Activator.IMG_EXPAND)); expandAll.setToolTipText("Expand all nodes"); expandAll.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { tv.expandAll(); } }); ToolItem collapseAll = new ToolItem(tb, SWT.PUSH); collapseAll .setImage(Migration34Activator.getDefault().getImageRegistry().get(Migration34Activator.IMG_COLLAPSE)); collapseAll.setToolTipText("Collapse nodes"); collapseAll.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { tv.collapseAll(); } }); // Add filters... new ToolItem(tb, SWT.SEPARATOR); ToolItem ti = new ToolItem(tb, SWT.CHECK | SWT.BORDER); ti.setImage(Migration34Activator.getDefault().getImageRegistry().get(Migration34Activator.IMG_FILTER)); ti.setToolTipText("Filter empty lines"); ti.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // filter empty lines... filter.setFilterEmptyLines(!filter.getFilterEmptyLines()); tv.refresh(); } }); // Create filter deprecated ToolItem item = new ToolItem(tb, SWT.DROP_DOWN); item.setImage(Migration34Activator.getDefault().getImageRegistry().get(Migration34Activator.IMG_DEPRECATED)); item.setToolTipText("Filter here deprecated extensions points"); DropdownSelectionListener dslistener = new DropdownSelectionListener(item); dslistener.add(FilterStats.SHOW_ALL); dslistener.add(FilterStats.REMOVE_DEPRECATED); dslistener.add(FilterStats.ONLY_DEPRECATED); item.addSelectionListener(dslistener); // Create the prefix filter button ToolItem prefixTitle = new ToolItem(tb, SWT.PUSH | SWT.BORDER); prefixTitle.setImage( Migration34Activator.getDefault().getImageRegistry().get(Migration34Activator.IMG_PREFIX_COLUMNTITLE)); prefixTitle.setToolTipText("Enter here prefixes to reduce the size of column titles"); prefixTitle.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // ask for the prefix to remove for column names askForColumnPrefixes(); } }); // Add CustomExtension button new ToolItem(tb, SWT.SEPARATOR); ToolItem extItem = new ToolItem(tb, SWT.PUSH | SWT.BORDER); extItem.setImage(Migration34Activator.getDefault().getImageRegistry().get(Migration34Activator.IMG_EXTENSION)); extItem.setToolTipText("Add a custom extensions points"); extItem.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { askForAdditionalExtension(); } }); // Add help button new ToolItem(tb, SWT.SEPARATOR); ToolItem th = new ToolItem(tb, SWT.PUSH | SWT.BORDER); th.setImage(Migration34Activator.getDefault().getImageRegistry().get(Migration34Activator.IMG_HELP)); th.setToolTipText(HELP_TXT); th.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { MessageDialog.openInformation(Display.getCurrent().getActiveShell(), "Help", HELP_TXT); } }); } private void askForColumnPrefixes() { // filter empty lines... InputDialog dlg = new InputDialog(Display.getCurrent().getActiveShell(), "Column name prefix filters", "Enter a comma separated list of prefix filters to apply on column names", prefixFiltersString, null); if (dlg.open() == Dialog.OK) { computePrefixFilterList(dlg.getValue()); for (IPluginModelBase p : columnsCache.keySet()) { TreeViewerColumn tc = columnsCache.get(p); String newTitle = getColumnName(p); if (!tc.getColumn().isDisposed() && !tc.getColumn().getText().equals(newTitle)) { tc.getColumn().setText(newTitle); tc.getColumn().pack(); } } tv.refresh(); } } private void askForAdditionalExtension() { // ask for additional extension InputDialog dlg = new InputDialog(Display.getCurrent().getActiveShell(), "Custom extension statistics", "Give the extension point as you want to follow in statistic", "", null); if (dlg.open() == Dialog.OK) { String value = dlg.getValue(); E4MigrationRegistry.getDefault().addCustomExtensionPointIdentifier(value); tv.refresh(); } } private void computePrefixFilterList(String value) { prefixFiltersString = value; StringTokenizer stk = new StringTokenizer(value, ","); prefixFilters.clear(); if (prefixFiltersString.trim().length() == 0) return; do { String s = stk.nextToken(); prefixFilters.add(s.trim()); } while (stk.hasMoreTokens()); } protected void export(TreeViewer tv) { Shell parent = Display.getCurrent().getActiveShell(); FileDialog dialog = new FileDialog(parent, SWT.SAVE); dialog.setFilterExtensions(new String[] { "*.csv" }); dialog.setOverwrite(true); String filePath = dialog.open(); if (filePath != null) { Collection<IExtensionPoint> extPts = E4MigrationRegistry.getDefault().getExtensionsToParse(); Collection<CustomExtensionPoint> cExt = E4MigrationRegistry.getDefault().getCustomExtensionToParse(); CvsExport cvsE = new CvsExport(); try { cvsE.save(filePath, extPts, cExt, displayedPlugins); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } private void createDashBoard(Composite parent) { maindashboard = new Group(parent, SWT.BORDER); maindashboard.setLayout(new FillLayout()); maindashboard.setText("Usual Extension points"); maindashboard.setLayout(new GridLayout(2, false)); createCounter(maindashboard, "views/view : ", "org.eclipse.ui.views/view"); createCounter(maindashboard, "editors/editor : ", "org.eclipse.ui.editors/editor"); createCounter(maindashboard, "preferencePages/page : ", "org.eclipse.ui.preferencePages/page"); createCounter(maindashboard, "propertyPages/page : ", "org.eclipse.ui.propertyPages/page"); createCounter(maindashboard, "commands/command : ", "org.eclipse.ui.commands/command"); createCounter(maindashboard, "handlers/handler : ", "org.eclipse.ui.handlers/handler"); createCounter(maindashboard, "menus/menuContribution : ", "org.eclipse.ui.menus/menuContribution"); createCounter(maindashboard, "newWizards/wizard : ", "org.eclipse.ui.newWizards/wizard"); createCounter(maindashboard, "importWizards/wizard : ", "org.eclipse.ui.importWizards/wizard"); createCounter(maindashboard, "exportWizards/wizard : ", "org.eclipse.ui.exportWizards/wizard"); maindashboard.pack(); } private void createDeprecatedDashBoard(Composite parent) { deprdashboard = new Group(parent, SWT.BORDER); deprdashboard.setLayout(new FillLayout()); deprdashboard.setText("Deprecated Extension points"); deprdashboard.setLayout(new GridLayout(4, false)); for (IExtensionPoint iep : E4MigrationRegistry.getDefault().getExtensionsToParse()) { // Search for deprecated elements. for (Object node : provider.getChildren(iep)) { if (node instanceof ISchemaElement) { ISchemaElement se = (ISchemaElement) node; if (se.isDeprecated()) createCounter(deprdashboard, iep.getSimpleIdentifier() + "/" + se.getName() + " : ", iep.getUniqueIdentifier() + "/" + se.getName()); } } } deprdashboard.pack(); } /** * Create the counter label and remember of it to compute it according to * selection * * @param parent * @param title * : the title for the counter * @param xpath * : the xpath to search for in the plugin xml : ex : views/view, * editors/editor must not give the full extension point name, * only simple name */ public void createCounter(Composite parent, String title, String xpath) { Label titleLabel = new Label(parent, SWT.NONE); titleLabel.setText(title); titleLabel.setToolTipText(xpath); Label valueLabel = new Label(parent, SWT.NONE); valueLabel.setText("???"); countLabels.put(xpath, valueLabel); } /** * Just update the contents of dashboard according to selected plugins */ private void updateDashboard() { E4MigrationRegistry reg = E4MigrationRegistry.getDefault(); for (String xpath : countLabels.keySet()) { int count = reg.countNumberOfExtensions(xpath, displayedPlugins); Label label = countLabels.get(xpath); label.setText("" + count); if (label.getParent() == deprdashboard) { // stand in the deprecated group.. set red if > 0 if (count > 0) { label.setForeground(provider.red); } else { label.setForeground(null); } } } maindashboard.pack(); deprdashboard.pack(); } private void createPluginColumns(IPluginModelBase pm) { // Add columns in the tree one column per selected plugin. // Create the first column for the key TreeViewerColumn col = new TreeViewerColumn(tv, SWT.NONE); TreeColumn swtCol = col.getColumn(); swtCol.setText(getColumnName(pm)); swtCol.setAlignment(SWT.CENTER); PluginDataProvider labelProvider = new PluginDataProvider(); labelProvider.setPlugin(pm); col.setLabelProvider(labelProvider); swtCol.setToolTipText(pm.getBundleDescription().getName()); swtCol.pack(); columnsCache.put(pm, col); } private String getColumnName(IPluginModelBase pm) { BundleDescription bundleDescription = pm.getBundleDescription(); String pluginName = (bundleDescription != null) ? bundleDescription.getName() : "NO NAME ???"; for (String prefix : prefixFilters) { if (pluginName.startsWith(prefix)) { if (prefix.length() < pluginName.length()) { pluginName = pluginName.substring(prefix.length()); // Adjust to remove the first '.' if present if ((pluginName.startsWith(".")) && pluginName.length() > 2) pluginName = pluginName.substring(1); } break; } } return pluginName; } private void createCountDataColumns(Collection<IPluginModelBase> pmbs) { // Always Remove column to recreate it at the end if (countCol != null) { countCol.getColumn().dispose(); countCol = null; } // Add columns in the tree one column per selected plugin. // Create the first column for the key // Only if there are plugins selected if (pmbs.size() > 0) { countCol = new TreeViewerColumn(tv, SWT.NONE); TreeColumn swtCol = countCol.getColumn(); swtCol.setText(COUNT_COLUMN); swtCol.setAlignment(SWT.CENTER); countProvider = new CountDataProvider(); countProvider.setPlugins(pmbs); countCol.setLabelProvider(countProvider); swtCol.setToolTipText("Sum the line"); swtCol.pack(); } } @Focus public void setFocus() { tv.getControl().setFocus(); } @Inject @Optional public void selectionChanged(@Named(IServiceConstants.ACTIVE_SELECTION) IStructuredSelection ss) { if (ss == null) return; currentSelectedPlugins = new HashSet<IPluginModelBase>(); for (@SuppressWarnings("unchecked") Iterator<IPluginModelBase> it = ss.iterator(); it.hasNext();) { Object selected = it.next(); IProject proj = (IProject) Platform.getAdapterManager().getAdapter(selected, IProject.class); if (proj != null) { IPluginModelBase m = PDECore.getDefault().getModelManager().findModel(proj); if (m != null) { currentSelectedPlugins.add(m); } else { // Try to see if it is a feature. IFeatureModel fm = PDECore.getDefault().getFeatureModelManager().getFeatureModel(proj); if (fm != null) { for (IFeaturePlugin fp : fm.getFeature().getPlugins()) { IPluginModelBase pm = PDECore.getDefault().getModelManager().findModel(fp.getId()); if (pm != null) currentSelectedPlugins.add(pm); } } } } } mergeTableViewerColumns(currentSelectedPlugins); if (tv != null) { // Must refresh without filter and then refilter... tv.setFilters(new ViewerFilter[] {}); tv.setFilters(new ViewerFilter[] { filter }); } updateDashboard(); } private void mergeTableViewerColumns(Collection<IPluginModelBase> currentSelectedPlugins) { // Search for plugins to be added or removed Collection<IPluginModelBase> toBeAdded = new ArrayList<IPluginModelBase>(); Collection<IPluginModelBase> toBeRemoved = new ArrayList<IPluginModelBase>(); for (IPluginModelBase p : currentSelectedPlugins) { if (!displayedPlugins.contains(p)) { toBeAdded.add(p); } } for (IPluginModelBase p : displayedPlugins) { if (!currentSelectedPlugins.contains(p)) { toBeRemoved.add(p); } } // Now remove and add columns in viewer.. for (IPluginModelBase p : toBeRemoved) { TreeViewerColumn tc = columnsCache.get(p); if (tc != null) { tc.getColumn().dispose(); } } for (IPluginModelBase p : toBeAdded) { createPluginColumns(p); } createCountDataColumns(currentSelectedPlugins); displayedPlugins = currentSelectedPlugins; } private SelectionAdapter getHeaderSelectionAdapter(final TreeViewer viewer, final TreeColumn column, final int columnIndex, final ILabelProvider textProvider) { SelectionAdapter selectionAdapter = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { viewer.setComparator(comparator); comparator.setColumn(columnIndex); comparator.setLabelProvider(textProvider); viewer.getTree().setSortDirection(comparator.getDirection()); viewer.getTree().setSortColumn(column); viewer.refresh(); } }; return selectionAdapter; } /** *  * This class provides the "drop down" functionality for our dropdown * tool items.   */ private class DropdownSelectionListener extends SelectionAdapter { private final Menu menu; private MenuItem currentSelected; public DropdownSelectionListener(ToolItem dropdown) { menu = new Menu(dropdown.getParent().getShell()); } /** * Adds an item to the dropdown list     * @param item the item to add *     */ public void add(String item) { MenuItem menuItem = new MenuItem(menu, SWT.CHECK); menuItem.setText(item); menuItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { MenuItem selected = (MenuItem) event.widget; if ((currentSelected != null) && (currentSelected != selected)) { currentSelected.setSelection(false); } selected.setSelection(true); currentSelected = selected; // Update the filter and refresh filter.setFilterDeprecated(selected.getText()); tv.refresh(); } }); } /** * Called when either the button itself or the dropdown arrow is clicked *   */ @Override public void widgetSelected(SelectionEvent event) { // If they clicked the arrow, we show the list if (event.detail == SWT.ARROW) { // Determine where to put the dropdown list ToolItem item = (ToolItem) event.widget; Rectangle rect = item.getBounds(); Point pt = item.getParent().toDisplay(new Point(rect.x, rect.y)); menu.setLocation(pt.x, pt.y + rect.height); menu.setVisible(true); } else { // Nothing to do... // System.out.println("button pressed"); } } } class FilterStats extends ViewerFilter { static final String EMPTY_LINES = "Filter empty lines"; static final String SHOW_ALL = "Show all"; static final String REMOVE_DEPRECATED = "Remove deprecated"; static final String ONLY_DEPRECATED = "Show only deprecated"; private boolean filterEmptyLines = false; private String filterDeprecated = SHOW_ALL; void setFilterEmptyLines(boolean fel) { filterEmptyLines = fel; } public boolean getFilterEmptyLines() { return filterEmptyLines; } void setFilterDeprecated(String mode) { filterDeprecated = mode; } @Override public boolean select(Viewer viewer, Object parentElement, Object element) { if (filterDeprecated != SHOW_ALL) { // Must filter the deprecated and may be empty lines boolean elementIsDeprecated = provider.isDeprecated(element); if ((filterDeprecated == ONLY_DEPRECATED) && !elementIsDeprecated) { return false; } if ((filterDeprecated == REMOVE_DEPRECATED) && elementIsDeprecated) { return false; } // Can now check if line is empty return countProvider == null ? true : !(filterEmptyLines && "0".equals(countProvider.getText(element))); } else { return countProvider == null ? true : !(filterEmptyLines && "0".equals(countProvider.getText(element))); } } } }