/** @author: Roman Chernyatchik */ public class SMTestRunnerResultsForm extends TestResultsPanel implements TestFrameworkRunningModel, TestResultsViewer, SMTRunnerEventsListener { @NonNls public static final String HISTORY_DATE_FORMAT = "yyyy.MM.dd 'at' HH'h' mm'm' ss's'"; @NonNls private static final String DEFAULT_SM_RUNNER_SPLITTER_PROPERTY = "SMTestRunner.Splitter.Proportion"; public static final Color DARK_YELLOW = JBColor.YELLOW.darker(); private static final Logger LOG = Logger.getInstance("#" + SMTestRunnerResultsForm.class.getName()); private SMTRunnerTestTreeView myTreeView; private TestsProgressAnimator myAnimator; /** Fake parent suite for all tests and suites */ private final SMTestProxy.SMRootTestProxy myTestsRootNode; private SMTRunnerTreeBuilder myTreeBuilder; private final List<EventsListener> myEventListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private PropagateSelectionHandler myShowStatisticForProxyHandler; private final Project myProject; private int myTotalTestCount = 0; private int myStartedTestCount = 0; private int myFinishedTestCount = 0; private int myFailedTestCount = 0; private int myIgnoredTestCount = 0; private long myStartTime; private long myEndTime; private StatisticsPanel myStatisticsPane; // custom progress private String myCurrentCustomProgressCategory; private final Set<String> myMentionedCategories = new LinkedHashSet<String>(); private boolean myTestsRunning = true; private AbstractTestProxy myLastSelected; private Alarm myUpdateQueue; private Set<Update> myRequests = Collections.synchronizedSet(new HashSet<Update>()); public SMTestRunnerResultsForm( @NotNull final JComponent console, final TestConsoleProperties consoleProperties) { this(console, AnAction.EMPTY_ARRAY, consoleProperties, null); } public SMTestRunnerResultsForm( @NotNull final JComponent console, AnAction[] consoleActions, final TestConsoleProperties consoleProperties, @Nullable String splitterPropertyName) { super( console, consoleActions, consoleProperties, StringUtil.notNullize(splitterPropertyName, DEFAULT_SM_RUNNER_SPLITTER_PROPERTY), 0.2f); myProject = consoleProperties.getProject(); // Create tests common suite root //noinspection HardCodedStringLiteral myTestsRootNode = new SMTestProxy.SMRootTestProxy(); // todo myTestsRootNode.setOutputFilePath(runConfiguration.getOutputFilePath()); // Fire selection changed and move focus on SHIFT+ENTER // TODO[romeo] improve /* final ArrayList<Component> components = new ArrayList<Component>(); components.add(myTreeView); components.add(myTabs.getComponent()); myContentPane.setFocusTraversalPolicy(new MyFocusTraversalPolicy(components)); myContentPane.setFocusCycleRoot(true); */ } @Override public void initUI() { super.initUI(); if (Registry.is("tests.view.old.statistics.panel")) { final KeyStroke shiftEnterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_MASK); SMRunnerUtil.registerAsAction( shiftEnterKey, "show-statistics-for-test-proxy", new Runnable() { public void run() { showStatisticsForSelectedProxy(); } }, myTreeView); } } protected ToolbarPanel createToolbarPanel() { return new SMTRunnerToolbarPanel(myProperties, this, this); } protected JComponent createTestTreeView() { myTreeView = new SMTRunnerTestTreeView(); myTreeView.setLargeModel(true); myTreeView.attachToModel(this); myTreeView.setTestResultsViewer(this); if (Registry.is("tests.view.old.statistics.panel")) { addTestsTreeSelectionListener( new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { AbstractTestProxy selectedTest = getTreeView().getSelectedTest(); if (selectedTest instanceof SMTestProxy) { myStatisticsPane.selectProxy(((SMTestProxy) selectedTest), this, false); } } }); } final SMTRunnerTreeStructure structure = new SMTRunnerTreeStructure(myProject, myTestsRootNode); myTreeBuilder = new SMTRunnerTreeBuilder(myTreeView, structure); myTreeBuilder.setTestsComparator(TestConsoleProperties.SORT_ALPHABETICALLY.value(myProperties)); Disposer.register(this, myTreeBuilder); myAnimator = new TestsProgressAnimator(myTreeBuilder); TrackRunningTestUtil.installStopListeners( myTreeView, myProperties, new Pass<AbstractTestProxy>() { @Override public void pass(AbstractTestProxy testProxy) { if (testProxy == null) return; // drill to the first leaf while (!testProxy.isLeaf()) { final List<? extends AbstractTestProxy> children = testProxy.getChildren(); if (!children.isEmpty()) { final AbstractTestProxy firstChild = children.get(0); if (firstChild != null) { testProxy = firstChild; continue; } } break; } // pretend the selection on the first leaf // so if test would be run, tracking would be restarted myLastSelected = testProxy; } }); // TODO always hide root node // myTreeView.setRootVisible(false); myUpdateQueue = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this); return myTreeView; } protected JComponent createStatisticsPanel() { // Statistics tab final StatisticsPanel statisticsPane = new StatisticsPanel(myProject, this); // handler to select in results viewer by statistics pane events statisticsPane.addPropagateSelectionListener(createSelectMeListener()); // handler to select test statistics pane by result viewer events setShowStatisticForProxyHandler(statisticsPane.createSelectMeListener()); myStatisticsPane = statisticsPane; return myStatisticsPane.getContentPane(); } public StatisticsPanel getStatisticsPane() { return myStatisticsPane; } public void addTestsTreeSelectionListener(final TreeSelectionListener listener) { myTreeView.getSelectionModel().addTreeSelectionListener(listener); } /** * Is used for navigation from tree view to other UI components * * @param handler */ public void setShowStatisticForProxyHandler(final PropagateSelectionHandler handler) { myShowStatisticForProxyHandler = handler; } /** * Returns root node, fake parent suite for all tests and suites * * @param testsRoot * @return */ public void onTestingStarted(@NotNull SMTestProxy.SMRootTestProxy testsRoot) { myAnimator.setCurrentTestCase(myTestsRootNode); myTreeBuilder.updateFromRoot(); // Status line myStatusLine.setStatusColor(ColorProgressBar.GREEN); // Tests tree selectAndNotify(myTestsRootNode); myStartTime = System.currentTimeMillis(); boolean printTestingStartedTime = true; if (myProperties instanceof SMTRunnerConsoleProperties) { printTestingStartedTime = ((SMTRunnerConsoleProperties) myProperties).isPrintTestingStartedTime(); } if (printTestingStartedTime) { myTestsRootNode.addSystemOutput( "Testing started at " + DateFormatUtil.formatTime(myStartTime) + " ...\n"); } updateStatusLabel(false); // TODO : show info - "Loading..." msg fireOnTestingStarted(); } public void onTestingFinished(@NotNull SMTestProxy.SMRootTestProxy testsRoot) { myEndTime = System.currentTimeMillis(); if (myTotalTestCount == 0) { myTotalTestCount = myStartedTestCount; myStatusLine.setFraction(1); } updateStatusLabel(true); updateIconProgress(true); myAnimator.stopMovie(); myTreeBuilder.updateFromRoot(); LvcsHelper.addLabel(this); final Runnable onDone = new Runnable() { @Override public void run() { myTestsRunning = false; final boolean sortByDuration = TestConsoleProperties.SORT_BY_DURATION.value(myProperties); if (sortByDuration) { myTreeBuilder.setStatisticsComparator(myProperties, sortByDuration); } } }; if (myLastSelected == null) { selectAndNotify(myTestsRootNode, onDone); } else { onDone.run(); } fireOnTestingFinished(); if (testsRoot.wasTerminated() && myStatusLine.getStatusColor() == ColorProgressBar.GREEN) { myStatusLine.setStatusColor(JBColor.LIGHT_GRAY); } if (testsRoot.isEmptySuite() && testsRoot.isTestsReporterAttached() && myProperties instanceof SMTRunnerConsoleProperties && ((SMTRunnerConsoleProperties) myProperties).fixEmptySuite()) { return; } final TestsUIUtil.TestResultPresentation presentation = new TestsUIUtil.TestResultPresentation(testsRoot, myStartTime > 0, null) .getPresentation( myFailedTestCount, myFinishedTestCount - myFailedTestCount - myIgnoredTestCount, myTotalTestCount - myFinishedTestCount, myIgnoredTestCount); TestsUIUtil.notifyByBalloon(myProperties.getProject(), testsRoot, myProperties, presentation); addToHistory(testsRoot, myProperties, this); } private static void addToHistory( final SMTestProxy.SMRootTestProxy root, TestConsoleProperties consoleProperties, Disposable parentDisposable) { final RunProfile configuration = consoleProperties.getConfiguration(); if (configuration instanceof RunConfiguration && !(consoleProperties instanceof ImportedTestConsoleProperties)) { final MySaveHistoryTask backgroundable = new MySaveHistoryTask(consoleProperties, root, (RunConfiguration) configuration); final BackgroundableProcessIndicator processIndicator = new BackgroundableProcessIndicator(backgroundable); Disposer.register( parentDisposable, new Disposable() { @Override public void dispose() { processIndicator.cancel(); backgroundable.dispose(); } }); Disposer.register(parentDisposable, processIndicator); ProgressManager.getInstance() .runProcessWithProgressAsynchronously(backgroundable, processIndicator); } } public void onTestsCountInSuite(final int count) { updateCountersAndProgressOnTestCount(count, false); } /** * Adds test to tree and updates status line. Test proxy should be initialized, proxy parent must * be some suite (already added to tree) * * @param testProxy Proxy */ public void onTestStarted(@NotNull final SMTestProxy testProxy) { if (!testProxy.isConfig()) { updateOnTestStarted(false); } _addTestOrSuite(testProxy); fireOnTestNodeAdded(testProxy); } @Override public void onSuiteTreeNodeAdded(SMTestProxy testProxy) { myTotalTestCount++; } @Override public void onSuiteTreeStarted(SMTestProxy suite) {} public void onTestFailed(@NotNull final SMTestProxy test) { updateOnTestFailed(false); if (test.isConfig()) { myStartedTestCount++; myFinishedTestCount++; } updateIconProgress(false); // still expand failure when user selected another test if (myLastSelected != null && TestConsoleProperties.TRACK_RUNNING_TEST.value(myProperties) && TestConsoleProperties.HIDE_PASSED_TESTS.value(myProperties)) { myTreeBuilder.expand(test, null); } } public void onTestIgnored(@NotNull final SMTestProxy test) { updateOnTestIgnored(); } /** * Adds suite to tree Suite proxy should be initialized, proxy parent must be some suite (already * added to tree) If parent is null, then suite will be added to tests root. * * @param newSuite Tests suite */ public void onSuiteStarted(@NotNull final SMTestProxy newSuite) { _addTestOrSuite(newSuite); } public void onCustomProgressTestsCategory(@Nullable String categoryName, int testCount) { myCurrentCustomProgressCategory = categoryName; updateCountersAndProgressOnTestCount(testCount, true); } public void onCustomProgressTestStarted() { updateOnTestStarted(true); } public void onCustomProgressTestFailed() { updateOnTestFailed(true); } @Override public void onCustomProgressTestFinished() { updateOnTestFinished(true); } public void onTestFinished(@NotNull final SMTestProxy test) { if (!test.isConfig()) { updateOnTestFinished(false); } updateIconProgress(false); } public void onSuiteFinished(@NotNull final SMTestProxy suite) { // Do nothing } public SMTestProxy.SMRootTestProxy getTestsRootNode() { return myTestsRootNode; } public TestConsoleProperties getProperties() { return myProperties; } public void setFilter(final Filter filter) { // is used by Test Runner actions, e.g. hide passed, etc final SMTRunnerTreeStructure treeStructure = myTreeBuilder.getSMRunnerTreeStructure(); treeStructure.setFilter(filter); // TODO - show correct info if no children are available // (e.g no tests found or all tests passed, etc.) // treeStructure.getChildElements(treeStructure.getRootElement()).length == 0 myTreeBuilder.queueUpdate(); } public boolean isRunning() { return myTestsRunning; } public TestTreeView getTreeView() { return myTreeView; } @Override public SMTRunnerTreeBuilder getTreeBuilder() { return myTreeBuilder; } public boolean hasTestSuites() { return getRoot().getChildren().size() > 0; } @NotNull public AbstractTestProxy getRoot() { return myTestsRootNode; } /** * Manual test proxy selection in tests tree. E.g. do select root node on testing started or do * select current node if TRACK_RUNNING_TEST is enabled * * <p> * * <p>Will select proxy in Event Dispatch Thread. Invocation of this method may be not in event * dispatch thread * * @param testProxy Test or suite */ @Override public void selectAndNotify(AbstractTestProxy testProxy) { selectAndNotify(testProxy, null); } private void selectAndNotify( @Nullable final AbstractTestProxy testProxy, @Nullable Runnable onDone) { selectWithoutNotify(testProxy, onDone); // Is used by Statistic tab to differ use selection in tree // from manual selection from API (e.g. test runner events) if (Registry.is("tests.view.old.statistics.panel")) { showStatisticsForSelectedProxy(testProxy, false); } } public void addEventsListener(final EventsListener listener) { myEventListeners.add(listener); addTestsTreeSelectionListener( new TreeSelectionListener() { public void valueChanged(final TreeSelectionEvent e) { // We should fire event only if it was generated by this component, // e.g. it is focused. Otherwise it is side effect of selecting proxy in // try by other component // if (myTreeView.isFocusOwner()) { @Nullable final SMTestProxy selectedProxy = (SMTestProxy) getTreeView().getSelectedTest(); listener.onSelected( selectedProxy, SMTestRunnerResultsForm.this, SMTestRunnerResultsForm.this); // } } }); } public void dispose() { super.dispose(); myShowStatisticForProxyHandler = null; myEventListeners.clear(); myStatisticsPane.doDispose(); } public void showStatisticsForSelectedProxy() { TestConsoleProperties.SHOW_STATISTICS.set(myProperties, true); final AbstractTestProxy selectedProxy = myTreeView.getSelectedTest(); showStatisticsForSelectedProxy(selectedProxy, true); } private void showStatisticsForSelectedProxy( final AbstractTestProxy selectedProxy, final boolean requestFocus) { if (selectedProxy instanceof SMTestProxy && myShowStatisticForProxyHandler != null) { myShowStatisticForProxyHandler.handlePropagateSelectionRequest( (SMTestProxy) selectedProxy, this, requestFocus); } } protected int getTotalTestCount() { return myTotalTestCount; } protected int getStartedTestCount() { return myStartedTestCount; } protected int getFinishedTestCount() { return myFinishedTestCount; } protected int getFailedTestCount() { return myFailedTestCount; } protected int getIgnoredTestCount() { return myIgnoredTestCount; } protected Color getTestsStatusColor() { return myStatusLine.getStatusColor(); } public Set<String> getMentionedCategories() { return myMentionedCategories; } protected long getStartTime() { return myStartTime; } protected long getEndTime() { return myEndTime; } private void _addTestOrSuite(@NotNull final SMTestProxy newTestOrSuite) { final SMTestProxy parentSuite = newTestOrSuite.getParent(); assert parentSuite != null; // Tree final Update update = new Update(parentSuite) { @Override public void run() { myRequests.remove(this); myTreeBuilder.updateTestsSubtree(parentSuite); } }; if (ApplicationManager.getApplication().isUnitTestMode()) { update.run(); } else if (myRequests.add(update) && !myUpdateQueue.isDisposed()) { myUpdateQueue.addRequest(update, 100); } myTreeBuilder.repaintWithParents(newTestOrSuite); myAnimator.setCurrentTestCase(newTestOrSuite); if (TestConsoleProperties.TRACK_RUNNING_TEST.value(myProperties)) { if (myLastSelected == null || myLastSelected == newTestOrSuite) { myLastSelected = null; selectAndNotify(newTestOrSuite); } } } private void fireOnTestNodeAdded(final SMTestProxy test) { for (EventsListener eventListener : myEventListeners) { eventListener.onTestNodeAdded(this, test); } } private void fireOnTestingFinished() { for (EventsListener eventListener : myEventListeners) { eventListener.onTestingFinished(this); } } private void fireOnTestingStarted() { for (EventsListener eventListener : myEventListeners) { eventListener.onTestingStarted(this); } } private void selectWithoutNotify( final AbstractTestProxy testProxy, @Nullable final Runnable onDone) { if (testProxy == null) { return; } SMRunnerUtil.runInEventDispatchThread( new Runnable() { public void run() { if (myTreeBuilder.isDisposed()) { return; } myTreeBuilder.select(testProxy, onDone); } }, ModalityState.NON_MODAL); } private void updateStatusLabel(final boolean testingFinished) { if (myFailedTestCount > 0) { myStatusLine.setStatusColor(ColorProgressBar.RED); } if (testingFinished) { if (myTotalTestCount == 0) { myStatusLine.setStatusColor( myTestsRootNode.wasLaunched() || !myTestsRootNode.isTestsReporterAttached() ? JBColor.LIGHT_GRAY : ColorProgressBar.RED); } // else color will be according failed/passed tests } // launchedAndFinished - is launched and not in progress. If we remove "launched' that // onTestingStarted() before // initializing will be "launchedAndFinished" final boolean launchedAndFinished = myTestsRootNode.wasLaunched() && !myTestsRootNode.isInProgress(); if (!TestsPresentationUtil.hasNonDefaultCategories(myMentionedCategories)) { myStatusLine.formatTestMessage( myTotalTestCount, myFinishedTestCount, myFailedTestCount, myIgnoredTestCount, myTestsRootNode.getDuration(), myEndTime); } else { myStatusLine.setText( TestsPresentationUtil.getProgressStatus_Text( myStartTime, myEndTime, myTotalTestCount, myFinishedTestCount, myFailedTestCount, myMentionedCategories, launchedAndFinished)); } } /** for java unit tests */ public void performUpdate() { myTreeBuilder.performUpdate(); } private void updateIconProgress(boolean updateWithAttention) { final int totalTestCount, doneTestCount; if (myTotalTestCount == 0) { totalTestCount = 2; doneTestCount = 1; } else { totalTestCount = myTotalTestCount; doneTestCount = myFinishedTestCount; } TestsUIUtil.showIconProgress( myProject, doneTestCount, totalTestCount, myFailedTestCount, updateWithAttention); } /** * On event change selection and probably requests focus. Is used when we want navigate from other * component to this * * @return Listener */ public PropagateSelectionHandler createSelectMeListener() { return new PropagateSelectionHandler() { public void handlePropagateSelectionRequest( @Nullable final SMTestProxy selectedTestProxy, @NotNull final Object sender, final boolean requestFocus) { SMRunnerUtil.addToInvokeLater( new Runnable() { public void run() { selectWithoutNotify(selectedTestProxy, null); // Request focus if necessary if (requestFocus) { // myTreeView.requestFocusInWindow(); IdeFocusManager.getInstance(myProject).requestFocus(myTreeView, true); } } }); } }; } private void updateCountersAndProgressOnTestCount( final int count, final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; // This is for better support groups of TestSuites // Each group notifies about it's size myTotalTestCount += count; updateStatusLabel(false); } private void updateOnTestStarted(final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; // for mixed tests results : mention category only if it contained tests myMentionedCategories.add( myCurrentCustomProgressCategory != null ? myCurrentCustomProgressCategory : TestsPresentationUtil.DEFAULT_TESTS_CATEGORY); myStartedTestCount++; // fix total count if it is corrupted // but if test count wasn't set at all let's process such case separately if (myStartedTestCount > myTotalTestCount && myTotalTestCount != 0) { myTotalTestCount = myStartedTestCount; } updateStatusLabel(false); } private void updateProgressOnTestDone() { int doneTestCount = myFinishedTestCount; // update progress if (myTotalTestCount != 0) { // if total is set myStatusLine.setFraction((double) doneTestCount / myTotalTestCount); } else { // if at least one test was launcher than just set progress in the middle to show user that // tests are running myStatusLine.setFraction(doneTestCount > 0 ? 0.5 : 0); } } private void updateOnTestFailed(final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; myFailedTestCount++; updateProgressOnTestDone(); updateStatusLabel(false); } private void updateOnTestFinished(final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; myFinishedTestCount++; updateProgressOnTestDone(); } private void updateOnTestIgnored() { myIgnoredTestCount++; updateProgressOnTestDone(); updateStatusLabel(false); } private boolean isModeConsistent(boolean isCustomMessage) { // check that we are in consistent mode return isCustomMessage != (myCurrentCustomProgressCategory == null); } private static class MySaveHistoryTask extends Task.Backgroundable { private final TestConsoleProperties myConsoleProperties; private SMTestProxy.SMRootTestProxy myRoot; private RunConfiguration myConfiguration; private File myOutputFile; public MySaveHistoryTask( TestConsoleProperties consoleProperties, SMTestProxy.SMRootTestProxy root, RunConfiguration configuration) { super(consoleProperties.getProject(), "Save Test Results", true); myConsoleProperties = consoleProperties; myRoot = root; myConfiguration = configuration; } @Override public void run(@NotNull ProgressIndicator indicator) { try { SAXTransformerFactory transformerFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); TransformerHandler handler = transformerFactory.newTransformerHandler(); handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes"); handler .getTransformer() .setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); final String configurationNameIncludedDate = PathUtil.suggestFileName(myConfiguration.getName()) + " - " + new SimpleDateFormat(HISTORY_DATE_FORMAT).format(new Date()); myOutputFile = new File( AbstractImportTestsAction.getTestHistoryRoot(myProject), configurationNameIncludedDate + ".xml"); FileUtilRt.createParentDirs(myOutputFile); handler.setResult(new StreamResult(new FileWriter(myOutputFile))); final SMTestProxy.SMRootTestProxy root = myRoot; final RunConfiguration configuration = myConfiguration; if (root != null && configuration != null) { TestResultsXmlFormatter.execute(root, configuration, myConsoleProperties, handler); } } catch (ProcessCanceledException e) { throw e; } catch (Exception e) { LOG.info("Export to history failed", e); } } @Override public void onSuccess() { if (myOutputFile != null && myOutputFile.exists()) { AbstractImportTestsAction.adjustHistory(myProject); TestHistoryConfiguration.getInstance(myProject) .registerHistoryItem( myOutputFile.getName(), myConfiguration.getName(), myConfiguration.getType().getId()); } } public void dispose() { myConfiguration = null; myRoot = null; myOutputFile = null; } } }
public abstract class PsiDocumentManagerBase extends PsiDocumentManager implements ProjectComponent, DocumentListener { protected static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiDocumentManagerImpl"); protected static final Key<PsiFile> HARD_REF_TO_PSI = new Key<PsiFile>("HARD_REFERENCE_TO_PSI"); protected static final Key<List<Runnable>> ACTION_AFTER_COMMIT = Key.create("ACTION_AFTER_COMMIT"); protected final Project myProject; protected final PsiManager myPsiManager; protected final DocumentCommitProcessor myDocumentCommitProcessor; protected final Set<Document> myUncommittedDocuments = Collections.synchronizedSet(new HashSet<Document>()); protected volatile boolean myIsCommitInProgress; protected final PsiToDocumentSynchronizer mySynchronizer; protected final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); protected final SmartPointerManagerImpl mySmartPointerManager; public PsiDocumentManagerBase( @NotNull final Project project, @NotNull PsiManager psiManager, @NotNull SmartPointerManager smartPointerManager, @NotNull MessageBus bus, @NonNls @NotNull final DocumentCommitProcessor documentCommitProcessor) { myProject = project; myPsiManager = psiManager; myDocumentCommitProcessor = documentCommitProcessor; mySmartPointerManager = (SmartPointerManagerImpl) smartPointerManager; mySynchronizer = new PsiToDocumentSynchronizer(this, bus); myPsiManager.addPsiTreeChangeListener(mySynchronizer); } @Override public void projectOpened() {} @Override public void projectClosed() {} @Override @NotNull public String getComponentName() { return "PsiDocumentManager"; } @Override public void initComponent() {} @Override public void disposeComponent() {} @Override @Nullable public PsiFile getPsiFile(@NotNull Document document) { final PsiFile userData = document.getUserData(HARD_REF_TO_PSI); if (userData != null) return userData; PsiFile psiFile = getCachedPsiFile(document); if (psiFile != null) return psiFile; final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !virtualFile.isValid()) return null; psiFile = getPsiFile(virtualFile); if (psiFile == null) return null; fireFileCreated(document, psiFile); return psiFile; } public static void cachePsi(@NotNull Document document, @NotNull PsiFile file) { document.putUserData(HARD_REF_TO_PSI, file); } @Override public PsiFile getCachedPsiFile(@NotNull Document document) { final PsiFile userData = document.getUserData(HARD_REF_TO_PSI); if (userData != null) return userData; final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !virtualFile.isValid()) return null; return getCachedPsiFile(virtualFile); } @Nullable public FileViewProvider getCachedViewProvider(@NotNull Document document) { final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); if (virtualFile == null || !virtualFile.isValid()) return null; return ((PsiManagerEx) myPsiManager).getFileManager().findCachedViewProvider(virtualFile); } @Nullable protected PsiFile getCachedPsiFile(VirtualFile virtualFile) { return ((PsiManagerEx) myPsiManager).getFileManager().getCachedPsiFile(virtualFile); } @Nullable protected PsiFile getPsiFile(VirtualFile virtualFile) { return ((PsiManagerEx) myPsiManager).getFileManager().findFile(virtualFile); } @Nullable @Override public Document getDocument(@NotNull PsiFile file) { if (file instanceof PsiBinaryFile) return null; Document document = getCachedDocument(file); if (document != null) { if (!file.getViewProvider().isPhysical() && document.getUserData(HARD_REF_TO_PSI) == null) { cachePsi(document, file); } return document; } if (!file.getViewProvider().isEventSystemEnabled()) return null; document = FileDocumentManager.getInstance().getDocument(file.getViewProvider().getVirtualFile()); if (document != null) { if (document.getTextLength() != file.getTextLength()) { throw new AssertionError( "Modified PSI with no document: " + file + "; physical=" + file.getViewProvider().isPhysical()); } if (!file.getViewProvider().isPhysical()) { cachePsi(document, file); } } return document; } @Override public Document getCachedDocument(@NotNull PsiFile file) { if (!file.isPhysical()) return null; VirtualFile vFile = file.getViewProvider().getVirtualFile(); return FileDocumentManager.getInstance().getCachedDocument(vFile); } @Override public void commitAllDocuments() { ApplicationManager.getApplication().assertIsDispatchThread(); if (myUncommittedDocuments.isEmpty()) return; final Document[] documents = getUncommittedDocuments(); for (Document document : documents) { commitDocument(document); } LOG.assertTrue(!hasUncommitedDocuments(), myUncommittedDocuments); } @Override public void performForCommittedDocument( @NotNull final Document doc, @NotNull final Runnable action) { final Document document = doc instanceof DocumentWindow ? ((DocumentWindow) doc).getDelegate() : doc; if (isCommitted(document)) { action.run(); } else { addRunOnCommit(document, action); } } private final Map<Object, Runnable> actionsWhenAllDocumentsAreCommitted = new LinkedHashMap<Object, Runnable>(); // accessed from EDT only private static final Object PERFORM_ALWAYS_KEY = new Object() { @Override @NonNls public String toString() { return "PERFORM_ALWAYS"; } }; /** * Cancel previously registered action and schedules (new) action to be executed when all * documents are committed. * * @param key the (unique) id of the action. * @param action The action to be executed after automatic commit. This action will overwrite any * action which was registered under this key earlier. The action will be executed in EDT. * @return true if action has been run immediately, or false if action was scheduled for execution * later. */ public boolean cancelAndRunWhenAllCommitted( @NonNls @NotNull Object key, @NotNull final Runnable action) { ApplicationManager.getApplication().assertIsDispatchThread(); if (myProject.isDisposed()) { action.run(); return true; } if (myUncommittedDocuments.isEmpty()) { action.run(); assert actionsWhenAllDocumentsAreCommitted.isEmpty() : actionsWhenAllDocumentsAreCommitted; return true; } actionsWhenAllDocumentsAreCommitted.put(key, action); return false; } public static void addRunOnCommit(@NotNull Document document, @NotNull Runnable action) { synchronized (ACTION_AFTER_COMMIT) { List<Runnable> list = document.getUserData(ACTION_AFTER_COMMIT); if (list == null) { document.putUserData(ACTION_AFTER_COMMIT, list = new SmartList<Runnable>()); } list.add(action); } } @Override public void commitDocument(@NotNull final Document doc) { final Document document = doc instanceof DocumentWindow ? ((DocumentWindow) doc).getDelegate() : doc; if (!isCommitted(document)) { doCommit(document, null); } } public boolean finishCommit( @NotNull final Document document, @NotNull final List<Processor<Document>> finishProcessors, final boolean synchronously, @NotNull final Object reason) { assert !myProject.isDisposed() : "Already disposed"; final boolean[] ok = {true}; ApplicationManager.getApplication() .runWriteAction( new CommitToPsiFileAction(document, myProject) { @Override public void run() { ok[0] = finishCommitInWriteAction(document, finishProcessors, synchronously); } }); if (ok[0]) { // otherwise changes maybe not synced to the document yet, and injectors will crash if (!mySynchronizer.isDocumentAffectedByTransactions(document)) { final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(myProject); if (injectedLanguageManager != null) injectedLanguageManager.startRunInjectors(document, synchronously); } // run after commit actions outside write action runAfterCommitActions(document); if (DebugUtil.DO_EXPENSIVE_CHECKS) { checkAllElementsValid(document, reason); } } return ok[0]; } protected boolean finishCommitInWriteAction( @NotNull final Document document, @NotNull final List<Processor<Document>> finishProcessors, final boolean synchronously) { if (myProject.isDisposed()) return false; assert !(document instanceof DocumentWindow); myIsCommitInProgress = true; boolean success = true; try { final FileViewProvider viewProvider = getCachedViewProvider(document); if (viewProvider != null) { for (Processor<Document> finishRunnable : finishProcessors) { success = finishRunnable.process(document); if (synchronously) { assert success; } if (!success) { break; } } viewProvider.contentsSynchronized(); } } finally { myDocumentCommitProcessor.log( "in PDI.finishDoc: ", null, synchronously, success, myUncommittedDocuments); if (success) { myUncommittedDocuments.remove(document); myDocumentCommitProcessor.log( "in PDI.finishDoc: removed doc", null, synchronously, success, myUncommittedDocuments); } myIsCommitInProgress = false; myDocumentCommitProcessor.log( "in PDI.finishDoc: exit", null, synchronously, success, myUncommittedDocuments); } return success; } private void checkAllElementsValid(@NotNull Document document, @NotNull final Object reason) { final PsiFile psiFile = getCachedPsiFile(document); if (psiFile != null) { psiFile.accept( new PsiRecursiveElementWalkingVisitor() { @Override public void visitElement(PsiElement element) { if (!element.isValid()) { throw new AssertionError( "Commit to '" + psiFile.getVirtualFile() + "' has led to invalid element: " + element + "; Reason: '" + reason + "'"); } } }); } } protected void doCommit(@NotNull final Document document, final PsiFile excludeFile) { assert !myIsCommitInProgress : "Do not call commitDocument() from inside PSI change listener"; ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { // otherwise there are many clients calling commitAllDocs() on PSI childrenChanged() if (getSynchronizer().isDocumentAffectedByTransactions(document) && excludeFile == null) return; myIsCommitInProgress = true; try { myDocumentCommitProcessor.commitSynchronously(document, myProject, excludeFile); } finally { myIsCommitInProgress = false; } assert !myUncommittedDocuments.contains(document) : "Document :" + document; } }); } public void commitOtherFilesAssociatedWithDocument( final Document document, final PsiFile psiFile) {} @Override public <T> T commitAndRunReadAction(@NotNull final Computable<T> computation) { final Ref<T> ref = Ref.create(null); commitAndRunReadAction( new Runnable() { @Override public void run() { ref.set(computation.compute()); } }); return ref.get(); } @Override public void commitAndRunReadAction(@NotNull final Runnable runnable) { final Application application = ApplicationManager.getApplication(); if (SwingUtilities.isEventDispatchThread()) { commitAllDocuments(); runnable.run(); } else { LOG.assertTrue( !ApplicationManager.getApplication().isReadAccessAllowed(), "Don't call commitAndRunReadAction inside ReadAction, it will cause a deadlock otherwise."); final Semaphore s1 = new Semaphore(); final Semaphore s2 = new Semaphore(); final boolean[] committed = {false}; application.runReadAction( new Runnable() { @Override public void run() { if (myUncommittedDocuments.isEmpty()) { runnable.run(); committed[0] = true; } else { s1.down(); s2.down(); final Runnable commitRunnable = new Runnable() { @Override public void run() { commitAllDocuments(); s1.up(); s2.waitFor(); } }; final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); if (progressIndicator == null) { ApplicationManager.getApplication().invokeLater(commitRunnable); } else { ApplicationManager.getApplication() .invokeLater(commitRunnable, progressIndicator.getModalityState()); } } } }); if (!committed[0]) { s1.waitFor(); application.runReadAction( new Runnable() { @Override public void run() { s2.up(); runnable.run(); } }); } } } /** * Schedules action to be executed when all documents are committed. * * @return true if action has been run immediately, or false if action was scheduled for execution * later. */ @Override public boolean performWhenAllCommitted(@NotNull final Runnable action) { ApplicationManager.getApplication().assertIsDispatchThread(); assert !myProject.isDisposed() : "Already disposed: " + myProject; if (myUncommittedDocuments.isEmpty()) { action.run(); return true; } CompositeRunnable actions = (CompositeRunnable) actionsWhenAllDocumentsAreCommitted.get(PERFORM_ALWAYS_KEY); if (actions == null) { actions = new CompositeRunnable(); actionsWhenAllDocumentsAreCommitted.put(PERFORM_ALWAYS_KEY, actions); } actions.add(action); myDocumentCommitProcessor.log( "PDI: added performWhenAllCommitted", null, false, action, myUncommittedDocuments); return false; } private static class CompositeRunnable extends ArrayList<Runnable> implements Runnable { @Override public void run() { for (Runnable runnable : this) { runnable.run(); } } } private void runAfterCommitActions(@NotNull Document document) { ApplicationManager.getApplication().assertIsDispatchThread(); List<Runnable> list; synchronized (ACTION_AFTER_COMMIT) { list = document.getUserData(ACTION_AFTER_COMMIT); if (list != null) { list = new ArrayList<Runnable>(list); document.putUserData(ACTION_AFTER_COMMIT, null); } } if (list != null) { for (final Runnable runnable : list) { runnable.run(); } } if (!hasUncommitedDocuments() && !actionsWhenAllDocumentsAreCommitted.isEmpty()) { List<Object> keys = new ArrayList<Object>(actionsWhenAllDocumentsAreCommitted.keySet()); for (Object key : keys) { Runnable action = actionsWhenAllDocumentsAreCommitted.remove(key); myDocumentCommitProcessor.log("Running after commit runnable: ", null, false, key, action); action.run(); } } } @Override public void addListener(@NotNull Listener listener) { myListeners.add(listener); } @Override public void removeListener(@NotNull Listener listener) { myListeners.remove(listener); } @Override public boolean isDocumentBlockedByPsi(@NotNull Document doc) { return false; } @Override public void doPostponedOperationsAndUnblockDocument(@NotNull Document doc) {} protected void fireDocumentCreated(@NotNull Document document, PsiFile file) { for (Listener listener : myListeners) { listener.documentCreated(document, file); } } private void fireFileCreated(Document document, PsiFile file) { for (Listener listener : myListeners) { listener.fileCreated(file, document); } } @Override @NotNull public Document[] getUncommittedDocuments() { ApplicationManager.getApplication().assertIsDispatchThread(); return myUncommittedDocuments.toArray(new Document[myUncommittedDocuments.size()]); } public Collection<Document> getUncommittedDocumentsUnsafe() { return myUncommittedDocuments; } @Override public boolean isUncommited(@NotNull Document document) { return !isCommitted(document); } @Override public boolean isCommitted(@NotNull Document document) { if (getSynchronizer().isInSynchronization(document)) return true; return !((DocumentEx) document).isInEventsHandling() && !myUncommittedDocuments.contains(document); } @Override public boolean hasUncommitedDocuments() { return !myIsCommitInProgress && !myUncommittedDocuments.isEmpty(); } @Override public void beforeDocumentChange(DocumentEvent event) { final Document document = event.getDocument(); final FileViewProvider viewProvider = getCachedViewProvider(document); if (viewProvider == null) return; if (!isRelevant(viewProvider)) return; VirtualFile virtualFile = viewProvider.getVirtualFile(); if (virtualFile.getFileType().isBinary()) return; final List<PsiFile> files = viewProvider.getAllFiles(); PsiFile psiCause = null; for (PsiFile file : files) { mySmartPointerManager.fastenBelts(file, event.getOffset(), null); if (TextBlock.get(file).isLocked()) { psiCause = file; } } if (psiCause == null) { beforeDocumentChangeOnUnlockedDocument(viewProvider); } ((SingleRootFileViewProvider) viewProvider).beforeDocumentChanged(psiCause); } protected void beforeDocumentChangeOnUnlockedDocument( @NotNull final FileViewProvider viewProvider) {} @Override public void documentChanged(DocumentEvent event) { final Document document = event.getDocument(); final FileViewProvider viewProvider = getCachedViewProvider(document); if (viewProvider == null) return; if (!isRelevant(viewProvider)) return; ApplicationManager.getApplication().assertWriteAccessAllowed(); final List<PsiFile> files = viewProvider.getAllFiles(); boolean commitNecessary = true; for (PsiFile file : files) { mySmartPointerManager.unfastenBelts(file, event.getOffset()); final TextBlock textBlock = TextBlock.get(file); if (textBlock.isLocked()) { commitNecessary = false; continue; } textBlock.documentChanged(event); assert file instanceof PsiFileImpl || "mock.file".equals(file.getName()) && ApplicationManager.getApplication().isUnitTestMode() : event + "; file=" + file + "; allFiles=" + files + "; viewProvider=" + viewProvider; } boolean forceCommit = ApplicationManager.getApplication().hasWriteAction(ExternalChangeAction.class) && (SystemProperties.getBooleanProperty("idea.force.commit.on.external.change", false) || ApplicationManager.getApplication().isHeadlessEnvironment()); // Consider that it's worth to perform complete re-parse instead of merge if the whole document // text is replaced and // current document lines number is roughly above 5000. This makes sense in situations when // external change is performed // for the huge file (that causes the whole document to be reloaded and 'merge' way takes a // while to complete). if (event.isWholeTextReplaced() && document.getTextLength() > 100000) { document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, Boolean.TRUE); } if (commitNecessary) { myUncommittedDocuments.add(document); if (forceCommit) { commitDocument(document); } else if (!((DocumentEx) document).isInBulkUpdate()) { myDocumentCommitProcessor.commitAsynchronously(myProject, document, event); } } } private boolean isRelevant(@NotNull FileViewProvider viewProvider) { VirtualFile virtualFile = viewProvider.getVirtualFile(); return !virtualFile.getFileType().isBinary() && viewProvider.getManager() == myPsiManager && !myPsiManager.getProject().isDisposed(); } public static boolean checkConsistency(PsiFile psiFile, Document document) { // todo hack if (psiFile.getVirtualFile() == null) return true; CharSequence editorText = document.getCharsSequence(); int documentLength = document.getTextLength(); if (psiFile.textMatches(editorText)) { LOG.assertTrue(psiFile.getTextLength() == documentLength); return true; } char[] fileText = psiFile.textToCharArray(); @SuppressWarnings({"NonConstantStringShouldBeStringBuffer"}) @NonNls String error = "File '" + psiFile.getName() + "' text mismatch after reparse. " + "File length=" + fileText.length + "; Doc length=" + documentLength + "\n"; int i = 0; for (; i < documentLength; i++) { if (i >= fileText.length) { error += "editorText.length > psiText.length i=" + i + "\n"; break; } if (i >= editorText.length()) { error += "editorText.length > psiText.length i=" + i + "\n"; break; } if (editorText.charAt(i) != fileText[i]) { error += "first unequal char i=" + i + "\n"; break; } } // error += "*********************************************" + "\n"; // if (i <= 500){ // error += "Equal part:" + editorText.subSequence(0, i) + "\n"; // } // else{ // error += "Equal part start:\n" + editorText.subSequence(0, 200) + "\n"; // error += "................................................" + "\n"; // error += "................................................" + "\n"; // error += "................................................" + "\n"; // error += "Equal part end:\n" + editorText.subSequence(i - 200, i) + "\n"; // } error += "*********************************************" + "\n"; error += "Editor Text tail:(" + (documentLength - i) + ")\n"; // + editorText.subSequence(i, Math.min(i + 300, documentLength)) + "\n"; error += "*********************************************" + "\n"; error += "Psi Text tail:(" + (fileText.length - i) + ")\n"; error += "*********************************************" + "\n"; if (document instanceof DocumentWindow) { error += "doc: '" + document.getText() + "'\n"; error += "psi: '" + psiFile.getText() + "'\n"; error += "ast: '" + psiFile.getNode().getText() + "'\n"; error += psiFile.getLanguage() + "\n"; PsiElement context = InjectedLanguageManager.getInstance(psiFile.getProject()).getInjectionHost(psiFile); if (context != null) { error += "context: " + context + "; text: '" + context.getText() + "'\n"; error += "context file: " + context.getContainingFile() + "\n"; } error += "document window ranges: " + Arrays.asList(((DocumentWindow) document).getHostRanges()) + "\n"; } LOG.error(error); // document.replaceString(0, documentLength, psiFile.getText()); return false; } @TestOnly public void clearUncommittedDocuments() { myUncommittedDocuments.clear(); mySynchronizer.cleanupForNextTest(); } public PsiToDocumentSynchronizer getSynchronizer() { return mySynchronizer; } }
/** * Discards up or down messages based on a percentage; e.g., setting property 'up' to 0.1 causes 10% * of all up messages to be discarded. Setting 'down' or 'up' to 0 causes no loss, whereas 1 * discards all messages (not very useful). */ @Unsupported @MBean(description = "Discards messages") public class DISCARD extends Protocol { @Property protected double up = 0.0; // probability of dropping up msgs @Property protected double down = 0.0; // probability of dropping down msgs @Property protected boolean excludeItself = true; // if true don't discard messages sent/received in this stack protected Address localAddress; @ManagedAttribute(description = "Number of dropped down messages", name = "dropped_down_messages") protected int num_down = 0; @ManagedAttribute(description = "Number of dropped up messages", name = "dropped_up_messages") protected int num_up = 0; protected final Set<Address> ignoredMembers = Collections.synchronizedSet(new HashSet<>()); protected final Collection<Address> members = Collections.synchronizedList(new ArrayList<>()); @Property(description = "drop all messages (up or down)", writable = true) protected boolean discard_all = false; @Property( description = "Number of subsequent unicasts to drop in the down direction", writable = true) protected int drop_down_unicasts = 0; @Property( description = "Number of subsequent multicasts to drop in the down direction", writable = true) protected int drop_down_multicasts = 0; protected DiscardDialog discard_dialog = null; @Property(name = "gui", description = "use a GUI or not") protected boolean use_gui = false; public DISCARD localAddress(Address addr) { setLocalAddress(addr); return this; } public Address localAddress() { if (localAddress == null) localAddress = (Address) up_prot.up(new Event(Event.GET_LOCAL_ADDRESS)); return localAddress; } public boolean isDiscardAll() { return discard_all; } public DISCARD setDiscardAll(boolean discard_all) { this.discard_all = discard_all; return this; } public boolean isExcludeItself() { return excludeItself; } public DISCARD setLocalAddress(Address localAddress) { this.localAddress = localAddress; if (discard_dialog != null) discard_dialog.setTitle(localAddress != null ? localAddress.toString() : "n/a"); return this; } public DISCARD setExcludeItself(boolean excludeItself) { this.excludeItself = excludeItself; return this; } public double getUpDiscardRate() { return up; } public DISCARD setUpDiscardRate(double up) { this.up = up; return this; } public double getDownDiscardRate() { return down; } public DISCARD setDownDiscardRate(double down) { this.down = down; return this; } public int getDropDownUnicasts() { return drop_down_unicasts; } /** * Drop the next N unicasts down the stack * * @param drop_down_unicasts */ public DISCARD setDropDownUnicasts(int drop_down_unicasts) { this.drop_down_unicasts = drop_down_unicasts; return this; } public int getDropDownMulticasts() { return drop_down_multicasts; } public DISCARD setDropDownMulticasts(int drop_down_multicasts) { this.drop_down_multicasts = drop_down_multicasts; return this; } /** Messages from this sender will get dropped */ public DISCARD addIgnoreMember(Address sender) { ignoredMembers.add(sender); return this; } public DISCARD removeIgnoredMember(Address member) { ignoredMembers.remove(member); return this; } public DISCARD resetIgnoredMembers() { ignoredMembers.clear(); return this; } @ManagedOperation public void startGui() { if (discard_dialog == null) { discard_dialog = new DiscardDialog(); discard_dialog.init(); discard_dialog.setTitle(localAddress() != null ? localAddress().toString() : "n/a"); discard_dialog.handleView(members); } } @ManagedOperation public void stopGui() { if (discard_dialog != null) discard_dialog.dispose(); discard_dialog = null; } public void start() throws Exception { super.start(); if (use_gui) { discard_dialog = new DiscardDialog(); discard_dialog.init(); } } public void stop() { super.stop(); if (discard_dialog != null) discard_dialog.dispose(); } public Object up(Event evt) { if (evt.getType() == Event.SET_LOCAL_ADDRESS) { localAddress = evt.getArg(); if (discard_dialog != null) discard_dialog.setTitle("Discard dialog (" + localAddress + ")"); } return up_prot.up(evt); } public Object up(Message msg) { if (shouldDropUpMessage(msg, msg.getSrc())) return null; return up_prot.up(msg); } public void up(MessageBatch batch) { for (Iterator<Message> it = batch.iterator(); it.hasNext(); ) { Message msg = it.next(); if (msg != null && shouldDropUpMessage(msg, msg.getSrc())) it.remove(); } if (!batch.isEmpty()) up_prot.up(batch); } public Object down(Event evt) { switch (evt.getType()) { case Event.VIEW_CHANGE: View view = evt.getArg(); List<Address> mbrs = view.getMembers(); members.clear(); members.addAll(mbrs); // ignoredMembers.retainAll(mbrs); // remove all non members if (discard_dialog != null) discard_dialog.handleView(mbrs); break; case Event.SET_LOCAL_ADDRESS: localAddress = evt.getArg(); if (discard_dialog != null) discard_dialog.setTitle("Discard dialog (" + localAddress + ")"); break; case Event.GET_PING_DATA: if (discard_all) return null; break; } return down_prot.down(evt); } public Object down(Message msg) { Address dest = msg.getDest(); boolean multicast = dest == null; if (msg.getSrc() == null) msg.setSrc(localAddress()); if (discard_all) { if (dest == null || dest.equals(localAddress())) loopback(msg); return null; } if (!multicast && drop_down_unicasts > 0) { drop_down_unicasts = Math.max(0, drop_down_unicasts - 1); return null; } if (multicast && drop_down_multicasts > 0) { drop_down_multicasts = Math.max(0, drop_down_multicasts - 1); return null; } if (down > 0) { double r = Math.random(); if (r < down) { if (excludeItself && dest != null && dest.equals(localAddress())) { if (log.isTraceEnabled()) log.trace("excluding itself"); } else { log.trace("dropping message"); num_down++; return null; } } } return down_prot.down(msg); } /** Checks if a message should be passed up, or not */ protected boolean shouldDropUpMessage( @SuppressWarnings("UnusedParameters") Message msg, Address sender) { if (discard_all && !sender.equals(localAddress())) return true; if (ignoredMembers.contains(sender)) { if (log.isTraceEnabled()) log.trace(localAddress + ": dropping message from " + sender); num_up++; return true; } if (up > 0) { double r = Math.random(); if (r < up) { if (excludeItself && sender.equals(localAddress())) { if (log.isTraceEnabled()) log.trace("excluding myself"); } else { if (log.isTraceEnabled()) log.trace(localAddress + ": dropping message from " + sender); num_up++; return true; } } } return false; } private void loopback(Message msg) { final Message rsp = msg.copy(true); if (rsp.getSrc() == null) rsp.setSrc(localAddress()); // pretty inefficient: creates one thread per message, okay for testing only Thread thread = new Thread( () -> { up_prot.up(rsp); }); thread.start(); } public void resetStats() { super.resetStats(); num_down = num_up = 0; } protected class DiscardDialog extends JFrame implements ActionListener { private final JButton start_discarding_button = new JButton("start discarding"); private final JButton stop_discarding_button = new JButton("stop discarding"); private final JPanel checkboxes = new JPanel(); protected DiscardDialog() {} void init() { getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); checkboxes.setLayout(new BoxLayout(checkboxes, BoxLayout.Y_AXIS)); getContentPane().add(start_discarding_button); getContentPane().add(stop_discarding_button); start_discarding_button.addActionListener(this); stop_discarding_button.addActionListener(this); getContentPane().add(checkboxes); pack(); setVisible(true); setTitle(localAddress() != null ? localAddress().toString() : "n/a"); } public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); if (command.startsWith("start")) { discard_all = true; } else if (command.startsWith("stop")) { discard_all = false; Component[] comps = checkboxes.getComponents(); for (Component c : comps) { if (c instanceof JCheckBox) { ((JCheckBox) c).setSelected(false); } } ignoredMembers.clear(); } } void handleView(Collection<Address> mbrs) { checkboxes.removeAll(); for (final Address addr : mbrs) { final MyCheckBox box = new MyCheckBox("discard traffic from " + addr, addr); box.addActionListener( e -> { if (box.isSelected()) ignoredMembers.add(addr); else ignoredMembers.remove(addr); }); checkboxes.add(box); } for (Component comp : checkboxes.getComponents()) { MyCheckBox box = (MyCheckBox) comp; if (ignoredMembers.contains(box.mbr)) box.setSelected(true); } pack(); } } private static class MyCheckBox extends JCheckBox { final Address mbr; public MyCheckBox(String name, Address member) { super(name); this.mbr = member; } public String toString() { return super.toString() + " [mbr=" + mbr + "]"; } } }