mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
UiInterceptors: ability to intercept UI components in tests and emulate user action
Currently only popup chooser is supported A usage example added for AddExceptionToExistingCatchTest
This commit is contained in:
+1
-5
@@ -5,8 +5,6 @@ import com.intellij.codeInsight.ExceptionUtil;
|
||||
import com.intellij.codeInsight.FileModificationService;
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -47,9 +45,7 @@ public class AddExceptionToExistingCatchFix extends PsiElementBaseIntentionActio
|
||||
|
||||
setText(context.getMessage());
|
||||
|
||||
Application application = ApplicationManager.getApplication();
|
||||
|
||||
if (catchSections.size() == 1 || application.isUnitTestMode()) {
|
||||
if (catchSections.size() == 1) {
|
||||
PsiCatchSection selectedSection = catchSections.get(0);
|
||||
addTypeToCatch(unhandledExceptions, selectedSection);
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// "Add exception to existing catch clause" "true"
|
||||
import java.io.IOException;
|
||||
|
||||
class A extends Exception {}
|
||||
class B extends A {}
|
||||
class C extends A {}
|
||||
|
||||
class Test {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
throw new A();
|
||||
} catch (B e) {
|
||||
} catch (A e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// "Add exception to existing catch clause" "true"
|
||||
import java.io.IOException;
|
||||
|
||||
class X extends RuntimeException {}
|
||||
class Y extends RuntimeException {}
|
||||
|
||||
class Test {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
try {
|
||||
throw new IOException();
|
||||
}
|
||||
catch (X | IOException x) {
|
||||
}
|
||||
}
|
||||
catch (Y y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// "Add exception to existing catch clause" "true"
|
||||
import java.io.IOException;
|
||||
|
||||
class X extends RuntimeException {}
|
||||
class Y extends RuntimeException {}
|
||||
|
||||
class Test {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
try {
|
||||
throw new IOException();
|
||||
}
|
||||
catch (X x) {
|
||||
}
|
||||
}
|
||||
catch (Y | IOException y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// "Add exception to existing catch clause" "true"
|
||||
import java.io.IOException;
|
||||
|
||||
class A extends Exception {}
|
||||
class B extends A {}
|
||||
class C extends A {}
|
||||
|
||||
class Test {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
throw new A<caret>();
|
||||
} catch (B e) {
|
||||
} catch (C e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// "Add exception to existing catch clause" "true"
|
||||
import java.io.IOException;
|
||||
|
||||
class X extends RuntimeException {}
|
||||
class Y extends RuntimeException {}
|
||||
|
||||
class Test {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
try {
|
||||
throw new <caret>IOException();
|
||||
}
|
||||
catch (X x) {
|
||||
}
|
||||
}
|
||||
catch (Y y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// "Add exception to existing catch clause" "true"
|
||||
import java.io.IOException;
|
||||
|
||||
class X extends RuntimeException {}
|
||||
class Y extends RuntimeException {}
|
||||
|
||||
class Test {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
try {
|
||||
throw new <caret>IOException();
|
||||
}
|
||||
catch (X x) {
|
||||
}
|
||||
}
|
||||
catch (Y y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-2
@@ -2,13 +2,26 @@
|
||||
package com.intellij.java.codeInsight.daemon.quickFix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.LightIntentionActionTestCase;
|
||||
import com.intellij.ui.ChooserInterceptor;
|
||||
import com.intellij.ui.UiInterceptors;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class AddExceptionToExistingCatchTest extends LightIntentionActionTestCase {
|
||||
private static final Pattern CHOOSER_TEST_NAME = Pattern.compile(".+_Choose_(.+)\\.java");
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
Matcher matcher = CHOOSER_TEST_NAME.matcher(getTestName(false));
|
||||
if (matcher.matches()) {
|
||||
UiInterceptors.register(new ChooserInterceptor(null, matcher.group(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ public class IntroduceTargetChooser {
|
||||
}).createPopup();
|
||||
popup.showInBestPositionFor(editor);
|
||||
Project project = editor.getProject();
|
||||
if (project != null) {
|
||||
if (project != null && !popup.isDisposed()) {
|
||||
NavigationUtil.hidePopupIfDumbModeStarts(popup, project);
|
||||
}
|
||||
}
|
||||
@@ -177,5 +177,10 @@ public class IntroduceTargetChooser {
|
||||
public String render() {
|
||||
return myRenderer.fun(getPlace());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.List;
|
||||
|
||||
@@ -166,4 +167,13 @@ public interface ComponentPopupBuilder {
|
||||
default ComponentPopupBuilder setBorderColor(Color color) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a handler to be called when popup is closed via {@link JBPopup#closeOk(InputEvent)}.
|
||||
*
|
||||
* @param okHandler handler to call
|
||||
* @return this builder
|
||||
*/
|
||||
@NotNull
|
||||
ComponentPopupBuilder setOkHandler(@Nullable Runnable okHandler);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class PopupChooserBuilder<T> implements IPopupChooserBuilder<T> {
|
||||
|
||||
private Function<Object,String> myItemsNamer = null;
|
||||
private boolean myMayBeParent;
|
||||
private int myAdAlignment = SwingUtilities.LEFT;
|
||||
private int myAdAlignment = SwingConstants.LEFT;
|
||||
private boolean myModalContext;
|
||||
private boolean myCloseOnEnter = true;
|
||||
private boolean myCancelOnWindowDeactivation = true;
|
||||
@@ -322,7 +322,7 @@ public class PopupChooserBuilder<T> implements IPopupChooserBuilder<T> {
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED) && !UIUtil.isSelectionButtonDown(e) && !e.isConsumed()) {
|
||||
if (myCloseOnEnter) {
|
||||
closePopup(true, e, true);
|
||||
closePopup(e, true);
|
||||
}
|
||||
else {
|
||||
myItemChosenRunnable.run();
|
||||
@@ -391,7 +391,8 @@ public class PopupChooserBuilder<T> implements IPopupChooserBuilder<T> {
|
||||
.setModalContext(myModalContext)
|
||||
.setCancelOnWindowDeactivation(myCancelOnWindowDeactivation)
|
||||
.setCancelOnClickOutside(myCancelOnClickOutside)
|
||||
.setCouldPin(myCouldPin);
|
||||
.setCouldPin(myCouldPin)
|
||||
.setOkHandler(myItemChosenRunnable);
|
||||
|
||||
BooleanFunction<KeyEvent> keyEventHandler = myChooserComponent.getKeyEventHandler();
|
||||
if (keyEventHandler != null) {
|
||||
@@ -442,7 +443,7 @@ public class PopupChooserBuilder<T> implements IPopupChooserBuilder<T> {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (!shouldPerformAction && myChooserComponent.checkResetFilter()) return;
|
||||
closePopup(shouldPerformAction, null, shouldPerformAction);
|
||||
closePopup(null, shouldPerformAction);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -451,11 +452,7 @@ public class PopupChooserBuilder<T> implements IPopupChooserBuilder<T> {
|
||||
myChooserComponent.getComponent().registerKeyboardAction(action, keyStroke, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
||||
}
|
||||
|
||||
private void closePopup(boolean shouldPerformAction, MouseEvent e, boolean isOk) {
|
||||
if (shouldPerformAction) {
|
||||
myPopup.setFinalRunnable(myItemChosenRunnable);
|
||||
}
|
||||
|
||||
private void closePopup(MouseEvent e, boolean isOk) {
|
||||
if (isOk) {
|
||||
myPopup.closeOk(e);
|
||||
} else {
|
||||
@@ -508,7 +505,7 @@ public class PopupChooserBuilder<T> implements IPopupChooserBuilder<T> {
|
||||
@Override
|
||||
@NotNull
|
||||
public PopupChooserBuilder<T> setAdText(String ad) {
|
||||
setAdText(ad, SwingUtilities.LEFT);
|
||||
setAdText(ad, SwingConstants.LEFT);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.ui;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class UiInterceptors {
|
||||
private static final ConcurrentLinkedQueue<UiInterceptor<?>> ourInterceptors = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* Called from UI component
|
||||
*
|
||||
* @param uiComponent UI component which is about to be displayed
|
||||
* @return true if interception was successful, in this case no UI should be actually shown
|
||||
*/
|
||||
public static boolean tryIntercept(@NotNull Object uiComponent) {
|
||||
UiInterceptor<?> interceptor = ourInterceptors.poll();
|
||||
if (interceptor == null) return false;
|
||||
interceptor.intercept(uiComponent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register interceptor to intercept next shown UI component
|
||||
*
|
||||
* @param interceptor interceptor to register
|
||||
*/
|
||||
@TestOnly
|
||||
public static void register(UiInterceptor<?> interceptor) {
|
||||
ourInterceptors.offer(interceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called in test tearDown to ensure that all registered interceptors were actually used.
|
||||
*/
|
||||
@TestOnly
|
||||
public static void clear() {
|
||||
ArrayList<UiInterceptor<?>> interceptors = new ArrayList<>(ourInterceptors);
|
||||
ourInterceptors.clear();
|
||||
if (!interceptors.isEmpty()) {
|
||||
throw new IllegalStateException("Expected UI was not shown: " + interceptors);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class UiInterceptor<T> {
|
||||
private final @NotNull Class<T> myClass;
|
||||
|
||||
protected UiInterceptor(@NotNull Class<T> componentClass) {
|
||||
myClass = componentClass;
|
||||
}
|
||||
|
||||
public final void intercept(@NotNull Object component) {
|
||||
if (!myClass.isInstance(component)) {
|
||||
throw new IllegalStateException("Unexpected UI component appears: wanted " + myClass.getName() + "; got: "
|
||||
+ component.getClass().getName() + " (" + component + ")");
|
||||
}
|
||||
doIntercept(myClass.cast(component));
|
||||
}
|
||||
|
||||
protected abstract void doIntercept(T component);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myClass.getName()+" (interceptor: " + getClass().getName() + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,10 +33,7 @@ import com.intellij.ui.border.CustomLineBorder;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
import com.intellij.ui.mac.touchbar.TouchBarsManager;
|
||||
import com.intellij.ui.speedSearch.SpeedSearch;
|
||||
import com.intellij.util.Alarm;
|
||||
import com.intellij.util.BooleanFunction;
|
||||
import com.intellij.util.IJSwingUtilities;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.*;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.WeakList;
|
||||
import com.intellij.util.ui.*;
|
||||
@@ -48,8 +45,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static java.awt.AWTEvent.MOUSE_EVENT_MASK;
|
||||
@@ -122,6 +119,7 @@ public class AbstractPopup implements JBPopup {
|
||||
InputEvent myDisposeEvent;
|
||||
|
||||
private Runnable myFinalRunnable;
|
||||
private Runnable myOkHandler;
|
||||
@Nullable private BooleanFunction<KeyEvent> myKeyEventHandler;
|
||||
|
||||
protected boolean myOk;
|
||||
@@ -441,6 +439,7 @@ public class AbstractPopup implements JBPopup {
|
||||
|
||||
@Override
|
||||
public void show(@NotNull RelativePoint aPoint) {
|
||||
if (UiInterceptors.tryIntercept(this)) return;
|
||||
HelpTooltip.setMasterPopup(aPoint.getOriginalComponent(), this);
|
||||
Point screenPoint = aPoint.getScreenPoint();
|
||||
show(aPoint.getComponent(), screenPoint.x, screenPoint.y, false);
|
||||
@@ -541,6 +540,8 @@ public class AbstractPopup implements JBPopup {
|
||||
|
||||
@Override
|
||||
public void showInBestPositionFor(@NotNull Editor editor) {
|
||||
// Intercept before the following assert; otherwise assertion may fail
|
||||
if (UiInterceptors.tryIntercept(this)) return;
|
||||
assert editor.getComponent().isShowing() : "Editor must be showing on the screen";
|
||||
|
||||
// Set the accessible parent so that screen readers don't announce
|
||||
@@ -664,6 +665,7 @@ public class AbstractPopup implements JBPopup {
|
||||
@Override
|
||||
public final void closeOk(@Nullable InputEvent e) {
|
||||
setOk(true);
|
||||
myFinalRunnable = FunctionUtil.composeRunnables(myOkHandler, myFinalRunnable);
|
||||
cancel(e);
|
||||
}
|
||||
|
||||
@@ -1869,6 +1871,10 @@ public class AbstractPopup implements JBPopup {
|
||||
myWindow.setMinimumSize(new Dimension(width, height));
|
||||
}
|
||||
}
|
||||
|
||||
public void setOkHandler(Runnable okHandler) {
|
||||
myOkHandler = okHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFinalRunnable(Runnable finalRunnable) {
|
||||
|
||||
@@ -33,8 +33,8 @@ import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author anna
|
||||
@@ -83,6 +83,7 @@ public class ComponentPopupBuilderImpl implements ComponentPopupBuilder {
|
||||
private BooleanFunction<KeyEvent> myKeyEventHandler;
|
||||
private Color myBorderColor;
|
||||
private boolean myNormalWindowLevel;
|
||||
private @Nullable Runnable myOkHandler;
|
||||
|
||||
public ComponentPopupBuilderImpl(@NotNull JComponent component, JComponent preferredFocusedComponent) {
|
||||
myComponent = component;
|
||||
@@ -242,6 +243,7 @@ public class ComponentPopupBuilderImpl implements ComponentPopupBuilder {
|
||||
);
|
||||
|
||||
popup.setNormalWindowLevel(myNormalWindowLevel);
|
||||
popup.setOkHandler(myOkHandler);
|
||||
|
||||
if (myUserData != null) {
|
||||
popup.setUserData(myUserData);
|
||||
@@ -378,4 +380,11 @@ public class ComponentPopupBuilderImpl implements ComponentPopupBuilder {
|
||||
myBorderColor = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ComponentPopupBuilder setOkHandler(@Nullable Runnable okHandler) {
|
||||
myOkHandler = okHandler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ import com.intellij.psi.impl.PsiDocumentManagerImpl;
|
||||
import com.intellij.psi.impl.PsiManagerImpl;
|
||||
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageManagerImpl;
|
||||
import com.intellij.psi.templateLanguages.TemplateDataLanguageMappings;
|
||||
import com.intellij.ui.UiInterceptors;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.LocalTimeCounter;
|
||||
import com.intellij.util.ReflectionUtil;
|
||||
@@ -449,6 +450,7 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da
|
||||
append(() -> PlatformTestCase.waitForProjectLeakingThreads(project, 10, TimeUnit.SECONDS)).
|
||||
append(() -> ProjectManagerEx.getInstanceEx().closeTestProject(project)).
|
||||
append(() -> application.setDataProvider(null)).
|
||||
append(() -> UiInterceptors.clear()).
|
||||
append(() -> ourTestCase = null).
|
||||
append(() -> CompletionProgressIndicator.cleanupForNextTest()).
|
||||
append(() -> {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.ui;
|
||||
|
||||
import com.intellij.openapi.ui.popup.JBPopup;
|
||||
import com.intellij.ui.components.JBList;
|
||||
import one.util.streamex.IntStreamEx;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.intellij.lang.annotations.RegExp;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Interceptor for the next popup chooser
|
||||
*/
|
||||
public class ChooserInterceptor extends UiInterceptors.UiInterceptor<JBPopup> {
|
||||
final List<String> myOptions;
|
||||
final Pattern myToSelect;
|
||||
|
||||
/**
|
||||
* Create an interceptor which will assert the expected options and select the given option when chooser will appear
|
||||
*
|
||||
* @param expectedOptions expected options to assert; pass null to skip checking the expected options
|
||||
* @param pattern a regexp which should match the wanted option
|
||||
*/
|
||||
public ChooserInterceptor(@Nullable List<String> expectedOptions, @NotNull @RegExp String pattern) {
|
||||
super(JBPopup.class);
|
||||
myOptions = expectedOptions;
|
||||
myToSelect = Pattern.compile(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doIntercept(JBPopup popup) {
|
||||
JComponent component = popup.getContent();
|
||||
JBList<?> content = StreamEx.ofTree((Component)component, Container.class, c -> StreamEx.of(c.getComponents()))
|
||||
.select(JBList.class).findFirst().orElse(null);
|
||||
if (content == null) {
|
||||
fail("Expected chooser; got: " + component);
|
||||
}
|
||||
ListModel<?> model = content.getModel();
|
||||
List<String> actualOptions = IntStreamEx.range(model.getSize()).mapToObj(model::getElementAt).map(Object::toString).toList();
|
||||
if (myOptions != null) {
|
||||
assertEquals(myOptions, actualOptions);
|
||||
}
|
||||
List<String> matched = StreamEx.of(actualOptions).filter(opt -> myToSelect.matcher(opt).matches()).toList();
|
||||
if (matched.isEmpty()) {
|
||||
fail("No option matches " + myToSelect);
|
||||
}
|
||||
if (matched.size() > 1) {
|
||||
fail("Several options matched: " + matched + " (pattern: " + myToSelect + ")");
|
||||
}
|
||||
content.setSelectedIndex(actualOptions.indexOf(matched.get(0)));
|
||||
popup.closeOk(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Popup Chooser where '" + myToSelect + "' should be selected";
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
package com.intellij.util;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author nik
|
||||
@@ -50,4 +52,33 @@ public class FunctionUtil {
|
||||
public static <A, B, C> NotNullFunction<A, C> composition(@NotNull final NotNullFunction<? super B, ? extends C> f, @NotNull final NotNullFunction<? super A, ? extends B> g) {
|
||||
return a -> f.fun(g.fun(a));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a runnable which runs both supplied runnables. If any of them throws, the second one is still executed.
|
||||
* If both throw, the second exception is added to the first one as suppressed.
|
||||
*
|
||||
* @param r1 first runnable to run
|
||||
* @param r2 second runnable to run
|
||||
* @return composed runnable. If one of arguments is null, returns other argument.
|
||||
*/
|
||||
@Contract(value = "_, null -> param1; null, !null -> param2", pure = true)
|
||||
public static Runnable composeRunnables(@Nullable Runnable r1, @Nullable Runnable r2) {
|
||||
if (r2 == null) return r1;
|
||||
if (r1 == null) return r2;
|
||||
return () -> {
|
||||
try {
|
||||
r1.run();
|
||||
}
|
||||
catch (RuntimeException | Error ex) {
|
||||
try {
|
||||
r2.run();
|
||||
}
|
||||
catch (RuntimeException | Error ex2) {
|
||||
ex.addSuppressed(ex2);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
r2.run();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user