e7cb96652371d9f785fa2ba4653e701edb8c0597
[idea/community.git] / platform / platform-api / src / com / intellij / execution / process / ProcessHandler.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
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 com.intellij.execution.process;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.progress.ProcessCanceledException;
20 import com.intellij.openapi.util.Key;
21 import com.intellij.openapi.util.UserDataHolderBase;
22 import com.intellij.util.concurrency.Semaphore;
23 import com.intellij.util.containers.ContainerUtil;
24 import org.jetbrains.annotations.Nullable;
25
26 import java.io.OutputStream;
27 import java.lang.reflect.InvocationHandler;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Proxy;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.concurrent.atomic.AtomicInteger;
33
34 public abstract class ProcessHandler extends UserDataHolderBase {
35   private static final Logger LOG = Logger.getInstance("#com.intellij.execution.process.ProcessHandler");
36   public static final Key<Boolean> SILENTLY_DESTROY_ON_CLOSE = Key.create("SILENTLY_DESTROY_ON_CLOSE");
37
38   private final List<ProcessListener> myListeners = ContainerUtil.createEmptyCOWList();
39
40   private static final int STATE_INITIAL     = 0;
41   private static final int STATE_RUNNING     = 1;
42   private static final int STATE_TERMINATING = 2;
43   private static final int STATE_TERMINATED  = 3;
44
45   private final AtomicInteger myState = new AtomicInteger(STATE_INITIAL);
46
47   private final Semaphore    myWaitSemaphore;
48   private final ProcessListener myEventMulticaster;
49
50   protected ProcessHandler() {
51     myEventMulticaster = createEventMulticaster();
52     myWaitSemaphore = new Semaphore();
53     myWaitSemaphore.down();
54   }
55
56   public void startNotify() {
57     if(myState.compareAndSet(STATE_INITIAL, STATE_RUNNING)) {
58       myEventMulticaster.startNotified(new ProcessEvent(this));
59     }
60     else {
61       LOG.error("startNotify called already");
62     }
63   }
64
65   protected abstract void destroyProcessImpl();
66
67   protected abstract void detachProcessImpl();
68
69   public abstract boolean detachIsDefault();
70
71   public void waitFor() {
72     try {
73       myWaitSemaphore.waitFor();
74     }
75     catch (ProcessCanceledException e) {
76       // Ignore
77     }
78   }
79
80   public boolean waitFor(long timeoutInMilliseconds) {
81     try {
82       return myWaitSemaphore.waitFor(timeoutInMilliseconds);
83     }
84     catch (ProcessCanceledException e) {
85       return false;
86     }
87   }
88
89   public void destroyProcess() {
90     afterStartNotified(new Runnable() {
91       public void run() {
92         if (myState.compareAndSet(STATE_RUNNING, STATE_TERMINATING)) {
93           fireProcessWillTerminate(true);
94           destroyProcessImpl();
95         }
96       }
97     });
98   }
99
100   public void detachProcess() {
101     afterStartNotified(new Runnable() {
102       public void run() {
103         if (myState.compareAndSet(STATE_RUNNING, STATE_TERMINATING)) {
104           fireProcessWillTerminate(false);
105           detachProcessImpl();
106         }
107       }
108     });
109   }
110
111   public boolean isProcessTerminated() {
112     return myState.get() == STATE_TERMINATED;
113   }
114
115   public boolean isProcessTerminating() {
116     return myState.get() == STATE_TERMINATING;
117   }
118
119   public void addProcessListener(final ProcessListener listener) {
120     synchronized(myListeners) {
121       myListeners.add(listener);
122     }
123   }
124
125   public void removeProcessListener(final ProcessListener listener) {
126     synchronized(myListeners) {
127       myListeners.remove(listener);
128     }
129   }
130
131   protected void notifyProcessDetached() {
132     notifyTerminated(0, false);
133   }
134
135   protected void notifyProcessTerminated(final int exitCode) {
136     notifyTerminated(exitCode, true);
137   }
138
139   private void notifyTerminated(final int exitCode, final boolean willBeDestroyed) {
140     afterStartNotified(new Runnable() {
141       public void run() {
142         LOG.assertTrue(isStartNotified(), "Start notify is not called");
143
144         if (myState.compareAndSet(STATE_RUNNING, STATE_TERMINATING)) {
145           try {
146             fireProcessWillTerminate(willBeDestroyed);
147           }
148           catch (Throwable e) {
149             LOG.error(e);
150           }
151         }
152
153         if (myState.compareAndSet(STATE_TERMINATING, STATE_TERMINATED)) {
154           try {
155             myEventMulticaster.processTerminated(new ProcessEvent(ProcessHandler.this, exitCode));
156           }
157           catch (Throwable e) {
158             LOG.error(e);
159           }
160           finally {
161             myWaitSemaphore.up();
162           }
163         }
164       }
165     });
166   }
167
168   public void notifyTextAvailable(final String text, final Key outputType) {
169     final ProcessEvent event = new ProcessEvent(this, text);
170     myEventMulticaster.onTextAvailable(event, outputType);
171   }
172
173   @Nullable
174   public abstract OutputStream getProcessInput();
175
176   private void fireProcessWillTerminate(final boolean willBeDestroyed) {
177     LOG.assertTrue(isStartNotified(), "All events should be fired after startNotify is called");
178     myEventMulticaster.processWillTerminate(new ProcessEvent(this), willBeDestroyed);
179   }
180
181   private void afterStartNotified(final Runnable runnable) {
182     if (isStartNotified()) {
183       runnable.run();
184     }
185     else {
186       addProcessListener(new ProcessAdapter() {
187         public void startNotified(ProcessEvent event) {
188           removeProcessListener(this);
189           runnable.run();
190         }
191       });
192     }
193   }
194
195   public boolean isStartNotified() {
196     return myState.get() > STATE_INITIAL;
197   }
198
199   private ProcessListener createEventMulticaster() {
200     final Class<ProcessListener> listenerClass = ProcessListener.class;
201     return (ProcessListener)Proxy.newProxyInstance(listenerClass.getClassLoader(), new Class[] {listenerClass}, new InvocationHandler() {
202       public Object invoke(Object object, Method method, Object[] params) throws Throwable {
203         final Iterator<ProcessListener> iterator;
204         synchronized (myListeners) {
205           iterator = myListeners.iterator();
206         }
207         while (iterator.hasNext()) {
208           final ProcessListener processListener = iterator.next();
209           try {
210             method.invoke(processListener, params);
211           }
212           catch (Throwable e) {
213             LOG.error(e);
214           }
215         }
216         return null;
217       }
218     });
219   }
220 }