Report statistics of stream debugger usage
[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.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;
42
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.Set;
46 import java.util.stream.Collectors;
47 import java.util.stream.Stream;
48
49 /**
50  * @author Vitaliy.Bibaev
51  */
52 public class TraceStreamAction extends AnAction {
53   private static final Logger LOG = Logger.getInstance(TraceStreamAction.class);
54
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;
60
61   @Override
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);
69     }
70     else {
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;
80         }
81       }
82       else {
83         presentation.setEnabledAndVisible(false);
84       }
85     }
86   }
87
88   @Override
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);
93
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());
98       return;
99     }
100
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");
109         return;
110       }
111
112       if (chains.size() == 1) {
113         runTrace(chains.get(0).chain, chains.get(0).library, session);
114       }
115       else {
116         final Editor editor = PsiEditorUtil.Service.getInstance().findEditorByPsiElement(element);
117         if (editor == null) {
118           throw new RuntimeException("editor not found");
119         }
120
121         new MyStreamChainChooser(editor).show(chains.stream().map(StreamChainOption::new).collect(Collectors.toList()),
122                                               provider -> runTrace(provider.chain, provider.library, session));
123       }
124     }
125     else {
126       LOG.info("element at cursor not found");
127     }
128   }
129
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)) {
133         return true;
134       }
135     }
136
137     return false;
138   }
139
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() {
149       @Override
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));
154       }
155
156       @Override
157       public void evaluationFailed(@NotNull String traceExpression, @NotNull String message) {
158         notifyUI(message);
159         throw new TraceEvaluationException(message, traceExpression);
160       }
161
162       @Override
163       public void compilationFailed(@NotNull String traceExpression, @NotNull String message) {
164         notifyUI(message);
165         throw new TraceCompilationException(message, traceExpression);
166       }
167
168       private void notifyUI(@NotNull String message) {
169         ApplicationManager.getApplication().invokeLater(() -> window.setFailMessage(message));
170       }
171     });
172   }
173
174   @Nullable
175   private static XDebugSession getCurrentSession(@NotNull AnActionEvent e) {
176     final Project project = e.getProject();
177     return project == null ? null : XDebuggerManager.getInstance(project).getCurrentSession();
178   }
179
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();
183       if (sdk != null) {
184         final JavaSdkVersion javaVersion = JavaSdk.getInstance().getVersion(sdk);
185         if (javaVersion != null) return javaVersion.isAtLeast(JavaSdkVersion.JDK_1_9);
186       }
187     }
188
189     return false;
190   }
191
192   private static class MyStreamChainChooser extends ElementChooserImpl<StreamChainOption> {
193     MyStreamChainChooser(@NotNull Editor editor) {
194       super(editor);
195     }
196   }
197
198   private static class SupportedLibrary {
199     final String languageId;
200     final StreamChainBuilder builder;
201     final LibrarySupport librarySupport;
202     private final LibrarySupportProvider mySupportProvider;
203
204     SupportedLibrary(@NotNull LibrarySupportProvider provider) {
205       languageId = provider.getLanguageId();
206       builder = provider.getChainBuilder();
207       librarySupport = provider.getLibrarySupport();
208       mySupportProvider = provider;
209     }
210
211     TraceExpressionBuilder createExpressionBuilder(@NotNull Project project) {
212       return mySupportProvider.getExpressionBuilder(project);
213     }
214   }
215
216   private static class StreamChainWithLibrary {
217     final StreamChain chain;
218     final SupportedLibrary library;
219
220     StreamChainWithLibrary(@NotNull StreamChain chain, @NotNull SupportedLibrary library) {
221       this.chain = chain;
222       this.library = library;
223     }
224   }
225
226   private static class StreamChainOption implements ChooserOption {
227     final StreamChain chain;
228     final SupportedLibrary library;
229
230     StreamChainOption(@NotNull StreamChainWithLibrary chain) {
231       this.chain = chain.chain;
232       library = chain.library;
233     }
234
235     @NotNull
236     @Override
237     public Stream<TextRange> rangeStream() {
238       return Stream.of(
239         new TextRange(chain.getQualifierExpression().getTextRange().getStartOffset(),
240                       chain.getTerminationCall().getTextRange().getEndOffset()));
241     }
242
243     @NotNull
244     @Override
245     public String getText() {
246       return chain.getCompactText();
247     }
248   }
249 }