blob: 016b01ed6f964d13448ebb6b0b83b4cd5720b2a2 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
package com.intellij.debugger.engine;
import com.intellij.debugger.NoDataException;
import com.intellij.debugger.PositionManager;
import com.intellij.debugger.SourcePosition;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.debugger.requests.ClassPrepareRequestor;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NullableComputable;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiUtil;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.request.ClassPrepareRequest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author lex
*/
public class PositionManagerImpl implements PositionManager {
private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.engine.PositionManagerImpl");
private final DebugProcessImpl myDebugProcess;
public PositionManagerImpl(DebugProcessImpl debugProcess) {
myDebugProcess = debugProcess;
}
public DebugProcess getDebugProcess() {
return myDebugProcess;
}
@NotNull
public List<Location> locationsOfLine(@NotNull ReferenceType type, @NotNull SourcePosition position) throws NoDataException {
try {
final int line = position.getLine() + 1;
return type.locationsOfLine(DebugProcess.JAVA_STRATUM, null, line);
}
catch (AbsentInformationException ignored) {
}
return Collections.emptyList();
}
public ClassPrepareRequest createPrepareRequest(@NotNull final ClassPrepareRequestor requestor, @NotNull final SourcePosition position) throws NoDataException {
final Ref<String> waitPrepareFor = new Ref<String>(null);
final Ref<ClassPrepareRequestor> waitRequestor = new Ref<ClassPrepareRequestor>(null);
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
PsiClass psiClass = JVMNameUtil.getClassAt(position);
if (psiClass == null) {
return;
}
if (PsiUtil.isLocalOrAnonymousClass(psiClass)) {
final PsiClass parent = JVMNameUtil.getTopLevelParentClass(psiClass);
if (parent == null) {
return;
}
final String parentQName = JVMNameUtil.getNonAnonymousClassName(parent);
if (parentQName == null) {
return;
}
waitPrepareFor.set(parentQName + "*");
waitRequestor.set(new ClassPrepareRequestor() {
public void processClassPrepare(DebugProcess debuggerProcess, ReferenceType referenceType) {
final CompoundPositionManager positionManager = ((DebugProcessImpl)debuggerProcess).getPositionManager();
final List<ReferenceType> positionClasses = positionManager.getAllClasses(position);
if (positionClasses.contains(referenceType)) {
requestor.processClassPrepare(debuggerProcess, referenceType);
}
}
});
}
else {
waitPrepareFor.set(JVMNameUtil.getNonAnonymousClassName(psiClass));
waitRequestor.set(requestor);
}
}
});
if (waitPrepareFor.get() == null) {
return null; // no suitable class found for this name
}
return myDebugProcess.getRequestsManager().createClassPrepareRequest(waitRequestor.get(), waitPrepareFor.get());
}
public SourcePosition getSourcePosition(final Location location) throws NoDataException {
DebuggerManagerThreadImpl.assertIsManagerThread();
if(location == null) {
return null;
}
PsiFile psiFile = getPsiFileByLocation(getDebugProcess().getProject(), location);
if(psiFile == null ) {
return null;
}
LOG.assertTrue(myDebugProcess != null);
if (location == null) {
return SourcePosition.createFromLine(psiFile, -1);
}
int lineNumber;
try {
lineNumber = location.lineNumber() - 1;
}
catch (InternalError e) {
lineNumber = -1;
}
if (psiFile instanceof PsiCompiledElement || lineNumber < 0) {
final String methodSignature = location.method().signature();
if (methodSignature == null) {
return SourcePosition.createFromLine(psiFile, -1);
}
final String methodName = location.method().name();
if(methodName == null) {
return SourcePosition.createFromLine(psiFile, -1);
}
if(location.declaringType() == null) {
return SourcePosition.createFromLine(psiFile, -1);
}
final MethodFinder finder = new MethodFinder(location.declaringType().name(), methodSignature);
psiFile.accept(finder);
final PsiMethod compiledMethod = finder.getCompiledMethod();
if (compiledMethod == null) {
return SourcePosition.createFromLine(psiFile, -1);
}
return SourcePosition.createFromElement(compiledMethod);
}
return SourcePosition.createFromLine(psiFile, lineNumber);
}
@Nullable
private PsiFile getPsiFileByLocation(final Project project, final Location location) {
if (location == null) {
return null;
}
final ReferenceType refType = location.declaringType();
if (refType == null) {
return null;
}
if (DumbService.getInstance(project).isDumb()) {
return null;
}
final String originalQName = refType.name();
final GlobalSearchScope searchScope = myDebugProcess.getSearchScope();
PsiClass psiClass = DebuggerUtils.findClass(originalQName, project, searchScope); // try to lookup original name first
if (psiClass == null) {
int dollar = originalQName.indexOf('$');
if (dollar > 0) {
final String qName = originalQName.substring(0, dollar);
psiClass = DebuggerUtils.findClass(qName, project, searchScope);
}
}
if (psiClass != null) {
final PsiElement element = psiClass.getNavigationElement();
return element.getContainingFile();
}
else {
// try to search by filename
try {
PsiFile[] files = FilenameIndex.getFilesByName(project, refType.sourceName(), GlobalSearchScope.allScope(project));
for (PsiFile file : files) {
if (file instanceof PsiJavaFile) {
for (PsiClass cls : ((PsiJavaFile)file).getClasses()) {
if (StringUtil.equals(originalQName, cls.getQualifiedName())) {
return file;
}
}
}
}
}
catch (AbsentInformationException ignore) {
}
}
return null;
}
@NotNull
public List<ReferenceType> getAllClasses(@NotNull final SourcePosition position) throws NoDataException {
final Ref<String> baseClassNameRef = new Ref<String>(null);
final Ref<PsiClass> classAtPositionRef = new Ref<PsiClass>(null);
final Ref<Boolean> isLocalOrAnonymous = new Ref<Boolean>(Boolean.FALSE);
final Ref<Integer> requiredDepth = new Ref<Integer>(0);
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
final PsiClass psiClass = JVMNameUtil.getClassAt(position);
if (psiClass != null) {
classAtPositionRef.set(psiClass);
if (PsiUtil.isLocalOrAnonymousClass(psiClass)) {
isLocalOrAnonymous.set(Boolean.TRUE);
final PsiClass topLevelClass = JVMNameUtil.getTopLevelParentClass(psiClass);
if (topLevelClass != null) {
final String parentClassName = JVMNameUtil.getNonAnonymousClassName(topLevelClass);
if (parentClassName != null) {
requiredDepth.set(getNestingDepth(psiClass));
baseClassNameRef.set(parentClassName);
}
else {
LOG.error("The name of a parent of a local (anonymous) class is null");
}
}
else {
LOG.error("Local or anonymous class has no non-local parent");
}
}
else {
final String className = JVMNameUtil.getNonAnonymousClassName(psiClass);
if (className != null) {
baseClassNameRef.set(className);
}
}
}
}
});
final String className = baseClassNameRef.get();
if (className == null) {
return Collections.emptyList();
}
if (!isLocalOrAnonymous.get()) {
return myDebugProcess.getVirtualMachineProxy().classesByName(className);
}
// the name is a parent class for a local or anonymous class
final List<ReferenceType> outers = myDebugProcess.getVirtualMachineProxy().classesByName(className);
final List<ReferenceType> result = new ArrayList<ReferenceType>(outers.size());
for (ReferenceType outer : outers) {
final ReferenceType nested = findNested(outer, 0, classAtPositionRef.get(), requiredDepth.get(), position);
if (nested != null) {
result.add(nested);
}
}
return result;
}
private static int getNestingDepth(PsiClass aClass) {
int depth = 0;
PsiClass enclosing = getEnclosingClass(aClass);
while (enclosing != null) {
depth++;
enclosing = getEnclosingClass(enclosing);
}
return depth;
}
/**
* See IDEA-121739
* Anonymous classes inside other anonymous class parameters list should belong to parent class
* Inner in = new Inner(new Inner2(){}) {};
* Parent of Inner2 sub class here is not Inner sub class
*/
private static PsiClass getEnclosingClass(PsiElement element) {
if (element == null) {
return null;
}
element = element.getParent();
PsiElement previous = null;
while (element != null) {
if (PsiClass.class.isInstance(element) && !(previous instanceof PsiExpressionList)) {
//noinspection unchecked
return (PsiClass)element;
}
if (element instanceof PsiFile) {
return null;
}
previous = element;
element = element.getParent();
}
return null;
}
@Nullable
private ReferenceType findNested(final ReferenceType fromClass, final int currentDepth, final PsiClass classToFind, final int requiredDepth, final SourcePosition position) {
final VirtualMachineProxyImpl vmProxy = myDebugProcess.getVirtualMachineProxy();
if (fromClass.isPrepared()) {
try {
if (currentDepth < requiredDepth) {
final List<ReferenceType> nestedTypes = vmProxy.nestedTypes(fromClass);
for (ReferenceType nested : nestedTypes) {
final ReferenceType found = findNested(nested, currentDepth + 1, classToFind, requiredDepth, position);
if (found != null) {
return found;
}
}
return null;
}
final boolean canGetSynthetic = vmProxy.canGetSyntheticAttribute();
int rangeBegin = Integer.MAX_VALUE;
int rangeEnd = Integer.MIN_VALUE;
for (Location location : fromClass.allLineLocations()) {
final int lnumber = location.lineNumber();
if (lnumber <= 1) {
// should be a native method, skipping
// sometimes compiler generates location where line number is exactly 1 (e.g. GWT)
// such locations are hardly correspond to real lines in code, so skipping them too
continue;
}
final Method method = location.method();
if (method == null || (canGetSynthetic && method.isSynthetic()) || method.isBridge()) {
// do not take into account synthetic stuff
continue;
}
final int locationLine = lnumber - 1;
rangeBegin = Math.min(rangeBegin, locationLine);
rangeEnd = Math.max(rangeEnd, locationLine);
}
final int positionLine = position.getLine();
if (positionLine >= rangeBegin && positionLine <= rangeEnd) {
// choose the second line to make sure that only this class' code exists on the line chosen
// Otherwise the line (depending on the offset in it) can contain code that belongs to different classes
// and JVMNameUtil.getClassAt(candidatePosition) will return the wrong class.
// Example of such line:
// list.add(new Runnable(){......
// First offsets belong to parent class, and offsets inside te substring "new Runnable(){" belong to anonymous runnable.
final int finalRangeBegin = rangeBegin;
final int finalRangeEnd = rangeEnd;
return ApplicationManager.getApplication().runReadAction(new NullableComputable<ReferenceType>() {
public ReferenceType compute() {
if (!classToFind.isValid()) {
return null;
}
final int line = Math.min(finalRangeBegin + 1, finalRangeEnd);
final SourcePosition candidatePosition = positionLine == line? position : SourcePosition.createFromLine(position.getFile(), line);
return classToFind.equals(JVMNameUtil.getClassAt(candidatePosition)) ? fromClass : null;
}
});
}
}
catch (AbsentInformationException ignored) {
}
}
return null;
}
//don't use JavaRecursiveElementWalkingVisitor because getNextSibling() works slowly for compiled elements
private class MethodFinder extends JavaRecursiveElementVisitor {
private final String myClassName;
private PsiClass myCompiledClass;
private final String myMethodSignature;
private PsiMethod myCompiledMethod;
public MethodFinder(final String className, final String methodSignature) {
myClassName = className;
myMethodSignature = methodSignature;
}
@Override public void visitClass(PsiClass aClass) {
final List<ReferenceType> allClasses = myDebugProcess.getPositionManager().getAllClasses(SourcePosition.createFromElement(aClass));
for (ReferenceType referenceType : allClasses) {
if (referenceType.name().equals(myClassName)) {
myCompiledClass = aClass;
}
}
aClass.acceptChildren(this);
}
@Override public void visitMethod(PsiMethod method) {
try {
//noinspection HardCodedStringLiteral
String methodName = method.isConstructor() ? "<init>" : method.getName();
PsiClass containingClass = method.getContainingClass();
if(containingClass != null &&
containingClass.equals(myCompiledClass) &&
methodName.equals(methodName) &&
JVMNameUtil.getJVMSignature(method).getName(myDebugProcess).equals(myMethodSignature)) {
myCompiledMethod = method;
}
}
catch (EvaluateException e) {
LOG.debug(e);
}
}
public PsiClass getCompiledClass() {
return myCompiledClass;
}
public PsiMethod getCompiledMethod() {
return myCompiledMethod;
}
}
}