eu.numberfour.n4js.utils.resources.ExternalProjectBuildOrderProvider.java Source code

Java tutorial

Introduction

Here is the source code for eu.numberfour.n4js.utils.resources.ExternalProjectBuildOrderProvider.java

Source

/**
 * Copyright (c) 2016 NumberFour AG.
 * 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:
 *   NumberFour AG - Initial API and implementation
 */
package eu.numberfour.n4js.utils.resources;

import static com.google.common.collect.FluentIterable.from;
import static com.google.common.collect.Sets.newHashSet;
import static eu.numberfour.n4js.utils.Arrays2.transform;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;

import org.eclipse.core.internal.resources.Project;
import org.eclipse.core.internal.resources.Workspace.ProjectBuildConfigOrder;
import org.eclipse.core.resources.IBuildConfiguration;
import org.eclipse.core.resources.IProject;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import eu.numberfour.n4js.utils.resources.ComputeProjectOrder.VertexOrder;

/**
 * Utility class for providing build order (as {@link IBuildConfiguration build configuration} instances) for external
 * projects.
 */
@SuppressWarnings("restriction")
public abstract class ExternalProjectBuildOrderProvider {

    /**
     * Computes the build order among specified projects given as the argument.
     *
     * @param first
     *            the first mandatory project.
     * @param second
     *            the second project.
     * @param rest
     *            the rest of the projects. Can be omitted but must not be {@code null}, or must not contain
     *            {@code null} elements.
     *
     * @return the build order as an array of build configuration instances.
     */
    public static IBuildConfiguration[] getBuildOrder(final ExternalProject first, final ExternalProject second,
            final ExternalProject... rest) {

        return getBuildOrder(newHashSet(Lists.asList(first, second, rest)));
    }

    /**
     * Computes the build order among specified projects given as the argument.
     *
     * @param projects
     *            the projects.
     * @return the build order as an array of build configuration instances.
     */
    public static IBuildConfiguration[] getBuildOrder(final Iterable<ExternalProject> projects) {
        return vertexOrderToProjectBuildConfigOrder(computeActiveBuildConfigOrder(projects)).buildConfigurations;
    }

    /**
     * Computes the global total ordering of all open projects' active buildConfigs in the workspace based on build
     * configuration references. If an existing and open project's build config P references another existing and open
     * project's build config Q, then Q should come before P in the resulting ordering. If a build config references a
     * non-active build config it is added to the resulting ordered list. Closed and non-existent projects/buildConfigs
     * are ignored, and will not appear in the result. References to non-existent or closed projects/buildConfigs are
     * also ignored, as are any self-references.
     * <p>
     * When there are choices, the choice is made in a reasonably stable way. For example, given an arbitrary choice
     * between two project buildConfigs, the one with the lower collating project name and build config name will appear
     * earlier in the list.
     * </p>
     * <p>
     * When the build configuration reference graph contains cyclic references, it is impossible to honor all of the
     * relationships. In this case, the result ignores as few relationships as possible. For example, if P2 references
     * P1, P4 references P3, and P2 and P3 reference each other, then exactly one of the relationships between P2 and P3
     * will have to be ignored. The outcome will be either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also
     * contains complete details of any cycles present.
     * </p>
     *
     * @param projects
     *            the projects to calculate the active build configuration order among them.
     *
     * @return result describing the global active build configuration order
     */
    public static VertexOrder computeActiveBuildConfigOrder(final Iterable<ExternalProject> projects) {
        // Determine the full set of accessible active project buildConfigs in the workspace,
        // and all the accessible project buildConfigs that they reference. This forms a set
        // of all the project buildConfigs that will be returned.
        // Order the set in descending alphabetical order of project name then build config name,
        // as a secondary sort applied after sorting based on references, to achieve a stable
        // ordering.
        final SortedSet<IBuildConfiguration> allAccessibleBuildConfigs = new TreeSet<>(
                new BuildConfigurationComparator());

        // For each project's active build config, perform a depth first search in the reference graph
        // rooted at that build config.
        // This generates the required subset of the reference graph that is required to order all
        // the dependencies of the active project buildConfigs.
        final ExternalProject[] allProjects = Iterables.toArray(projects, ExternalProject.class);
        final List<IBuildConfiguration[]> edges = new ArrayList<>(allProjects.length);

        for (int i = 0; i < allProjects.length; i++) {
            final ExternalProject project = allProjects[i];
            // Ignore projects that are not accessible
            if (!project.isAccessible()) {
                continue;
            }

            // If the active build configuration hasn't already been explored
            // perform a depth first search rooted at it
            if (!allAccessibleBuildConfigs.contains(project.unsafeGetActiveBuildConfig())) {
                allAccessibleBuildConfigs.add(project.unsafeGetActiveBuildConfig());
                final Stack<IBuildConfiguration> stack = new Stack<>();
                stack.push(project.unsafeGetActiveBuildConfig());

                while (!stack.isEmpty()) {
                    final IBuildConfiguration buildConfiguration = stack.pop();

                    // Add all referenced buildConfigs from the current configuration
                    // (it is guaranteed to be accessible as it was pushed onto the stack)
                    final Project subProject = (Project) buildConfiguration.getProject();
                    final IBuildConfiguration[] refs = subProject
                            .internalGetReferencedBuildConfigs(buildConfiguration.getName(), false);
                    for (int j = 0; j < refs.length; j++) {
                        final IBuildConfiguration ref = refs[j];

                        // Ignore self references and references to projects that are not accessible
                        if (ref.equals(buildConfiguration)) {
                            continue;
                        }

                        // Add the referenced accessible configuration
                        edges.add(new IBuildConfiguration[] { buildConfiguration, ref });

                        // If we have already explored the referenced configuration, don't explore it again
                        if (allAccessibleBuildConfigs.contains(ref)) {
                            continue;
                        }

                        allAccessibleBuildConfigs.add(ref);

                        // Push the referenced configuration onto the stack so that it is explored by the depth first
                        // search
                        stack.push(ref);
                    }
                }
            }
        }
        return ComputeProjectOrder.computeVertexOrder(allAccessibleBuildConfigs, edges);
    }

