/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.example.ping.client.controllers; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.swt.SWT; import org.eclipse.swt.events.TreeEvent; import org.eclipse.swt.events.TreeListener; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Tree; import org.eclipse.riena.core.ping.IPingable; import org.eclipse.riena.core.ping.PingResult; import org.eclipse.riena.core.ping.PingVisitor; import org.eclipse.riena.core.util.Nop; import org.eclipse.riena.example.ping.client.model.NoPingableFound; import org.eclipse.riena.example.ping.client.model.PingResultTreeNode; import org.eclipse.riena.example.ping.client.model.PingableTreeNode; import org.eclipse.riena.example.ping.client.nls.Messages; import org.eclipse.riena.example.ping.client.ridgets.ProgressbarRidget; import org.eclipse.riena.example.ping.client.views.SonarView; import org.eclipse.riena.navigation.ui.controllers.SubModuleController; import org.eclipse.riena.ui.core.uiprocess.UIProcess; import org.eclipse.riena.ui.core.uiprocess.UISynchronizer; import org.eclipse.riena.ui.ridgets.IActionListener; import org.eclipse.riena.ui.ridgets.IActionRidget; import org.eclipse.riena.ui.ridgets.ILabelRidget; import org.eclipse.riena.ui.ridgets.ITextRidget; import org.eclipse.riena.ui.ridgets.ITreeRidget; import org.eclipse.riena.ui.ridgets.listener.ISelectionListener; import org.eclipse.riena.ui.ridgets.listener.SelectionEvent; import org.eclipse.riena.ui.ridgets.tree2.ITreeNode; /** * This is the main controller for the sonar SubModule. */ public class SonarController extends SubModuleController { private static enum SearchDirection { previous, next; } private static final String STACKFRAME_ICON = "stackframe.gif"; //$NON-NLS-1$ private static final String NEXT_ERROR_ICON = "select_next.gif"; //$NON-NLS-1$ private static final String PREVIOUS_ERROR_ICON = "select_prev.gif"; //$NON-NLS-1$ private static final String STOP_ICON = "stop.gif"; //$NON-NLS-1$ private static final String START_ICON = "relaunch.gif"; //$NON-NLS-1$ private ProgressbarRidget progressRidget; private IActionRidget startAction; private IActionRidget stopAction; private IActionRidget previousErrorAction; private IActionRidget nextErrorAction; private ITreeRidget treeRidget; private ITextRidget stackTraceTextRidget; private ILabelRidget pingLabel; private ILabelRidget failureLabel; private ILabelRidget failureMessageIconRidget; private ILabelRidget failureMessageTextRidget; private PingableTreeNode[] rootNodes; private Cursor oldCursor; @Override public void configureRidgets() { super.configureRidgets(); stackTraceTextRidget = getRidget(SonarView.BID_STACK_TRACE_TEXT); treeRidget = getRidget(SonarView.BID_SONAR_TREE); treeRidget.addSelectionListener(new ISelectionListener() { public void ridgetSelected(final SelectionEvent event) { treeNodeSelected(); } }); failureMessageIconRidget = getRidget(SonarView.BID_FAILURE_MESSAGE_ICON_LABEL); failureMessageIconRidget.setIcon(STACKFRAME_ICON); failureMessageIconRidget.setEnabled(false); failureMessageTextRidget = getRidget(SonarView.BID_FAILURE_MESSAGE_TEXT_LABEL); failureMessageTextRidget.setEnabled(false); progressRidget = getRidget(SonarView.BID_PROGRESS_BAR); progressRidget.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); progressRidget.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_GREEN)); progressRidget.updateFromModel(); startAction = getRidget(SonarView.BID_START_BUTTON); startAction.setIcon(START_ICON); startAction.setToolTipText(Messages.start_tooltip); startAction.addListener(new IActionListener() { public void callback() { startSonar(); } }); stopAction = getRidget(SonarView.BID_STOP_BUTTON); stopAction.setIcon(STOP_ICON); stopAction.setToolTipText(Messages.stop_tooltip); stopAction.setEnabled(false); previousErrorAction = getRidget(SonarView.BID_PREVIOUS_ERROR_BUTTON); previousErrorAction.setIcon(PREVIOUS_ERROR_ICON); previousErrorAction.setToolTipText(Messages.previous_error_tooltip); previousErrorAction.setEnabled(false); previousErrorAction.addListener(new IActionListener() { public void callback() { selectPreviousFailure(); } }); nextErrorAction = getRidget(SonarView.BID_NEXT_ERROR_BUTTON); nextErrorAction.setIcon(NEXT_ERROR_ICON); nextErrorAction.setToolTipText(Messages.next_error_tooltip); nextErrorAction.setEnabled(false); nextErrorAction.addListener(new IActionListener() { public void callback() { selectNextFailure(); } }); pingLabel = getRidget(SonarView.BID_PING_LABEL); failureLabel = getRidget(SonarView.BID_FAILED_LABEL); } @Override public void afterBind() { super.afterBind(); ((Tree) treeRidget.getUIControl()).addTreeListener(new TreeListener() { public void treeExpanded(final TreeEvent e) { if (e.item.getData() instanceof PingResultTreeNode) { final PingResultTreeNode node = (PingResultTreeNode) e.item.getData(); UISynchronizer.createSynchronizer().asyncExec(new Runnable() { public void run() { final List<ITreeNode> children = node.getChildren(); if (children != null) { for (final ITreeNode iTreeNode : children) { treeRidget.refresh(iTreeNode); } } } }); } } public void treeCollapsed(final TreeEvent e) { Nop.reason("nothing to do here"); //$NON-NLS-1$ } }); } /** * Updates the UI state. */ protected void updateUI() { updateNextPreviousButtons(); updateAllRidgetsFromModel(); } /** * Returns the selected TreeNode or <code>null</code> if there is none. * * @return the selected TreeNode. */ protected PingResultTreeNode getSelectedTreeNode() { final List<Object> selection = treeRidget.getSelection(); if (selection.size() > 0) { return (PingResultTreeNode) selection.get(0); } return null; } /** * Called if a TreeNode is selected. It shows the failure message of the * selected node (if there is one) and updates the state of the * next/previous failure buttons. */ protected void treeNodeSelected() { boolean hasFailure = false; final PingResultTreeNode selectedNode = getSelectedTreeNode(); if (selectedNode != null && selectedNode.getPingResult() != null) { stackTraceTextRidget.setText(selectedNode.getPingResult().getPingFailure()); hasFailure = selectedNode.getPingResult().hasPingFailed(); } failureMessageIconRidget.setEnabled(hasFailure); failureMessageTextRidget.setEnabled(hasFailure); updateNextPreviousButtons(); } /** * Updates the enabled state of the next/previous failure button, depending * on whether there is a next/previous failure to show. */ protected void updateNextPreviousButtons() { nextErrorAction.setEnabled(getFailureNode(SearchDirection.next) != null); previousErrorAction.setEnabled(getFailureNode(SearchDirection.previous) != null); } /** * Selects the first node that has a failure message, if there is one. */ protected void selectFirstFailure() { final PingResultTreeNode nextFailureNode = getFailureNode(SearchDirection.next, null); if (nextFailureNode != null) { treeRidget.setSelection(nextFailureNode); } updateNextPreviousButtons(); } /** * Selects the next node that has a failure message, if there is one. */ protected void selectNextFailure() { final PingResultTreeNode nextFailureNode = getFailureNode(SearchDirection.next); if (nextFailureNode != null) { treeRidget.setSelection(nextFailureNode); } updateNextPreviousButtons(); } /** * Selects the previous node that has a failure message, if there is one. */ protected void selectPreviousFailure() { final PingResultTreeNode nextFailureNode = getFailureNode(SearchDirection.previous); if (nextFailureNode != null) { treeRidget.setSelection(nextFailureNode); } updateNextPreviousButtons(); } /** * Returns the next/previous node that has a failure message, starting the * search from the currently selected node. * * @param searchDirection * if <code>true</code> the next failure will searched, otherwise * the * @return the failure node if there is one. */ private PingResultTreeNode getFailureNode(final SearchDirection searchDirection) { final PingResultTreeNode selectedTreeNode = getSelectedTreeNode(); return getFailureNode(searchDirection, selectedTreeNode); } /** * Returns the next/previous node that has a failure message, starting the * search from the given node. * * @param searchDirection * if <code>true</code> the next failure will searched, otherwise * the * @param startFrom * the node to start the search from. If <code>null</code>, the * search is started from root. * @return the failure node if there is one. */ private PingResultTreeNode getFailureNode(final SearchDirection searchDirection, final PingResultTreeNode startFrom) { final List<PingResultTreeNode> nodes = flattenTree(); if (nodes.size() == 0) { return null; } int end = nodes.size(); int increment = 1; if (searchDirection == SearchDirection.previous) { end = 0; increment = -1; } int start = nodes.indexOf(startFrom); if (start < 0) { if (searchDirection == SearchDirection.next) { start = 0; } else { start = nodes.size() - 1; } } else { start += increment; } for (int i = start; i != end; i += increment) { if (i > nodes.size() || i < 0) { return null; } final PingResultTreeNode currentNode = nodes.get(i); if (currentNode.hasPingFailed()) { return currentNode; } } return null; } /** * Returns the tree structure as a flattened List in depth first order. * * @return the tree structure as a flattened List. */ protected List<PingResultTreeNode> flattenTree() { final PingableTreeNode[] nodes = getRootNodes(); if (nodes == null || nodes.length == 0) { return Collections.EMPTY_LIST; } final List<PingResultTreeNode> result = new ArrayList<PingResultTreeNode>(); for (final PingableTreeNode current : nodes) { flattenTree(current, result); } return result; } private void flattenTree(final PingResultTreeNode current, final List<PingResultTreeNode> result) { result.add(current); final List<ITreeNode> children = current.getChildren(); for (final ITreeNode child : children) { flattenTree((PingResultTreeNode) child, result); } } /** * Returns the root nodes. * * @return the root nodes. */ protected PingableTreeNode[] getRootNodes() { return rootNodes; } /** * Sets the root nodes of the tree. * * @param nodes * the root nodes. */ protected void setRootNodes(final PingableTreeNode[] nodes) { this.rootNodes = nodes; treeRidget.bindToModel(nodes, PingResultTreeNode.class, ITreeNode.PROPERTY_CHILDREN, ITreeNode.PROPERTY_PARENT, "label", null, null, "icon", "icon"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ treeRidget.updateFromModel(); } /** * Starts a new {@link SonarUIProcess}. */ protected void startSonar() { final SonarUIProcess process = new SonarUIProcess(); process.start(); } /** * Creates the root nodes for the given {@link IPingable} services. * * @param pingableServices * the pingables acting as root nodes. * @return the root nodes. */ protected PingableTreeNode[] createRootNodes(final List<IPingable> pingableServices) { final PingableTreeNode[] result = new PingableTreeNode[pingableServices.size()]; for (int i = 0; i < result.length; i++) { result[i] = new PingableTreeNode(pingableServices.get(i)); } return result; } /** * Retrieves all osgi services that implement {@link IPingable}. * * @return all {@link IPingable} services. */ @SuppressWarnings("unchecked") protected List<IPingable> getPingableServices() { // fetch ALL services final BundleContext context = FrameworkUtil.getBundle(this.getClass()).getBundleContext(); ServiceReference[] allServiceReferences = null; try { allServiceReferences = context.getAllServiceReferences(null, null); } catch (final InvalidSyntaxException e) { throw new RuntimeException("This should never happen since we do not use a filter?!?", e); //$NON-NLS-1$ } // no services at all :-( if (allServiceReferences == null) { return Collections.EMPTY_LIST; } final List<IPingable> result = new ArrayList<IPingable>(); // filter the pingable services for (final ServiceReference serviceReference : allServiceReferences) { final Object service = context.getService(serviceReference); if (service instanceof IPingable) { result.add((IPingable) service); } } if (result.isEmpty()) { result.add(new NoPingableFound()); } return result; } protected void setShowHourGlassCursor(final boolean show) { final Control uiControl = (Control) getWindowRidget().getUIControl(); if (show) { oldCursor = uiControl.getCursor(); final Cursor waitCursor = uiControl.getDisplay().getSystemCursor(SWT.CURSOR_WAIT); uiControl.setCursor(waitCursor); } else { if (oldCursor != null) { uiControl.setCursor(oldCursor); oldCursor = null; } } } /** * The {@link UIProcess} that pings all * {@link SonarController#getRootNodes() IPingables}. */ private class SonarUIProcess extends UIProcess { private volatile int pinged = 0; private volatile int pingedRootNodes = 0; private volatile boolean canceled; private volatile int failureCount = 0; private volatile boolean endRun; private volatile PingResultTreeNode currentNode; private volatile boolean expand = false; private final IActionListener stopActionListener = new IActionListener() { public void callback() { canceled = true; end(); } }; /** * Default constructor. */ public SonarUIProcess() { super(Messages.sonar, false); } @Override public void initialUpdateUI(final int totalWork) { final List<IPingable> pingableServices = getPingableServices(); final PingableTreeNode[] nodes = createRootNodes(pingableServices); setRootNodes(nodes); progressRidget.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_GREEN)); progressRidget.setMinimum(0); progressRidget.setMaximum(nodes.length); progressRidget.setSelection(pinged); stackTraceTextRidget.setText(""); //$NON-NLS-1$ pingLabel.setText(Integer.toString(pinged)); failureLabel.setText(Integer.toString(failureCount)); stopAction.addListener(stopActionListener); stopAction.setEnabled(true); startAction.setEnabled(false); nextErrorAction.setEnabled(false); previousErrorAction.setEnabled(false); failureMessageIconRidget.setEnabled(false); failureMessageTextRidget.setEnabled(false); SonarController.this.setShowHourGlassCursor(false); } @Override public boolean runJob(final IProgressMonitor monitor) { for (final PingableTreeNode pingableNode : getRootNodes()) { if (canceled) { break; } PingVisitor visitor = new PingVisitor(); visitor = visitor.ping(pingableNode.getPingable()); if (canceled) { break; } final List<PingResult> pingResults = visitor.getPingResults(); ++pinged; ++pingedRootNodes; final PingResult pingResult = pingResults.get(0); pingableNode.setPingResult(pingResult); if (pingResult.hasPingFailed()) { ++failureCount; } createChildNodesUISync(pingableNode); monitor.worked(pinged); notifyUpdateUI(); } return true; } /** * Calls {@link #createChildNodes(PingResultTreeNode)} using a * {@link UISynchronizer}. * * @param parent * the node for which to create the child nodes. */ private void createChildNodesUISync(final PingResultTreeNode parent) { UISynchronizer.createSynchronizer().asyncExec(new Runnable() { public void run() { createChildNodes(parent); } }); } /** * Recursively creates child nodes for the given node based on its * {@link PingResult}. * * @param parent * the node for which to create the child nodes. */ private void createChildNodes(final PingResultTreeNode parent) { currentNode = parent; expand = true; updateUi(); final PingResult pingResult = parent.getPingResult(); final Iterable<PingResult> nestedResults = pingResult.getNestedResults(); boolean failed = false; for (final PingResult nested : nestedResults) { ++pinged; final PingResultTreeNode child = new PingResultTreeNode(parent, nested.getPingableName()); child.setPingResult(nested); if (nested.hasPingFailed()) { ++failureCount; failed = true; } // updateUi(); treeRidget.setSelection(child); createChildNodes(child); } currentNode = parent; expand = failed; updateUi(); } @Override public void updateUi() { if (canceled) { return; } if (currentNode != null) { if (expand) { treeRidget.expand(currentNode); } else { treeRidget.collapse(currentNode); } } treeRidget.updateFromModel(); if (failureCount > 0) { progressRidget.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_RED)); } progressRidget.setSelection(pingedRootNodes); pingLabel.setText(Integer.toString(pinged)); failureLabel.setText(Integer.toString(failureCount)); } @Override public void finalUpdateUI() { end(); } @Override protected int getTotalWork() { if (getRootNodes() == null) { return 0; } return getRootNodes().length; } /** * En-/disables all actions as appropriate and selects first failure * node. The code is run just once, so any subsequent calls have not * effect. */ protected void end() { if (endRun) { return; } endRun = true; stopAction.removeListener(stopActionListener); stopAction.setEnabled(false); startAction.setEnabled(true); treeRidget.updateFromModel(); SonarController.this.updateUI(); selectFirstFailure(); SonarController.this.setShowHourGlassCursor(false); } } }