junit 5 tags support (IDEA-163481)
[idea/community.git] / plugins / stream-debugger / src / com / intellij / debugger / streams / action / TraceStreamAction.java
1 // Copyright 2000-2017 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.streams.action;
3
4 import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
5 import com.intellij.debugger.streams.diagnostic.ex.TraceCompilationException;
6 import com.intellij.debugger.streams.diagnostic.ex.TraceEvaluationException;
7 import com.intellij.debugger.streams.lib.LibrarySupport;
8 import com.intellij.debugger.streams.lib.LibrarySupportProvider;
9 import com.intellij.debugger.streams.psi.DebuggerPositionResolver;
10 import com.intellij.debugger.streams.psi.impl.DebuggerPositionResolverImpl;
11 import com.intellij.debugger.streams.trace.*;
12 import com.intellij.debugger.streams.trace.impl.TraceResultInterpreterImpl;
13 import com.intellij.debugger.streams.ui.ChooserOption;
14 import com.intellij.debugger.streams.ui.impl.ElementChooserImpl;
15 import com.intellij.debugger.streams.ui.impl.EvaluationAwareTraceWindow;
16 import com.intellij.debugger.streams.wrapper.StreamChain;
17 import com.intellij.debugger.streams.wrapper.StreamChainBuilder;
18 import com.intellij.lang.java.JavaLanguage;
19 import com.intellij.openapi.actionSystem.AnAction;
20 import com.intellij.openapi.actionSystem.AnActionEvent;
21 import com.intellij.openapi.actionSystem.Presentation;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.editor.Editor;
25 import com.intellij.openapi.extensions.Extensions;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.projectRoots.JavaSdk;
28 import com.intellij.openapi.projectRoots.JavaSdkVersion;
29 import com.intellij.openapi.projectRoots.Sdk;
30 import com.intellij.openapi.roots.ProjectRootManager;
31 import com.intellij.openapi.ui.MessageType;
32 import com.intellij.openapi.util.TextRange;
33 import com.intellij.psi.PsiElement;
34 import com.intellij.psi.util.PsiEditorUtil;
35 import com.intellij.xdebugger.XDebugSession;
36 import com.intellij.xdebugger.XDebuggerManager;
37 import com.intellij.xdebugger.impl.XDebuggerManagerImpl;
38 import one.util.streamex.StreamEx;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41
42 import java.util.List;
43 import java.util.Set;
44 import java.util.stream.Collectors;
45 import java.util.stream.Stream;
46
47 /**
48  * @author Vitaliy.Bibaev
49  */
50 public class TraceStreamAction extends AnAction {
51   private static final Logger LOG = Logger.getInstance(TraceStreamAction.class);
52
53   private final DebuggerPositionResolver myPositionResolver = new DebuggerPositionResolverImpl();
54   private final List<SupportedLibrary> mySupportedLibraries =
55     LibrarySupportProvider.getList().stream().map(SupportedLibrary::new).collect(Collectors.toList());
56   private final Set<String> mySupportedLanguages = StreamEx.of(mySupportedLibraries).map(x -> x.languageId).toSet();
57
58   @Override
59   public void update(@NotNull AnActionEvent e) {
60     final XDebugSession session = getCurrentSession(e);
61     final PsiElement element = session == null ? null : myPositionResolver.getNearestElementToBreakpoint(session);
62     final Presentation presentation = e.getPresentation();
63     if (element == null) {
64       presentation.setVisible(true);
65       presentation.setEnabled(false);
66     }
67     else {
68       if (mySupportedLanguages.contains(element.getLanguage().getID())) {
69         presentation.setVisible(true);
70         presentation.setEnabled(isChainExists(element));
71       }
72       else {
73         presentation.setEnabledAndVisible(false);
74       }
75     }
76   }
77
78   @Override
79   public void actionPerformed(@NotNull AnActionEvent e) {
80     final XDebugSession session = getCurrentSession(e);
81     Extensions.getExtensions(LibrarySupportProvider.EP_NAME);
82     final PsiElement element = session == null ? null : myPositionResolver.getNearestElementToBreakpoint(session);
83
84     if (element != null && isJdkAtLeast9(session.getProject(), element)) {
85       XDebuggerManagerImpl.NOTIFICATION_GROUP
86         .createNotification("This action does not work with JDK 9 yet", MessageType.WARNING)
87         .notify(session.getProject());
88       return;
89     }
90
91     if (element != null) {
92       final List<StreamChainWithLibrary> chains = mySupportedLibraries.stream()
93         .filter(library -> library.languageId.equals(element.getLanguage().getID()))
94         .filter(library -> library.builder.isChainExists(element))
95         .flatMap(library -> library.builder.build(element).stream().map(x -> new StreamChainWithLibrary(x, library)))
96         .collect(Collectors.toList());
97       if (chains.isEmpty()) {
98         LOG.warn("stream chain is not built");
99         return;
100       }
101
102       if (chains.size() == 1) {
103         runTrace(chains.get(0).chain, chains.get(0).library, session);
104       }
105       else {
106         final Editor editor = PsiEditorUtil.Service.getInstance().findEditorByPsiElement(element);
107         if (editor == null) {
108           throw new RuntimeException("editor not found");
109         }
110
111         new MyStreamChainChooser(editor).show(chains.stream().map(StreamChainOption::new).collect(Collectors.toList()),
112                                               provider -> runTrace(provider.chain, provider.library, session));
113       }
114     }
115     else {
116       LOG.info("element at cursor not found");
117     }
118   }
119
120   private boolean isChainExists(@NotNull PsiElement element) {
121     for (final SupportedLibrary library : mySupportedLibraries) {
122       if (element.getLanguage().getID().equals(library.languageId) && library.builder.isChainExists(element)) {
123         return true;
124       }
125     }
126
127     return false;
128   }
129
130   private static void runTrace(@NotNull StreamChain chain, @NotNull SupportedLibrary library, @NotNull XDebugSession session) {
131     final EvaluationAwareTraceWindow window = new EvaluationAwareTraceWindow(session, chain);
132     ApplicationManager.getApplication().invokeLater(window::show);
133     final Project project = session.getProject();
134     final TraceExpressionBuilder expressionBuilder = library.createExpressionBuilder(project);
135     final TraceResultInterpreterImpl resultInterpreter = new TraceResultInterpreterImpl(library.librarySupport.getInterpreterFactory());
136     final StreamTracer tracer = new EvaluateExpressionTracer(session, expressionBuilder, resultInterpreter);
137     tracer.trace(chain, new TracingCallback() {
138       @Override
139       public void evaluated(@NotNull TracingResult result, @NotNull EvaluationContextImpl context) {
140         final ResolvedTracingResult resolvedTrace = result.resolve(library.librarySupport.getResolverFactory());
141         ApplicationManager.getApplication()
142           .invokeLater(() -> window.setTrace(resolvedTrace, context));
143       }
144
145       @Override
146       public void evaluationFailed(@NotNull String traceExpression, @NotNull String message) {
147         notifyUI(message);
148         throw new TraceEvaluationException(message, traceExpression);
149       }
150
151       @Override
152       public void compilationFailed(@NotNull String traceExpression, @NotNull String message) {
153         notifyUI(message);
154         throw new TraceCompilationException(message, traceExpression);
155       }
156
157       private void notifyUI(@NotNull String message) {
158         ApplicationManager.getApplication().invokeLater(() -> window.setFailMessage(message));
159       }
160     });
161   }
162
163   @Nullable
164   private static XDebugSession getCurrentSession(@NotNull AnActionEvent e) {
165     final Project project = e.getProject();
166     return project == null ? null : XDebuggerManager.getInstance(project).getCurrentSession();
167   }
168
169   private static boolean isJdkAtLeast9(@NotNull Project project, @NotNull PsiElement element) {
170     if (element.getLanguage().is(JavaLanguage.INSTANCE)) {
171       final Sdk sdk = ProjectRootManager.getInstance(project).getProjectSdk();
172       if (sdk != null) {
173         final JavaSdkVersion javaVersion = JavaSdk.getInstance().getVersion(sdk);
174         if (javaVersion != null) return javaVersion.isAtLeast(JavaSdkVersion.JDK_1_9);
175       }
176     }
177
178     return false;
179   }
180
181   private static class MyStreamChainChooser extends ElementChooserImpl<StreamChainOption> {
182     MyStreamChainChooser(@NotNull Editor editor) {
183       super(editor);
184     }
185   }
186
187   private static class SupportedLibrary {
188     final String languageId;
189     final StreamChainBuilder builder;
190     final LibrarySupport librarySupport;
191     private final LibrarySupportProvider mySupportProvider;
192
193     SupportedLibrary(@NotNull LibrarySupportProvider provider) {
194       languageId = provider.getLanguageId();
195       builder = provider.getChainBuilder();
196       librarySupport = provider.getLibrarySupport();
197       mySupportProvider = provider;
198     }
199
200     TraceExpressionBuilder createExpressionBuilder(@NotNull Project project) {
201       return mySupportProvider.getExpressionBuilder(project);
202     }
203   }
204
205   private static class StreamChainWithLibrary {
206     final StreamChain chain;
207     final SupportedLibrary library;
208
209     StreamChainWithLibrary(@NotNull StreamChain chain, @NotNull SupportedLibrary library) {
210       this.chain = chain;
211       this.library = library;
212     }
213   }
214
215   private static class StreamChainOption implements ChooserOption {
216     final StreamChain chain;
217     final SupportedLibrary library;
218
219     StreamChainOption(@NotNull StreamChainWithLibrary chain) {
220       this.chain = chain.chain;
221       library = chain.library;
222     }
223
224     @NotNull
225     @Override
226     public Stream<TextRange> rangeStream() {
227       return Stream.of(
228         new TextRange(chain.getQualifierExpression().getTextRange().getStartOffset(),
229                       chain.getTerminationCall().getTextRange().getEndOffset()));
230     }
231
232     @NotNull
233     @Override
234     public String getText() {
235       return chain.getCompactText();
236     }
237   }
238 }