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