All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.java.debug.plugin.internal.JdtUtils Maven / Gradle / Ivy

There is a newer version: 0.53.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2017 Microsoft 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:
 *     Microsoft Corporation - initial API and implementation
*******************************************************************************/

package com.microsoft.java.debug.plugin.internal;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.debug.core.sourcelookup.ISourceContainer;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IModuleDescription;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.launching.sourcelookup.containers.JavaProjectSourceContainer;
import org.eclipse.jdt.launching.sourcelookup.containers.PackageFragmentRootSourceContainer;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;

import com.microsoft.java.debug.core.DebugException;
import com.microsoft.java.debug.core.StackFrameUtility;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ArrayType;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.Location;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StackFrame;
import com.sun.jdi.Type;

public class JdtUtils {
    private static final String TEST_SCOPE = "test";
    private static final String MAVEN_SCOPE_ATTRIBUTE = "maven.scope";
    private static final String GRADLE_SCOPE_ATTRIBUTE = "gradle_scope";

    /**
     * Returns the module this project represents or null if the Java project doesn't represent any named module.
     */
    public static String getModuleName(IJavaProject project) {
        if (project == null || !JavaRuntime.isModularProject(project)) {
            return null;
        }
        IModuleDescription module;
        try {
            module = project.getModuleDescription();
        } catch (CoreException e) {
            return null;
        }
        return module == null ? null : module.getElementName();
    }

    /**
     * Check if the project is a java project or not.
     */
    public static boolean isJavaProject(IProject project) {
        if (project == null || !project.exists()) {
            return false;
        }
        try {
            if (!project.isNatureEnabled(JavaCore.NATURE_ID)) {
                return false;
            }
        } catch (CoreException e) {
            return false;
        }
        return true;
    }

    /**
     * If the project represents a java project, then convert it to a java project.
     * Otherwise, return null.
     */
    public static IJavaProject getJavaProject(IProject project) {
        if (isJavaProject(project)) {
            return JavaCore.create(project);
        }
        return null;
    }

    /**
     * Given the project name, return the corresponding java project model.
     * If the project doesn't exist or not a java project, return null.
     */
    public static IJavaProject getJavaProject(String projectName) {
        if (StringUtils.isBlank(projectName)) {
            return null;
        }
        IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
        IProject project = root.getProject(projectName);
        return getJavaProject(project);
    }

    /**
     * List all available Java projects of the specified workspace.
     */
    public static List listJavaProjects(IWorkspaceRoot workspace) {
        List results = new ArrayList<>();
        for (IProject project : workspace.getProjects()) {
            if (isJavaProject(project)) {
                results.add(JavaCore.create(project));
            }
        }
        return results;
    }

    /**
     * Given the project name, return the corresponding project object.
     * If the project doesn't exist, return null.
     */
    public static IProject getProject(String projectName) {
        if (StringUtils.isBlank(projectName)) {
            return null;
        }
        IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
        return root.getProject(projectName);
    }

    /**
     * Compute the fragment roots for each test source folders.
     *
     * @param project the java project.
     * @return the fragment roots for each test source folders.
     */
    public static IPackageFragmentRoot[] getTestPackageFragmentRoots(IJavaProject project) {
        try {
            IPackageFragmentRoot[] packageFragmentRoot = project.getPackageFragmentRoots();
            List sources = new ArrayList<>();
            for (int i = 0; i < packageFragmentRoot.length; i++) {
                if (packageFragmentRoot[i].getElementType() == IJavaElement.PACKAGE_FRAGMENT_ROOT
                        && packageFragmentRoot[i].getKind() == IPackageFragmentRoot.K_SOURCE) {
                    IClasspathEntry cpe = packageFragmentRoot[i].getResolvedClasspathEntry();

                    if (isTest(cpe)) {
                        sources.add(packageFragmentRoot[i]);
                    }
                }
            }
            return sources.toArray(new IPackageFragmentRoot[0]);
        } catch (JavaModelException e) {
            // ignore
            return new IPackageFragmentRoot[0];
        }
    }

    /**
     * There is an issue on IClasspathEntry#isTest: it will return true if the scope is runtime, so we will this method for testing whether
     * the classpath entry is for test only.
     *
     * @param classpathEntry classpath entry
     * @return whether this classpath entry is only used in test
     */
    public static boolean isTest(final IClasspathEntry classpathEntry) {
        for (IClasspathAttribute attribute : classpathEntry.getExtraAttributes()) {
            if (GRADLE_SCOPE_ATTRIBUTE.equals(attribute.getName()) || MAVEN_SCOPE_ATTRIBUTE.equals(attribute.getName())) {
                return TEST_SCOPE.equals(attribute.getValue());
            }
        }
        return classpathEntry.isTest();
    }

