SVN auth
[fedora-idea.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / RequestsMerger.java
blob6498bef75e143d6e2fe32a104a781982505ebb6d
1 /*
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;
30 import java.util.Map;
32 /**
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
40 @SomeQueue
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) {
56 myAlarm = 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);
75 request();
78 public void ensureInitialization(final Runnable runnable) {
79 LOG.debug("ext: ensure init");
80 synchronized (myLock) {
81 if (myWorker.isInitialized()) {
82 runnable.run();
83 return;
85 myWaitingStartListeners.add(runnable);
87 request();
90 private class MyWorker implements Runnable {
91 private boolean myInitialized;
92 private final Runnable myRunnable;
94 private MyWorker(Runnable runnable) {
95 myRunnable = runnable;
98 public void run() {
99 LOG.debug("worker: started refresh");
100 try {
101 doAction(MyAction.start);
102 myRunnable.run();
103 synchronized (myLock) {
104 myInitialized = true;
106 } finally {
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();
133 }, " ");
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) {
159 runnable.run();
162 LOG.debug("doAction: END " + action.name());
165 private static enum MyState {
166 empty() {
167 @NotNull
168 public MyState transition(MyAction action) {
169 if (MyAction.request.equals(action)) {
170 return MyState.requestSubmitted;
172 logWrongAction(this, action);
173 return this;
175 inProgress() {
176 @NotNull
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);
184 return this;
186 inProgressRequestSubmitted() {
187 @NotNull
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);
195 return this;
197 requestSubmitted() {
198 @NotNull
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;
207 return this;
210 // under lock
211 @NotNull
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[]>();
222 static {
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);
237 @Nullable
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 {
244 empty,
245 submitRequestToExecutor,
246 markStart,
247 markEnd
250 private static enum MyAction {
251 request,
252 start,
253 finish