org.cloudfoundry.ide.eclipse.server.standalone.internal.application.JavaPackageFragmentRootHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.ide.eclipse.server.standalone.internal.application.JavaPackageFragmentRootHandler.java

Source

/*******************************************************************************
 * Copyright (c) 2013, 2015 Pivotal Software, Inc. 
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of 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.
 *  
 *  Contributors:
 *     Pivotal Software, Inc. - initial API and implementation
 ********************************************************************************/
package org.cloudfoundry.ide.eclipse.server.standalone.internal.application;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.cloudfoundry.ide.eclipse.server.core.internal.CloudErrorUtil;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryPlugin;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryServer;
import org.cloudfoundry.ide.eclipse.server.standalone.internal.Messages;
import org.cloudfoundry.ide.eclipse.server.standalone.internal.startcommand.JavaTypeResolver;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
import org.eclipse.jdt.launching.JavaRuntime;

/**
 * Resolves all the package fragment roots and main type for a give Java
 * project. It includes handling all required Java projects for the given java
 * project.
 * 
 */
public class JavaPackageFragmentRootHandler {

    private IJavaProject javaProject;

    private IType type;

    private CloudFoundryServer cloudServer;

    public JavaPackageFragmentRootHandler(IJavaProject javaProject, CloudFoundryServer cloudServer) {
        this.javaProject = javaProject;
        this.cloudServer = cloudServer;
    }

    public IPackageFragmentRoot[] getPackageFragmentRoots(IProgressMonitor monitor) throws CoreException {
        IType type = getMainType(monitor);

        IPath[] classpathEntries = null;
        ILaunchConfiguration configuration = null;
        try {
            configuration = createConfiguration(type);
            classpathEntries = getRuntimeClasspaths(configuration);
        } finally {
            if (configuration != null) {
                configuration.delete();
            }
        }

        List<IPackageFragmentRoot> pckRoots = new ArrayList<IPackageFragmentRoot>();

        // Since the java project may have other required projects, fetch the
        // ordered
        // list of required projects that will be used to search for package
        // fragment roots
        // corresponding to the resolved class paths. The order of the java
        // projects to search in should
        // start with the most immediate list of required projects of the java
        // project.
        List<IJavaProject> javaProjectsToSearch = getOrderedJavaProjects(javaProject);

        // Find package fragment roots corresponding to the path entries.
        // Search through all java projects, not just the immediate java project
        // for the application that is being pushed to CF.
        for (IPath path : classpathEntries) {

            for (IJavaProject javaProject : javaProjectsToSearch) {

                try {
                    IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();

                    if (roots != null) {
                        List<IPackageFragmentRoot> foundRoots = new ArrayList<IPackageFragmentRoot>();

                        for (IPackageFragmentRoot packageFragmentRoot : roots) {
                            // Note that a class path entry may correspond to a
                            // Java project's target directory.
                            // Different fragment roots may use the same target
                            // directory, so relationship between fragment roots
                            // to a class path entry may be many-to-one.

                            if (isRootAtEntry(packageFragmentRoot, path)) {
                                foundRoots.add(packageFragmentRoot);
                            }
                        }

                        // Stop after the first successful search
                        if (!foundRoots.isEmpty()) {
                            pckRoots.addAll(foundRoots);
                            break;
                        }
                    }

                } catch (Exception e) {
                    throw CloudErrorUtil.toCoreException(e);
                }
            }

        }

        return pckRoots.toArray(new IPackageFragmentRoot[pckRoots.size()]);

    }

    /**
     * Attempts to resolve a main type in the associated Java project. Throws
     * {@link CoreException} if it failed to resolve main type or no main type
     * found.
     * 
     * @param monitor
     * @return non-null Main type.
     * @throws CoreException
     *             if failure occurred while resolving main type or no main type
     *             found
     */
    public IType getMainType(IProgressMonitor monitor) throws CoreException {
        if (type == null) {
            type = new JavaTypeResolver(javaProject, cloudServer.getServer().getServerType().getId())
                    .getMainTypesFromSource(monitor);
            if (type == null) {
                throw CloudErrorUtil.toCoreException(Messages.JavaCloudFoundryArchiver_ERROR_NO_MAIN);
            }
        }
        return type;
    }

    protected List<IJavaProject> getOrderedJavaProjects(IJavaProject project) {
        List<String> collectedProjects = new ArrayList<String>();
        getOrderedJavaProjectNames(Arrays.asList(project.getProject().getName()), collectedProjects);

        List<IJavaProject> projects = new ArrayList<IJavaProject>();

        for (String name : collectedProjects) {
            IProject prj = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
            if (prj != null) {
                IJavaProject jvPrj = JavaCore.create(prj);
                if (jvPrj != null && jvPrj.exists()) {
                    projects.add(jvPrj);
                }
            }
        }

        return projects;

    }

