package org.jetbrains.plugins.clojure.debugger; import com.intellij.debugger.NoDataException; import com.intellij.debugger.PositionManager; import com.intellij.debugger.SourcePosition; import com.intellij.debugger.engine.CompoundPositionManager; import com.intellij.debugger.engine.DebugProcess; import com.intellij.debugger.engine.DebugProcessImpl; import com.intellij.debugger.requests.ClassPrepareRequestor; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.roots.impl.DirectoryIndex; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.PathUtil; import com.intellij.util.Processor; import com.intellij.util.Query; import com.intellij.util.containers.HashSet; import com.sun.jdi.AbsentInformationException; import com.sun.jdi.Location; import com.sun.jdi.ReferenceType; import com.sun.jdi.request.ClassPrepareRequest; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.clojure.ClojureLanguage; import org.jetbrains.plugins.clojure.psi.api.ClList; import org.jetbrains.plugins.clojure.psi.api.ClojureFile; import org.jetbrains.plugins.clojure.psi.api.defs.ClDef; import org.jetbrains.plugins.clojure.psi.api.ns.ClNs; import org.jetbrains.plugins.clojure.psi.api.symbols.ClSymbol; import org.jetbrains.plugins.clojure.psi.stubs.ClojureShortNamesCache; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * Created by IntelliJ IDEA. * User: peter * Date: Jan 16, 2009 * Time: 4:17:46 PM * Copyright 2007, 2008, 2009 Red Shark Technology * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class ClojurePositionManager implements PositionManager { private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.engine.PositionManagerImpl"); private final DebugProcess myDebugProcess; public ClojurePositionManager(DebugProcess debugProcess) { myDebugProcess = debugProcess; } public DebugProcess getDebugProcess() { return myDebugProcess; } @NotNull public List<Location> locationsOfLine(ReferenceType type, SourcePosition position) throws NoDataException { try { int line = position.getLine() + 1; List<Location> locations = getDebugProcess().getVirtualMachineProxy().versionHigher("1.4") ? type.locationsOfLine(DebugProcessImpl.JAVA_STRATUM, null, line) : type.locationsOfLine(line); if (locations == null || locations.isEmpty()) throw new NoDataException(); return locations; } catch (AbsentInformationException e) { throw new NoDataException(); } } public ClassPrepareRequest createPrepareRequest(final ClassPrepareRequestor requestor, final SourcePosition position) throws NoDataException { final PsiFile file = position.getFile(); if (!(file instanceof ClojureFile)) throw new NoDataException(); final String query = ApplicationManager.getApplication().runReadAction(new Computable<String>() { public String compute() { final ClojureFile clojureFile = (ClojureFile) file; PsiElement element = clojureFile.findElementAt(position.getOffset()); String nsName = getNameSpaceName(element); final String nsPrefix = nsName != null ? nsName + "$" : "user$"; final ClDef def = PsiTreeUtil.getParentOfType(element, ClDef.class); final String name = def == null ? null : def.getName(); return (nsPrefix + (name != null ? name : "")).replace('-', '_') + "*"; } }); ClassPrepareRequestor waitRequestor = new MyClassPrepareRequestor(position, requestor); final ClassPrepareRequest prepareRequest = myDebugProcess.getRequestsManager().createClassPrepareRequest(waitRequestor, query); prepareRequest.addSourceNameFilter(file.getName()); return prepareRequest; } private String getNameSpaceName(final PsiElement _element) { final Ref<String> stringRef = new Ref<String>(null); ApplicationManager.getApplication().runReadAction(new Runnable() { public void run() { PsiElement element = _element; while (!(element.getParent() instanceof ClojureFile)) { element = element.getParent(); } final PsiElement parent = element.getParent(); if (parent instanceof ClojureFile) { while (element != null) { if (element instanceof ClList) { ClList list = (ClList) element; final ClSymbol first = list.getFirstSymbol(); if (first != null && first.getText().equals("ns")) { final ClSymbol snd = PsiTreeUtil.getNextSiblingOfType(first, ClSymbol.class); if (snd != null) { stringRef.set(snd.getText()); return; } } } element = element.getPrevSibling(); } } } }); return stringRef.get(); } @NotNull public List<ReferenceType> getAllClasses(final SourcePosition position) throws NoDataException { PsiFile file = position.getFile(); if (!(file instanceof ClojureFile)) throw new NoDataException(); final List<ReferenceType> list = myDebugProcess.getVirtualMachineProxy().allClasses(); final ArrayList<ReferenceType> result = new ArrayList<ReferenceType>(); final String fileName = position.getFile().getName(); for (ReferenceType type : list) { try { final String name = type.sourceName(); if (fileName.equals(name)) { result.add(type); } } catch (AbsentInformationException e) { //do nothing } } return result; } public SourcePosition getSourcePosition(final Location location) throws NoDataException { if (location == null) throw new NoDataException(); PsiFile psiFile = getPsiFileByLocation(getDebugProcess().getProject(), location); if (!(psiFile instanceof ClojureFile)) throw new NoDataException(); int lineNumber = location.lineNumber(); if (lineNumber < 1) throw new NoDataException(); return SourcePosition.createFromLine(psiFile, lineNumber - 1); } @Nullable private PsiFile getPsiFileByLocation(final Project project, final Location location) { final Ref<PsiFile> result = new Ref<PsiFile>(null); ApplicationManager.getApplication().runReadAction(new Runnable() { public void run() { if (location == null) return; final ReferenceType refType = location.declaringType(); if (refType == null) return; String fileName = null; try { fileName = location.sourceName(); } catch (AbsentInformationException ignore) {} final String originalQName = refType.name().replace('/', '.'); final GlobalSearchScope searchScope = myDebugProcess.getSearchScope(); int dollar = originalQName.indexOf('$'); final String qName = dollar >= 0 ? originalQName.substring(0, dollar) : originalQName; final ClNs[] nses = ClojureShortNamesCache.getInstance(project).getNsByQualifiedName(qName, searchScope); if (nses.length == 1) { final PsiFile containingFile = nses[0].getContainingFile(); if (fileName == null || containingFile.getName().equals(fileName)) { result.set(containingFile); return; } } DirectoryIndex directoryIndex = DirectoryIndex.getInstance(project); int dotIndex = qName.lastIndexOf("."); String packageName = dotIndex >= 0 ? qName.substring(0, dotIndex) : ""; Query<VirtualFile> query = directoryIndex.getDirectoriesByPackageName(packageName, true); if (fileName == null) { fileName = dotIndex >= 0 ? qName.substring(dotIndex + 1) : qName; fileName += ".clj"; } final String finalFileName = fileName; query.forEach(new Processor<VirtualFile>() { public boolean process(VirtualFile vDir) { VirtualFile vFile = vDir.findChild(finalFileName); if (vFile != null) { PsiFile psiFile = PsiManager.getInstance(project).findFile(vFile); if (psiFile instanceof ClojureFile) { result.set(psiFile); return false; } } return true; } }); } }); return result.get(); } private static class MyClassPrepareRequestor implements ClassPrepareRequestor { private final SourcePosition position; private final ClassPrepareRequestor requestor; public MyClassPrepareRequestor(SourcePosition position, ClassPrepareRequestor requestor) { this.position = position; this.requestor = requestor; } public void processClassPrepare(DebugProcess debuggerProcess, ReferenceType referenceType) { final CompoundPositionManager positionManager = ((DebugProcessImpl) debuggerProcess).getPositionManager(); if (positionManager.locationsOfLine(referenceType, position).size() > 0) { requestor.processClassPrepare(debuggerProcess, referenceType); } else { final List<ReferenceType> positionClasses = positionManager.getAllClasses(position); if (positionClasses.contains(referenceType)) { requestor.processClassPrepare(debuggerProcess, referenceType); } } } } }