6a6ef6bc66af6d0aadba29e6bec6e4d21bc3eb44
[idea/community.git] / java / compiler / impl / src / com / intellij / compiler / CompileServerManager.java
1 /*
2  * Copyright 2000-2011 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.compiler;
17
18 import com.intellij.ProjectTopics;
19 import com.intellij.application.options.PathMacrosImpl;
20 import com.intellij.execution.ExecutionException;
21 import com.intellij.execution.configurations.GeneralCommandLine;
22 import com.intellij.execution.process.OSProcessHandler;
23 import com.intellij.execution.process.ProcessAdapter;
24 import com.intellij.execution.process.ProcessEvent;
25 import com.intellij.execution.process.ProcessOutputTypes;
26 import com.intellij.notification.Notification;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.PathMacros;
29 import com.intellij.openapi.application.PathManager;
30 import com.intellij.openapi.compiler.CompilerManager;
31 import com.intellij.openapi.components.ApplicationComponent;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.project.ProjectManager;
35 import com.intellij.openapi.project.ProjectManagerAdapter;
36 import com.intellij.openapi.projectRoots.JavaSdkType;
37 import com.intellij.openapi.projectRoots.ProjectJdkTable;
38 import com.intellij.openapi.projectRoots.Sdk;
39 import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
40 import com.intellij.openapi.roots.ModuleRootEvent;
41 import com.intellij.openapi.roots.ModuleRootListener;
42 import com.intellij.openapi.roots.OrderRootType;
43 import com.intellij.openapi.roots.libraries.Library;
44 import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
45 import com.intellij.openapi.ui.MessageType;
46 import com.intellij.openapi.util.Key;
47 import com.intellij.openapi.util.Ref;
48 import com.intellij.openapi.util.ShutDownTracker;
49 import com.intellij.openapi.util.io.FileUtil;
50 import com.intellij.openapi.util.registry.Registry;
51 import com.intellij.openapi.util.text.StringUtil;
52 import com.intellij.openapi.vfs.*;
53 import com.intellij.openapi.vfs.encoding.EncodingManager;
54 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
55 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
56 import com.intellij.problems.Problem;
57 import com.intellij.problems.WolfTheProblemSolver;
58 import com.intellij.util.Alarm;
59 import com.intellij.util.concurrency.Semaphore;
60 import com.intellij.util.messages.MessageBusConnection;
61 import com.intellij.util.net.NetUtils;
62 import org.jetbrains.annotations.NotNull;
63 import org.jetbrains.annotations.Nullable;
64 import org.jetbrains.jps.api.*;
65 import org.jetbrains.jps.client.CompileServerClient;
66 import org.jetbrains.jps.server.ClasspathBootstrap;
67 import org.jetbrains.jps.server.Server;
68
69 import javax.tools.*;
70 import java.io.File;
71 import java.io.FileOutputStream;
72 import java.io.IOException;
73 import java.io.InputStream;
74 import java.util.*;
75 import java.util.concurrent.Future;
76 import java.util.concurrent.RunnableFuture;
77 import java.util.concurrent.TimeUnit;
78 import java.util.concurrent.atomic.AtomicBoolean;
79
80 /**
81  * @author Eugene Zhuravlev
82  *         Date: 9/6/11
83  */
84 public class CompileServerManager implements ApplicationComponent{
85   private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.CompileServerManager");
86   private static final String COMPILE_SERVER_SYSTEM_ROOT = "compile-server";
87   private static final String LOGGER_CONFIG = "log.xml";
88   private static final String DEFAULT_LOGGER_CONFIG = "defaultLogConfig.xml";
89   private volatile OSProcessHandler myProcessHandler;
90   private final File mySystemDirectory;
91   private volatile CompileServerClient myClient = new CompileServerClient();
92   private final SequentialTaskExecutor myTaskExecutor = new SequentialTaskExecutor(new SequentialTaskExecutor.AsyncTaskExecutor() {
93     public void submit(Runnable runnable) {
94       ApplicationManager.getApplication().executeOnPooledThread(runnable);
95     }
96   });
97   private final ProjectManager myProjectManager;
98   private static final int MAKE_TRIGGER_DELAY = 5 * 1000 /*5 seconds*/;
99   private final Map<RequestFuture, Project> myAutomakeFutures = new HashMap<RequestFuture, Project>();
100
101   public CompileServerManager(final ProjectManager projectManager) {
102     myProjectManager = projectManager;
103     final String systemPath = PathManager.getSystemPath();
104     File system = new File(systemPath);
105     try {
106       system = system.getCanonicalFile();
107     }
108     catch (IOException e) {
109       LOG.info(e);
110     }
111     mySystemDirectory = system;
112
113     projectManager.addProjectManagerListener(new ProjectWatcher());
114     final MessageBusConnection conn = ApplicationManager.getApplication().getMessageBus().connect();
115     conn.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
116       private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
117       private final AtomicBoolean myAutoMakeInProgress = new AtomicBoolean(false);
118       @Override
119       public void before(List<? extends VFileEvent> events) {
120       }
121
122       @Override
123       public void after(List<? extends VFileEvent> events) {
124         if (shouldTriggerMake(events)) {
125           scheduleMake(new Runnable() {
126             @Override
127             public void run() {
128               if (!myAutoMakeInProgress.getAndSet(true)) {
129                 try {
130                   runAutoMake();
131                 }
132                 finally {
133                   myAutoMakeInProgress.set(false);
134                 }
135               }
136               else {
137                 scheduleMake(this);
138               }
139             }
140           });
141         }
142       }
143
144       private void scheduleMake(Runnable runnable) {
145         myAlarm.cancelAllRequests();
146         myAlarm.addRequest(runnable, MAKE_TRIGGER_DELAY);
147       }
148
149       private boolean shouldTriggerMake(List<? extends VFileEvent> events) {
150         for (VFileEvent event : events) {
151           if (event.isFromRefresh() || event.getRequestor() instanceof SavingRequestor) {
152             return true;
153           }
154         }
155         return false;
156       }
157     });
158
159     ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
160       @Override
161       public void run() {
162         shutdownServer(myClient, myProcessHandler);
163       }
164     });
165   }
166
167   public static CompileServerManager getInstance() {
168     return ApplicationManager.getApplication().getComponent(CompileServerManager.class);
169   }
170
171   public void notifyFilesChanged(Collection<String> paths) {
172     sendNotification(paths, false);
173   }
174
175   public void notifyFilesDeleted(Collection<String> paths) {
176     sendNotification(paths, true);
177   }
178
179   public void sendReloadRequest(final Project project) {
180     if (!project.isDefault() && project.isOpen()) {
181       myTaskExecutor.submit(new Runnable() {
182         @Override
183         public void run() {
184           try {
185             if (!project.isDisposed()) {
186               final CompileServerClient client = ensureServerRunningAndClientConnected(false);
187               if (client != null) {
188                 client.sendProjectReloadRequest(Collections.singletonList(project.getLocation()));
189               }
190             }
191           }
192           catch (Throwable e) {
193             LOG.info(e);
194           }
195         }
196       });
197     }
198   }
199
200   public void sendCancelBuildRequest(final UUID sessionId) {
201     myTaskExecutor.submit(new Runnable() {
202       @Override
203       public void run() {
204         try {
205           final CompileServerClient client = ensureServerRunningAndClientConnected(false);
206           if (client != null) {
207             client.sendCancelBuildRequest(sessionId);
208           }
209         }
210         catch (Throwable e) {
211           LOG.info(e);
212         }
213       }
214     });
215   }
216
217   private void sendNotification(final Collection<String> paths, final boolean isDeleted) {
218     if (paths.isEmpty()) {
219       return;
220     }
221     try {
222       final CompileServerClient client = ensureServerRunningAndClientConnected(false);
223       if (client != null) {
224         myTaskExecutor.submit(new Runnable() {
225           public void run() {
226             final Project[] openProjects = myProjectManager.getOpenProjects();
227             if (openProjects.length > 0) {
228               final Collection<String> changed, deleted;
229               if (isDeleted) {
230                 changed = Collections.emptyList();
231                 deleted = paths;
232               }
233               else {
234                 changed = paths;
235                 deleted = Collections.emptyList();
236               }
237               for (Project project : openProjects) {
238                 try {
239                   client.sendFSEvent(project.getLocation(), changed, deleted);
240                 }
241                 catch (Exception e) {
242                   LOG.info(e);
243                 }
244               }
245             }
246           }
247         });
248       }
249     }
250     catch (Throwable th) {
251       LOG.error(th); // should not happen
252     }
253   }
254
255   private void runAutoMake() {
256     final Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
257     if (openProjects.length > 0) {
258       final List<RequestFuture> futures = new ArrayList<RequestFuture>();
259       for (final Project project : openProjects) {
260         if (project.isDefault()) {
261           continue;
262         }
263         final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project);
264         if (!config.USE_COMPILE_SERVER || !config.MAKE_PROJECT_ON_SAVE) {
265           continue;
266         }
267         final RequestFuture future = submitCompilationTask(
268           project, false, true, Collections.<String>emptyList(), Collections.<String>emptyList(), new AutoMakeResponseHandler(project)
269         );
270         if (future != null) {
271           futures.add(future);
272           synchronized (myAutomakeFutures) {
273             myAutomakeFutures.put(future, project);
274           }
275         }
276       }
277       try {
278         for (RequestFuture future : futures) {
279           try {
280             future.get();
281           }
282           catch (InterruptedException ignored) {
283           }
284           catch (java.util.concurrent.ExecutionException ignored) {
285           }
286         }
287       }
288       finally {
289         synchronized (myAutomakeFutures) {
290           myAutomakeFutures.keySet().removeAll(futures);
291         }
292       }
293     }
294   }
295
296   public void cancelAutoMakeTasks(Project project) {
297     synchronized (myAutomakeFutures) {
298       for (Map.Entry<RequestFuture, Project> entry : myAutomakeFutures.entrySet()) {
299         if (entry.getValue().equals(project)) {
300           entry.getKey().cancel(true);
301         }
302       }
303     }
304   }
305
306   @Nullable
307   public RequestFuture submitCompilationTask(final Project project, final boolean isRebuild, final boolean isMake, final Collection<String> modules, final Collection<String> paths, final JpsServerResponseHandler handler) {
308     final String projectId = project.getLocation();
309     final Ref<RequestFuture> futureRef = new Ref<RequestFuture>(null);
310     final RunnableFuture future = myTaskExecutor.submit(new Runnable() {
311       public void run() {
312         try {
313           final CompileServerClient client = ensureServerRunningAndClientConnected(true);
314           if (client != null) {
315             final RequestFuture requestFuture = isRebuild ?
316               client.sendRebuildRequest(projectId, handler) :
317               client.sendCompileRequest(isMake, projectId, modules, paths, handler);
318             futureRef.set(requestFuture);
319           }
320           else {
321             handler.sessionTerminated();
322           }
323         }
324         catch (Throwable e) {
325           try {
326             handler.handleFailure(ProtoUtil.createFailure(e.getMessage(), e));
327           }
328           finally {
329             handler.sessionTerminated();
330           }
331         }
332       }
333     });
334     try {
335       future.get();
336     }
337     catch (Throwable e) {
338       LOG.info(e);
339     }
340     return futureRef.get();
341   }
342
343   @Override
344   public void initComponent() {
345   }
346
347   @Override
348   public void disposeComponent() {
349     shutdownServer(myClient, myProcessHandler);
350   }
351
352   @NotNull
353   @Override
354   public String getComponentName() {
355     return "com.intellij.compiler.JpsServerManager";
356   }
357
358   // executed in one thread at a time
359   @Nullable
360   private CompileServerClient ensureServerRunningAndClientConnected(boolean forceRestart) throws Throwable {
361     final OSProcessHandler ph = myProcessHandler;
362     final CompileServerClient cl = myClient;
363     final boolean processNotRunning = ph == null || ph.isProcessTerminated() || ph.isProcessTerminating();
364     final boolean clientNotConnected = cl == null || !cl.isConnected();
365
366     if (processNotRunning || clientNotConnected) {
367       // cleanup; ensure the process is not running
368       shutdownServer(cl, ph);
369       myProcessHandler = null;
370       myClient = null;
371
372       if (!forceRestart) {
373         return null;
374       }
375
376       final int port = NetUtils.findAvailableSocketPort();
377       final Process process = launchServer(port);
378
379       final OSProcessHandler processHandler = new OSProcessHandler(process, null) {
380         protected boolean shouldDestroyProcessRecursively() {
381           return true;
382         }
383       };
384       final StringBuilder serverStartMessage = new StringBuilder();
385       final Semaphore semaphore  = new Semaphore();
386       semaphore.down();
387       processHandler.addProcessListener(new ProcessAdapter() {
388         public void onTextAvailable(ProcessEvent event, Key outputType) {
389           // re-translate server's output to idea.log
390           final String text = event.getText();
391           if (!StringUtil.isEmpty(text)) {
392             LOG.info("COMPILE_SERVER [" +outputType.toString() +"]: "+ text.trim());
393           }
394         }
395       });
396       processHandler.addProcessListener(new ProcessAdapter() {
397         public void processTerminated(ProcessEvent event) {
398           try {
399             processHandler.removeProcessListener(this);
400           }
401           finally {
402             semaphore.up();
403           }
404         }
405
406         public void onTextAvailable(ProcessEvent event, Key outputType) {
407           if (outputType == ProcessOutputTypes.STDERR) {
408             try {
409               final String text = event.getText();
410               if (text != null) {
411                 if (text.contains(Server.SERVER_SUCCESS_START_MESSAGE) || text.contains(Server.SERVER_ERROR_START_MESSAGE)) {
412                   processHandler.removeProcessListener(this);
413                 }
414                 if (serverStartMessage.length() > 0) {
415                   serverStartMessage.append("\n");
416                 }
417                 serverStartMessage.append(text);
418               }
419             }
420             finally {
421               semaphore.up();
422             }
423           }
424         }
425       });
426       processHandler.startNotify();
427       semaphore.waitFor();
428
429       final String startupMsg = serverStartMessage.toString();
430       if (!startupMsg.contains(Server.SERVER_SUCCESS_START_MESSAGE)) {
431         throw new Exception("Server startup failed: " + startupMsg);
432       }
433
434       CompileServerClient client = new CompileServerClient();
435       boolean connected = false;
436       try {
437         connected = client.connect(NetUtils.getLocalHostString(), port);
438         if (connected) {
439           final RequestFuture setupFuture = sendSetupRequest(client);
440           setupFuture.get();
441           myProcessHandler = processHandler;
442           myClient = client;
443         }
444       }
445       finally {
446         if (!connected) {
447           shutdownServer(cl, processHandler);
448         }
449       }
450     }
451     return myClient;
452   }
453
454   private static RequestFuture sendSetupRequest(final @NotNull CompileServerClient client) throws Exception {
455     final Map<String, String> data = new HashMap<String, String>();
456
457     // need this for tests and when this macro is missing from PathMacros registry
458     data.put(PathMacrosImpl.APPLICATION_HOME_MACRO_NAME, FileUtil.toSystemIndependentName(PathManager.getHomePath()));
459
460     final PathMacros pathVars = PathMacros.getInstance();
461     for (String name : pathVars.getAllMacroNames()) {
462       final String path = pathVars.getValue(name);
463       if (path != null) {
464         data.put(name, FileUtil.toSystemIndependentName(path));
465       }
466     }
467
468     final List<GlobalLibrary> globals = new ArrayList<GlobalLibrary>();
469
470     fillSdks(globals);
471     fillGlobalLibraries(globals);
472
473     return client.sendSetupRequest(data, globals, EncodingManager.getInstance().getDefaultCharsetName());
474   }
475
476   private static void fillSdks(List<GlobalLibrary> globals) {
477     for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) {
478       final String name = sdk.getName();
479       final String homePath = sdk.getHomePath();
480       if (homePath == null) {
481         continue;
482       }
483       final List<String> paths = convertToLocalPaths(sdk.getRootProvider().getFiles(OrderRootType.CLASSES));
484       globals.add(new SdkLibrary(name, homePath, paths));
485     }
486   }
487
488   private static void fillGlobalLibraries(List<GlobalLibrary> globals) {
489     final Iterator<Library> iterator = LibraryTablesRegistrar.getInstance().getLibraryTable().getLibraryIterator();
490     while (iterator.hasNext()) {
491       Library library = iterator.next();
492       final String name = library.getName();
493
494       if (name != null) {
495         final List<String> paths = convertToLocalPaths(library.getFiles(OrderRootType.CLASSES));
496         globals.add(new GlobalLibrary(name, paths));
497       }
498     }
499   }
500
501   private static List<String> convertToLocalPaths(VirtualFile[] files) {
502     final List<String> paths = new ArrayList<String>();
503     for (VirtualFile file : files) {
504       if (file.isValid()) {
505         paths.add(StringUtil.trimEnd(FileUtil.toSystemIndependentName(file.getPath()), JarFileSystem.JAR_SEPARATOR));
506       }
507     }
508     return paths;
509   }
510
511   //public static void addLocaleOptions(final List<String> commandLine, final boolean launcherUsed) {
512   //  // need to specify default encoding so that javac outputs messages in 'correct' language
513   //  commandLine.add((launcherUsed? "-J" : "") + "-D" + CharsetToolkit.FILE_ENCODING_PROPERTY + "=" + CharsetToolkit.getDefaultSystemCharset().name());
514   //}
515
516   private Process launchServer(final int port) throws ExecutionException {
517     // validate tools.jar presence
518     final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler();
519     if (systemCompiler == null) {
520       throw new ExecutionException("No system java compiler is provided by the JRE. Make sure tools.jar is present in IntelliJ IDEA classpath.");
521     }
522
523     final Sdk projectJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
524     final GeneralCommandLine cmdLine = new GeneralCommandLine();
525     final String vmExecutablePath = ((JavaSdkType)projectJdk.getSdkType()).getVMExecutablePath(projectJdk);
526     cmdLine.setExePath(vmExecutablePath);
527     cmdLine.addParameter("-server");
528     cmdLine.addParameter("-XX:MaxPermSize=150m");
529     cmdLine.addParameter("-XX:ReservedCodeCacheSize=64m");
530     cmdLine.addParameter("-Xmx" + Registry.intValue("compiler.server.heap.size") + "m");
531     cmdLine.addParameter("-Djava.awt.headless=true");
532     //cmdLine.addParameter("-DuseJavaUtilZip");
533     final String additionalOptions = Registry.stringValue("compiler.server.vm.options");
534     if (!StringUtil.isEmpty(additionalOptions)) {
535       final StringTokenizer tokenizer = new StringTokenizer(additionalOptions, " ", false);
536       while (tokenizer.hasMoreTokens()) {
537         cmdLine.addParameter(tokenizer.nextToken());
538       }
539     }
540
541     // debugging
542     final int debugPort = Registry.intValue("compiler.server.debug.port");
543     if (debugPort > 0) {
544       cmdLine.addParameter("-XX:+HeapDumpOnOutOfMemoryError");
545       cmdLine.addParameter("-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=" + debugPort);
546     }
547
548     if (Registry.is("compiler.server.use.memory.temp.cache")) {
549       cmdLine.addParameter("-D"+ GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION + "=true");
550     }
551     if (Registry.is("compiler.server.use.external.javac.process")) {
552       cmdLine.addParameter("-D"+ GlobalOptions.USE_EXTERNAL_JAVAC_OPTION + "=true");
553     }
554     cmdLine.addParameter("-D"+ GlobalOptions.HOSTNAME_OPTION + "=" + NetUtils.getLocalHostString());
555     cmdLine.addParameter("-D"+ GlobalOptions.VM_EXE_PATH_OPTION + "=" + FileUtil.toSystemIndependentName(vmExecutablePath));
556
557     // javac's VM should use the same default locale that IDEA uses in order for javac to print messages in 'correct' language
558     final String lang = System.getProperty("user.language");
559     if (lang != null) {
560       //noinspection HardCodedStringLiteral
561       cmdLine.addParameter("-Duser.language=" + lang);
562     }
563     final String country = System.getProperty("user.country");
564     if (country != null) {
565       //noinspection HardCodedStringLiteral
566       cmdLine.addParameter("-Duser.country=" + country);
567     }
568     //noinspection HardCodedStringLiteral
569     final String region = System.getProperty("user.region");
570     if (region != null) {
571       //noinspection HardCodedStringLiteral
572       cmdLine.addParameter("-Duser.region=" + region);
573     }
574
575     cmdLine.addParameter("-classpath");
576
577     final List<File> cp = ClasspathBootstrap.getCompileServerApplicationClasspath();
578
579     cmdLine.addParameter(classpathToString(cp));
580
581     cmdLine.addParameter(org.jetbrains.jps.server.Server.class.getName());
582     cmdLine.addParameter(Integer.toString(port));
583
584     final File workDirectory = new File(mySystemDirectory, COMPILE_SERVER_SYSTEM_ROOT);
585     workDirectory.mkdirs();
586     ensureLogConfigExists(workDirectory);
587
588     cmdLine.addParameter(FileUtil.toSystemIndependentName(workDirectory.getPath()));
589
590     cmdLine.setWorkDirectory(workDirectory);
591
592
593     return cmdLine.createProcess();
594   }
595
596   private static void ensureLogConfigExists(File workDirectory) {
597     final File logConfig = new File(workDirectory, LOGGER_CONFIG);
598     if (!logConfig.exists()) {
599       FileUtil.createIfDoesntExist(logConfig);
600       try {
601         final InputStream in = Server.class.getResourceAsStream("/" + DEFAULT_LOGGER_CONFIG);
602         if (in != null) {
603           try {
604             final FileOutputStream out = new FileOutputStream(logConfig);
605             try {
606               FileUtil.copy(in, out);
607             }
608             finally {
609               out.close();
610             }
611           }
612           finally {
613             in.close();
614           }
615         }
616       }
617       catch (IOException e) {
618         LOG.error(e);
619       }
620     }
621   }
622
623   public void shutdownServer() {
624     shutdownServer(myClient, myProcessHandler);
625   }
626
627   private static void shutdownServer(final CompileServerClient client, final OSProcessHandler processHandler) {
628     try {
629       if (client != null && client.isConnected()) {
630         final Future future = client.sendShutdownRequest();
631         future.get(500, TimeUnit.MILLISECONDS);
632         client.disconnect();
633       }
634     }
635     catch (Throwable ignored) {
636       LOG.info(ignored);
637     }
638     finally {
639       if (processHandler != null) {
640         processHandler.destroyProcess();
641       }
642     }
643   }
644
645   private static String classpathToString(List<File> cp) {
646     StringBuilder builder = new StringBuilder();
647     for (File file : cp) {
648       if (builder.length() > 0) {
649         builder.append(File.pathSeparator);
650       }
651       builder.append(file.getAbsolutePath());
652     }
653     return builder.toString();
654   }
655
656   private static class AutoMakeResponseHandler extends JpsServerResponseHandler {
657     private JpsRemoteProto.Message.Response.BuildEvent.Status myBuildStatus;
658     private final Project myProject;
659     private final WolfTheProblemSolver myWolf;
660
661     public AutoMakeResponseHandler(Project project) {
662       myProject = project;
663       myBuildStatus = JpsRemoteProto.Message.Response.BuildEvent.Status.SUCCESS;
664       myWolf = WolfTheProblemSolver.getInstance(project);
665     }
666
667     @Override
668     public boolean handleBuildEvent(JpsRemoteProto.Message.Response.BuildEvent event) {
669       final JpsRemoteProto.Message.Response.BuildEvent.Type type = event.getEventType();
670       if (type == JpsRemoteProto.Message.Response.BuildEvent.Type.FILES_GENERATED) {
671         for (JpsRemoteProto.Message.Response.BuildEvent.GeneratedFile gf : event.getGeneratedFilesList()) {
672         }
673       }
674       if (type == JpsRemoteProto.Message.Response.BuildEvent.Type.BUILD_COMPLETED) {
675         if (event.hasCompletionStatus()) {
676           myBuildStatus = event.getCompletionStatus();
677         }
678         return true;
679       }
680       return false;
681     }
682
683     @Override
684     public void handleCompileMessage(JpsRemoteProto.Message.Response.CompileMessage compileResponse) {
685       final JpsRemoteProto.Message.Response.CompileMessage.Kind kind = compileResponse.getKind();
686       if (kind == JpsRemoteProto.Message.Response.CompileMessage.Kind.ERROR) {
687         informWolf(myProject, compileResponse);
688       }
689     }
690
691     @Override
692     public void handleFailure(JpsRemoteProto.Message.Failure failure) {
693       CompilerManager.NOTIFICATION_GROUP.createNotification("Auto make failure: " + failure.getDescription(), MessageType.INFO);
694     }
695
696     @Override
697     public void sessionTerminated() {
698       String statusMessage = "Auto make completed";
699       switch (myBuildStatus) {
700         case SUCCESS:
701           statusMessage = "Auto make completed successfully";
702           break;
703         case UP_TO_DATE:
704           statusMessage = "All files are up-to-date";
705           break;
706         case ERRORS:
707           statusMessage = "Auto make completed with errors";
708           break;
709         case CANCELED:
710           statusMessage = "Auto make has been canceled";
711           break;
712       }
713       final Notification notification = CompilerManager.NOTIFICATION_GROUP.createNotification(statusMessage, MessageType.INFO);
714       if (!myProject.isDisposed()) {
715         notification.notify(myProject);
716       }
717     }
718
719     private void informWolf(Project project, JpsRemoteProto.Message.Response.CompileMessage message) {
720       final String srcPath = message.getSourceFilePath();
721       if (srcPath != null && !project.isDisposed()) {
722         final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(srcPath);
723         if (vFile != null) {
724           final int line = (int)message.getLine();
725           final int column = (int)message.getColumn();
726           if (line > 0 && column > 0) {
727             final Problem problem = myWolf.convertToProblem(vFile, line, column, new String[]{message.getText()});
728             myWolf.weHaveGotProblems(vFile, Collections.singletonList(problem));
729           }
730           else {
731             myWolf.queue(vFile);
732           }
733         }
734       }
735     }
736   }
737
738   private class ProjectWatcher extends ProjectManagerAdapter {
739     private final Map<Project, MessageBusConnection> myConnections = new HashMap<Project, MessageBusConnection>();
740
741     public void projectOpened(final Project project) {
742       final MessageBusConnection conn = project.getMessageBus().connect();
743       myConnections.put(project, conn);
744       conn.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() {
745         public void beforeRootsChange(final ModuleRootEvent event) {
746           sendReloadRequest(project);
747         }
748
749         public void rootsChanged(final ModuleRootEvent event) {
750           myTaskExecutor.submit(new Runnable() {
751             public void run() {
752               try {
753                 // this will reload sdks and global libraries
754                 final CompileServerClient client = ensureServerRunningAndClientConnected(false);
755                 if (client != null) {
756                   sendSetupRequest(client);
757                 }
758               }
759               catch (Throwable e) {
760                 LOG.info(e);
761               }
762             }
763           });
764         }
765       });
766     }
767
768     public void projectClosing(Project project) {
769       sendReloadRequest(project);
770     }
771
772     public void projectClosed(Project project) {
773       final MessageBusConnection conn = myConnections.remove(project);
774       if (conn != null) {
775         conn.disconnect();
776       }
777     }
778   }
779 }