Java tutorial
/******************************************************************************* * Copyright (c) 2010, 2014 Tasktop Technologies and others. * 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: * Tasktop Technologies - initial API and implementation * Mickael Istria (Red Hat Inc.) * - Extracted from {@link DiscoveryViewer} into own file * - Added support for versions/updates * - UI improvements *******************************************************************************/ package org.jboss.tools.central.editors.xpl; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.equinox.p2.core.IProvisioningAgent; import org.eclipse.equinox.p2.core.IProvisioningAgentProvider; import org.eclipse.equinox.p2.core.ProvisionException; import org.eclipse.equinox.p2.engine.IProfile; import org.eclipse.equinox.p2.engine.IProfileRegistry; import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.equinox.p2.metadata.Version; import org.eclipse.equinox.p2.query.IQueryResult; import org.eclipse.equinox.p2.query.QueryUtil; import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.resource.ColorRegistry; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.mylyn.commons.workbench.browser.BrowserUtil; import org.eclipse.mylyn.internal.discovery.core.model.ConnectorDescriptor; import org.eclipse.mylyn.internal.discovery.core.model.DiscoveryConnector; import org.eclipse.mylyn.internal.discovery.core.model.Overview; import org.eclipse.mylyn.internal.discovery.ui.wizards.Messages; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.FocusAdapter; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Resource; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.browser.IWorkbenchBrowserSupport; import org.jboss.tools.central.JBossCentralActivator; import org.jboss.tools.discovery.core.internal.connectors.JBossDiscoveryUi; /** * Wraps a {@link DiscoveryConnector} in a UI element and provide additional * methods to handle versions and updates * @author mistria * */ public class ConnectorDescriptorItemUi implements PropertyChangeListener, Runnable { /** * All jobs using this rule instance will be exclusive so they will use a single thread */ private static final ISchedulingRule SINGLE_JOB_RULE = new ISchedulingRule() { @Override public boolean isConflicting(ISchedulingRule arg0) { return arg0 == this; } @Override public boolean contains(ISchedulingRule arg0) { return arg0 == this; } }; public static enum ConnectorInstallationStatus { UNKNOWN, UP_TO_DATE, UPDATE_AVAILABLE, MORE_RECENT_VERSION_INSTALLED }; private DiscoveryConnector connector; private Map<String, org.eclipse.equinox.p2.metadata.Version> connectorUnits; private ConnectorInstallationStatus installationStatus = ConnectorInstallationStatus.UNKNOWN; private boolean visible; private boolean isRealConnector; private DiscoveryViewer discoveryViewer; private Job connectorUnitJob; private final Button checkbox; private final Label iconLabel; private final Label nameLabel; private final Label statusLabel; private ToolItem infoButton; private final Link providerLabel; private final Label description; private final Composite checkboxContainer; private final Composite connectorContainer; private final Display display; private Image iconImage; private static final String COLOR_DARK_GRAY = "DarkGray"; //$NON-NLS-1$ private Color colorDisabled; private Font titleFont; private Image infoImage; private Set<Resource> disposables = new HashSet<Resource>(); public ConnectorDescriptorItemUi(DiscoveryViewer discoveryViewer, final DiscoveryConnector connector, Composite categoryChildrenContainer, Color background, Font titleFont, Image infoImage) { if (colorDisabled == null) { ColorRegistry colorRegistry = JFaceResources.getColorRegistry(); if (!colorRegistry.hasValueFor(COLOR_DARK_GRAY)) { colorRegistry.put(COLOR_DARK_GRAY, new RGB(0x69, 0x69, 0x69)); } colorDisabled = colorRegistry.get(COLOR_DARK_GRAY); } this.discoveryViewer = discoveryViewer; this.titleFont = titleFont; this.connector = connector; this.infoImage = infoImage; display = categoryChildrenContainer.getDisplay(); connector.addPropertyChangeListener(this); connectorContainer = new Composite(categoryChildrenContainer, SWT.NULL); connectorContainer.setBackground(background); GridDataFactory.fillDefaults().grab(true, false).applyTo(connectorContainer); GridLayout layout = new GridLayout(5, false); layout.marginLeft = 7; layout.marginTop = 2; layout.marginBottom = 2; connectorContainer.setLayout(layout); checkboxContainer = new Composite(connectorContainer, SWT.NULL); checkboxContainer.setBackground(background); GridDataFactory.swtDefaults().align(SWT.CENTER, SWT.BEGINNING).span(1, 2).applyTo(checkboxContainer); GridLayoutFactory.fillDefaults().spacing(1, 1).numColumns(2).applyTo(checkboxContainer); checkbox = new Button(checkboxContainer, SWT.CHECK); checkbox.setText(" "); //$NON-NLS-1$ // help UI tests checkbox.setData("connectorId", connector.getId()); //$NON-NLS-1$ checkbox.setVisible(connector.isInstallable()); checkbox.setSelection(connector.isSelected()); checkbox.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { ConnectorDescriptorItemUi.this.discoveryViewer.showConnectorControl(ConnectorDescriptorItemUi.this); } }); this.isRealConnector = JBossDiscoveryUi.isInstallableConnector(connector); checkbox.setVisible(this.isRealConnector); checkbox.setEnabled(this.isRealConnector); GridDataFactory.swtDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(checkbox); iconLabel = new Label(checkboxContainer, SWT.NULL); iconLabel.setBackground(background); GridDataFactory.swtDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(iconLabel); if (connector.getIcon() != null) { iconImage = DiscoveryViewer.computeIconImage(connector.getSource(), connector.getIcon(), 32, false); this.disposables.add(iconImage); if (iconImage != null) { iconLabel.setImage(iconImage); } } nameLabel = new Label(connectorContainer, SWT.NULL); nameLabel.setBackground(background); GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(nameLabel); nameLabel.setFont(this.titleFont); nameLabel.setText(connector.getName()); this.statusLabel = new Label(connectorContainer, SWT.NULL); this.statusLabel.setBackground(background); GridDataFactory.fillDefaults().grab(true, false).align(SWT.BEGINNING, SWT.CENTER).applyTo(this.statusLabel); setUpToDateStatus(); // As resolution of version is a long operation, we create a job for that if (this.connector.isInstalled()) { createAndScheduleConnectorUnitJob(); } providerLabel = new Link(connectorContainer, SWT.RIGHT); providerLabel.setBackground(background); GridDataFactory.fillDefaults().align(SWT.END, SWT.CENTER).applyTo(providerLabel); if (connector.getCertification() != null) { providerLabel.setText(NLS.bind(org.jboss.tools.central.Messages.DiscoveryViewer_Certification_Label0, new String[] { connector.getProvider(), connector.getLicense(), connector.getCertification().getName() })); if (connector.getCertification().getUrl() != null) { providerLabel.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { BrowserUtil.openUrl(connector.getCertification().getUrl(), IWorkbenchBrowserSupport.AS_EXTERNAL); } }); } Overview overview = new Overview(); overview.setSummary(connector.getCertification().getDescription()); overview.setUrl(connector.getCertification().getUrl()); Image image = DiscoveryViewer.computeIconImage(connector.getSource(), connector.getCertification().getIcon(), 48, true); DiscoveryViewer.hookTooltip(providerLabel, providerLabel, connectorContainer, providerLabel, connector.getSource(), overview, image); } else { providerLabel.setText(NLS.bind(Messages.ConnectorDiscoveryWizardMainPage_provider_and_license, connector.getProvider(), connector.getLicense())); } if (hasTooltip(connector)) { ToolBar toolBar = new ToolBar(connectorContainer, SWT.FLAT); toolBar.setBackground(background); infoButton = new ToolItem(toolBar, SWT.PUSH); infoButton.setImage(this.infoImage); infoButton.setToolTipText(Messages.ConnectorDiscoveryWizardMainPage_tooltip_showOverview); DiscoveryViewer.hookTooltip(toolBar, infoButton, connectorContainer, nameLabel, connector.getSource(), connector.getOverview(), null); GridDataFactory.fillDefaults().align(SWT.END, SWT.CENTER).applyTo(toolBar); } else { Label label = new Label(connectorContainer, SWT.NULL); label.setText(" "); //$NON-NLS-1$ label.setBackground(background); } description = new Label(connectorContainer, SWT.NULL | SWT.WRAP); description.setBackground(background); GridDataFactory.fillDefaults().grab(true, false).span(3, 1).hint(100, SWT.DEFAULT).applyTo(description); String descriptionText = connector.getDescription(); int maxDescriptionLength = 162; if (descriptionText.length() > maxDescriptionLength) { descriptionText = descriptionText.substring(0, maxDescriptionLength); } description.setText(descriptionText.replaceAll("(\\r\\n)|\\n|\\r", " ")); //$NON-NLS-1$ //$NON-NLS-2$ // always disabled color to make it less prominent providerLabel.setForeground(this.colorDisabled); if (this.isRealConnector) { checkbox.addSelectionListener(new SelectionListener() { public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } public void widgetSelected(SelectionEvent e) { boolean selected = checkbox.getSelection(); maybeModifySelection(selected); } }); MouseListener connectorItemMouseListener = new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { boolean selected = !checkbox.getSelection(); if (maybeModifySelection(selected)) { checkbox.setSelection(selected); } } }; checkboxContainer.addMouseListener(connectorItemMouseListener); connectorContainer.addMouseListener(connectorItemMouseListener); iconLabel.addMouseListener(connectorItemMouseListener); nameLabel.addMouseListener(connectorItemMouseListener); // the provider has clickable links // providerLabel.addMouseListener(connectorItemMouseListener); description.addMouseListener(connectorItemMouseListener); } } /** * Need asynchronous as it's long-running */ private void createAndScheduleConnectorUnitJob() { this.connectorUnitJob = new Job("Computing connector status") { private boolean cancelled; @Override protected IStatus run(IProgressMonitor arg0) { ConnectorDescriptorItemUi.this.connectorUnits = resolveConnectorUnits(connector); if (connector.isInstalled() && ConnectorDescriptorItemUi.this.connectorUnits != null) { Map<String, org.eclipse.equinox.p2.metadata.Version> profileUnits = resolveProfileUnits( connector.getInstallableUnits()); if (profileUnits != null && !profileUnits.isEmpty()) { for (String unitId : connector.getInstallableUnits()) { Version version = profileUnits.get(unitId); Version connectorUnitVersion = ConnectorDescriptorItemUi.this.connectorUnits .get(unitId); if (connectorUnitVersion == null) { JBossCentralActivator.log("Could not resolve remote IU '" + unitId + "' in repository '" + connector.getSiteUrl() + "'"); } else if (version != null) { int compare = version.compareTo(connectorUnitVersion); if (compare == 0) { ConnectorDescriptorItemUi.this.installationStatus = ConnectorInstallationStatus.UP_TO_DATE; discoveryViewer.modifySelection(ConnectorDescriptorItemUi.this, ConnectorDescriptorItemUi.this.connector.isSelected()); break; } else if (compare <= 0) { ConnectorDescriptorItemUi.this.installationStatus = ConnectorInstallationStatus.UPDATE_AVAILABLE; // default enables update, so no need to change it break; } else if (compare >= 0) { ConnectorDescriptorItemUi.this.installationStatus = ConnectorInstallationStatus.MORE_RECENT_VERSION_INSTALLED; discoveryViewer.modifySelection(ConnectorDescriptorItemUi.this, ConnectorDescriptorItemUi.this.connector.isSelected()); break; } } } } if (this.cancelled) { return Status.CANCEL_STATUS; } if (!ConnectorDescriptorItemUi.this.checkboxContainer.isDisposed()) { ConnectorDescriptorItemUi.this.checkboxContainer.getDisplay().syncExec(new Runnable() { @Override public void run() { setUpToDateStatus(); } }); } } return Status.CANCEL_STATUS; } @Override protected void canceling() { this.cancelled = true; } }; this.connectorUnitJob.setSystem(true); // As the output of this job is more interesting for installed threads, // we put lower (==more important) priority on installed jobs // In case of join in UIThread, priority must be raised to limit freeze duration. if (this.connector.isInstalled()) { this.connectorUnitJob.setPriority(Job.SHORT); } else { this.connectorUnitJob.setPriority(Job.LONG); } // All jobs use the same thread this.connectorUnitJob.setRule(SINGLE_JOB_RULE); this.connectorUnitJob.belongsTo(this.discoveryViewer); this.connectorUnitJob.schedule(); } /** * @param connector */ private void setUpToDateStatus() { if (!this.connector.isInstalled() || this.statusLabel.isDisposed() || getControl().isDisposed()) { return; } String text = "(INSTALLED"; if (this.installationStatus == ConnectorInstallationStatus.UP_TO_DATE) { text += " - UP TO DATE"; this.statusLabel.setForeground(getControl().getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN)); } else if (this.installationStatus == ConnectorInstallationStatus.UPDATE_AVAILABLE) { text += " - UPDATE AVAILABLE"; this.statusLabel .setForeground(getControl().getShell().getDisplay().getSystemColor(SWT.COLOR_DARK_YELLOW)); } else if (this.installationStatus == ConnectorInstallationStatus.MORE_RECENT_VERSION_INSTALLED) { text += " - MORE RECENT VERSION INSTALLED"; this.statusLabel.setForeground(getControl().getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN)); } text += ")"; this.statusLabel.setText(text); this.statusLabel.getParent().layout(new Control[] { this.statusLabel }); } private static Map<String, org.eclipse.equinox.p2.metadata.Version> resolveProfileUnits( List<String> installableUnits) { IProvisioningAgentProvider provider = (IProvisioningAgentProvider) PlatformUI.getWorkbench() .getService(IProvisioningAgentProvider.class); if (provider == null) { // This is not expected to happen, but it actually does when shutting down IDE: JBIDE-18368 // so we just catch it there to avoid logging a NPE. return null; } try { IProvisioningAgent agent = provider.createAgent(null); // null = location for running system if (agent == null) throw new RuntimeException("Location was not provisioned by p2"); IProfileRegistry profileRegistry = (IProfileRegistry) agent.getService(IProfileRegistry.SERVICE_NAME); if (profileRegistry == null) { throw new RuntimeException("Unable to acquire the profile registry service."); } IProfile profile = profileRegistry.getProfile(IProfileRegistry.SELF); Map<String, org.eclipse.equinox.p2.metadata.Version> res = new HashMap<String, org.eclipse.equinox.p2.metadata.Version>(); for (String installableUnit : installableUnits) { IQueryResult<IInstallableUnit> queryResult = profile.query(QueryUtil.createIUQuery(installableUnit), new NullProgressMonitor()); for (IInstallableUnit unit : queryResult) { org.eclipse.equinox.p2.metadata.Version previousVersion = res.get(installableUnit); if (previousVersion == null || previousVersion.compareTo(unit.getVersion()) < 0) { res.put(installableUnit, unit.getVersion()); } } } return res; } catch (ProvisionException ex) { JBossCentralActivator.getDefault().getLog() .log(new Status(IStatus.ERROR, JBossCentralActivator.PLUGIN_ID, ex.getMessage(), ex)); return null; } } /** * This is a long-running operation! Don't call it in UI Thread. * @param connector * @return the map of id/version for installation units or null if connector repo can't be resolved. */ private static Map<String, org.eclipse.equinox.p2.metadata.Version> resolveConnectorUnits( ConnectorDescriptor connector) { IMetadataRepository repo = P2CachedRepoUtil.getRepoForConnector(connector); if (repo == null) { return null; } Map<String, org.eclipse.equinox.p2.metadata.Version> res = new HashMap<String, org.eclipse.equinox.p2.metadata.Version>(); for (String unitId : connector.getInstallableUnits()) { IQueryResult<IInstallableUnit> queryResult = repo.query(QueryUtil.createIUQuery(unitId), new NullProgressMonitor()); for (IInstallableUnit unit : queryResult) { org.eclipse.equinox.p2.metadata.Version previousVersion = res.get(unitId); if (previousVersion == null || previousVersion.compareTo(unit.getVersion()) < 0) { res.put(unitId, unit.getVersion()); } } } return res; } /** * * @return whether the connector is up-to-date. Ie whether a new version for * the included IUs exist on the associated site. */ public boolean isUpToDate() { return this.installationStatus == ConnectorInstallationStatus.MORE_RECENT_VERSION_INSTALLED || this.installationStatus == ConnectorInstallationStatus.UP_TO_DATE; } protected boolean maybeModifySelection(boolean selected) { if (!this.isRealConnector || !checkbox.isEnabled()) { return false; } if (selected) { if (!connector.isInstalled() && !connector.isInstallable()) { if (connector.getInstallMessage() != null) { MessageDialog.openInformation(this.checkbox.getShell(), Messages.DiscoveryViewer_Install_Connector_Title, connector.getInstallMessage()); } return false; } if (connector.getAvailable() != null && !connector.getAvailable()) { MessageDialog.openWarning(this.checkbox.getShell(), Messages.ConnectorDiscoveryWizardMainPage_warningTitleConnectorUnavailable, NLS.bind(Messages.ConnectorDiscoveryWizardMainPage_warningMessageConnectorUnavailable, connector.getName())); return false; } } this.discoveryViewer.modifySelection(this, selected); return true; } public void propertyChange(PropertyChangeEvent evt) { display.asyncExec(this); } public void run() { if (!connectorContainer.isDisposed()) { updateAvailability(); } } public void updateAvailability() { boolean enabled = !connector.isInstalled() && (connector.getAvailable() == null || connector.getAvailable()); checkbox.setEnabled(this.isRealConnector); nameLabel.setEnabled(enabled); providerLabel.setEnabled(enabled); description.setEnabled(enabled); Color foreground; if (enabled) { foreground = connectorContainer.getForeground(); } else { foreground = this.colorDisabled; } nameLabel.setForeground(foreground); description.setForeground(foreground); if (iconImage != null) { iconLabel.setImage(iconImage); } } void select(boolean select) { if (!checkbox.isDisposed() && checkbox.isVisible() && checkbox.getSelection() != select) { checkbox.setSelection(select); this.connector.setSelected(select); maybeModifySelection(select); } } boolean hasTooltip(final DiscoveryConnector connector) { return connector.getOverview() != null && connector.getOverview().getSummary() != null && connector.getOverview().getSummary().length() > 0; } public void dispose() { if (this.connectorUnitJob != null) { this.connectorUnitJob.cancel(); } for (Resource resource : this.disposables) { resource.dispose(); } } public Control getControl() { return this.connectorContainer; } public DiscoveryConnector getConnector() { return this.connector; } public void setVisible(boolean visible) { this.visible = visible; ((GridData) this.connectorContainer.getLayoutData()).exclude = !visible; this.connectorContainer.setVisible(visible); } public boolean isVisible() { // Use a field there to avoid called to UI elements and UI thread return this.visible; } /** * This is a synchronous, potentially long-running operation! * @return the map of connector units */ public Map<String, Version> getConnectorUnits() { if (this.connectorUnitJob == null) { createAndScheduleConnectorUnitJob(); } if (isComputingUnits()) { try { // on request, raise priority to maximal this.connectorUnitJob.setPriority(Job.INTERACTIVE); this.connectorUnitJob.join(); } catch (InterruptedException ex) { JBossCentralActivator.getDefault().getLog() .log(new Status(IStatus.ERROR, JBossCentralActivator.PLUGIN_ID, ex.getMessage(), ex)); } } return this.connectorUnits; } public boolean isComputingUnits() { return this.connectorUnitJob != null && this.connectorUnitJob.getState() != Job.NONE; } }