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