blob: 74f177a8775fc182f93cf4a2d5afd4a7fee84414 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.debug.core;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.ISourceLocator;
import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IOrdinaryClassFile;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.IJavaReferenceType;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.IJavaType;
import org.eclipse.jdt.debug.core.IJavaValue;
import com.sun.jdi.VMDisconnectedException;
/**
* A Utilities class.
*
* @since 3.2
*/
public class JavaDebugUtils {
// The value must match org.eclipse.jdi.internal.VirtualMachineImpl#JAVA_STRATUM_NAME}
public static final String JAVA_STRATUM = "Java"; //$NON-NLS-1$
/**
* Resolves and returns a type from the Java model that corresponds to the
* declaring type of the given stack frame, or <code>null</code> if none.
*
* @param frame
* frame to resolve declaring type for
* @return corresponding Java model type or <code>null</code>
* @exception CoreException
* if an exception occurs during the resolution
* @since 3.2
*/
public static IType resolveDeclaringType(IJavaStackFrame frame)
throws CoreException {
IJavaElement javaElement = resolveJavaElement(frame, frame.getLaunch());
if (javaElement != null) {
return resolveType(frame.getDeclaringTypeName(), javaElement);
}
return null;
}
/**
* Resolves and returns a type from the Java model that corresponds to the
* type of the given value, or <code>null</code> if none.
*
* @param value
* value to resolve type for
* @return corresponding Java model type or <code>null</code>
* @exception CoreException
* if an exception occurs during the resolution
*/
public static IType resolveType(IJavaValue value) throws CoreException {
IJavaElement javaElement = resolveJavaElement(value, value.getLaunch());
if (javaElement != null) {
return resolveType(value.getJavaType().getName(), javaElement);
}
return null;
}
/**
* Resolves and returns the Java model type associated with the given Java
* debug type, or <code>null</code> if none.
*
* @param type
* Java debug model type
* @return Java model type or <code>null</code>
* @throws CoreException if resolving the type fails
*/
public static IType resolveType(IJavaType type) throws CoreException {
IJavaElement element = resolveJavaElement(type, type.getLaunch());
if (element != null) {
return resolveType(type.getName(), element);
}
return null;
}
/**
* Returns the source name associated with the given object, or
* <code>null</code> if none.
*
* @param object
* an object with an <code>IJavaStackFrame</code> adapter, an
* IJavaValue or an IJavaType
* @return the source name associated with the given object, or
* <code>null</code> if none
* @exception CoreException
* if unable to retrieve the source name
*/
public static String getSourceName(Object object) throws CoreException {
if (object instanceof String) {
// assume it's a file name
return (String) object;
}
IJavaStackFrame frame = null;
if (object instanceof IAdaptable) {
frame = ((IAdaptable) object)
.getAdapter(IJavaStackFrame.class);
}
String typeName = null;
try {
if (frame != null) {
if (frame.isObsolete()) {
return null;
}
String sourceName = frame.getSourcePath();
// TODO: this may break fix to bug 21518
if (sourceName == null) {
// no debug attributes, guess at source name
typeName = frame.getDeclaringTypeName();
} else {
return sourceName;
}
} else {
if (object instanceof IJavaValue) {
// look at its type
object = ((IJavaValue) object).getJavaType();
}
if (object instanceof IJavaReferenceType) {
IJavaReferenceType refType = (IJavaReferenceType) object;
IJavaDebugTarget target = ((IJavaDebugTarget) refType.getDebugTarget());
String[] sourcePaths = refType.getSourcePaths(target.getDefaultStratum());
if (sourcePaths != null && sourcePaths.length > 0) {
return sourcePaths[0];
}
}
if (object instanceof IJavaType) {
typeName = ((IJavaType) object).getName();
}
}
} catch (DebugException e) {
int code = e.getStatus().getCode();
if (code == IJavaThread.ERR_THREAD_NOT_SUSPENDED
|| code == IJavaStackFrame.ERR_INVALID_STACK_FRAME
|| e.getStatus().getException() instanceof VMDisconnectedException) {
return null;
}
throw e;
}
if (typeName != null) {
return generateSourceName(typeName);
}
return null;
}
/**
* Generates and returns a source file path based on a qualified type name.
* For example, when <code>java.lang.String</code> is provided, the returned
* source name is <code>java/lang/String.java</code>.
*
* @param qualifiedTypeName
* fully qualified type name that may contain inner types denoted
* with <code>$</code> character
* @return a source file path corresponding to the type name
*/
public static String generateSourceName(String qualifiedTypeName) {
int index = qualifiedTypeName.lastIndexOf('.');
if (index < 0) {
index = 0;
}
qualifiedTypeName = qualifiedTypeName.replace('.', File.separatorChar);
index = qualifiedTypeName.indexOf('$');
if (index >= 0) {
qualifiedTypeName = qualifiedTypeName.substring(0, index);
}
if (qualifiedTypeName.length() == 0) {
// likely a proxy class (see bug 40815)
qualifiedTypeName = null;
} else {
qualifiedTypeName = qualifiedTypeName + ".java"; //$NON-NLS-1$
}
return qualifiedTypeName;
}
/**
* Resolves the type corresponding to the given name contained in the given
* top-level Java element (class file, compilation unit, or type).
*
* @param qualifiedName
* fully qualified type name
* @param javaElement
* java element containing the type
* @return type
*/
private static IType resolveType(final String qualifiedName,
IJavaElement javaElement) {
IType type = null;
String[] typeNames = getNestedTypeNames(qualifiedName);
if (javaElement instanceof IOrdinaryClassFile) {
type = ((IOrdinaryClassFile) javaElement).getType();
} else if (javaElement instanceof ICompilationUnit) {
type = ((ICompilationUnit) javaElement).getType(typeNames[0]);
} else if (javaElement instanceof IType) {
type = (IType) javaElement;
}
if (type != null) {
for (int i = 1; i < typeNames.length; i++) {
String innerTypeName = typeNames[i];
class ResultException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final IType fResult;
public ResultException(IType result) {
fResult = result;
}
}
if (innerTypeName.length() > 0) {
try {
Integer.parseInt(innerTypeName.substring(0, 1)); // throws NFE if not an integer
// perform expensive lookup for anonymous types:
ASTParser parser = ASTParser.newParser(AST.JLS4);
parser.setResolveBindings(true);
parser.setSource(type.getTypeRoot());
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
cu.accept(new ASTVisitor(false) {
@Override
public boolean visit(AnonymousClassDeclaration node) {
ITypeBinding binding = node.resolveBinding();
if (binding == null) {
return false;
}
if (qualifiedName.equals(binding.getBinaryName())) {
throw new ResultException((IType) binding.getJavaElement());
}
return true;
}
@Override
public boolean visit(TypeDeclaration node) {
ITypeBinding binding = node.resolveBinding();
if (binding == null) {
return false;
}
if (qualifiedName.equals(binding.getBinaryName())) {
throw new ResultException((IType) binding.getJavaElement());
}
return true;
}
});
return type; // return enclosing type if exact type not
// found
} catch (NumberFormatException e) {
// normal nested type, continue
} catch (IllegalStateException e) {
return type; // binary class without source
} catch (ResultException e) {
return e.fResult;
}
}
type = type.getType(innerTypeName);
}
}
return type;
}
/**
* Returns the Java element corresponding to the given object or
* <code>null</code> if none, in the context of the given launch.
*
* @param launch
* provides source locator
* @param object
* object to resolve Java model element for
* @return corresponding Java element or <code>null</code>
* @throws CoreException if an exception occurs
*/
public static IJavaElement resolveJavaElement(Object object, ILaunch launch) throws CoreException {
Object sourceElement = resolveSourceElement(object, launch);
IJavaElement javaElement = getJavaElement(sourceElement);
if (javaElement == null) {
// fallback if default stratum does not provide a Java element
sourceElement = resolveSourceElement(object, JAVA_STRATUM, launch);
javaElement = getJavaElement(sourceElement);
}
return javaElement;
}
/**
* Returns the {@link IJavaElement} associated with the given source element
* or <code>null</code> if none.
*
* @param sourceElement
* a java element, object that adapts to a java element, or a
* resource
* @return corresponding {@link IJavaElement} or <code>null</code>
* @since 3.4.0
*/
public static IJavaElement getJavaElement(Object sourceElement) {
IJavaElement javaElement = null;
if (sourceElement instanceof IJavaElement) {
javaElement = (IJavaElement) sourceElement;
} else if (sourceElement instanceof IAdaptable) {
javaElement = ((IAdaptable) sourceElement).getAdapter(IJavaElement.class);
}
if (javaElement == null && sourceElement instanceof IResource) {
javaElement = JavaCore.create((IResource) sourceElement);
}
if (javaElement == null) {
return null;
}
if (!javaElement.exists()) {
return null;
}
return javaElement;
}
/**
* Returns the source element corresponding to the given object or <code>null</code> if none, in the context of the given launch.
*
* @param launch
* provides source locator
* @param object
* object to resolve source element for
* @return corresponding source element or <code>null</code>
* @throws CoreException
* if an exception occurs
*/
public static Object resolveSourceElement(Object object, ILaunch launch) throws CoreException {
return resolveSourceElement(object, null, launch);
}
/**
* Returns the source element corresponding to the given object in the given stratum or <code>null</code> if none, in the context of the given
* launch.
*
* @param launch
* provides source locator
* @param object
* object to resolve source element for
* @param stratum
* the stratum to use
* @return corresponding source element or <code>null</code>
* @throws CoreException
* if an exception occurs
*/
public static Object resolveSourceElement(Object object, String stratum, ILaunch launch) throws CoreException {
ISourceLocator sourceLocator = launch.getSourceLocator();
if (stratum != null && object instanceof IDebugElement) {
IDebugTarget debugTarget = ((IDebugElement) object).getDebugTarget();
if (debugTarget instanceof IJavaDebugTarget) {
IJavaDebugTarget javaDebugTarget = (IJavaDebugTarget) debugTarget;
String def = javaDebugTarget.getDefaultStratum();
try {
javaDebugTarget.setDefaultStratum(stratum);
return doSourceLookup(object, sourceLocator);
}
finally {
javaDebugTarget.setDefaultStratum(def);
}
}
}
return doSourceLookup(object, sourceLocator);
}
private static Object doSourceLookup(Object object, ISourceLocator sourceLocator) {
if (sourceLocator instanceof ISourceLookupDirector) {
ISourceLookupDirector director = (ISourceLookupDirector) sourceLocator;
return director.getSourceElement(object);
}
return null;
}
/**
* Resolves the {@link IJavaProject} within the context of the given {@link IJavaStackFrame}
*
* @param frame
* @return the {@link IJavaProject} or <code>null</code>
* @since 3.8.0
*/
public static IJavaProject resolveJavaProject(IJavaStackFrame frame) {
ILaunch launch = frame.getLaunch();
if(launch != null) {
try {
Object sourceElement = resolveSourceElement(frame, launch);
IJavaElement element = getJavaElement(sourceElement);
if (element == null) {
Object sourceElement1 = resolveSourceElement(frame, JAVA_STRATUM, launch);
if (sourceElement1 != null){
sourceElement = sourceElement1;
element = getJavaElement(sourceElement);
}
}
if(element != null) {
return element.getJavaProject();
}
// If Source element is not a Java element
if (sourceElement instanceof IResource) {
IJavaProject project = JavaCore.create(((IResource) sourceElement).getProject());
if (project.exists()) {
return project;
}
}
}
catch(CoreException ce) {
//do nothing, return null
}
}
return null;
}
/**
* Returns an array of simple type names that are part of the given type's
* qualified name. For example, if the given name is <code>x.y.A$B</code>,
* an array with <code>["A", "B"]</code> is returned.
*
* @param typeName
* fully qualified type name
* @return array of nested type names
*/
private static String[] getNestedTypeNames(String typeName) {
int index = typeName.lastIndexOf('.');
if (index >= 0) {
typeName = typeName.substring(index + 1);
}
index = typeName.indexOf('$');
List<String> list = new ArrayList<>(1);
while (index >= 0) {
list.add(typeName.substring(0, index));
typeName = typeName.substring(index + 1);
index = typeName.indexOf('$');
}
list.add(typeName);
return list.toArray(new String[list.size()]);
}
/**
* Returns the class file or compilation unit containing the given fully
* qualified name in the specified project. All registered java like file
* extensions are considered.
*
* @param qualifiedTypeName
* fully qualified type name
* @param project
* project to search in
* @return class file or compilation unit or <code>null</code>
* @throws CoreException if an exception occurs
*/
public static IJavaElement findElement(String qualifiedTypeName, IJavaProject project) throws CoreException {
String[] javaLikeExtensions = JavaCore.getJavaLikeExtensions();
String path = qualifiedTypeName;
int pos = path.indexOf('$');
if (pos != -1) {
path = path.substring(0, pos);
}
path = path.replace('.', IPath.SEPARATOR);
path += "."; //$NON-NLS-1$
for (String ext : javaLikeExtensions) {
IJavaElement element = project.findElement(new Path(path + ext));
if (element != null) {
return element;
}
}
return null;
}
/**
* Returns if the type names are equal, where being equals means:
* <ul>
* <li>The names are non-null and equal</li>
* <li>The names are both null</li>
* </ul>
*
* @param name1
* The first name
* @param name2
* The second name
* @return If the type names are equal
* @since 3.8.100
*/
public static boolean typeNamesEqual(String name1, String name2) {
if (name1 == null) {
return name2 == null;
}
return name1.equals(name2);
}
}