    /**
     * Compute the possible source containers that the specified project could be associated with.
     * 

* If the project name is specified, it will put the source containers parsed from the specified project's * classpath entries in the front of the result, then the other projects at the same workspace. *

*

* Otherwise, just loop every projects at the current workspace and combine the parsed source containers directly. *

* @param projectName * the project name. * @return the possible source container list. */ public static ISourceContainer[] getSourceContainers(String projectName) { Set containers = new LinkedHashSet<>(); List projects = new ArrayList<>(); // If the project name is specified, firstly compute the source containers from the specified project's // classpath entries so that they can be placed in the front of the result. IProject targetProject = JdtUtils.getProject(projectName); if (targetProject != null) { projects.add(targetProject); } List workspaceProjects = Arrays.asList(ResourcesPlugin.getWorkspace().getRoot().getProjects()); projects.addAll(workspaceProjects); Set calculated = new LinkedHashSet<>(); projects.stream().distinct().map(project -> JdtUtils.getJavaProject(project)) .filter(javaProject -> javaProject != null && javaProject.exists()) .forEach(javaProject -> { // Add source containers associated with the project's source folders. containers.add(new JavaProjectSourceContainer(javaProject)); // Add source containers associated with the project's runtime classpath entries. containers.addAll(Arrays.asList(getSourceContainers(javaProject, calculated))); }); return containers.toArray(new ISourceContainer[0]); } private static ISourceContainer[] getSourceContainers(IJavaProject project, Set calculated) { if (project == null || !project.exists()) { return new ISourceContainer[0]; } try { IRuntimeClasspathEntry[] unresolved = JavaRuntime.computeUnresolvedRuntimeClasspath(project); List resolved = new ArrayList<>(); for (IRuntimeClasspathEntry entry : unresolved) { for (IRuntimeClasspathEntry resolvedEntry : JavaRuntime.resolveRuntimeClasspathEntry(entry, project)) { if (!calculated.contains(resolvedEntry)) { calculated.add(resolvedEntry); resolved.add(resolvedEntry); } } } Set containers = new LinkedHashSet<>(); containers.addAll(Arrays.asList( JavaRuntime.getSourceContainers(resolved.toArray(new IRuntimeClasspathEntry[0])))); // Due to a known jdt java 9 support bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=525840, // it would miss some JRE libraries source containers when the debugger is running on JDK9. // As a workaround, recompute the possible source containers for JDK9 jrt-fs.jar libraries. IRuntimeClasspathEntry jrtFs = resolved.stream().filter(entry -> { return entry.getType() == IRuntimeClasspathEntry.ARCHIVE && entry.getPath().lastSegment().equals("jrt-fs.jar"); }).findFirst().orElse(null); if (jrtFs != null && project.isOpen()) { IPackageFragmentRoot[] allRoots = project.getPackageFragmentRoots(); for (IPackageFragmentRoot root : allRoots) { if (root.getPath().equals(jrtFs.getPath()) && isSourceAttachmentEqual(root, jrtFs)) { containers.add(new PackageFragmentRootSourceContainer(root)); } } } return containers.toArray(new ISourceContainer[0]); } catch (CoreException ex) { // do nothing. } return new ISourceContainer[0]; } private static boolean isSourceAttachmentEqual(IPackageFragmentRoot root, IRuntimeClasspathEntry entry) throws JavaModelException { IPath entryPath = entry.getSourceAttachmentPath(); if (entryPath == null) { return true; } IPath rootPath = root.getSourceAttachmentPath(); if (rootPath == null) { // entry has a source attachment that the package root does not return false; } return rootPath.equals(entryPath); } /** * Given a source name info, search the associated source file or class file from the source container list. * * @param sourcePath * the target source name (e.g. com\microsoft\java\debug\xxx.java). * @param containers * the source container list. * @return the associated source file or class file. */ public static Object findSourceElement(String sourcePath, ISourceContainer[] containers) { if (containers == null) { return null; } for (ISourceContainer container : containers) { try { Object[] objects = container.findSourceElements(sourcePath); if (objects.length > 0 && (objects[0] instanceof IResource || objects[0] instanceof IClassFile)) { return objects[0]; } } catch (CoreException e) { // do nothing. } } return null; } /** * Given a stack frame, find the target java project that the associated source file belongs to. * * @param stackFrame * the stack frame. * @param containers * the source container list. * @return the java project. */ public static IJavaProject findProject(StackFrame stackFrame, ISourceContainer[] containers) { Location location = stackFrame.location(); try { Object sourceElement = findSourceElement(location.sourcePath(), containers); if (sourceElement instanceof IResource) { return JavaCore.create(((IResource) sourceElement).getProject()); } else if (sourceElement instanceof IClassFile) { IJavaProject javaProject = ((IClassFile) sourceElement).getJavaProject(); if (javaProject != null) { return javaProject; } } } catch (AbsentInformationException e) { // When the compiled .class file doesn't contain debug source information, return null. } return null; } /** * Given a stack frame, get the fully qualified type name that associated with the frame. * @param frame the stack frame * @return the fully qualified type name * @throws DebugException debug exception */ public static String getDeclaringTypeName(StackFrame frame) throws DebugException { return getGenericName(StackFrameUtility.getDeclaringType(frame)); } private static String getGenericName(ReferenceType type) throws DebugException { if (type instanceof ArrayType) { try { Type componentType; componentType = ((ArrayType) type).componentType(); if (componentType instanceof ReferenceType) { return getGenericName((ReferenceType) componentType) + "[]"; //$NON-NLS-1$ } return type.name(); } catch (ClassNotLoadedException e) { // we cannot create the generic name using the component type, // just try to create one with the information } } String signature = type.signature(); StringBuffer res = new StringBuffer(getTypeName(signature)); String genericSignature = type.genericSignature(); if (genericSignature != null) { String[] typeParameters = Signature.getTypeParameters(genericSignature); if (typeParameters.length > 0) { res.append('<').append(Signature.getTypeVariable(typeParameters[0])); for (int i = 1; i < typeParameters.length; i++) { res.append(',').append(Signature.getTypeVariable(typeParameters[i])); } res.append('>'); } } return res.toString(); } private static String getTypeName(String genericTypeSignature) { int arrayDimension = 0; while (genericTypeSignature.charAt(arrayDimension) == '[') { arrayDimension++; } int parameterStart = genericTypeSignature.indexOf('<'); StringBuffer name = new StringBuffer(); if (parameterStart < 0) { name.append(genericTypeSignature.substring(arrayDimension + 1, genericTypeSignature.length() - 1) .replace('/', '.')); } else { if (parameterStart != 0) { name.append(genericTypeSignature.substring(arrayDimension + 1, parameterStart).replace('/', '.')); } try { String sig = Signature.toString(genericTypeSignature) .substring(Math.max(parameterStart - 1, 0) - arrayDimension); name.append(sig.replace('/', '.')); } catch (IllegalArgumentException iae) { // do nothing name.append(genericTypeSignature); } } for (int i = 0; i < arrayDimension; i++) { name.append("[]"); //$NON-NLS-1$ } return name.toString(); } /** * Check whether two resources point to the same physical file. */ public static boolean isSameFile(IResource resource1, IResource resource2) { if (resource1 == null || resource2 == null) { return false; } if (Objects.equals(resource1, resource2)) { return true; } return Objects.equals(resource1.getLocation(), resource2.getLocation()); } /** * Check if the project is managed by Gradle Build Server. */ public static boolean isBspProject(IProject project) { return project != null && ProjectUtils.isJavaProject(project) && ProjectUtils.hasNature(project, "com.microsoft.gradle.bs.importer.GradleBuildServerProjectNature"); } /** * Get main project according to the main project name or main class name, * or return null if the main project cannot be resolved. */ public static IProject getMainProject(String mainProjectName, String mainClassName) { IProject mainProject = null; if (StringUtils.isNotBlank(mainProjectName)) { mainProject = ProjectUtils.getProject(mainProjectName); } if (mainProject == null && StringUtils.isNotBlank(mainClassName)) { try { List javaProjects = ResolveClasspathsHandler.getJavaProjectFromType(mainClassName); if (javaProjects.size() == 1) { mainProject = javaProjects.get(0).getProject(); } } catch (CoreException e) { JavaLanguageServerPlugin.logException("Failed to resolve project from main class name.", e); } } return mainProject; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy