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;
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.internal.statistic.UsageTrigger;
19 import com.intellij.lang.java.JavaLanguage;
20 import com.intellij.openapi.actionSystem.AnAction;
21 import com.intellij.openapi.actionSystem.AnActionEvent;
22 import com.intellij.openapi.actionSystem.Presentation;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.extensions.Extensions;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.projectRoots.JavaSdk;
29 import com.intellij.openapi.projectRoots.JavaSdkVersion;
30 import com.intellij.openapi.projectRoots.Sdk;
31 import com.intellij.openapi.roots.ProjectRootManager;
32 import com.intellij.openapi.ui.MessageType;
33 import com.intellij.openapi.util.TextRange;
34 import com.intellij.psi.PsiElement;
35 import com.intellij.psi.util.PsiEditorUtil;
36 import com.intellij.xdebugger.XDebugSession;
37 import com.intellij.xdebugger.XDebuggerManager;
38 import com.intellij.xdebugger.impl.XDebuggerManagerImpl;
39 import one.util.streamex.StreamEx;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
43 import java.util.List;
44 import java.util.Locale;
46 import java.util.stream.Collectors;
47 import java.util.stream.Stream;
50 * @author Vitaliy.Bibaev
52 public class TraceStreamAction extends AnAction {
53 private static final Logger LOG = Logger.getInstance(TraceStreamAction.class);
55 private final DebuggerPositionResolver myPositionResolver = new DebuggerPositionResolverImpl();
56 private final List<SupportedLibrary> mySupportedLibraries =
57 LibrarySupportProvider.getList().stream().map(SupportedLibrary::new).collect(Collectors.toList());
58 private final Set<String> mySupportedLanguages = StreamEx.of(mySupportedLibraries).map(x -> x.languageId).toSet();
59 private int myLastVisitedPsiElementHash;
62 public void update(@NotNull AnActionEvent e) {
63 final XDebugSession session = getCurrentSession(e);
64 final PsiElement element = session == null ? null : myPositionResolver.getNearestElementToBreakpoint(session);
65 final Presentation presentation = e.getPresentation();
66 if (element == null) {
67 presentation.setVisible(true);
68 presentation.setEnabled(false);
71 final String languageId = element.getLanguage().getID();
72 if (mySupportedLanguages.contains(languageId)) {
73 presentation.setVisible(true);
74 final boolean chainExists = isChainExists(element);
75 presentation.setEnabled(chainExists);
76 final int elementHash = System.identityHashCode(element);
77 if (chainExists && myLastVisitedPsiElementHash != elementHash) {
78 UsageTrigger.trigger("debugger.streams." + languageId.toLowerCase(Locale.US) + ".activated");
79 myLastVisitedPsiElementHash = elementHash;
83 presentation.setEnabledAndVisible(false);
89 public void actionPerformed(@NotNull AnActionEvent e) {
90 final XDebugSession session = getCurrentSession(e);
91 Extensions.getExtensions(LibrarySupportProvider.EP_NAME);
92 final PsiElement element = session == null ? null : myPositionResolver.getNearestElementToBreakpoint(session);
94 if (element != null && isJdkAtLeast9(session.getProject(), element)) {
95 XDebuggerManagerImpl.NOTIFICATION_GROUP
96 .createNotification("This action does not work with JDK 9 yet", MessageType.WARNING)
97 .notify(session.getProject());
101 if (element != null) {
102 final List<StreamChainWithLibrary> chains = mySupportedLibraries.stream()
103 .filter(library -> library.languageId.equals(element.getLanguage().getID()))
104 .filter(library -> library.builder.isChainExists(element))
105 .flatMap(library -> library.builder.build(element).stream().map(x -> new StreamChainWithLibrary(x, library)))
106 .collect(Collectors.toList());
107 if (chains.isEmpty()) {
108 LOG.warn("stream chain is not built");
112 if (chains.size() == 1) {
113 runTrace(chains.get(0).chain, chains.get(0).library, session);
116 final Editor editor = PsiEditorUtil.Service.getInstance().findEditorByPsiElement(element);
117 if (editor == null) {
118 throw new RuntimeException("editor not found");
121 new MyStreamChainChooser(editor).show(chains.stream().map(StreamChainOption::new).collect(Collectors.toList()),
122 provider -> runTrace(provider.chain, provider.library, session));
126 LOG.info("element at cursor not found");
130 private boolean isChainExists(@NotNull PsiElement element) {
131 for (final SupportedLibrary library : mySupportedLibraries) {
132 if (element.getLanguage().getID().equals(library.languageId) && library.builder.isChainExists(element)) {
140 private static void runTrace(@NotNull StreamChain chain, @NotNull SupportedLibrary library, @NotNull XDebugSession session) {
141 final EvaluationAwareTraceWindow window = new EvaluationAwareTraceWindow(session, chain);
142 UsageTrigger.trigger("debugger.streams." + library.languageId.toLowerCase(Locale.US) + ".used");
143 ApplicationManager.getApplication().invokeLater(window::show);
144 final Project project = session.getProject();
145 final TraceExpressionBuilder expressionBuilder = library.createExpressionBuilder(project);
146 final TraceResultInterpreterImpl resultInterpreter = new TraceResultInterpreterImpl(library.librarySupport.getInterpreterFactory());
147 final StreamTracer tracer = new EvaluateExpressionTracer(session, expressionBuilder, resultInterpreter);
148 tracer.trace(chain, new TracingCallback() {
150 public void evaluated(@NotNull TracingResult result, @NotNull EvaluationContextImpl context) {
151 final ResolvedTracingResult resolvedTrace = result.resolve(library.librarySupport.getResolverFactory());
152 ApplicationManager.getApplication()
153 .invokeLater(() -> window.setTrace(resolvedTrace, context));
157 public void evaluationFailed(@NotNull String traceExpression, @NotNull String message) {
159 throw new TraceEvaluationException(message, traceExpression);
163 public void compilationFailed(@NotNull String traceExpression, @NotNull String message) {
165 throw new TraceCompilationException(message, traceExpression);
168 private void notifyUI(@NotNull String message) {
169 ApplicationManager.getApplication().invokeLater(() -> window.setFailMessage(message));
175 private static XDebugSession getCurrentSession(@NotNull AnActionEvent e) {
176 final Project project = e.getProject();
177 return project == null ? null : XDebuggerManager.getInstance(project).getCurrentSession();
180 private static boolean isJdkAtLeast9(@NotNull Project project, @NotNull PsiElement element) {
181 if (element.getLanguage().is(JavaLanguage.INSTANCE)) {
182 final Sdk sdk = ProjectRootManager.getInstance(project).getProjectSdk();
184 final JavaSdkVersion javaVersion = JavaSdk.getInstance().getVersion(sdk);
185 if (javaVersion != null) return javaVersion.isAtLeast(JavaSdkVersion.JDK_1_9);
192 private static class MyStreamChainChooser extends ElementChooserImpl<StreamChainOption> {
193 MyStreamChainChooser(@NotNull Editor editor) {
198 private static class SupportedLibrary {
199 final String languageId;
200 final StreamChainBuilder builder;
201 final LibrarySupport librarySupport;
202 private final LibrarySupportProvider mySupportProvider;
204 SupportedLibrary(@NotNull LibrarySupportProvider provider) {
205 languageId = provider.getLanguageId();
206 builder = provider.getChainBuilder();
207 librarySupport = provider.getLibrarySupport();
208 mySupportProvider = provider;
211 TraceExpressionBuilder createExpressionBuilder(@NotNull Project project) {
212 return mySupportProvider.getExpressionBuilder(project);
216 private static class StreamChainWithLibrary {
217 final StreamChain chain;
218 final SupportedLibrary library;
220 StreamChainWithLibrary(@NotNull StreamChain chain, @NotNull SupportedLibrary library) {
222 this.library = library;
226 private static class StreamChainOption implements ChooserOption {
227 final StreamChain chain;
228 final SupportedLibrary library;
230 StreamChainOption(@NotNull StreamChainWithLibrary chain) {
231 this.chain = chain.chain;
232 library = chain.library;
237 public Stream<TextRange> rangeStream() {
239 new TextRange(chain.getQualifierExpression().getTextRange().getStartOffset(),
240 chain.getTerminationCall().getTextRange().getEndOffset()));
245 public String getText() {
246 return chain.getCompactText();