IDEA-172425 Add ability to enable reflection replacement with any JDK
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / ui / impl / watch / CompilingEvaluatorImpl.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.debugger.ui.impl.watch;
3
4 import com.intellij.compiler.CompilerConfiguration;
5 import com.intellij.compiler.server.BuildManager;
6 import com.intellij.debugger.engine.SuspendContextImpl;
7 import com.intellij.debugger.engine.evaluation.EvaluateException;
8 import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator;
9 import com.intellij.openapi.application.ApplicationManager;
10 import com.intellij.openapi.application.ReadAction;
11 import com.intellij.openapi.compiler.ClassObject;
12 import com.intellij.openapi.compiler.CompilationException;
13 import com.intellij.openapi.compiler.CompilerManager;
14 import com.intellij.openapi.compiler.CompilerMessageCategory;
15 import com.intellij.openapi.module.Module;
16 import com.intellij.openapi.module.ModuleUtilCore;
17 import com.intellij.openapi.project.Project;
18 import com.intellij.openapi.projectRoots.JavaSdkVersion;
19 import com.intellij.openapi.projectRoots.Sdk;
20 import com.intellij.openapi.roots.ModuleRootManager;
21 import com.intellij.openapi.util.Pair;
22 import com.intellij.openapi.util.ThrowableComputable;
23 import com.intellij.openapi.util.io.FileUtil;
24 import com.intellij.openapi.util.registry.Registry;
25 import com.intellij.psi.PsiCodeFragment;
26 import com.intellij.psi.PsiElement;
27 import com.intellij.psi.PsiFile;
28 import com.intellij.refactoring.extractMethod.PrepareFailedException;
29 import com.intellij.refactoring.extractMethodObject.ExtractLightMethodObjectHandler;
30 import com.intellij.xdebugger.XDebuggerManager;
31 import com.intellij.xdebugger.frame.XSuspendContext;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34 import org.jetbrains.jps.incremental.java.JavaBuilder;
35 import org.jetbrains.jps.model.java.JpsJavaSdkType;
36 import org.jetbrains.jps.model.java.compiler.AnnotationProcessingConfiguration;
37
38 import java.io.File;
39 import java.io.IOException;
40 import java.util.*;
41 import java.util.function.Function;
42
43 // todo: consider batching compilations in order not to start a separate process for every class that needs to be compiled
44 public class CompilingEvaluatorImpl extends CompilingEvaluator {
45   private Collection<ClassObject> myCompiledClasses;
46
47   public CompilingEvaluatorImpl(@NotNull Project project,
48                                 @NotNull PsiElement context,
49                                 @NotNull ExtractLightMethodObjectHandler.ExtractedData data) {
50     super(project, context, data);
51   }
52
53   @Override
54   @NotNull
55   protected Collection<ClassObject> compile(@Nullable JavaSdkVersion debuggeeVersion) throws EvaluateException {
56     if (myCompiledClasses == null) {
57       Module module = ReadAction.compute(() -> ModuleUtilCore.findModuleForPsiElement(myPsiContext));
58       List<String> options = new ArrayList<>();
59       options.add("-encoding");
60       options.add("UTF-8");
61       List<File> platformClasspath = new ArrayList<>();
62       List<File> classpath = new ArrayList<>();
63       AnnotationProcessingConfiguration profile = null;
64       if (module != null) {
65         assert myProject.equals(module.getProject()) : module + " is from another project";
66         profile = CompilerConfiguration.getInstance(myProject).getAnnotationProcessingConfiguration(module);
67         ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
68         for (String s : rootManager.orderEntries().compileOnly().recursively().exportedOnly().withoutSdk().getPathsList().getPathList()) {
69           classpath.add(new File(s));
70         }
71         for (String s : rootManager.orderEntries().compileOnly().sdkOnly().getPathsList().getPathList()) {
72           platformClasspath.add(new File(s));
73         }
74       }
75       JavaBuilder.addAnnotationProcessingOptions(options, profile);
76
77       Pair<Sdk, JavaSdkVersion> runtime = BuildManager.getJavacRuntimeSdk(myProject);
78       JavaSdkVersion buildRuntimeVersion = runtime.getSecond();
79       // if compiler or debuggee version or both are unknown, let source and target be the compiler's defaults
80       if (buildRuntimeVersion != null && debuggeeVersion != null) {
81         JavaSdkVersion minVersion = debuggeeVersion.compareTo(buildRuntimeVersion) < 0 ? debuggeeVersion : buildRuntimeVersion;
82         String sourceOption = JpsJavaSdkType.complianceOption(minVersion.getMaxLanguageLevel().toJavaVersion());
83         options.add("-source");
84         options.add(sourceOption);
85         options.add("-target");
86         options.add(sourceOption);
87       }
88
89       CompilerManager compilerManager = CompilerManager.getInstance(myProject);
90
91       File sourceFile = null;
92       try {
93         sourceFile = generateTempSourceFile(compilerManager.getJavacCompilerWorkingDir());
94         File srcDir = sourceFile.getParentFile();
95         List<File> sourcePath = Collections.emptyList();
96         Set<File> sources = Collections.singleton(sourceFile);
97
98         myCompiledClasses =
99           compilerManager.compileJavaCode(options, platformClasspath, classpath, Collections.emptyList(), sourcePath, sources, srcDir);
100       }
101       catch (CompilationException e) {
102         StringBuilder res = new StringBuilder("Compilation failed:\n");
103         for (CompilationException.Message m : e.getMessages()) {
104           if (m.getCategory() == CompilerMessageCategory.ERROR) {
105             res.append(m.getText()).append("\n");
106           }
107         }
108         throw new EvaluateException(res.toString());
109       }
110       catch (Exception e) {
111         throw new EvaluateException(e.getMessage());
112       }
113       finally {
114         if (sourceFile != null) {
115           FileUtil.delete(sourceFile);
116         }
117       }
118     }
119     return myCompiledClasses;
120   }
121
122   private File generateTempSourceFile(File workingDir) throws IOException {
123     Pair<String, String> fileData = ReadAction.compute(() -> {
124       PsiFile file = myData.getGeneratedInnerClass().getContainingFile();
125       return Pair.create(file.getName(), file.getText());
126     });
127     if (fileData.first == null) {
128       throw new IOException("Class file name not specified");
129     }
130     if (fileData.second == null) {
131       throw new IOException("Class source code not specified");
132     }
133     File file = new File(workingDir, "debugger/src/" + fileData.first);
134     FileUtil.writeToFile(file, fileData.second);
135     return file;
136   }
137
138   @Nullable
139   public static ExpressionEvaluator create(@NotNull Project project,
140                                            @Nullable PsiElement psiContext,
141                                            @NotNull Function<PsiElement, PsiCodeFragment> fragmentFactory)
142     throws EvaluateException {
143     if (Registry.is("debugger.compiling.evaluator") && psiContext != null) {
144       return ApplicationManager.getApplication().runReadAction((ThrowableComputable<ExpressionEvaluator, EvaluateException>)() -> {
145         try {
146           boolean useReflection = !Registry.is("debugger.compiling.evaluator.magic.accessor", true);
147           if (!useReflection) {
148             XSuspendContext suspendContext = XDebuggerManager.getInstance(project).getCurrentSession().getSuspendContext();
149             if (suspendContext instanceof SuspendContextImpl) {
150               JavaSdkVersion version =
151                 JavaSdkVersion.fromVersionString(((SuspendContextImpl)suspendContext).getDebugProcess().getVirtualMachineProxy().version());
152               useReflection = version != null && version.isAtLeast(JavaSdkVersion.JDK_1_9);
153             }
154           }
155
156           ExtractLightMethodObjectHandler.ExtractedData data = ExtractLightMethodObjectHandler.extractLightMethodObject(
157             project,
158             findPhysicalContext(psiContext),
159             fragmentFactory.apply(psiContext),
160             getGeneratedClassName(),
161             useReflection);
162           if (data != null) {
163             return new CompilingEvaluatorImpl(project, psiContext, data);
164           }
165         }
166         catch (PrepareFailedException e) {
167           NodeDescriptorImpl.LOG.info(e);
168         }
169         return null;
170       });
171     }
172     return null;
173   }
174
175   @NotNull
176   private static PsiElement findPhysicalContext(@NotNull PsiElement element) {
177     while (!element.isPhysical()) {
178       PsiElement context = element.getContext();
179       if (context == null) {
180         break;
181       }
182       element = context;
183     }
184     return element;
185   }
186 }