Skip to content

Commit a77f90a

Browse files
author
jmhofer
committed
Added a scheduler for scheduling actions on the Swing event thread.
1 parent 04068c1 commit a77f90a

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/**
2+
* Copyright 2013 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package rx.concurrency;
17+
18+
import static org.mockito.Mockito.*;
19+
20+
import java.awt.EventQueue;
21+
import java.awt.event.ActionEvent;
22+
import java.awt.event.ActionListener;
23+
import java.lang.reflect.InvocationTargetException;
24+
import java.util.concurrent.TimeUnit;
25+
import java.util.concurrent.atomic.AtomicReference;
26+
27+
import javax.swing.Timer;
28+
29+
import org.junit.Test;
30+
import org.mockito.InOrder;
31+
32+
import rx.Subscription;
33+
import rx.subscriptions.Subscriptions;
34+
import rx.util.functions.Action0;
35+
import rx.util.functions.Func0;
36+
37+
/**
38+
* Executes work on the Swing UI thread.
39+
* This scheduler should only be used with actions that execute quickly.
40+
*/
41+
public final class SwingScheduler extends AbstractScheduler {
42+
private static final SwingScheduler INSTANCE = new SwingScheduler();
43+
44+
public static SwingScheduler getInstance() {
45+
return INSTANCE;
46+
}
47+
48+
private SwingScheduler() {
49+
}
50+
51+
@Override
52+
public Subscription schedule(final Func0<Subscription> action) {
53+
final AtomicReference<Subscription> sub = new AtomicReference<Subscription>();
54+
EventQueue.invokeLater(new Runnable() {
55+
@Override
56+
public void run() {
57+
sub.set(action.call());
58+
}
59+
});
60+
return Subscriptions.create(new Action0() {
61+
@Override
62+
public void call() {
63+
Subscription subscription = sub.get();
64+
if (subscription != null) {
65+
subscription.unsubscribe();
66+
}
67+
}
68+
});
69+
}
70+
71+
@Override
72+
public Subscription schedule(final Func0<Subscription> action, long dueTime, TimeUnit unit) {
73+
final AtomicReference<Subscription> sub = new AtomicReference<Subscription>();
74+
long delay = unit.toMillis(dueTime);
75+
76+
if (delay > Integer.MAX_VALUE) {
77+
throw new IllegalArgumentException(String.format(
78+
"The swing timer only accepts delays up to %d milliseconds.", Integer.MAX_VALUE));
79+
}
80+
81+
class ExecuteOnceAction implements ActionListener {
82+
private Timer timer;
83+
84+
private void setTimer(Timer timer) {
85+
this.timer = timer;
86+
}
87+
88+
@Override
89+
public void actionPerformed(@SuppressWarnings("unused") ActionEvent e) {
90+
if (timer != null) {
91+
timer.stop();
92+
}
93+
sub.set(action.call());
94+
}
95+
}
96+
97+
ExecuteOnceAction executeOnce = new ExecuteOnceAction();
98+
final Timer timer = new Timer((int) delay, executeOnce);
99+
executeOnce.setTimer(timer);
100+
timer.start();
101+
102+
return Subscriptions.create(new Action0() {
103+
@Override
104+
public void call() {
105+
timer.stop();
106+
107+
Subscription subscription = sub.get();
108+
if (subscription != null) {
109+
subscription.unsubscribe();
110+
}
111+
}
112+
});
113+
}
114+
115+
public static class UnitTest {
116+
@Test
117+
public void testNestedActions() throws InterruptedException, InvocationTargetException {
118+
final SwingScheduler scheduler = new SwingScheduler();
119+
120+
final Action0 firstStepStart = mock(Action0.class);
121+
final Action0 firstStepEnd = mock(Action0.class);
122+
123+
final Action0 secondStepStart = mock(Action0.class);
124+
final Action0 secondStepEnd = mock(Action0.class);
125+
126+
final Action0 thirdStepStart = mock(Action0.class);
127+
final Action0 thirdStepEnd = mock(Action0.class);
128+
129+
final Action0 firstAction = new Action0() {
130+
@Override
131+
public void call() {
132+
firstStepStart.call();
133+
firstStepEnd.call();
134+
}
135+
};
136+
final Action0 secondAction = new Action0() {
137+
@Override
138+
public void call() {
139+
secondStepStart.call();
140+
scheduler.schedule(firstAction);
141+
secondStepEnd.call();
142+
}
143+
};
144+
final Action0 thirdAction = new Action0() {
145+
@Override
146+
public void call() {
147+
thirdStepStart.call();
148+
scheduler.schedule(secondAction);
149+
thirdStepEnd.call();
150+
}
151+
};
152+
153+
InOrder inOrder = inOrder(firstStepStart, firstStepEnd, secondStepStart, secondStepEnd, thirdStepStart, thirdStepEnd);
154+
155+
scheduler.schedule(thirdAction);
156+
EventQueue.invokeAndWait(new Runnable() {
157+
@Override
158+
public void run() {
159+
// nothing to do, we're just waiting here for the event queue to be emptied
160+
}
161+
});
162+
163+
inOrder.verify(thirdStepStart, times(1)).call();
164+
inOrder.verify(thirdStepEnd, times(1)).call();
165+
inOrder.verify(secondStepStart, times(1)).call();
166+
inOrder.verify(secondStepEnd, times(1)).call();
167+
inOrder.verify(firstStepStart, times(1)).call();
168+
inOrder.verify(firstStepEnd, times(1)).call();
169+
}
170+
171+
}
172+
173+
}

0 commit comments

Comments
 (0)