e7027c7fe8bf030fea80e7e2e3b13fd129b7a48f
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / ui / impl / watch / CompilingEvaluatorImpl.java
1 /*
2  * Copyright 2000-2014 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.debugger.ui.impl.watch;
17
18 import com.intellij.compiler.server.BuildManager;
19 import com.intellij.debugger.engine.DebugProcess;
20 import com.intellij.debugger.engine.DebugProcessAdapter;
21 import com.intellij.debugger.engine.DebugProcessImpl;
22 import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
23 import com.intellij.debugger.engine.evaluation.EvaluateException;
24 import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.module.Module;
27 import com.intellij.openapi.module.ModuleUtilCore;
28 import com.intellij.openapi.projectRoots.JavaSdkType;
29 import com.intellij.openapi.projectRoots.JavaSdkVersion;
30 import com.intellij.openapi.projectRoots.Sdk;
31 import com.intellij.openapi.projectRoots.SdkTypeId;
32 import com.intellij.openapi.roots.ModuleRootManager;
33 import com.intellij.openapi.util.Computable;
34 import com.intellij.openapi.util.Key;
35 import com.intellij.openapi.util.Pair;
36 import com.intellij.openapi.util.io.FileUtil;
37 import com.intellij.pom.java.LanguageLevel;
38 import com.intellij.psi.PsiElement;
39 import com.intellij.psi.PsiFile;
40 import com.intellij.refactoring.extractMethodObject.ExtractLightMethodObjectHandler;
41 import com.intellij.util.net.NetUtils;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44 import org.jetbrains.jps.api.CanceledStatus;
45 import org.jetbrains.jps.builders.impl.java.JavacCompilerTool;
46 import org.jetbrains.jps.javac.DiagnosticOutputConsumer;
47 import org.jetbrains.jps.javac.ExternalJavacManager;
48 import org.jetbrains.jps.javac.OutputFileConsumer;
49 import org.jetbrains.jps.javac.OutputFileObject;
50
51 import javax.tools.Diagnostic;
52 import javax.tools.JavaFileObject;
53 import java.io.File;
54 import java.io.IOException;
55 import java.util.*;
56
57 // todo: consider batching compilations in order not to start a separate process for every class that needs to be compiled
58 public class CompilingEvaluatorImpl extends CompilingEvaluator {
59
60   private final EvaluationContextImpl myEvaluationContext;
61
62   public CompilingEvaluatorImpl(EvaluationContextImpl evaluationContext, @NotNull PsiElement context, @NotNull ExtractLightMethodObjectHandler.ExtractedData data) {
63     super(context, data);
64     myEvaluationContext = evaluationContext;
65   }
66
67   @Override
68   @NotNull
69   protected Collection<OutputFileObject> compile(@Nullable JavaSdkVersion debuggeeVersion) throws EvaluateException {
70     final Pair<Sdk, JavaSdkVersion> runtime = BuildManager.getBuildProcessRuntimeSdk(myEvaluationContext.getProject());
71     final Module module = ApplicationManager.getApplication().runReadAction(new Computable<Module>() {
72       @Override
73       public Module compute() {
74         return ModuleUtilCore.findModuleForPsiElement(myPsiContext);
75       }
76     });
77     String javaHome = null;
78     final Sdk sdk = runtime.getFirst();
79     final SdkTypeId type = sdk.getSdkType();
80     if (type instanceof JavaSdkType) {
81       javaHome = sdk.getHomePath();
82     }
83     if (javaHome == null) {
84       throw new EvaluateException("Was not able to determine JDK for current evaluation context");
85     }
86     final List<String> options = new ArrayList<String>();
87     options.add("-proc:none"); // for our purposes annotation processing is not needed
88     options.add("-encoding");
89     options.add("UTF-8");
90     final List<File> platformClasspath = new ArrayList<File>();
91     final List<File> classpath = new ArrayList<File>();
92     if (module != null) {
93       final ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
94       rootManager.orderEntries().compileOnly().recursively().exportedOnly().withoutSdk().getPathsList().addAllFiles(classpath);
95       rootManager.orderEntries().compileOnly().sdkOnly().getPathsList().addAllFiles(platformClasspath);
96     }
97
98     final JavaSdkVersion buildRuntimeVersion = runtime.getSecond();
99     // if compiler or debuggee version or both are unknown, let source and target be the compiler's defaults
100     if (buildRuntimeVersion != null && debuggeeVersion != null) {
101       final JavaSdkVersion minVersion = buildRuntimeVersion.ordinal() > debuggeeVersion.ordinal() ? debuggeeVersion : buildRuntimeVersion;
102       final String sourceOption = getSourceOption(minVersion.getMaxLanguageLevel());
103       options.add("-source");
104       options.add(sourceOption);
105       options.add("-target");
106       options.add(sourceOption);
107     }
108
109     File sourceFile = null;
110     final OutputCollector outputSink = new OutputCollector();
111     try {
112       final ExternalJavacManager javacManager = getJavacManager();
113       if (javacManager == null) {
114         throw new EvaluateException("Cannot compile java code");
115       }
116       sourceFile = generateTempSourceFile(javacManager.getWorkingDir());
117       final File srcDir = sourceFile.getParentFile();
118       final Map<File, Set<File>> output = Collections.singletonMap(srcDir, Collections.singleton(srcDir));
119       DiagnosticCollector diagnostic = new DiagnosticCollector();
120       final List<String> vmOptions = Collections.emptyList();
121       final List<File> sourcePath = Collections.emptyList();
122       final Set<File> sources = Collections.singleton(sourceFile);
123       boolean compiledOK = javacManager.forkJavac(
124         javaHome, -1, vmOptions, options, platformClasspath, classpath, sourcePath, sources, output, diagnostic, outputSink, new JavacCompilerTool(), CanceledStatus.NULL
125       );
126
127       if (!compiledOK) {
128         final StringBuilder res = new StringBuilder("Compilation failed:\n");
129         for (Diagnostic<? extends JavaFileObject> d : diagnostic.getDiagnostics()) {
130           if (d.getKind() == Diagnostic.Kind.ERROR) {
131             res.append(d.getMessage(Locale.US));
132           }
133         }
134         throw new EvaluateException(res.toString());
135       }
136     }
137     catch (EvaluateException e) {
138       throw e;
139     }
140     catch (Exception e) {
141       throw new EvaluateException(e.getMessage());
142     }
143     finally {
144       if (sourceFile != null) {
145         FileUtil.delete(sourceFile);
146       }
147     }
148     return outputSink.getCompiledClasses();
149   }
150
151   @NotNull
152   private static String getSourceOption(@NotNull LanguageLevel languageLevel) {
153     return "1." + Integer.valueOf(3 + languageLevel.ordinal());
154   }
155
156   private File generateTempSourceFile(File workingDir) throws IOException {
157     final Pair<String, String> fileData = ApplicationManager.getApplication().runReadAction(new Computable<Pair<String, String>>() {
158       @Override
159       public Pair<String, String> compute() {
160         final PsiFile file = myData.getGeneratedInnerClass().getContainingFile();
161         return Pair.create(file.getName(), file.getText());
162       }
163     });
164     if (fileData.first == null) {
165       throw new IOException("Class file name not specified");
166     }
167     if (fileData.second == null) {
168       throw new IOException("Class source code not specified");
169     }
170     final File file = new File(workingDir, "src/"+fileData.first);
171     FileUtil.writeToFile(file, fileData.second);
172     return file;
173   }
174
175   private static final Key<ExternalJavacManager> JAVAC_MANAGER_KEY = Key.create("_external_java_compiler_manager_");
176
177   @Nullable
178   private ExternalJavacManager getJavacManager() throws IOException {
179     // need dedicated thread access to be able to cache the manager in the user data
180     DebuggerManagerThreadImpl.assertIsManagerThread();
181
182     final DebugProcessImpl debugProcess = myEvaluationContext.getDebugProcess();
183     ExternalJavacManager manager = JAVAC_MANAGER_KEY.get(debugProcess);
184     if (manager == null && debugProcess.isAttached()) {
185       final File compilerWorkingDir = getCompilerWorkingDir();
186       if (compilerWorkingDir == null) {
187         return null; // should not happen for real projects
188       }
189       final int listenPort = NetUtils.findAvailableSocketPort();
190       manager = new ExternalJavacManager(compilerWorkingDir);
191       manager.start(listenPort);
192       final ExternalJavacManager _manager = manager;
193       debugProcess.addDebugProcessListener(new DebugProcessAdapter() {
194         public void processDetached(DebugProcess process, boolean closedByUser) {
195           if (process == debugProcess) {
196             _manager.stop();
197           }
198         }
199       });
200       JAVAC_MANAGER_KEY.set(debugProcess, manager);
201     }
202     return manager;
203   }
204
205   @Nullable
206   private File getCompilerWorkingDir() {
207     final File projectBuildDir = BuildManager.getInstance().getProjectSystemDirectory(myEvaluationContext.getProject());
208     if (projectBuildDir == null) {
209       return null;
210     }
211     final File root = new File(projectBuildDir, "debugger");
212     root.mkdirs();
213     return root;
214   }
215
216   private static class DiagnosticCollector implements DiagnosticOutputConsumer {
217     private final List<Diagnostic<? extends JavaFileObject>> myDiagnostics = new ArrayList<Diagnostic<? extends JavaFileObject>>();
218     public void outputLineAvailable(String line) {
219       // for debugging purposes uncomment this line
220       //System.out.println(line);
221     }
222
223     public void registerImports(String className, Collection<String> imports, Collection<String> staticImports) {
224       // ignore
225     }
226
227     public void javaFileLoaded(File file) {
228       // ignore
229     }
230
231     public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
232       myDiagnostics.add(diagnostic);
233     }
234
235     public List<Diagnostic<? extends JavaFileObject>> getDiagnostics() {
236       return myDiagnostics;
237     }
238   }
239
240   private static class OutputCollector implements OutputFileConsumer {
241     private List<OutputFileObject> myClasses = new ArrayList<OutputFileObject>();
242
243     public void save(@NotNull OutputFileObject fileObject) {
244       myClasses.add(fileObject);
245     }
246
247     public List<OutputFileObject> getCompiledClasses() {
248       return myClasses;
249     }
250   }
251 }