/******************************************************************************* * Copyright (c) 2005, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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.IClassFile; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; 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 IClassFile) { type = ((IClassFile) 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); } }