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