    /**
     * Computes the global total ordering of all project buildConfigs in the workspace based on build config references.
     * If an existing and open build config P references another existing and open project build config Q, then Q should
     * come before P in the resulting ordering. Closed and non-existent projects/buildConfigs are ignored, and will not
     * appear in the result. References to non-existent or closed projects/buildConfigs are also ignored, as are any
     * self-references.
     * <p>
     * When there are choices, the choice is made in a reasonably stable way. For example, given an arbitrary choice
     * between two project buildConfigs, the one with the lower collating project name and build config name will appear
     * earlier in the list.
     * </p>
     * <p>
     * When the build config reference graph contains cyclic references, it is impossible to honor all of the
     * relationships. In this case, the result ignores as few relationships as possible. For example, if P2 references
     * P1, P4 references P3, and P2 and P3 reference each other, then exactly one of the relationships between P2 and P3
     * will have to be ignored. The outcome will be either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also
     * contains complete details of any cycles present.
     * </p>
     *
     * @return result describing the global project build configuration order
     */
    public static VertexOrder computeFullBuildConfigOrder(IBuildConfiguration[] preCalculatedConfigs) {
        // Compute the order for all accessible project buildConfigs
        SortedSet<IBuildConfiguration> allAccessibleBuildConfigurations = new TreeSet<>(
                new BuildConfigurationComparator());

        IProject[] allProjects = from(transform(preCalculatedConfigs, config -> config.getProject()))
                .toArray(IProject.class);
        List<IBuildConfiguration[]> edges = new ArrayList<>(allProjects.length);

        for (int i = 0; i < allProjects.length; i++) {
            Project project = (Project) allProjects[i];
            // Ignore projects that are not accessible
            if (!project.isAccessible())
                continue;

            IBuildConfiguration[] configs = project.internalGetBuildConfigs(false);
            for (int j = 0; j < configs.length; j++) {
                IBuildConfiguration config = configs[j];
                allAccessibleBuildConfigurations.add(config);
                IBuildConfiguration[] refs = project.internalGetReferencedBuildConfigs(config.getName(), false);
                for (int k = 0; k < refs.length; k++) {
                    IBuildConfiguration ref = refs[k];

                    // Ignore self references
                    if (ref.equals(config))
                        continue;

                    // Add the reference to the set of reachable configs + add an edge
                    allAccessibleBuildConfigurations.add(ref);
                    edges.add(new IBuildConfiguration[] { config, ref });
                }
            }
        }
        return ComputeProjectOrder.computeVertexOrder(allAccessibleBuildConfigurations, edges);
    }

    /**
     * Converts the {@link VertexOrder vertex order} into the appropriate {@link ProjectBuildConfigOrder build
     * configuration order}.
     *
     * @param order
     *            the order to convert.
     * @return the converted vertex order as a build configuration order. The returning configuration can be consumed by
     *         the Eclipse build framework.
     */
    public static ProjectBuildConfigOrder vertexOrderToProjectBuildConfigOrder(final VertexOrder order) {
        final IBuildConfiguration[] buildConfigs = new IBuildConfiguration[order.vertexes.length];
        System.arraycopy(order.vertexes, 0, buildConfigs, 0, order.vertexes.length);
        final IBuildConfiguration[][] knots = new IBuildConfiguration[order.knots.length][];
        for (int i = 0; i < order.knots.length; i++) {
            knots[i] = new IBuildConfiguration[order.knots[i].length];
            System.arraycopy(order.knots[i], 0, knots[i], 0, order.knots[i].length);
        }
        return new ProjectBuildConfigOrder(buildConfigs, order.hasCycles, knots);
    }

    // Comparator used to provide a stable ordering of project buildConfigs
    private static class BuildConfigurationComparator implements Comparator<IBuildConfiguration> {

        @Override
        public int compare(final IBuildConfiguration px, final IBuildConfiguration py) {
            int cmp = py.getProject().getName().compareTo(px.getProject().getName());
            if (cmp == 0) {
                cmp = py.getName().compareTo(px.getName());
            }
            return cmp;
        }
    }

}