add utility method for containing method
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / engine / PositionManagerImpl.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 package com.intellij.debugger.engine;
17
18 import com.intellij.debugger.MultiRequestPositionManager;
19 import com.intellij.debugger.NoDataException;
20 import com.intellij.debugger.PositionManager;
21 import com.intellij.debugger.SourcePosition;
22 import com.intellij.debugger.engine.evaluation.EvaluateException;
23 import com.intellij.debugger.impl.DebuggerUtilsEx;
24 import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
25 import com.intellij.debugger.requests.ClassPrepareRequestor;
26 import com.intellij.execution.filters.LineNumbersMapping;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.editor.Document;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.util.*;
32 import com.intellij.openapi.util.text.StringUtil;
33 import com.intellij.openapi.vfs.VirtualFile;
34 import com.intellij.psi.*;
35 import com.intellij.psi.search.FilenameIndex;
36 import com.intellij.psi.search.GlobalSearchScope;
37 import com.intellij.psi.util.PsiTreeUtil;
38 import com.intellij.util.Function;
39 import com.intellij.util.containers.ContainerUtil;
40 import com.intellij.util.containers.EmptyIterable;
41 import com.intellij.xdebugger.impl.ui.ExecutionPointHighlighter;
42 import com.sun.jdi.AbsentInformationException;
43 import com.sun.jdi.Location;
44 import com.sun.jdi.Method;
45 import com.sun.jdi.ReferenceType;
46 import com.sun.jdi.request.ClassPrepareRequest;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 import java.util.*;
51
52 /**
53  * @author lex
54  */
55 public class PositionManagerImpl implements PositionManager, MultiRequestPositionManager {
56   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.engine.PositionManagerImpl");
57
58   private final DebugProcessImpl myDebugProcess;
59
60   public PositionManagerImpl(DebugProcessImpl debugProcess) {
61     myDebugProcess = debugProcess;
62   }
63
64   public DebugProcess getDebugProcess() {
65     return myDebugProcess;
66   }
67
68   @NotNull
69   public List<Location> locationsOfLine(@NotNull ReferenceType type, @NotNull SourcePosition position) throws NoDataException {
70     try {
71       final int line = position.getLine() + 1;
72       return type.locationsOfLine(DebugProcess.JAVA_STRATUM, null, line);
73     }
74     catch (AbsentInformationException ignored) {
75     }
76     return Collections.emptyList();
77   }
78
79   public ClassPrepareRequest createPrepareRequest(@NotNull final ClassPrepareRequestor requestor, @NotNull final SourcePosition position)
80     throws NoDataException {
81     throw new IllegalStateException("This class implements MultiRequestPositionManager, corresponding createPrepareRequests version should be used");
82   }
83
84   @NotNull
85   @Override
86   public List<ClassPrepareRequest> createPrepareRequests(@NotNull final ClassPrepareRequestor requestor, @NotNull final SourcePosition position)
87     throws NoDataException {
88     return ApplicationManager.getApplication().runReadAction(new Computable<List<ClassPrepareRequest>>() {
89       @Override
90       public List<ClassPrepareRequest> compute() {
91         List<ClassPrepareRequest> res = new ArrayList<ClassPrepareRequest>();
92         for (PsiClass psiClass : getLineClasses(position.getFile(), position.getLine())) {
93           ClassPrepareRequestor prepareRequestor = requestor;
94           String classPattern = JVMNameUtil.getNonAnonymousClassName(psiClass);
95           if (classPattern == null) {
96             final PsiClass parent = JVMNameUtil.getTopLevelParentClass(psiClass);
97             if (parent == null) {
98               continue;
99             }
100             final String parentQName = JVMNameUtil.getNonAnonymousClassName(parent);
101             if (parentQName == null) {
102               continue;
103             }
104             classPattern = parentQName + "*";
105             prepareRequestor = new ClassPrepareRequestor() {
106               public void processClassPrepare(DebugProcess debuggerProcess, ReferenceType referenceType) {
107                 final CompoundPositionManager positionManager = ((DebugProcessImpl)debuggerProcess).getPositionManager();
108                 final List<ReferenceType> positionClasses = positionManager.getAllClasses(position);
109                 if (positionClasses.contains(referenceType)) {
110                   requestor.processClassPrepare(debuggerProcess, referenceType);
111                 }
112               }
113             };
114           }
115           res.add(myDebugProcess.getRequestsManager().createClassPrepareRequest(prepareRequestor, classPattern));
116         }
117         return res;
118       }
119     });
120   }
121
122   @Nullable
123   public SourcePosition getSourcePosition(final Location location) throws NoDataException {
124     DebuggerManagerThreadImpl.assertIsManagerThread();
125     if(location == null) {
126       return null;
127     }
128
129     PsiFile psiFile = getPsiFileByLocation(getDebugProcess().getProject(), location);
130     if(psiFile == null ) {
131       return null;
132     }
133
134     LOG.assertTrue(myDebugProcess != null);
135
136     int lineNumber;
137     try {
138       lineNumber = location.lineNumber() - 1;
139     }
140     catch (InternalError e) {
141       lineNumber = -1;
142     }
143
144     if (lineNumber > -1) {
145       SourcePosition position = calcLineMappedSourcePosition(psiFile, lineNumber);
146       if (position != null) {
147         return position;
148       }
149     }
150
151     final Method method = location.method();
152
153     if (psiFile instanceof PsiCompiledElement || lineNumber < 0) {
154       final String methodSignature = method.signature();
155       if (methodSignature == null) {
156         return SourcePosition.createFromLine(psiFile, -1);
157       }
158       final String methodName = method.name();
159       if (methodName == null) {
160         return SourcePosition.createFromLine(psiFile, -1);
161       }
162       if (location.declaringType() == null) {
163         return SourcePosition.createFromLine(psiFile, -1);
164       }
165
166       final MethodFinder finder = new MethodFinder(location.declaringType().name(), methodName, methodSignature);
167       psiFile.accept(finder);
168
169       final PsiMethod compiledMethod = finder.getCompiledMethod();
170       if (compiledMethod == null) {
171         return SourcePosition.createFromLine(psiFile, -1);
172       }
173       SourcePosition sourcePosition = SourcePosition.createFromElement(compiledMethod);
174       if (lineNumber >= 0) {
175         sourcePosition = new ClsSourcePosition(sourcePosition, lineNumber);
176       }
177       return sourcePosition;
178     }
179
180     SourcePosition sourcePosition = SourcePosition.createFromLine(psiFile, lineNumber);
181     int lambdaOrdinal = -1;
182     if (LambdaMethodFilter.isLambdaName(method.name())) {
183       Set<Method> lambdas =
184         ContainerUtil.map2SetNotNull(locationsOfLine(location.declaringType(), sourcePosition), new Function<Location, Method>() {
185           @Override
186           public Method fun(Location location) {
187             Method method = location.method();
188             if (LambdaMethodFilter.isLambdaName(method.name())) {
189               return method;
190             }
191             return null;
192           }
193         });
194       if (lambdas.size() > 1) {
195         ArrayList<Method> lambdasList = new ArrayList<Method>(lambdas);
196         Collections.sort(lambdasList, DebuggerUtilsEx.LAMBDA_ORDINAL_COMPARATOR);
197         lambdaOrdinal = lambdasList.indexOf(method);
198       }
199     }
200     return new JavaSourcePosition(sourcePosition, location.declaringType(), method, lambdaOrdinal);
201   }
202
203   private static class JavaSourcePosition extends RemappedSourcePosition implements ExecutionPointHighlighter.HighlighterProvider {
204     private final String myExpectedClassName;
205     private final String myExpectedMethodName;
206     private final int myLambdaOrdinal;
207
208     public JavaSourcePosition(SourcePosition delegate, ReferenceType declaringType, Method method, int lambdaOrdinal) {
209       super(delegate);
210       myExpectedClassName = declaringType != null ? declaringType.name() : null;
211       myExpectedMethodName = method != null ? method.name() : null;
212       myLambdaOrdinal = lambdaOrdinal;
213     }
214
215     private PsiElement remapElement(PsiElement element) {
216       PsiClass aClass = getEnclosingClass(element);
217       if (!Comparing.equal(myExpectedClassName, JVMNameUtil.getClassVMName(aClass))) {
218         return null;
219       }
220       PsiElement method = DebuggerUtilsEx.getContainingMethod(element);
221       if (!StringUtil.isEmpty(myExpectedMethodName)) {
222         if (method == null) {
223           return null;
224         }
225         else if ((method instanceof PsiMethod && myExpectedMethodName.equals(((PsiMethod)method).getName()))) {
226           if (insideBody(element, ((PsiMethod)method).getBody())) return element;
227         }
228         else if (method instanceof PsiLambdaExpression && LambdaMethodFilter.isLambdaName(myExpectedMethodName)) {
229           if (insideBody(element, ((PsiLambdaExpression)method).getBody())) return element;
230         }
231       }
232       return null;
233     }
234
235     private static boolean insideBody(@NotNull PsiElement element, @Nullable PsiElement body) {
236       if (!PsiTreeUtil.isAncestor(body, element, false)) return false;
237       if (body instanceof PsiCodeBlock) {
238         return !element.equals(((PsiCodeBlock)body).getRBrace()) && !element.equals(((PsiCodeBlock)body).getLBrace());
239       }
240       return true;
241     }
242
243     @Override
244     public SourcePosition mapDelegate(final SourcePosition original) {
245       return ApplicationManager.getApplication().runReadAction(new Computable<SourcePosition>() {
246         @Override
247         public SourcePosition compute() {
248           PsiFile file = original.getFile();
249           int line = original.getLine();
250           if (LambdaMethodFilter.isLambdaName(myExpectedMethodName) && myLambdaOrdinal > -1) {
251             List<PsiLambdaExpression> lambdas = DebuggerUtilsEx.collectLambdas(original, false);
252
253             Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
254             if (document == null || line >= document.getLineCount()) {
255               return original;
256             }
257             if (myLambdaOrdinal < lambdas.size()) {
258               PsiElement firstElem = DebuggerUtilsEx.getFirstElementOnTheLine(lambdas.get(myLambdaOrdinal), document, line);
259               if (firstElem != null) {
260                 return SourcePosition.createFromElement(firstElem);
261               }
262             }
263           }
264           else {
265             // There may be more than one class/method code on the line, so we need to find out the correct place
266             for (PsiElement elem : getLineElements(file, line)) {
267               PsiElement remappedElement = remapElement(elem);
268               if (remappedElement != null) {
269                 if (remappedElement.getTextOffset() <= original.getOffset()) break;
270                 return SourcePosition.createFromElement(remappedElement);
271               }
272             }
273           }
274           return original;
275         }
276       });
277     }
278
279     @Nullable
280     @Override
281     public TextRange getHighlightRange() {
282       PsiElement method = DebuggerUtilsEx.getContainingMethod(this);
283       if (method instanceof PsiLambdaExpression) {
284         return method.getTextRange();
285       }
286       return null;
287     }
288   }
289
290   private static Iterable<PsiElement> getLineElements(final PsiFile file, int lineNumber) {
291     ApplicationManager.getApplication().assertReadAccessAllowed();
292     Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
293     if (document == null || lineNumber >= document.getLineCount()) {
294       return EmptyIterable.getInstance();
295     }
296     final int startOffset = document.getLineStartOffset(lineNumber);
297     final int endOffset = document.getLineEndOffset(lineNumber);
298     return new Iterable<PsiElement>() {
299       @Override
300       public Iterator<PsiElement> iterator() {
301         return new Iterator<PsiElement>() {
302           PsiElement myElement = file.findElementAt(startOffset);
303
304           @Override
305           public boolean hasNext() {
306             return myElement != null;
307           }
308
309           @Override
310           public PsiElement next() {
311             PsiElement res = myElement;
312             do {
313               myElement = PsiTreeUtil.nextLeaf(myElement);
314               if (myElement == null || myElement.getTextOffset() > endOffset) {
315                 myElement = null;
316                 break;
317               }
318             } while (myElement.getTextLength() == 0);
319             return res;
320           }
321
322           @Override
323           public void remove() {}
324         };
325       }
326     };
327   }
328
329   private static Set<PsiClass> getLineClasses(final PsiFile file, int lineNumber) {
330     ApplicationManager.getApplication().assertReadAccessAllowed();
331     Set<PsiClass> res = new HashSet<PsiClass>();
332     for (PsiElement element : getLineElements(file, lineNumber)) {
333       PsiClass aClass = getEnclosingClass(element);
334       if (aClass != null) {
335         res.add(aClass);
336       }
337     }
338     return res;
339   }
340
341   @Nullable
342   private PsiFile getPsiFileByLocation(final Project project, final Location location) {
343     if (location == null) {
344       return null;
345     }
346     final ReferenceType refType = location.declaringType();
347     if (refType == null) {
348       return null;
349     }
350
351     // We should find a class no matter what
352     // setAlternativeResolveEnabled is turned on here
353     //if (DumbService.getInstance(project).isDumb()) {
354     //  return null;
355     //}
356
357     final String originalQName = refType.name();
358     final GlobalSearchScope searchScope = myDebugProcess.getSearchScope();
359     PsiClass psiClass = DebuggerUtils.findClass(originalQName, project, searchScope); // try to lookup original name first
360     if (psiClass == null) {
361       int dollar = originalQName.indexOf('$');
362       if (dollar > 0) {
363         final String qName = originalQName.substring(0, dollar);
364         psiClass = DebuggerUtils.findClass(qName, project, searchScope);
365       }
366     }
367
368     if (psiClass != null) {
369       PsiElement element = psiClass.getNavigationElement();
370       // see IDEA-137167, prefer not compiled elements
371       if (element instanceof PsiCompiledElement) {
372         PsiElement fileElement = psiClass.getContainingFile().getNavigationElement();
373         if (!(fileElement instanceof PsiCompiledElement)) {
374           element = fileElement;
375         }
376       }
377       return element.getContainingFile();
378     }
379     else {
380       // try to search by filename
381       try {
382         PsiFile[] files = FilenameIndex.getFilesByName(project, refType.sourceName(), GlobalSearchScope.allScope(project));
383         for (PsiFile file : files) {
384           if (file instanceof PsiJavaFile) {
385             for (PsiClass cls : ((PsiJavaFile)file).getClasses()) {
386               if (StringUtil.equals(originalQName, cls.getQualifiedName())) {
387                 return file;
388               }
389             }
390           }
391         }
392       }
393       catch (AbsentInformationException ignore) {
394       }
395     }
396
397     return null;
398   }
399
400   @NotNull
401   public List<ReferenceType> getAllClasses(@NotNull final SourcePosition position) throws NoDataException {
402     return ApplicationManager.getApplication().runReadAction(new Computable<List<ReferenceType>>() {
403       @Override
404       public List<ReferenceType> compute() {
405         List<ReferenceType> res = new ArrayList<ReferenceType>();
406         for (PsiClass aClass : getLineClasses(position.getFile(), position.getLine())) {
407           res.addAll(getClassReferences(aClass, position));
408         }
409         return res;
410       }
411     });
412   }
413
414   private List<ReferenceType> getClassReferences(@NotNull final PsiClass psiClass, SourcePosition position) {
415     final Ref<String> baseClassNameRef = new Ref<String>(null);
416     final Ref<PsiClass> classAtPositionRef = new Ref<PsiClass>(null);
417     final Ref<Boolean> isLocalOrAnonymous = new Ref<Boolean>(Boolean.FALSE);
418     final Ref<Integer> requiredDepth = new Ref<Integer>(0);
419     ApplicationManager.getApplication().runReadAction(new Runnable() {
420       public void run() {
421         classAtPositionRef.set(psiClass);
422         String className = JVMNameUtil.getNonAnonymousClassName(psiClass);
423         if (className == null) {
424           isLocalOrAnonymous.set(Boolean.TRUE);
425           final PsiClass topLevelClass = JVMNameUtil.getTopLevelParentClass(psiClass);
426           if (topLevelClass != null) {
427             final String parentClassName = JVMNameUtil.getNonAnonymousClassName(topLevelClass);
428             if (parentClassName != null) {
429               requiredDepth.set(getNestingDepth(psiClass));
430               baseClassNameRef.set(parentClassName);
431             }
432           }
433           else {
434             LOG.error("Local or anonymous class has no non-local parent");
435           }
436         }
437         else {
438           baseClassNameRef.set(className);
439         }
440       }
441     });
442
443     final String className = baseClassNameRef.get();
444     if (className == null) {
445       return Collections.emptyList();
446     }
447
448     if (!isLocalOrAnonymous.get()) {
449       return myDebugProcess.getVirtualMachineProxy().classesByName(className);
450     }
451     
452     // the name is a parent class for a local or anonymous class
453     final List<ReferenceType> outers = myDebugProcess.getVirtualMachineProxy().classesByName(className);
454     final List<ReferenceType> result = new ArrayList<ReferenceType>(outers.size());
455     for (ReferenceType outer : outers) {
456       final ReferenceType nested = findNested(outer, 0, classAtPositionRef.get(), requiredDepth.get(), position);
457       if (nested != null) {
458         result.add(nested);
459       }
460     }
461     return result;
462   }
463
464   private static int getNestingDepth(PsiClass aClass) {
465     int depth = 0;
466     PsiClass enclosing = getEnclosingClass(aClass);
467     while (enclosing != null) {
468       depth++;
469       enclosing = getEnclosingClass(enclosing);
470     }
471     return depth;
472   }
473
474   /**
475    * See IDEA-121739
476    * Anonymous classes inside other anonymous class parameters list should belong to parent class
477    * Inner in = new Inner(new Inner2(){}) {};
478    * Parent of Inner2 sub class here is not Inner sub class
479    */
480   @Nullable
481   private static PsiClass getEnclosingClass(@Nullable PsiElement element) {
482     if (element == null) {
483       return null;
484     }
485
486     element = element.getParent();
487     PsiElement previous = null;
488
489     while (element != null) {
490       if (PsiClass.class.isInstance(element) && !(previous instanceof PsiExpressionList)) {
491         //noinspection unchecked
492         return (PsiClass)element;
493       }
494       if (element instanceof PsiFile) {
495         return null;
496       }
497       previous = element;
498       element = element.getParent();
499     }
500
501     return null;
502   }
503
504   @Nullable
505   private ReferenceType findNested(final ReferenceType fromClass, final int currentDepth, final PsiClass classToFind, final int requiredDepth, final SourcePosition position) {
506     final VirtualMachineProxyImpl vmProxy = myDebugProcess.getVirtualMachineProxy();
507     if (fromClass.isPrepared()) {
508       try {
509         if (currentDepth < requiredDepth) {
510           final List<ReferenceType> nestedTypes = vmProxy.nestedTypes(fromClass);
511           for (ReferenceType nested : nestedTypes) {
512             final ReferenceType found = findNested(nested, currentDepth + 1, classToFind, requiredDepth, position);
513             if (found != null) {
514               return found;
515             }
516           }
517           return null;
518         }
519
520         int rangeBegin = Integer.MAX_VALUE;
521         int rangeEnd = Integer.MIN_VALUE;
522         for (Location location : fromClass.allLineLocations()) {
523           final int lnumber = location.lineNumber();
524           if (lnumber <= 1) {
525             // should be a native method, skipping
526             // sometimes compiler generates location where line number is exactly 1 (e.g. GWT)
527             // such locations are hardly correspond to real lines in code, so skipping them too
528             continue;
529           }
530           final Method method = location.method();
531           if (method == null || DebuggerUtils.isSynthetic(method) || method.isBridge()) {
532             // do not take into account synthetic stuff
533             continue;
534           }
535           int locationLine = lnumber - 1;
536           PsiFile psiFile = position.getFile().getOriginalFile();
537           if (psiFile instanceof PsiCompiledFile) {
538             locationLine = bytecodeToSourceLine(psiFile, locationLine);
539             if (locationLine < 0) continue;
540           }
541           rangeBegin = Math.min(rangeBegin,  locationLine);
542           rangeEnd = Math.max(rangeEnd,  locationLine);
543         }
544
545         final int positionLine = position.getLine();
546         if (positionLine >= rangeBegin && positionLine <= rangeEnd) {
547           // choose the second line to make sure that only this class' code exists on the line chosen
548           // Otherwise the line (depending on the offset in it) can contain code that belongs to different classes
549           // and JVMNameUtil.getClassAt(candidatePosition) will return the wrong class.
550           // Example of such line:
551           // list.add(new Runnable(){......
552           // First offsets belong to parent class, and offsets inside te substring "new Runnable(){" belong to anonymous runnable.
553           final int finalRangeBegin = rangeBegin;
554           final int finalRangeEnd = rangeEnd;
555           return ApplicationManager.getApplication().runReadAction(new NullableComputable<ReferenceType>() {
556             public ReferenceType compute() {
557               if (!classToFind.isValid()) {
558                 return null;
559               }
560               final int line = Math.min(finalRangeBegin + 1, finalRangeEnd);
561               Set<PsiClass> lineClasses = getLineClasses(position.getFile(), line);
562               if (lineClasses.size() > 1) {
563                 // if there's more than one class on the line - try to match by name
564                 for (PsiClass aClass : lineClasses) {
565                   if (classToFind.equals(aClass)) {
566                     return fromClass;
567                   }
568                 }
569               }
570               else if (!lineClasses.isEmpty()){
571                 return classToFind.equals(lineClasses.iterator().next())? fromClass : null;
572               }
573               return null;
574             }
575           });
576         }
577       }
578       catch (AbsentInformationException ignored) {
579       }
580     }
581     return null;
582   }
583
584   //don't use JavaRecursiveElementWalkingVisitor because getNextSibling() works slowly for compiled elements 
585   private class MethodFinder extends JavaRecursiveElementVisitor {
586     private final String myClassName;
587     private PsiClass myCompiledClass;
588     private final String myMethodName;
589     private final String myMethodSignature;
590     private PsiMethod myCompiledMethod;
591
592     public MethodFinder(final String className, final String methodName, final String methodSignature) {
593       myClassName = className;
594       myMethodName = methodName;
595       myMethodSignature = methodSignature;
596     }
597
598     @Override public void visitClass(PsiClass aClass) {
599       final List<ReferenceType> allClasses = myDebugProcess.getPositionManager().getAllClasses(SourcePosition.createFromElement(aClass));
600       for (ReferenceType referenceType : allClasses) {
601         if (referenceType.name().equals(myClassName)) {
602           myCompiledClass = aClass;
603         }
604       }
605
606       aClass.acceptChildren(this);
607     }
608
609     @Override public void visitMethod(PsiMethod method) {
610       try {
611         //noinspection HardCodedStringLiteral
612         String methodName = method.isConstructor() ? "<init>" : method.getName();
613         PsiClass containingClass = method.getContainingClass();
614
615         if(containingClass != null &&
616            containingClass.equals(myCompiledClass) &&
617            methodName.equals(myMethodName) &&
618            JVMNameUtil.getJVMSignature(method).getName(myDebugProcess).equals(myMethodSignature)) {
619           myCompiledMethod = method;
620         }
621       }
622       catch (EvaluateException e) {
623         LOG.debug(e);
624       }
625     }
626
627     public PsiClass getCompiledClass() {
628       return myCompiledClass;
629     }
630
631     public PsiMethod getCompiledMethod() {
632       return myCompiledMethod;
633     }
634   }
635
636   private static class ClsSourcePosition extends RemappedSourcePosition {
637     private final int myOriginalLine;
638
639     public ClsSourcePosition(SourcePosition delegate, int originalLine) {
640       super(delegate);
641       myOriginalLine = originalLine;
642     }
643
644     @Override
645     public SourcePosition mapDelegate(SourcePosition original) {
646       if (myOriginalLine < 0) return original;
647       SourcePosition position = calcLineMappedSourcePosition(getFile(), myOriginalLine);
648       return position != null ? position : original;
649     }
650   }
651
652   @Nullable
653   private static SourcePosition calcLineMappedSourcePosition(PsiFile psiFile, int originalLine) {
654     int line = bytecodeToSourceLine(psiFile, originalLine);
655     if (line > -1) {
656       return SourcePosition.createFromLine(psiFile, line - 1);
657     }
658     return null;
659   }
660
661   private static int bytecodeToSourceLine(PsiFile psiFile, int originalLine) {
662     VirtualFile file = psiFile.getVirtualFile();
663     if (file != null) {
664       LineNumbersMapping mapping = file.getUserData(LineNumbersMapping.LINE_NUMBERS_MAPPING_KEY);
665       if (mapping != null) {
666         int line = mapping.bytecodeToSource(originalLine + 1);
667         if (line > -1) {
668           return line;
669         }
670       }
671     }
672     return -1;
673   }
674 }