    protected void getOrderedJavaProjectNames(List<String> sameLevelRequiredProjects,
            List<String> collectedProjects) {
        // The order in which required projects are collected is as follows,
        // with the RHS
        // being required projects of the LHS
        // A -> BC
        // B -> D
        // C -> E
        // = total 5 projects, added in the order that they are encountered.
        // so final ordered list should be ABCDE
        if (sameLevelRequiredProjects == null) {
            return;
        }
        List<String> nextLevelRequiredProjects = new ArrayList<String>();
        // First add the current level java projects in the order they appear
        // and also collect each one's required names.
        for (String name : sameLevelRequiredProjects) {
            try {
                IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
                if (project != null) {
                    IJavaProject jvPrj = JavaCore.create(project);
                    if (jvPrj != null && jvPrj.exists()) {
                        if (!collectedProjects.contains(name)) {
                            collectedProjects.add(name);
                        }
                        String[] names = jvPrj.getRequiredProjectNames();
                        if (names != null && names.length > 0) {
                            for (String reqName : names) {
                                if (!nextLevelRequiredProjects.contains(reqName)) {
                                    nextLevelRequiredProjects.add(reqName);
                                }
                            }
                        }
                    }
                }

            } catch (JavaModelException e) {
                CloudFoundryPlugin.logError(e);
            }

        }

        // Now recurse to fetch the required projects for the
        // list of java projects that were added at the current level above
        if (!nextLevelRequiredProjects.isEmpty()) {
            getOrderedJavaProjectNames(nextLevelRequiredProjects, collectedProjects);

        }
    }

    protected ILaunchConfiguration createConfiguration(IType type) throws CoreException {

        ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();

        ILaunchConfigurationType configType = manager
                .getLaunchConfigurationType(IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION);

        ILaunchConfigurationWorkingCopy workingCopy = configType.newInstance(null,
                manager.generateLaunchConfigurationName(type.getTypeQualifiedName('.')));
        workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
                type.getFullyQualifiedName());
        workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
                type.getJavaProject().getElementName());
        workingCopy.setMappedResources(new IResource[] { type.getUnderlyingResource() });
        return workingCopy.doSave();
    }

    protected IPath[] getRuntimeClasspaths(ILaunchConfiguration configuration) throws CoreException {
        IRuntimeClasspathEntry[] entries = JavaRuntime.computeUnresolvedRuntimeClasspath(configuration);
        entries = JavaRuntime.resolveRuntimeClasspath(entries, configuration);

        ArrayList<IPath> userEntries = new ArrayList<IPath>(entries.length);
        for (int i = 0; i < entries.length; i++) {
            if (entries[i].getClasspathProperty() == IRuntimeClasspathEntry.USER_CLASSES) {

                String location = entries[i].getLocation();
                if (location != null) {
                    IPath entry = Path.fromOSString(location);
                    if (!userEntries.contains(entry)) {
                        userEntries.add(entry);
                    }
                }
            }
        }
        return userEntries.toArray(new IPath[userEntries.size()]);
    }

    /**
     * 
     * Determines if the given package fragment root corresponds to the class
     * path entry path.
     * <p/>
     * Note that different package fragment roots may point to the same class
     * path entry.
     * <p/>
     * Example:
     * <p/>
     * A Java project may have the following package fragment roots:
     * <p/>
     * - src/main/java
     * <p/>
     * - src/main/resources
     * <p/>
     * Both may be using the same output folder:
     * <p/>
     * target/classes.
     * <p/>
     * In this case, the output folder will have a class path entry -
     * target/classes - and it will be the same for both roots, and this method
     * will return true for both roots if passed the entry for target/classes
     * 
     * @param root
     *            to check if it corresponds to the given class path entry path
     * @param entry
     * @return true if root is at the given entry
     */
    private static boolean isRootAtEntry(IPackageFragmentRoot root, IPath entry) {
        try {
            IClasspathEntry cpe = root.getRawClasspathEntry();
            if (cpe.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
                IPath outputLocation = cpe.getOutputLocation();
                if (outputLocation == null) {
                    outputLocation = root.getJavaProject().getOutputLocation();
                }

                IPath location = ResourcesPlugin.getWorkspace().getRoot().findMember(outputLocation).getLocation();
                if (entry.equals(location)) {
                    return true;
                }
            }
        } catch (JavaModelException e) {
            CloudFoundryPlugin.logError(e);
        }

        IResource resource = root.getResource();
        if (resource != null && entry.equals(resource.getLocation())) {
            return true;
        }

        IPath path = root.getPath();
        if (path != null && entry.equals(path)) {
            return true;
        }

        return false;
    }
}