SWTAwareStateMachine introduced that automatically runs all transition notifications in the user-interface thread, removing the need to explicitly call Display.asyncExec() in the listeners.

git-svn-id: https://ipscan.svn.sourceforge.net/svnroot/ipscan/trunk@453 375186e5-ef17-0410-b0b6-91563547dcda
This commit is contained in:
angryziber 2009-03-22 21:32:22 +00:00
parent 9e45946202
commit fd736c2edb
14 changed files with 216 additions and 186 deletions

View File

@ -24,7 +24,7 @@ import net.azib.ipscan.feeders.FeederRegistry;
*
* @author Anton Keks
*/
public class CommandLineProcessor implements StateTransitionListener {
public class CommandLineProcessor implements CommandProcessor, StateTransitionListener {
private final FeederRegistry<FeederCreator> feederRegistry;
private final ExporterRegistry exporters;
@ -53,6 +53,14 @@ public class CommandLineProcessor implements StateTransitionListener {
stateMachine.addTransitionListener(this);
}
public boolean shouldAutoQuit() {
return autoQuit;
}
public boolean shouldAutoStart() {
return autoStart;
}
public void parse(String ...args) {
for (int i = 0; i < args.length; i++) {
String arg = args[i];

View File

@ -0,0 +1,17 @@
/**
* This file is a part of Angry IP Scanner source code,
* see http://www.azib.net/ for more information.
* Licensed under GPLv2.
*/
package net.azib.ipscan.config;
/**
* Interface for providing of various commands to the application
*
* @author Anton Keks
*/
public interface CommandProcessor {
boolean shouldAutoStart();
boolean shouldAutoQuit();
}

View File

@ -12,7 +12,6 @@ import net.azib.ipscan.core.ScannerDispatcherThreadFactory;
import net.azib.ipscan.core.ScanningResultList;
import net.azib.ipscan.core.net.PingerRegistry;
import net.azib.ipscan.core.net.PingerRegistryImpl;
import net.azib.ipscan.core.state.StateMachine;
import net.azib.ipscan.exporters.CSVExporter;
import net.azib.ipscan.exporters.ExporterRegistry;
import net.azib.ipscan.exporters.IPListExporter;
@ -37,6 +36,7 @@ import net.azib.ipscan.gui.MainMenu;
import net.azib.ipscan.gui.MainWindow;
import net.azib.ipscan.gui.PreferencesDialog;
import net.azib.ipscan.gui.ResultTable;
import net.azib.ipscan.gui.SWTAwareStateMachine;
import net.azib.ipscan.gui.SelectFetchersDialog;
import net.azib.ipscan.gui.StatisticsDialog;
import net.azib.ipscan.gui.StatusBar;
@ -56,6 +56,7 @@ import net.azib.ipscan.gui.feeders.RangeFeederGUI;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.picocontainer.MutablePicoContainer;
@ -66,22 +67,22 @@ import org.picocontainer.defaults.ConstantParameter;
import org.picocontainer.defaults.DefaultPicoContainer;
/**
* This class is the dependency injection configuration
* using the Pico Container.
*
* This class is the dependency injection configuration using the Pico Container.
*
* @author Anton Keks
*/
public class ComponentRegistry {
private PicoContainer container;
private boolean containerStarted;
public ComponentRegistry() {
MutablePicoContainer container = new DefaultPicoContainer();
this.container = container;
ComponentParameter anyComponentParameter = new ComponentParameter();
// non-GUI
Config globalConfig = Config.getConfig();
container.registerComponentInstance(globalConfig.getPreferences());
@ -92,13 +93,13 @@ public class ComponentRegistry {
container.registerComponentInstance(Labels.getInstance());
container.registerComponentImplementation(CommentsConfig.class);
container.registerComponentImplementation(ConfigDetector.class);
container.registerComponentImplementation(ExporterRegistry.class);
container.registerComponentImplementation(TXTExporter.class);
container.registerComponentImplementation(CSVExporter.class);
container.registerComponentImplementation(XMLExporter.class);
container.registerComponentImplementation(IPListExporter.class);
container.registerComponentImplementation(FetcherRegistry.class, FetcherRegistryImpl.class);
container.registerComponentImplementation(IPFetcher.class);
container.registerComponentImplementation(PingFetcher.class);
@ -110,91 +111,67 @@ public class ComponentRegistry {
container.registerComponentImplementation(HTTPSenderFetcher.class);
container.registerComponentImplementation(CommentFetcher.class);
container.registerComponentImplementation(NetBIOSInfoFetcher.class);
container.registerComponentImplementation(PingerRegistry.class, PingerRegistryImpl.class);
container.registerComponentImplementation(ScanningResultList.class);
container.registerComponentImplementation(Scanner.class);
container.registerComponentImplementation(StateMachine.class);
container.registerComponentImplementation(SWTAwareStateMachine.class);
container.registerComponentImplementation(ScannerDispatcherThreadFactory.class);
container.registerComponentImplementation(CommandLineProcessor.class);
// GUI follows (TODO: move GUI to a separate place)
// Some "shared" GUI components
container.registerComponentInstance(Display.getDefault());
container.registerComponentImplementation("mainShell", Shell.class);
container.registerComponentImplementation("mainMenu", Menu.class, new Parameter[] {
new ComponentParameter("mainShell"),
new ConstantParameter(new Integer(SWT.BAR))});
new ComponentParameter("mainShell"), new ConstantParameter(new Integer(SWT.BAR)) });
container.registerComponentImplementation("commandsMenu", CommandsMenu.class);
container.registerComponentImplementation("feederArea", Composite.class, new Parameter[] {
new ComponentParameter("mainShell"),
new ConstantParameter(new Integer(SWT.NONE))});
new ComponentParameter("mainShell"), new ConstantParameter(new Integer(SWT.NONE)) });
container.registerComponentImplementation("controlsArea", Composite.class, new Parameter[] {
new ComponentParameter("mainShell"),
new ConstantParameter(new Integer(SWT.NONE))});
new ComponentParameter("mainShell"), new ConstantParameter(new Integer(SWT.NONE)) });
container.registerComponentImplementation("startStopButton", Button.class, new Parameter[] {
new ComponentParameter("controlsArea"),
new ConstantParameter(new Integer(SWT.NONE))});
container.registerComponentImplementation("feederSelectionCombo", FeederSelectionCombo.class, new Parameter[] {
new ComponentParameter("controlsArea")});
new ComponentParameter("controlsArea"), new ConstantParameter(new Integer(SWT.NONE)) });
container.registerComponentImplementation("feederSelectionCombo", FeederSelectionCombo.class,
new Parameter[] { new ComponentParameter("controlsArea") });
// GUI Feeders
container.registerComponentImplementation(FeederGUIRegistry.class);
Parameter[] feederGUIParameters = new Parameter[] {new ComponentParameter("feederArea")};
Parameter[] feederGUIParameters = new Parameter[] { new ComponentParameter("feederArea") };
container.registerComponentImplementation(RangeFeederGUI.class, RangeFeederGUI.class, feederGUIParameters);
container.registerComponentImplementation(RandomFeederGUI.class, RandomFeederGUI.class, feederGUIParameters);
container.registerComponentImplementation(FileFeederGUI.class, FileFeederGUI.class, feederGUIParameters);
container.registerComponentImplementation(OpenerLauncher.class);
container.registerComponentImplementation(MainWindow.class, MainWindow.class, new Parameter[] {
new ComponentParameter("mainShell"),
anyComponentParameter,
new ComponentParameter("feederArea"),
new ComponentParameter("controlsArea"),
new ComponentParameter("feederSelectionCombo"),
new ComponentParameter("startStopButton"),
anyComponentParameter,
anyComponentParameter,
anyComponentParameter,
anyComponentParameter,
anyComponentParameter,
anyComponentParameter,
anyComponentParameter,
anyComponentParameter});
new ComponentParameter("mainShell"), anyComponentParameter, new ComponentParameter("feederArea"),
new ComponentParameter("controlsArea"), new ComponentParameter("feederSelectionCombo"),
new ComponentParameter("startStopButton"), anyComponentParameter, anyComponentParameter,
anyComponentParameter, anyComponentParameter, anyComponentParameter, anyComponentParameter,
anyComponentParameter, anyComponentParameter });
container.registerComponentImplementation(ResultTable.class, ResultTable.class, new Parameter[] {
new ComponentParameter("mainShell"),
anyComponentParameter,
anyComponentParameter,
anyComponentParameter,
anyComponentParameter,
anyComponentParameter,
anyComponentParameter,
anyComponentParameter});
new ComponentParameter("mainShell"), anyComponentParameter, anyComponentParameter,
anyComponentParameter, anyComponentParameter, anyComponentParameter, anyComponentParameter,
anyComponentParameter });
container.registerComponentImplementation(StatusBar.class, StatusBar.class, new Parameter[] {
new ComponentParameter("mainShell"),
anyComponentParameter,
anyComponentParameter});
new ComponentParameter("mainShell"), anyComponentParameter, anyComponentParameter });
container.registerComponentImplementation(MainMenu.class, MainMenu.class, new Parameter[] {
new ComponentParameter("mainShell"),
new ComponentParameter("mainMenu"),
new ComponentParameter("commandsMenu"),
anyComponentParameter,
new ConstantParameter(container)});
container.registerComponentImplementation(MainMenu.ColumnsMenu.class, MainMenu.ColumnsMenu.class, new Parameter[] {
new ComponentParameter("mainShell"),
anyComponentParameter,
anyComponentParameter,
anyComponentParameter});
new ComponentParameter("mainShell"), new ComponentParameter("mainMenu"),
new ComponentParameter("commandsMenu"), anyComponentParameter, new ConstantParameter(container) });
container.registerComponentImplementation(MainMenu.ColumnsMenu.class, MainMenu.ColumnsMenu.class,
new Parameter[] { new ComponentParameter("mainShell"), anyComponentParameter, anyComponentParameter,
anyComponentParameter });
container.registerComponentImplementation(AboutDialog.class);
container.registerComponentImplementation(PreferencesDialog.class);
container.registerComponentImplementation(ConfigDetectorDialog.class);
container.registerComponentImplementation(SelectFetchersDialog.class);
container.registerComponentImplementation(DetailsWindow.class);
container.registerComponentImplementation(StatisticsDialog.class);
// various actions / listeners
container.registerComponentImplementation(StartStopScanningAction.class);
container.registerComponentImplementation(ColumnsActions.SortBy.class);
@ -211,21 +188,22 @@ public class ComponentRegistry {
if (Platform.MAC_OS) {
// initialize mac-specific stuff
try {
container.registerComponentImplementation(Class.forName("net.azib.ipscan.platform.mac.MacApplicationMenu"));
container.registerComponentImplementation(Class
.forName("net.azib.ipscan.platform.mac.MacApplicationMenu"));
}
catch (Exception e) {
Logger.getLogger(getClass().getName()).warning("Cannot initialize MacApplicationMenu: " + e);
}
}
}
private void start() {
if (!containerStarted) {
containerStarted = true;
container.start();
}
}
public MainWindow getMainWindow() {
// initialize all startable components
start();

View File

@ -26,7 +26,7 @@ import net.azib.ipscan.util.InetAddressUtils;
*/
public class ScannerDispatcherThread extends Thread implements ThreadFactory, StateTransitionListener {
private static final long UI_UPDATE_INTERVAL = 100;
private static final long UI_UPDATE_INTERVAL_MS = 150;
private ScannerConfig config;
private Scanner scanner;
@ -100,9 +100,10 @@ public class ScannerDispatcherThread extends Thread implements ThreadFactory, St
threadPool.execute(scanningTask);
}
// notify listeners of the progress we are doing (max 5 times per second)
if (System.currentTimeMillis() - lastNotifyTime >= UI_UPDATE_INTERVAL) {
lastNotifyTime = System.currentTimeMillis();
// notify listeners of the progress we are doing (limiting the update rate)
long now = System.currentTimeMillis();
if (now - lastNotifyTime >= UI_UPDATE_INTERVAL_MS) {
lastNotifyTime = now;
progressCallback.updateProgress(subject.getAddress(), numActiveThreads.intValue(), feeder.percentageComplete());
}
}
@ -120,7 +121,7 @@ public class ScannerDispatcherThread extends Thread implements ThreadFactory, St
try {
// now wait for all threads, which are still running
while (!threadPool.awaitTermination(UI_UPDATE_INTERVAL, TimeUnit.MILLISECONDS)) {
while (!threadPool.awaitTermination(UI_UPDATE_INTERVAL_MS, TimeUnit.MILLISECONDS)) {
progressCallback.updateProgress(null, numActiveThreads.intValue(), 100);
}
}

View File

@ -318,13 +318,15 @@ public class ScanningResultList implements Iterable<ScanningResult> {
class StopScanningListener implements StateTransitionListener {
public void transitionTo(ScanningState state, Transition transition) {
if (transition == Transition.COMPLETE && state == ScanningState.IDLE) {
info.endTime = System.currentTimeMillis();
info.scanFinished = true;
}
else
if (state == ScanningState.KILLING) {
info.scanAborted = true;
synchronized (ScanningResultList.this) {
if (transition == Transition.COMPLETE && state == ScanningState.IDLE) {
info.endTime = System.currentTimeMillis();
info.scanFinished = true;
}
else
if (state == ScanningState.KILLING) {
info.scanAborted = true;
}
}
}
}

View File

@ -10,12 +10,15 @@ import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* StateMachine implementation.
* Generic StateMachine implementation.
* It holds the current state and performs transitions with corresponding methods.
* <p/>
* Note: the class is abstract because notification of listeners often should happen in the correct thread,
* so subclasses should provide this functionality.
*
* @author Anton Keks
*/
public class StateMachine {
public abstract class StateMachine {
public enum Transition {INIT, START, STOP, NEXT, COMPLETE, RESET, RESCAN}
@ -79,7 +82,7 @@ public class StateMachine {
}
}
private void notifyAboutTransition(Transition transition) {
protected void notifyAboutTransition(Transition transition) {
try {
listenersLock.readLock().lock();
for (StateTransitionListener listener : transitionListeners) {

View File

@ -280,27 +280,22 @@ public class MainMenu implements Startable {
}
public void transitionTo(final ScanningState state, Transition transition) {
if (state != ScanningState.SCANNING && state != ScanningState.IDLE)
if (transition != Transition.START && transition != Transition.COMPLETE)
return;
menu.getDisplay().asyncExec(new Runnable() {
public void run() {
processMenu(menu);
processMenu(menu, state == ScanningState.IDLE);
}
public void processMenu(Menu menu, boolean isEnabled) {
// processes menu items recursively
for (MenuItem item : menu.getItems()) {
if (item.getData("disableDuringScanning") == Boolean.TRUE) {
item.setEnabled(isEnabled);
}
public void processMenu(Menu menu) {
// processes menu items recursively
for (MenuItem item : menu.getItems()) {
if (item.getData("disableDuringScanning") == Boolean.TRUE) {
item.setEnabled(state == ScanningState.IDLE);
}
else
if (item.getMenu() != null) {
processMenu(item.getMenu());
}
}
else
if (item.getMenu() != null) {
processMenu(item.getMenu(), isEnabled);
}
});
}
}
}
}

View File

@ -268,19 +268,15 @@ public class MainWindow {
class EnablerDisabler implements StateTransitionListener {
public void transitionTo(final ScanningState state, Transition transition) {
if (state != ScanningState.SCANNING && state != ScanningState.IDLE)
if (transition != Transition.START && transition != Transition.COMPLETE)
return;
shell.getDisplay().asyncExec(new Runnable() {
public void run() {
boolean enabled = state == ScanningState.IDLE;
feederArea.setEnabled(enabled);
feederSelectionCombo.setEnabled(enabled);
prefsButton.setEnabled(enabled);
fetchersButton.setEnabled(enabled);
statusBar.setEnabled(enabled);
}
});
boolean enabled = state == ScanningState.IDLE;
feederArea.setEnabled(enabled);
feederSelectionCombo.setEnabled(enabled);
prefsButton.setEnabled(enabled);
fetchersButton.setEnabled(enabled);
statusBar.setEnabled(enabled);
}
}

View File

@ -132,7 +132,7 @@ public class ResultTable extends Table implements FetcherRegistryUpdateListener,
public void addOrUpdateResultRow(final ScanningResult result) {
if (isDisposed())
return;
getDisplay().syncExec(new Runnable() {
getDisplay().asyncExec(new Runnable() {
public void run() {
if (isDisposed())
return;
@ -242,13 +242,9 @@ public class ResultTable extends Table implements FetcherRegistryUpdateListener,
}
public void transitionTo(final ScanningState state, Transition transition) {
public void transitionTo(ScanningState state, Transition transition) {
// change cursor while scanning
getDisplay().asyncExec(new Runnable() {
public void run() {
setCursor(getDisplay().getSystemCursor(state == ScanningState.IDLE ? SWT.CURSOR_ARROW : SWT.CURSOR_APPSTARTING));
}
});
setCursor(getDisplay().getSystemCursor(state == ScanningState.IDLE ? SWT.CURSOR_ARROW : SWT.CURSOR_APPSTARTING));
}
}

View File

@ -0,0 +1,42 @@
/**
* This file is a part of Angry IP Scanner source code,
* see http://www.azib.net/ for more information.
* Licensed under GPLv2.
*/
package net.azib.ipscan.gui;
import org.eclipse.swt.widgets.Display;
import net.azib.ipscan.core.state.StateMachine;
import net.azib.ipscan.core.state.StateTransitionListener;
/**
* Extends the generic {@link StateMachine} in order to run state transition notifications
* in the SWT user-interface thread. This will allow {@link StateTransitionListener}s to call SWT methods without
* the bloat of using the {@link Display#asyncExec(Runnable)} themselves.
*
* @author Anton Keks
*/
public class SWTAwareStateMachine extends StateMachine {
private Display display;
public SWTAwareStateMachine(Display display) {
this.display = display;
}
@Override
protected void notifyAboutTransition(final Transition transition) {
if (display.isDisposed())
return;
// call super asynchronously in the correct thread
display.asyncExec(new Runnable() {
public void run() {
SWTAwareStateMachine.super.notifyAboutTransition(transition);
}
});
}
}

View File

@ -138,63 +138,57 @@ public class StartStopScanningAction implements SelectionListener, ScanningProgr
}
public void transitionTo(final ScanningState state, final Transition transition) {
if (display.isDisposed())
if (statusBar.isDisposed() || transition == Transition.INIT)
return;
display.syncExec(new Runnable() {
public void run() {
if (statusBar.isDisposed() || transition == Transition.INIT)
return;
// TODO: separate GUI and non-GUI stuff
switch (state) {
case IDLE:
// reset state text
button.setEnabled(true);
updateProgress(null, 0, 0);
statusBar.setStatusText(null);
break;
case STARTING:
// start the scan from scratch!
resultTable.removeAll();
try {
scannerThread = scannerThreadFactory.createScannerThread(feederRegistry.createFeeder(), StartStopScanningAction.this, createResultsCallback());
stateMachine.startScanning();
mainWindowTitle = statusBar.getShell().getText();
}
catch (RuntimeException e) {
stateMachine.reset();
throw e;
}
break;
case RESTARTING:
// restart the scanning - rescan
resultTable.resetSelection();
try {
scannerThread = scannerThreadFactory.createScannerThread(feederRegistry.createRescanFeeder(resultTable.getSelection()), StartStopScanningAction.this, createResultsCallback());
stateMachine.startScanning();
mainWindowTitle = statusBar.getShell().getText();
}
catch (RuntimeException e) {
stateMachine.reset();
throw e;
}
break;
case SCANNING:
scannerThread.start();
break;
case STOPPING:
statusBar.setStatusText(Labels.getLabel("state.waitForThreads"));
break;
case KILLING:
button.setEnabled(false);
statusBar.setStatusText(Labels.getLabel("state.killingThreads"));
break;
// TODO: separate GUI and non-GUI stuff
switch (state) {
case IDLE:
// reset state text
button.setEnabled(true);
updateProgress(null, 0, 0);
statusBar.setStatusText(null);
break;
case STARTING:
// start the scan from scratch!
resultTable.removeAll();
try {
scannerThread = scannerThreadFactory.createScannerThread(feederRegistry.createFeeder(), StartStopScanningAction.this, createResultsCallback());
stateMachine.startScanning();
mainWindowTitle = statusBar.getShell().getText();
}
// change button image
button.setImage(buttonImages[state.ordinal()]);
button.setText(buttonTexts[state.ordinal()]);
}
});
catch (RuntimeException e) {
stateMachine.reset();
throw e;
}
break;
case RESTARTING:
// restart the scanning - rescan
resultTable.resetSelection();
try {
scannerThread = scannerThreadFactory.createScannerThread(feederRegistry.createRescanFeeder(resultTable.getSelection()), StartStopScanningAction.this, createResultsCallback());
stateMachine.startScanning();
mainWindowTitle = statusBar.getShell().getText();
}
catch (RuntimeException e) {
stateMachine.reset();
throw e;
}
break;
case SCANNING:
scannerThread.start();
break;
case STOPPING:
statusBar.setStatusText(Labels.getLabel("state.waitForThreads"));
break;
case KILLING:
button.setEnabled(false);
statusBar.setStatusText(Labels.getLabel("state.killingThreads"));
break;
}
// change button image
button.setImage(buttonImages[state.ordinal()]);
button.setText(buttonTexts[state.ordinal()]);
}
/**

View File

@ -5,6 +5,7 @@
*/
package net.azib.ipscan.gui.actions;
import net.azib.ipscan.config.CommandProcessor;
import net.azib.ipscan.config.GUIConfig;
import net.azib.ipscan.config.Labels;
import net.azib.ipscan.core.ScanningResultList;
@ -20,7 +21,6 @@ import net.azib.ipscan.gui.StatisticsDialog;
import net.azib.ipscan.gui.StatusBar;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
@ -74,11 +74,12 @@ public class ToolsActions {
private final StatisticsDialog statisticsDialog;
private final GUIConfig guiConfig;
public ScanStatistics(GUIConfig guiConfig, StatisticsDialog statisticsDialog, StateMachine stateMachine) {
public ScanStatistics(GUIConfig guiConfig, StatisticsDialog statisticsDialog, StateMachine stateMachine, CommandProcessor commandProcessor) {
this.guiConfig = guiConfig;
this.statisticsDialog = statisticsDialog;
// register for state changes
stateMachine.addTransitionListener(this);
if (!commandProcessor.shouldAutoQuit())
stateMachine.addTransitionListener(this);
}
public void handleEvent(Event event) {
@ -88,11 +89,7 @@ public class ToolsActions {
public void transitionTo(ScanningState state, Transition transition) {
// switching to IDLE means the end of scanning
if (transition == Transition.COMPLETE && guiConfig.showScanStats) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
handleEvent(null);
}
});
handleEvent(null);
}
}
}

View File

@ -66,7 +66,7 @@ public class ScanningResultListTest {
@Test
public void testConstructor() throws Exception {
StateMachine stateMachine = new StateMachine();
StateMachine stateMachine = new StateMachine(){};
scanningResults = new ScanningResultList(fetcherRegistry, stateMachine);
scanningResults.initNewScan(createMockFeeder("inff"));
assertFalse(scanningResults.getScanInfo().isCompletedNormally());

View File

@ -23,7 +23,8 @@ public class StateMachineTest {
@Before
public void createStateMachine() {
stateMachine = new StateMachine();
// create empty subclass of StateMachine
stateMachine = new StateMachine(){};
}
@Test