399cf688258862aba883afb7d7c23df548f891af
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / debugger / GroovyPositionManager.java
1 /*
2  * Copyright 2000-2007 JetBrains s.r.o.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 package org.jetbrains.plugins.groovy.debugger;
17
18 import com.intellij.debugger.NoDataException;
19 import com.intellij.debugger.PositionManager;
20 import com.intellij.debugger.SourcePosition;
21 import com.intellij.debugger.engine.CompoundPositionManager;
22 import com.intellij.debugger.engine.DebugProcess;
23 import com.intellij.debugger.engine.DebugProcessImpl;
24 import com.intellij.debugger.engine.jdi.VirtualMachineProxy;
25 import com.intellij.debugger.requests.ClassPrepareRequestor;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.roots.impl.DirectoryIndex;
30 import com.intellij.openapi.util.Computable;
31 import com.intellij.openapi.util.Ref;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.psi.PsiClass;
34 import com.intellij.psi.PsiElement;
35 import com.intellij.psi.PsiFile;
36 import com.intellij.psi.PsiManager;
37 import com.intellij.psi.search.GlobalSearchScope;
38 import com.intellij.psi.util.PsiTreeUtil;
39 import com.intellij.util.Processor;
40 import com.intellij.util.Query;
41 import com.intellij.util.containers.HashSet;
42 import com.sun.jdi.AbsentInformationException;
43 import com.sun.jdi.Location;
44 import com.sun.jdi.ReferenceType;
45 import com.sun.jdi.request.ClassPrepareRequest;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
48 import org.jetbrains.plugins.groovy.GroovyFileTypeLoader;
49 import org.jetbrains.plugins.groovy.extensions.debugger.ScriptPositionManagerHelper;
50 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
51 import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
52 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
53 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
54 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
55 import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager;
56
57 import java.util.*;
58
59 public class GroovyPositionManager implements PositionManager {
60   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.engine.PositionManagerImpl");
61
62   private final DebugProcess myDebugProcess;
63   @NotNull
64   public static final Set<String> GROOVY_EXTENSIONS = new java.util.HashSet<String>(Arrays.asList("groovy", "gvy", "gy", "gsh"));
65
66   public GroovyPositionManager(DebugProcess debugProcess) {
67     myDebugProcess = debugProcess;
68   }
69
70   public DebugProcess getDebugProcess() {
71     return myDebugProcess;
72   }
73
74   @NotNull
75   public List<Location> locationsOfLine(ReferenceType type, SourcePosition position) throws NoDataException {
76     try {
77       int line = position.getLine() + 1;
78       List<Location> locations = getDebugProcess().getVirtualMachineProxy().versionHigher("1.4")
79                                  ? type.locationsOfLine(DebugProcessImpl.JAVA_STRATUM, null, line)
80                                  : type.locationsOfLine(line);
81       if (locations == null || locations.isEmpty()) throw new NoDataException();
82       return locations;
83     }
84     catch (AbsentInformationException e) {
85       throw new NoDataException();
86     }
87   }
88
89   @Nullable
90   private static GroovyPsiElement findReferenceTypeSourceImage(SourcePosition position) {
91     PsiFile file = position.getFile();
92     if (!(file instanceof GroovyFileBase)) return null;
93     PsiElement element = file.findElementAt(position.getOffset());
94     if (element == null) return null;
95     return PsiTreeUtil.getParentOfType(element, GrClosableBlock.class, GrTypeDefinition.class);
96   }
97
98   @Nullable
99   private static GrTypeDefinition findEnclosingTypeDefinition(SourcePosition position) {
100     PsiFile file = position.getFile();
101     if (!(file instanceof GroovyFileBase)) return null;
102     PsiElement element = file.findElementAt(position.getOffset());
103     if (element == null) return null;
104     return PsiTreeUtil.getParentOfType(element, GrTypeDefinition.class);
105   }
106
107   public ClassPrepareRequest createPrepareRequest(final ClassPrepareRequestor requestor, final SourcePosition position)
108     throws NoDataException {
109     String qName = getOuterClassName(position);
110     if (qName != null) {
111       return myDebugProcess.getRequestsManager().createClassPrepareRequest(requestor, qName);
112     }
113
114     qName = findEnclosingName(position);
115
116     if (qName == null) throw new NoDataException();
117     ClassPrepareRequestor waitRequestor = new ClassPrepareRequestor() {
118       public void processClassPrepare(DebugProcess debuggerProcess, ReferenceType referenceType) {
119         final CompoundPositionManager positionManager = ((DebugProcessImpl)debuggerProcess).getPositionManager();
120         if (positionManager.locationsOfLine(referenceType, position).size() > 0) {
121           requestor.processClassPrepare(debuggerProcess, referenceType);
122         }
123         else {
124           final List<ReferenceType> positionClasses = positionManager.getAllClasses(position);
125           if (positionClasses.contains(referenceType)) {
126             requestor.processClassPrepare(debuggerProcess, referenceType);
127           }
128         }
129       }
130     };
131     return myDebugProcess.getRequestsManager().createClassPrepareRequest(waitRequestor, qName + "$*");
132   }
133
134   @Nullable
135   private static String findEnclosingName(final SourcePosition position) {
136     return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
137       @Nullable
138       public String compute() {
139         GrTypeDefinition typeDefinition = findEnclosingTypeDefinition(position);
140         if (typeDefinition != null) {
141           return typeDefinition.getQualifiedName();
142         }
143         return getScriptQualifiedName(position);
144       }
145     });
146   }
147
148   @Nullable
149   private static String getOuterClassName(final SourcePosition position) {
150     return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
151       @Nullable
152       public String compute() {
153         GroovyPsiElement sourceImage = findReferenceTypeSourceImage(position);
154         if (sourceImage instanceof GrTypeDefinition) {
155           return ((GrTypeDefinition)sourceImage).getQualifiedName();
156         } else if (sourceImage == null) {
157           return getScriptQualifiedName(position);
158         }
159         return null;
160       }
161     });
162   }
163
164   @Nullable
165   private static String getScriptQualifiedName(SourcePosition position) {
166     PsiFile file = position.getFile();
167     if (file instanceof GroovyFile) {
168       return getScriptFQName((GroovyFile)file);
169     }
170     return null;
171   }
172
173   public SourcePosition getSourcePosition(final Location location) throws NoDataException {
174     if (location == null) throw new NoDataException();
175
176     PsiFile psiFile = getPsiFileByLocation(getDebugProcess().getProject(), location);
177     if (psiFile == null) throw new NoDataException();
178
179     int lineNumber = calcLineIndex(location);
180     if (lineNumber < 0) throw new NoDataException();
181     return SourcePosition.createFromLine(psiFile, lineNumber);
182   }
183
184   private int calcLineIndex(Location location) {
185     LOG.assertTrue(myDebugProcess != null);
186     if (location == null) return -1;
187
188     try {
189       return location.lineNumber() - 1;
190     }
191     catch (InternalError e) {
192       return -1;
193     }
194   }
195
196   @Nullable
197   private PsiFile getPsiFileByLocation(final Project project, final Location location) {
198     if (location == null) return null;
199
200     final ReferenceType refType = location.declaringType();
201     if (refType == null) return null;
202
203     final String originalQName = refType.name().replace('/', '.');
204     int dollar = originalQName.indexOf('$');
205     String qName = dollar >= 0 ? originalQName.substring(0, dollar) : originalQName;
206
207     String runtimeName = qName;
208     for (ScriptPositionManagerHelper helper : ScriptPositionManagerHelper.EP_NAME.getExtensions()) {
209       if (helper.isAppropriateRuntimeName(runtimeName)) {
210         qName = helper.getOriginalScriptName(refType, runtimeName);
211         break;
212       }
213     }
214
215     final GlobalSearchScope searchScope = myDebugProcess.getSearchScope();
216     final PsiClass[] classes = GroovyPsiManager.getInstance(project).getNamesCache().getClassesByFQName(qName, searchScope);
217     PsiClass clazz = classes.length == 1 ? classes[0] : null;
218     if (clazz != null) return clazz.getContainingFile();
219
220     DirectoryIndex directoryIndex = DirectoryIndex.getInstance(project);
221     int dotIndex = qName.lastIndexOf(".");
222     String packageName = dotIndex > 0 ? qName.substring(0, dotIndex) : "";
223     Query<VirtualFile> query = directoryIndex.getDirectoriesByPackageName(packageName, true);
224     final String fileNameWithoutExtension = dotIndex > 0 ? qName.substring(dotIndex + 1) : qName;
225     final Set<String> extensions = getAllGroovyFileExtensions();
226     final Ref<PsiFile> result = new Ref<PsiFile>();
227     query.forEach(new Processor<VirtualFile>() {
228       public boolean process(VirtualFile vDir) {
229         for (final String extension : extensions) {
230           VirtualFile vFile = vDir.findChild(fileNameWithoutExtension + "." + extension);
231           if (vFile != null) {
232             PsiFile psiFile = PsiManager.getInstance(project).findFile(vFile);
233             if (psiFile instanceof GroovyFileBase) {
234               result.set(psiFile);
235               return false;
236             }
237           }
238         }
239         return true;
240       }
241     });
242
243     PsiFile res = result.get();
244     if (res != null) {
245       return res;
246     }
247     for (ScriptPositionManagerHelper helper : ScriptPositionManagerHelper.EP_NAME.getExtensions()) {
248       if (helper.isAppropriateRuntimeName(runtimeName)) {
249         PsiFile file = helper.getExtraScriptIfNotFound(refType, runtimeName, project);
250         if (file != null) return file;
251       }
252     }
253     return null;
254   }
255
256   private static Set<String> getAllGroovyFileExtensions() {
257     final Set<String> extensions = new HashSet<String>();
258     extensions.addAll(GroovyFileTypeLoader.getAllGroovyExtensions());
259     extensions.add("gvy");
260     extensions.add("gy");
261     extensions.add("gsh");
262     return extensions;
263   }
264
265   @NotNull
266   public List<ReferenceType> getAllClasses(final SourcePosition position) throws NoDataException {
267     List<ReferenceType> result = ApplicationManager.getApplication().runReadAction(new Computable<List<ReferenceType>>() {
268       public List<ReferenceType> compute() {
269         GroovyPsiElement sourceImage = findReferenceTypeSourceImage(position);
270         final String scriptName = getScriptQualifiedName(position);
271
272         if (sourceImage instanceof GrTypeDefinition) {
273           String qName = ((GrTypeDefinition)sourceImage).getQualifiedName();
274           if (qName != null) return myDebugProcess.getVirtualMachineProxy().classesByName(qName);
275         } else if (sourceImage == null) {
276           if (scriptName != null) return myDebugProcess.getVirtualMachineProxy().classesByName(scriptName);
277         } else {
278           final GrTypeDefinition typeDefinition = findEnclosingTypeDefinition(position);
279           String enclosingName;
280           if (typeDefinition != null) {
281             enclosingName = typeDefinition.getQualifiedName();
282           } else {
283             enclosingName = scriptName;
284           }
285           if (enclosingName == null) return Collections.emptyList();
286
287           final List<ReferenceType> outers = myDebugProcess.getVirtualMachineProxy().classesByName(enclosingName);
288           final List<ReferenceType> result = new ArrayList<ReferenceType>(outers.size());
289           for (ReferenceType outer : outers) {
290             final ReferenceType nested = findNested(outer, sourceImage, position);
291             if (nested != null) {
292               result.add(nested);
293             }
294           }
295           return result;
296         }
297         return Collections.emptyList();
298       }
299     });
300
301     if (result == null || result.isEmpty()) throw new NoDataException();
302     return result;
303   }
304
305   private static String getScriptFQName(GroovyFile groovyFile) {
306     String qName;
307     VirtualFile vFile = groovyFile.getVirtualFile();
308     assert vFile != null;
309     String packageName = groovyFile.getPackageName();
310     String plainName = vFile.getNameWithoutExtension();
311     String fileName = plainName;
312     if (groovyFile.isScript()) {
313       for (ScriptPositionManagerHelper helper : ScriptPositionManagerHelper.EP_NAME.getExtensions()) {
314         if (helper.isAppropriateScriptFile(groovyFile)) {
315           fileName = helper.getRuntimeScriptName(plainName, groovyFile);
316           break;
317         }
318       }
319     }
320     qName = packageName.length() > 0 ? packageName + "." + fileName : fileName;
321     return qName;
322   }
323
324   @Nullable
325   private ReferenceType findNested(ReferenceType fromClass, final GroovyPsiElement toFind, SourcePosition classPosition) {
326     final VirtualMachineProxy vmProxy = myDebugProcess.getVirtualMachineProxy();
327     if (fromClass.isPrepared()) {
328
329       final List<ReferenceType> nestedTypes = vmProxy.nestedTypes(fromClass);
330
331       for (ReferenceType nested : nestedTypes) {
332         final ReferenceType found = findNested(nested, toFind, classPosition);
333         if (found != null) {
334           return found;
335         }
336       }
337
338       try {
339         final int lineNumber = classPosition.getLine() + 1;
340         if (fromClass.locationsOfLine(lineNumber).size() > 0) {
341           return fromClass;
342         }
343         //noinspection LoopStatementThatDoesntLoop
344         for (Location location : fromClass.allLineLocations()) {
345           final SourcePosition candidateFirstPosition = SourcePosition.createFromLine(toFind.getContainingFile(), location.lineNumber() - 1)
346             ;
347           if (toFind.equals(findReferenceTypeSourceImage(candidateFirstPosition))) {
348             return fromClass;
349           }
350           break; // isApplicable only the first location
351         }
352       }
353       catch (AbsentInformationException ignored) {
354       }
355     }
356     return null;
357   }
358
359 }