2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org
.jetbrains
.idea
.svn
;
18 import com
.intellij
.openapi
.diagnostic
.Logger
;
19 import com
.intellij
.openapi
.progress
.SomeQueue
;
20 import com
.intellij
.openapi
.util
.Pair
;
21 import com
.intellij
.openapi
.util
.text
.StringUtil
;
22 import com
.intellij
.util
.Consumer
;
23 import com
.intellij
.util
.Function
;
24 import org
.jetbrains
.annotations
.NotNull
;
25 import org
.jetbrains
.annotations
.Nullable
;
27 import java
.util
.ArrayList
;
28 import java
.util
.HashMap
;
29 import java
.util
.List
;
33 * For exactly same refresh requests buffering:
35 * - refresh requests can be merged into one, but general principle is that each request should be reliably followed by refresh action
36 * - at the moment only one refresh action is being done
37 * - if request had been submitted while refresh action was in progress, new refresh action is initiated right after first refresh action finishes
41 public class RequestsMerger
{
42 private static final Logger LOG
= Logger
.getInstance("#org.jetbrains.idea.svn.RequestsMerger");
43 private static final int ourDelay
= 300;
45 private final MyWorker myWorker
;
47 private final Object myLock
= new Object();
49 private MyState myState
;
50 private final Consumer
<Runnable
> myAlarm
;
52 private final List
<Runnable
> myWaitingStartListeners
;
53 private final List
<Runnable
> myWaitingFinishListeners
;
55 public RequestsMerger(final Runnable runnable
, final Consumer
<Runnable
> alarm
) {
57 myWorker
= new MyWorker(runnable
);
59 myState
= MyState
.empty
;
61 myWaitingStartListeners
= new ArrayList
<Runnable
>();
62 myWaitingFinishListeners
= new ArrayList
<Runnable
>();
65 public void request() {
66 LOG
.debug("ext: request");
67 doAction(MyAction
.request
);
70 public void waitRefresh(final Runnable runnable
) {
71 LOG
.debug("ext: wait refresh");
72 synchronized (myLock
) {
73 myWaitingStartListeners
.add(runnable
);
78 public void ensureInitialization(final Runnable runnable
) {
79 LOG
.debug("ext: ensure init");
80 synchronized (myLock
) {
81 if (myWorker
.isInitialized()) {
85 myWaitingStartListeners
.add(runnable
);
90 private class MyWorker
implements Runnable
{
91 private boolean myInitialized
;
92 private final Runnable myRunnable
;
94 private MyWorker(Runnable runnable
) {
95 myRunnable
= runnable
;
99 LOG
.debug("worker: started refresh");
101 doAction(MyAction
.start
);
103 synchronized (myLock
) {
104 myInitialized
= true;
107 doAction(MyAction
.finish
);
111 public boolean isInitialized() {
112 return myInitialized
;
116 private void doAction(final MyAction action
) {
117 LOG
.debug("doAction: START " + action
.name());
118 final MyExitAction
[] exitActions
;
119 List
<Runnable
> toBeCalled
= null;
120 synchronized (myLock
) {
121 final MyState oldState
= myState
;
122 myState
= myState
.transition(action
);
123 if (oldState
.equals(myState
)) return;
124 exitActions
= MyTransitionAction
.getExit(oldState
, myState
);
126 LOG
.debug("doAction: oldState: " + oldState
.name() + ", newState: " + myState
.name());
128 if (LOG
.isDebugEnabled() && (exitActions
!= null)) {
129 final String debugExitActions
= StringUtil
.join(exitActions
, new Function
<MyExitAction
, String
>() {
130 public String
fun(MyExitAction exitAction
) {
131 return exitAction
.name();
134 LOG
.debug("exit actions: " + debugExitActions
);
136 if (exitActions
!= null) {
137 for (MyExitAction exitAction
: exitActions
) {
138 if (MyExitAction
.markStart
.equals(exitAction
)) {
139 myWaitingFinishListeners
.addAll(myWaitingStartListeners
);
140 myWaitingStartListeners
.clear();
141 } else if (MyExitAction
.markEnd
.equals(exitAction
)) {
142 toBeCalled
= new ArrayList
<Runnable
>(myWaitingFinishListeners
);
143 myWaitingFinishListeners
.clear();
148 if (exitActions
!= null) {
149 for (MyExitAction exitAction
: exitActions
) {
150 if (MyExitAction
.submitRequestToExecutor
.equals(exitAction
)) {
151 myAlarm
.consume(myWorker
);
152 //myAlarm.addRequest(myWorker, ourDelay);
153 //ApplicationManager.getApplication().executeOnPooledThread(myWorker);
157 if (toBeCalled
!= null) {
158 for (Runnable runnable
: toBeCalled
) {
162 LOG
.debug("doAction: END " + action
.name());
165 private static enum MyState
{
168 public MyState
transition(MyAction action
) {
169 if (MyAction
.request
.equals(action
)) {
170 return MyState
.requestSubmitted
;
172 logWrongAction(this, action
);
177 public MyState
transition(MyAction action
) {
178 if (MyAction
.finish
.equals(action
)) {
179 return MyState
.empty
;
180 } else if (MyAction
.request
.equals(action
)) {
181 return MyState
.inProgressRequestSubmitted
;
183 logWrongAction(this, action
);
186 inProgressRequestSubmitted() {
188 public MyState
transition(MyAction action
) {
189 if (MyAction
.finish
.equals(action
)) {
190 return MyState
.requestSubmitted
;
192 if (MyAction
.start
.equals(action
)) {
193 logWrongAction(this, action
);
199 public MyState
transition(MyAction action
) {
200 if (MyAction
.start
.equals(action
)) {
201 return MyState
.inProgress
;
202 } else if (MyAction
.finish
.equals(action
)) {
203 // to be able to be started by another request
204 logWrongAction(this, action
);
205 return MyState
.empty
;
212 public abstract MyState
transition(final MyAction action
);
214 private static void logWrongAction(final MyState state
, final MyAction action
) {
215 LOG
.info("Wrong action: state=" + state
.name() + ", action=" + action
.name());
219 private static class MyTransitionAction
{
220 private final static Map
<Pair
<MyState
, MyState
>, MyExitAction
[]> myMap
= new HashMap
<Pair
<MyState
, MyState
>, MyExitAction
[]>();
223 add(MyState
.empty
, MyState
.requestSubmitted
, MyExitAction
.submitRequestToExecutor
);
224 add(MyState
.requestSubmitted
, MyState
.inProgress
, MyExitAction
.markStart
);
225 add(MyState
.inProgress
, MyState
.empty
, MyExitAction
.markEnd
);
226 add(MyState
.inProgressRequestSubmitted
, MyState
.requestSubmitted
, MyExitAction
.submitRequestToExecutor
, MyExitAction
.markEnd
);
228 //... and not real but to be safe:
229 add(MyState
.inProgressRequestSubmitted
, MyState
.empty
, MyExitAction
.markEnd
);
230 add(MyState
.inProgress
, MyState
.requestSubmitted
, MyExitAction
.markEnd
);
233 private static void add(final MyState from
, final MyState to
, final MyExitAction
... action
) {
234 myMap
.put(new Pair
<MyState
, MyState
>(from
, to
), action
);
238 public static MyExitAction
[] getExit(final MyState from
, final MyState to
) {
239 return myMap
.get(new Pair
<MyState
, MyState
>(from
, to
));
243 private static enum MyExitAction
{
245 submitRequestToExecutor
,
250 private static enum MyAction
{