package org.xtest.runner.statusbar; import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import org.xtest.runner.RunnableTest; import org.xtest.runner.TestsProvider; import org.xtest.runner.events.TestDeleted; import org.xtest.runner.events.TestFinished; import org.xtest.runner.events.TestsCanceled; import org.xtest.runner.events.TestsStarted; import org.xtest.runner.external.TestResult; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import com.google.inject.Inject; import com.google.inject.Singleton; /** * Controller for the status bar * * @author Michael Barry */ @Singleton public class StatusBarController { private int failures = 0; private int files = 0; private final AtomicBoolean initialized = new AtomicBoolean(false); private final List<IStatusBarRepaintListener> listeners; private final EventBus notifiers; private int pending = 0; private final Map<URI, TestResult> resultsForUris = Maps.newHashMap(); @Inject private final TestsProvider testProvider; private int total = 0; private int worked = 0; /** * FOR GUICE ONLY * * @param bus * The event bus instance provided by Guice */ @Inject private StatusBarController(EventBus bus, TestsProvider provider) { List<IStatusBarRepaintListener> unsynchronizedList = Lists.newArrayList(); listeners = Collections.synchronizedList(unsynchronizedList); this.notifiers = bus; notifiers.register(this); this.testProvider = provider; // Don't initialize now since it Guice calls this method and it could cause deadlock, // instead lazy-initialize later before the first time the UI requests data } /** * Adds a listener that will be notified of when to repaint * * @param statusBar * Listener that wants to be notified when to repaint */ public void addListener(IStatusBarRepaintListener statusBar) { listeners.add(statusBar); } /** * FOR EVENT BUS ONLY * * @param event * Test canceled event */ @Subscribe public void cancel(TestsCanceled event) { testsMustRun(event.getTests()); notifyListeners(); } /** * FOR EVENT BUS ONLY * * @param event * Test deleted event */ @Subscribe public void deleted(TestDeleted event) { if (!event.getTests().isEmpty()) { initValues(); notifyListeners(); } } /** * Get the completion ration to display in the status bar * * @return The number of tests completed divided by the number of total tests */ public double getCompletionRatio() { lazyInit(); return files == 0 ? 1.0 : 1.0 * worked / files; } /** * Get the text to display in the status bar * * @return The text to display in the status bar */ public String getText() { lazyInit(); return Integer.toString(failures) + "F/" + Integer.toString(total); } /** * Returns true if there are no failures * * @return True if there are no failures, false if there are some */ public boolean isPassing() { lazyInit(); return failures == 0; } /** * Removes a listener that no longer wants to be notified of when to repaint * * @param statusBar * Listener that no longer wants to be notified when to repaint */ public void removeListener(IStatusBarRepaintListener statusBar) { listeners.remove(statusBar); } /** * FOR EVENT BUS ONLY * * @param event * Test started event */ @Subscribe public void start(TestsStarted event) { Collection<RunnableTest> tests = event.getTests(); testsMustRun(tests); notifyListeners(); } /** * FOR EVENT BUS ONLY * * @param event * Test finish event */ @Subscribe public void testRan(TestFinished event) { worked++; updateFor(event.getResult(), event.getFile().getLocationURI()); notifyListeners(); } /** * Add statistics from the list of tests to the total statistics for all tests * * @param allTests * The list of tests */ protected void addToTotals(Collection<RunnableTest> allTests) { for (RunnableTest test : allTests) { TestResult state = test.getState(); updateFor(state, test.getFile().getLocationURI()); } } /** * Re-initializes the list of tests from the workspace, marking the tests provided as to-be-run * * @param tests * The tests that are scheduled */ protected void testsMustRun(Collection<RunnableTest> tests) { initValues(); worked -= tests.size(); } /** * Updates cumulative totals with the test result provided * * @param state * The test result * @param uri * The URI that the test result is for */ protected void updateFor(TestResult state, URI uri) { removeFromTestCounts(uri); failures += state.getNumFail(); pending += state.getNumPend(); total += state.getNumTotal(); resultsForUris.put(uri, state); } private void initValues() { initialized.set(true); total = 0; failures = 0; pending = 0; Collection<RunnableTest> allTests = testProvider.getAllTests(); resultsForUris.clear(); worked = files = allTests.size(); addToTotals(allTests); } private void lazyInit() { if (!initialized.get()) { initValues(); } } private void notifyListeners() { for (IStatusBarRepaintListener listener : listeners) { listener.schedulePaint(); } } private boolean removeFromTestCounts(URI uri) { boolean changed = false; TestResult old = resultsForUris.get(uri); if (old != null) { failures -= old.getNumFail(); pending -= old.getNumPend(); total -= old.getNumTotal(); changed = true; } return changed; } }