do not propose lambdas if body is not on the line
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / impl / DebuggerUtilsEx.java
1 /*
2  * Copyright 2000-2015 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
17 /*
18  * Class DebuggerUtilsEx
19  * @author Jeka
20  */
21 package com.intellij.debugger.impl;
22
23 import com.intellij.debugger.DebuggerBundle;
24 import com.intellij.debugger.SourcePosition;
25 import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
26 import com.intellij.debugger.engine.DebuggerUtils;
27 import com.intellij.debugger.engine.SuspendContextImpl;
28 import com.intellij.debugger.engine.evaluation.*;
29 import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilder;
30 import com.intellij.debugger.engine.requests.RequestManagerImpl;
31 import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
32 import com.intellij.debugger.requests.Requestor;
33 import com.intellij.debugger.ui.CompletionEditor;
34 import com.intellij.debugger.ui.breakpoints.Breakpoint;
35 import com.intellij.debugger.ui.tree.DebuggerTreeNode;
36 import com.intellij.execution.filters.ExceptionFilters;
37 import com.intellij.execution.filters.TextConsoleBuilder;
38 import com.intellij.execution.filters.TextConsoleBuilderFactory;
39 import com.intellij.execution.ui.ConsoleView;
40 import com.intellij.execution.ui.RunnerLayoutUi;
41 import com.intellij.execution.ui.layout.impl.RunnerContentUi;
42 import com.intellij.openapi.Disposable;
43 import com.intellij.openapi.actionSystem.DataContext;
44 import com.intellij.openapi.actionSystem.DefaultActionGroup;
45 import com.intellij.openapi.application.ApplicationManager;
46 import com.intellij.openapi.diagnostic.Logger;
47 import com.intellij.openapi.editor.Document;
48 import com.intellij.openapi.editor.Editor;
49 import com.intellij.openapi.editor.markup.RangeHighlighter;
50 import com.intellij.openapi.editor.markup.TextAttributes;
51 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
52 import com.intellij.openapi.fileTypes.FileType;
53 import com.intellij.openapi.fileTypes.StdFileTypes;
54 import com.intellij.openapi.project.Project;
55 import com.intellij.openapi.util.*;
56 import com.intellij.openapi.util.text.StringUtil;
57 import com.intellij.openapi.vfs.VirtualFile;
58 import com.intellij.pom.Navigatable;
59 import com.intellij.psi.*;
60 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
61 import com.intellij.psi.util.PsiTreeUtil;
62 import com.intellij.ui.classFilter.ClassFilter;
63 import com.intellij.ui.content.Content;
64 import com.intellij.unscramble.ThreadDumpPanel;
65 import com.intellij.unscramble.ThreadState;
66 import com.intellij.util.DocumentUtil;
67 import com.intellij.util.SmartList;
68 import com.intellij.util.containers.ContainerUtil;
69 import com.intellij.xdebugger.XSourcePosition;
70 import com.intellij.xdebugger.frame.XValueNode;
71 import com.intellij.xdebugger.impl.XSourcePositionImpl;
72 import com.intellij.xdebugger.impl.ui.ExecutionPointHighlighter;
73 import com.sun.jdi.*;
74 import com.sun.jdi.event.Event;
75 import com.sun.jdi.event.EventSet;
76 import org.jdom.Attribute;
77 import org.jdom.Element;
78 import org.jetbrains.annotations.NonNls;
79 import org.jetbrains.annotations.NotNull;
80 import org.jetbrains.annotations.Nullable;
81
82 import java.util.*;
83 import java.util.regex.PatternSyntaxException;
84
85 public abstract class DebuggerUtilsEx extends DebuggerUtils {
86   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.impl.DebuggerUtilsEx");
87
88   /**
89    * @param context
90    * @return all CodeFragmentFactoryProviders that provide code fragment factories suitable in the context given
91    */
92   public static List<CodeFragmentFactory> getCodeFragmentFactories(@Nullable PsiElement context) {
93     final DefaultCodeFragmentFactory defaultFactory = DefaultCodeFragmentFactory.getInstance();
94     final CodeFragmentFactory[] providers = ApplicationManager.getApplication().getExtensions(CodeFragmentFactory.EXTENSION_POINT_NAME);
95     final List<CodeFragmentFactory> suitableFactories = new ArrayList<CodeFragmentFactory>(providers.length);
96     if (providers.length > 0) {
97       for (CodeFragmentFactory factory : providers) {
98         if (factory != defaultFactory && factory.isContextAccepted(context)) {
99           suitableFactories.add(factory);
100         }
101       }
102     }
103     suitableFactories.add(defaultFactory); // let default factory be the last one
104     return suitableFactories;
105   }
106
107
108   public static PsiMethod findPsiMethod(PsiFile file, int offset) {
109     PsiElement element = null;
110
111     while(offset >= 0) {
112       element = file.findElementAt(offset);
113       if(element != null) {
114         break;
115       }
116       offset --;
117     }
118
119     for (; element != null; element = element.getParent()) {
120       if (element instanceof PsiClass || element instanceof PsiLambdaExpression) {
121         return null;
122       }
123       if (element instanceof PsiMethod) {
124         return (PsiMethod)element;
125       }
126     }
127     return null;
128   }
129
130
131   public static boolean isAssignableFrom(final String baseQualifiedName, ReferenceType checkedType) {
132     if (CommonClassNames.JAVA_LANG_OBJECT.equals(baseQualifiedName)) {
133       return true;
134     }
135     return getSuperClass(baseQualifiedName, checkedType) != null;
136   }
137
138   public static ReferenceType getSuperClass(final String baseQualifiedName, ReferenceType checkedType) {
139     if (baseQualifiedName.equals(checkedType.name())) {
140       return checkedType;
141     }
142
143     if (checkedType instanceof ClassType) {
144       ClassType classType = (ClassType)checkedType;
145       ClassType superClassType = classType.superclass();
146       if (superClassType != null) {
147         ReferenceType superClass = getSuperClass(baseQualifiedName, superClassType);
148         if (superClass != null) {
149           return superClass;
150         }
151       }
152       List<InterfaceType> interfaces = classType.allInterfaces();
153       for (InterfaceType iface : interfaces) {
154         ReferenceType superClass = getSuperClass(baseQualifiedName, iface);
155         if (superClass != null) {
156           return superClass;
157         }
158       }
159     }
160
161     if (checkedType instanceof InterfaceType) {
162       List<InterfaceType> list = ((InterfaceType)checkedType).superinterfaces();
163       for (InterfaceType superInterface : list) {
164         ReferenceType superClass = getSuperClass(baseQualifiedName, superInterface);
165         if (superClass != null) {
166           return superClass;
167         }
168       }
169     }
170     return null;
171   }
172
173   public static boolean valuesEqual(Value val1, Value val2) {
174     if (val1 == null) {
175       return val2 == null;
176     }
177     if (val2 == null) {
178       return false;
179     }
180     if (val1 instanceof StringReference && val2 instanceof StringReference) {
181       return ((StringReference)val1).value().equals(((StringReference)val2).value());
182     }
183     return val1.equals(val2);
184   }
185
186   public static String getValueOrErrorAsString(final EvaluationContext evaluationContext, Value value) {
187     try {
188       return getValueAsString(evaluationContext, value);
189     }
190     catch (EvaluateException e) {
191       return e.getMessage();
192     }
193   }
194
195   public static boolean isCharOrInteger(Value value) {
196     return value instanceof CharValue || isInteger(value);
197   }
198
199   private static Set<String> myCharOrIntegers;
200
201   @SuppressWarnings({"HardCodedStringLiteral"})
202   public static boolean isCharOrIntegerArray(Value value) {
203     if (value == null) return false;
204     if (myCharOrIntegers == null) {
205       myCharOrIntegers = new HashSet<String>();
206       myCharOrIntegers.add("C");
207       myCharOrIntegers.add("B");
208       myCharOrIntegers.add("S");
209       myCharOrIntegers.add("I");
210       myCharOrIntegers.add("J");
211     }
212
213     String signature = value.type().signature();
214     int i;
215     for (i = 0; signature.charAt(i) == '['; i++) ;
216     if (i == 0) return false;
217     signature = signature.substring(i, signature.length());
218     return myCharOrIntegers.contains(signature);
219   }
220
221   public static ClassFilter create(Element element) throws InvalidDataException {
222     ClassFilter filter = new ClassFilter();
223     DefaultJDOMExternalizer.readExternal(filter, element);
224     return filter;
225   }
226
227   private static boolean isFiltered(ClassFilter classFilter, String qName) {
228     if (!classFilter.isEnabled()) {
229       return false;
230     }
231     try {
232       if (classFilter.matches(qName)) {
233         return true;
234       }
235     }
236     catch (PatternSyntaxException e) {
237       LOG.debug(e);
238     }
239     return false;
240   }
241
242   public static boolean isFiltered(String qName, ClassFilter[] classFilters) {
243     return isFiltered(qName, Arrays.asList(classFilters));
244   }
245   
246   public static boolean isFiltered(String qName, List<ClassFilter> classFilters) {
247     if(qName.indexOf('[') != -1) {
248       return false; //is array
249     }
250
251     for (ClassFilter filter : classFilters) {
252       if (isFiltered(filter, qName)) {
253         return true;
254       }
255     }
256     return false;
257   }
258   
259   public static int getEnabledNumber(ClassFilter[] classFilters) {
260     int res = 0;
261     for (ClassFilter filter : classFilters) {
262       if (filter.isEnabled()) {
263         res++;
264       }
265     }
266     return res;
267   }
268
269   public static ClassFilter[] readFilters(List<Element> children) throws InvalidDataException {
270     if (ContainerUtil.isEmpty(children)) {
271       return ClassFilter.EMPTY_ARRAY;
272     }
273
274     ClassFilter[] filters = new ClassFilter[children.size()];
275     for (int i = 0, size = children.size(); i < size; i++) {
276       filters[i] = create(children.get(i));
277     }
278     return filters;
279   }
280
281   public static void writeFilters(Element parentNode, @NonNls String tagName, ClassFilter[] filters) throws WriteExternalException {
282     for (ClassFilter filter : filters) {
283       Element element = new Element(tagName);
284       parentNode.addContent(element);
285       DefaultJDOMExternalizer.writeExternal(filter, element);
286     }
287   }
288
289   public static boolean filterEquals(ClassFilter[] filters1, ClassFilter[] filters2) {
290     if (filters1.length != filters2.length) {
291       return false;
292     }
293     final Set<ClassFilter> f1 = new HashSet<ClassFilter>(Math.max((int) (filters1.length/.75f) + 1, 16));
294     final Set<ClassFilter> f2 = new HashSet<ClassFilter>(Math.max((int) (filters2.length/.75f) + 1, 16));
295     Collections.addAll(f1, filters1);
296     Collections.addAll(f2, filters2);
297     return f2.equals(f1);
298   }
299
300   private static boolean elementListsEqual(List<Element> l1, List<Element> l2) {
301     if(l1 == null) return l2 == null;
302     if(l2 == null) return false;
303
304     if(l1.size() != l2.size()) return false;
305
306     Iterator<Element> i1 = l1.iterator();
307
308     for (Element aL2 : l2) {
309       Element elem1 = i1.next();
310
311       if (!elementsEqual(elem1, aL2)) return false;
312     }
313     return true;
314   }
315
316   private static boolean attributeListsEqual(List<Attribute> l1, List<Attribute> l2) {
317     if(l1 == null) return l2 == null;
318     if(l2 == null) return false;
319
320     if(l1.size() != l2.size()) return false;
321
322     Iterator<Attribute> i1 = l1.iterator();
323
324     for (Attribute aL2 : l2) {
325       Attribute attr1 = i1.next();
326
327       if (!Comparing.equal(attr1.getName(), aL2.getName()) || !Comparing.equal(attr1.getValue(), aL2.getValue())) {
328         return false;
329       }
330     }
331     return true;
332   }
333
334   public static boolean elementsEqual(Element e1, Element e2) {
335     if(e1 == null) {
336       return e2 == null;
337     }
338     if (!Comparing.equal(e1.getName(), e2.getName())) {
339       return false;
340     }
341     if (!elementListsEqual  (e1.getChildren(), e2.getChildren())) {
342       return false;
343     }
344     if (!attributeListsEqual(e1.getAttributes(), e2.getAttributes())) {
345       return false;
346     }
347     return true;
348   }
349
350   @SuppressWarnings({"HardCodedStringLiteral"})
351   public static boolean externalizableEqual(JDOMExternalizable  e1, JDOMExternalizable e2) {
352     Element root1 = new Element("root");
353     Element root2 = new Element("root");
354     try {
355       e1.writeExternal(root1);
356     }
357     catch (WriteExternalException e) {
358       LOG.debug(e);
359     }
360     try {
361       e2.writeExternal(root2);
362     }
363     catch (WriteExternalException e) {
364       LOG.debug(e);
365     }
366
367     return elementsEqual(root1, root2);
368   }
369
370   @NotNull
371   public static List<Pair<Breakpoint, Event>> getEventDescriptors(SuspendContextImpl suspendContext) {
372     DebuggerManagerThreadImpl.assertIsManagerThread();
373     if(suspendContext == null) {
374       return Collections.emptyList();
375     }
376     final EventSet events = suspendContext.getEventSet();
377     if(events == null) {
378       return Collections.emptyList();
379     }
380     final List<Pair<Breakpoint, Event>> eventDescriptors = new SmartList<Pair<Breakpoint, Event>>();
381
382     final RequestManagerImpl requestManager = suspendContext.getDebugProcess().getRequestsManager();
383     for (final Event event : events) {
384       final Requestor requestor = requestManager.findRequestor(event.request());
385       if (requestor instanceof Breakpoint) {
386         eventDescriptors.add(Pair.create((Breakpoint)requestor, event));
387       }
388     }
389     return eventDescriptors;
390   }
391
392   public static TextWithImports getEditorText(final Editor editor) {
393     if (editor == null) {
394       return null;
395     }
396     final Project project = editor.getProject();
397     if (project == null) return null;
398
399     String defaultExpression = editor.getSelectionModel().getSelectedText();
400     if (defaultExpression == null) {
401       int offset = editor.getCaretModel().getOffset();
402       PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
403       if (psiFile != null) {
404         PsiElement elementAtCursor = psiFile.findElementAt(offset);
405         if (elementAtCursor != null) {
406           final EditorTextProvider textProvider = EditorTextProvider.EP.forLanguage(elementAtCursor.getLanguage());
407           if (textProvider != null) {
408             final TextWithImports editorText = textProvider.getEditorText(elementAtCursor);
409             if (editorText != null) return editorText;
410           }
411         }
412       }
413     }
414     else {
415       return new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, defaultExpression);
416     }
417     return null;
418   }
419
420   private static int myThreadDumpsCount = 0;
421   private static int myCurrentThreadDumpId = 1;
422
423   private static final String THREAD_DUMP_CONTENT_PREFIX = "Dump";
424
425   public static void addThreadDump(Project project, List<ThreadState> threads, final RunnerLayoutUi ui, DebuggerSession session) {
426     final TextConsoleBuilder consoleBuilder = TextConsoleBuilderFactory.getInstance().createBuilder(project);
427     consoleBuilder.filters(ExceptionFilters.getFilters(session.getSearchScope()));
428     final ConsoleView consoleView = consoleBuilder.getConsole();
429     final DefaultActionGroup toolbarActions = new DefaultActionGroup();
430     consoleView.allowHeavyFilters();
431     final ThreadDumpPanel panel = new ThreadDumpPanel(project, consoleView, toolbarActions, threads);
432
433     final String id = THREAD_DUMP_CONTENT_PREFIX + " #" + myCurrentThreadDumpId;
434     final Content content = ui.createContent(id, panel, id, null, null);
435     content.putUserData(RunnerContentUi.LIGHTWEIGHT_CONTENT_MARKER, Boolean.TRUE);
436     content.setCloseable(true);
437     content.setDescription("Thread Dump");
438     ui.addContent(content);
439     ui.selectAndFocus(content, true, true);
440     myThreadDumpsCount++;
441     myCurrentThreadDumpId++;
442     Disposer.register(content, new Disposable() {
443       @Override
444       public void dispose() {
445         myThreadDumpsCount--;
446         if (myThreadDumpsCount == 0) {
447           myCurrentThreadDumpId = 1;
448         }
449       }
450     });
451     Disposer.register(content, consoleView);
452     ui.selectAndFocus(content, true, false);
453     if (threads.size() > 0) {
454       panel.selectStackFrame(0);
455     }
456   }
457
458   public static void keep(Value value, EvaluationContext context) {
459     if (value instanceof ObjectReference) {
460       ((SuspendContextImpl)context.getSuspendContext()).keep((ObjectReference)value);
461     }
462   }
463
464   public abstract DebuggerTreeNode  getSelectedNode    (DataContext context);
465
466   public abstract EvaluatorBuilder  getEvaluatorBuilder();
467
468   public abstract CompletionEditor createEditor(Project project, PsiElement context, @NonNls String recentsId);
469
470   @NotNull
471   public static CodeFragmentFactory findAppropriateCodeFragmentFactory(final TextWithImports text, final PsiElement context) {
472     CodeFragmentFactory factory = ApplicationManager.getApplication().runReadAction(new Computable<CodeFragmentFactory>() {
473       @Override
474       public CodeFragmentFactory compute() {
475         final FileType fileType = text.getFileType();
476         final List<CodeFragmentFactory> factories = getCodeFragmentFactories(context);
477         if (fileType == null) {
478           return factories.get(0);
479         }
480         for (CodeFragmentFactory factory : factories) {
481           if (factory.getFileType().equals(fileType)) {
482             return factory;
483           }
484         }
485         return DefaultCodeFragmentFactory.getInstance();
486       }
487     });
488     return new CodeFragmentFactoryContextWrapper(factory);
489   }
490
491   private static class SigReader {
492     final String buffer;
493     int pos = 0;
494
495     SigReader(String s) {
496       buffer = s;
497     }
498
499     int get() {
500       return buffer.charAt(pos++);
501     }
502
503     int peek() {
504       return buffer.charAt(pos);
505     }
506
507     boolean eof() {
508       return buffer.length() <= pos;
509     }
510
511     @NonNls String getSignature() {
512       if (eof()) return "";
513
514       switch (get()) {
515         case 'Z':
516           return "boolean";
517         case 'B':
518           return "byte";
519         case 'C':
520           return "char";
521         case 'S':
522           return "short";
523         case 'I':
524           return "int";
525         case 'J':
526           return "long";
527         case 'F':
528           return "float";
529         case 'D':
530           return "double";
531         case 'V':
532           return "void";
533         case 'L':
534           int start = pos;
535           pos = buffer.indexOf(';', start) + 1;
536           LOG.assertTrue(pos > 0);
537           return buffer.substring(start, pos - 1).replace('/', '.');
538         case '[':
539           return getSignature() + "[]";
540         case '(':
541           StringBuilder result = new StringBuilder("(");
542           String separator = "";
543           while (peek() != ')') {
544             result.append(separator);
545             result.append(getSignature());
546             separator = ", ";
547           }
548           get();
549           result.append(")");
550           return getSignature() + " " + getClassName() + "." + getMethodName() + " " + result;
551         default:
552 //          LOG.assertTrue(false, "unknown signature " + buffer);
553           return null;
554       }
555     }
556
557     String getMethodName() {
558       return "";
559     }
560
561     String getClassName() {
562       return "";
563     }
564   }
565
566   public static String methodName(final Method m) {
567     return methodName(signatureToName(m.declaringType().signature()), m.name(), m.signature());
568   }
569
570   public static String methodName(final String className, final String methodName, final String signature) {
571     try {
572       return new SigReader(signature) {
573         @Override
574         String getMethodName() {
575           return methodName;
576         }
577
578         @Override
579         String getClassName() {
580           return className;
581         }
582       }.getSignature();
583     }
584     catch (Exception ignored) {
585       if (LOG.isDebugEnabled()) {
586         LOG.debug("Internal error : unknown signature" + signature);
587       }
588       return className + "." + methodName;
589     }
590   }
591
592   public static String signatureToName(String s) {
593     return new SigReader(s).getSignature();
594   }
595
596   public static Value createValue(VirtualMachineProxyImpl vm, String expectedType, double value) {
597     if (PsiType.DOUBLE.getPresentableText().equals(expectedType)) {
598       return vm.mirrorOf(value);
599     }
600     if (PsiType.FLOAT.getPresentableText().equals(expectedType)) {
601       return vm.mirrorOf((float)value);
602     }
603     return createValue(vm, expectedType, (long)value);
604   }
605
606   public static Value createValue(VirtualMachineProxyImpl vm, String expectedType, long value) {
607     if (PsiType.LONG.getPresentableText().equals(expectedType)) {
608       return vm.mirrorOf(value);
609     }
610     if (PsiType.INT.getPresentableText().equals(expectedType)) {
611       return vm.mirrorOf((int)value);
612     }
613     if (PsiType.SHORT.getPresentableText().equals(expectedType)) {
614       return vm.mirrorOf((short)value);
615     }
616     if (PsiType.BYTE.getPresentableText().equals(expectedType)) {
617       return vm.mirrorOf((byte)value);
618     }
619     if (PsiType.CHAR.getPresentableText().equals(expectedType)) {
620       return vm.mirrorOf((char)value);
621     }
622     if (PsiType.DOUBLE.getPresentableText().equals(expectedType)) {
623       return vm.mirrorOf((double)value);
624     }
625     if (PsiType.FLOAT.getPresentableText().equals(expectedType)) {
626       return vm.mirrorOf((float)value);
627     }
628     return null;
629   }
630
631   public static Value createValue(VirtualMachineProxyImpl vm, String expectedType, boolean value) {
632     if (PsiType.BOOLEAN.getPresentableText().equals(expectedType)) {
633       return vm.mirrorOf(value);
634     }
635     return null;
636   }
637
638   public static Value createValue(VirtualMachineProxyImpl vm, String expectedType, char value) {
639     if (PsiType.CHAR.getPresentableText().equals(expectedType)) {
640       return vm.mirrorOf(value);
641     }
642     if (PsiType.LONG.getPresentableText().equals(expectedType)) {
643       return vm.mirrorOf((long)value);
644     }
645     if (PsiType.INT.getPresentableText().equals(expectedType)) {
646       return vm.mirrorOf((int)value);
647     }
648     if (PsiType.SHORT.getPresentableText().equals(expectedType)) {
649       return vm.mirrorOf((short)value);
650     }
651     if (PsiType.BYTE.getPresentableText().equals(expectedType)) {
652       return vm.mirrorOf((byte)value);
653     }
654     return null;
655   }
656
657   public static String truncateString(final String str) {
658     // leave a small gap over XValueNode.MAX_VALUE_LENGTH to detect oversize
659     if (str.length() > XValueNode.MAX_VALUE_LENGTH + 5) {
660       return str.substring(0, XValueNode.MAX_VALUE_LENGTH + 5);
661     }
662     return str;
663   }
664
665   public static String getThreadStatusText(int statusId) {
666     switch (statusId) {
667       case ThreadReference.THREAD_STATUS_MONITOR:
668         return DebuggerBundle.message("status.thread.monitor");
669       case ThreadReference.THREAD_STATUS_NOT_STARTED:
670         return DebuggerBundle.message("status.thread.not.started");
671       case ThreadReference.THREAD_STATUS_RUNNING:
672         return DebuggerBundle.message("status.thread.running");
673       case ThreadReference.THREAD_STATUS_SLEEPING:
674         return DebuggerBundle.message("status.thread.sleeping");
675       case ThreadReference.THREAD_STATUS_UNKNOWN:
676         return DebuggerBundle.message("status.thread.unknown");
677       case ThreadReference.THREAD_STATUS_WAIT:
678         return DebuggerBundle.message("status.thread.wait");
679       case ThreadReference.THREAD_STATUS_ZOMBIE:
680         return DebuggerBundle.message("status.thread.zombie");
681       default:
682         return DebuggerBundle.message("status.thread.undefined");
683     }
684   }
685
686   public static String prepareValueText(String text, Project project) {
687     text = StringUtil.unquoteString(text);
688     text = StringUtil.unescapeStringCharacters(text);
689     int tabSize = CodeStyleSettingsManager.getSettings(project).getTabSize(StdFileTypes.JAVA);
690     if (tabSize < 0) {
691       tabSize = 0;
692     }
693     return text.replace("\t", StringUtil.repeat(" ", tabSize));
694   }
695
696   @Nullable
697   public static XSourcePosition toXSourcePosition(@NotNull SourcePosition position) {
698     VirtualFile file = position.getFile().getVirtualFile();
699     if (file == null) {
700       file = position.getFile().getOriginalFile().getVirtualFile();
701     }
702     if (file == null) {
703       return null;
704     }
705     return new JavaXSourcePosition(position, file);
706   }
707
708   private static final Key<VirtualFile> ALTERNATIVE_SOURCE_KEY = new Key<VirtualFile>("DEBUGGER_ALTERNATIVE_SOURCE");
709
710   public static void setAlternativeSource(VirtualFile source, VirtualFile dest) {
711     ALTERNATIVE_SOURCE_KEY.set(source, dest);
712     ALTERNATIVE_SOURCE_KEY.set(dest, null);
713   }
714
715   private static class JavaXSourcePosition implements XSourcePosition, ExecutionPointHighlighter.HighlighterProvider {
716     private final SourcePosition mySourcePosition;
717     @NotNull private final VirtualFile myFile;
718
719     public JavaXSourcePosition(@NotNull SourcePosition sourcePosition, @NotNull VirtualFile file) {
720       mySourcePosition = sourcePosition;
721       myFile = file;
722     }
723
724     @Override
725     public int getLine() {
726       return mySourcePosition.getLine();
727     }
728
729     @Override
730     public int getOffset() {
731       return mySourcePosition.getOffset();
732     }
733
734     @NotNull
735     @Override
736     public VirtualFile getFile() {
737       VirtualFile file = ALTERNATIVE_SOURCE_KEY.get(myFile);
738       if (file != null) {
739         return file;
740       }
741       return myFile;
742     }
743
744     @NotNull
745     @Override
746     public Navigatable createNavigatable(@NotNull Project project) {
747       if (ALTERNATIVE_SOURCE_KEY.get(myFile) != null) {
748         return new OpenFileDescriptor(project, getFile(), getLine(), 0);
749       }
750       return XSourcePositionImpl.doCreateOpenFileDescriptor(project, this);
751     }
752
753     @Nullable
754     @Override
755     public RangeHighlighter createHighlighter(Document document, Project project, TextAttributes attributes) {
756       if (mySourcePosition instanceof ExecutionPointHighlighter.HighlighterProvider) {
757         return ((ExecutionPointHighlighter.HighlighterProvider)mySourcePosition).createHighlighter(document, project, attributes);
758       }
759       return null;
760     }
761   }
762
763   /**
764    * Decompiler aware version
765    */
766   @Nullable
767   public static PsiElement findElementAt(@Nullable PsiFile file, int offset) {
768     if (file instanceof PsiCompiledFile) {
769       file = ((PsiCompiledFile)file).getDecompiledPsiFile();
770     }
771     if (file == null) return null;
772     return file.findElementAt(offset);
773   }
774
775   public static String getLocationMethodQName(@NotNull Location location) {
776     StringBuilder res = new StringBuilder();
777     ReferenceType type = location.declaringType();
778     if (type != null) {
779       res.append(type.name()).append('.');
780     }
781     res.append(location.method().name());
782     return res.toString();
783   }
784
785   private static PsiElement getNextElement(PsiElement element) {
786     PsiElement sibling = element.getNextSibling();
787     if (sibling != null) return sibling;
788     element = element.getParent();
789     if (element != null) return getNextElement(element);
790     return null;
791   }
792
793   public static List<PsiLambdaExpression> collectLambdas(SourcePosition position, final boolean onlyOnTheLine) {
794     ApplicationManager.getApplication().assertReadAccessAllowed();
795     PsiFile file = position.getFile();
796     int line = position.getLine();
797     Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
798     if (document == null || line >= document.getLineCount()) {
799       return Collections.emptyList();
800     }
801     PsiElement element = position.getElementAt();
802     final TextRange lineRange = DocumentUtil.getLineTextRange(document, line);
803     do {
804       PsiElement parent = element.getParent();
805       if (parent == null || (parent.getTextOffset() < lineRange.getStartOffset())) {
806         break;
807       }
808       element = parent;
809     }
810     while(true);
811
812     final List<PsiLambdaExpression> lambdas = new ArrayList<PsiLambdaExpression>(3);
813     final PsiElementVisitor lambdaCollector = new JavaRecursiveElementVisitor() {
814       @Override
815       public void visitLambdaExpression(PsiLambdaExpression expression) {
816         super.visitLambdaExpression(expression);
817         PsiElement body = expression.getBody();
818         if (!onlyOnTheLine || (body != null && lineRange.intersects(body.getTextRange()))) {
819           lambdas.add(expression);
820         }
821       }
822     };
823     element.accept(lambdaCollector);
824     // add initial lambda if we're inside already
825     NavigatablePsiElement method = PsiTreeUtil.getParentOfType(element, PsiMethod.class, PsiLambdaExpression.class);
826     if (method instanceof PsiLambdaExpression) {
827       lambdas.add((PsiLambdaExpression)method);
828     }
829     for (PsiElement sibling = getNextElement(element); sibling != null; sibling = getNextElement(sibling)) {
830       if (!lineRange.intersects(sibling.getTextRange())) {
831         break;
832       }
833       sibling.accept(lambdaCollector);
834     }
835     return lambdas;
836   }
837
838   @Nullable
839   public static PsiElement getFirstElementOnTheLine(PsiLambdaExpression lambda, Document document, int line) {
840     ApplicationManager.getApplication().assertReadAccessAllowed();
841     TextRange lineRange = new TextRange(document.getLineStartOffset(line), document.getLineEndOffset(line));
842     if (!lineRange.intersects(lambda.getTextRange())) return null;
843     PsiElement body = lambda.getBody();
844     if (body instanceof PsiCodeBlock) {
845       for (PsiStatement statement : ((PsiCodeBlock)body).getStatements()) {
846         if (lineRange.intersects(statement.getTextRange())) {
847           return statement;
848         }
849       }
850     }
851     return body;
852   }
853
854   public static boolean inTheSameMethod(@NotNull SourcePosition pos1, @NotNull SourcePosition pos2) {
855     ApplicationManager.getApplication().assertReadAccessAllowed();
856     PsiElement elem1 = pos1.getElementAt();
857     PsiElement elem2 = pos2.getElementAt();
858     if (elem1 == null) return elem2 == null;
859     if (elem2 != null) {
860       NavigatablePsiElement expectedMethod = PsiTreeUtil.getParentOfType(elem1, PsiMethod.class, PsiLambdaExpression.class);
861       NavigatablePsiElement currentMethod = PsiTreeUtil.getParentOfType(elem2, PsiMethod.class, PsiLambdaExpression.class);
862       return Comparing.equal(expectedMethod, currentMethod);
863     }
864     return false;
865   }
866 }