com.facebook.buck.ide.intellij.IjModuleGraphFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.ide.intellij.IjModuleGraphFactory.java

Source

/*
 * Copyright 2015-present Facebook, Inc.
 *
 * Licensed under 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.
 */
package com.facebook.buck.ide.intellij;

import com.facebook.buck.ide.intellij.aggregation.AggregationModule;
import com.facebook.buck.ide.intellij.aggregation.AggregationModuleFactory;
import com.facebook.buck.ide.intellij.aggregation.AggregationTree;
import com.facebook.buck.ide.intellij.model.DependencyType;
import com.facebook.buck.ide.intellij.model.IjLibrary;
import com.facebook.buck.ide.intellij.model.IjLibraryFactory;
import com.facebook.buck.ide.intellij.model.IjModule;
import com.facebook.buck.ide.intellij.model.IjModuleFactory;
import com.facebook.buck.ide.intellij.model.IjProjectConfig;
import com.facebook.buck.ide.intellij.model.IjProjectElement;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.rules.AbstractDescriptionArg;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.TargetNode;
import com.facebook.buck.util.MoreCollectors;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

public final class IjModuleGraphFactory {
    /**
     * Create all the modules we are capable of representing in IntelliJ from the supplied graph.
     *
     * @param targetGraph graph whose nodes will be converted to {@link IjModule}s.
     * @return map which for every BuildTarget points to the corresponding IjModule. Multiple
     *     BuildTarget can point to one IjModule (many:one mapping), the BuildTargets which can't be
     *     prepresented in IntelliJ are missing from this mapping.
     */
    private static ImmutableMap<BuildTarget, IjModule> createModules(ProjectFilesystem projectFilesystem,
            TargetGraph targetGraph, IjModuleFactory moduleFactory,
            AggregationModuleFactory aggregationModuleFactory, final int minimumPathDepth,
            ImmutableSet<String> ignoredTargetLabels) {

        ImmutableListMultimap<Path, TargetNode<?, ?>> baseTargetPathMultimap = targetGraph.getNodes().stream()
                .filter(input -> SupportedTargetTypeRegistry
                        .isTargetTypeSupported(input.getDescription().getClass()))
                .filter(targetNode -> {
                    AbstractDescriptionArg arg = (AbstractDescriptionArg) targetNode.getConstructorArg();
                    return !arg.labelsContainsAnyOf(ignoredTargetLabels);
                })
                // IntelliJ doesn't support referring to source files which aren't below the root of the
                // project. Filter out those cases proactively, so that we don't try to resolve files
                // relative to the wrong ProjectFilesystem.
                // Maybe one day someone will fix this.
                .filter(targetNode -> isInRootCell(projectFilesystem, targetNode))
                .collect(MoreCollectors.toImmutableListMultimap(
                        targetNode -> targetNode.getBuildTarget().getBasePath(), targetNode -> targetNode));

        AggregationTree aggregationTree = createAggregationTree(aggregationModuleFactory, baseTargetPathMultimap);

        aggregationTree.aggregateModules(minimumPathDepth);

        ImmutableMap.Builder<BuildTarget, IjModule> moduleByBuildTarget = new ImmutableMap.Builder<>();

        aggregationTree.getModules().stream().filter(aggregationModule -> !aggregationModule.getTargets().isEmpty())
                .forEach(aggregationModule -> {
                    IjModule module = moduleFactory.createModule(aggregationModule.getModuleBasePath(),
                            aggregationModule.getTargets(), aggregationModule.getExcludes());
                    module.getTargets().forEach(buildTarget -> moduleByBuildTarget.put(buildTarget, module));
                });

        return moduleByBuildTarget.build();
    }

    private static AggregationTree createAggregationTree(AggregationModuleFactory aggregationModuleFactory,
            ImmutableListMultimap<Path, TargetNode<?, ?>> targetNodesByBasePath) {
        Map<Path, AggregationModule> pathToAggregationModuleMap = targetNodesByBasePath.asMap().entrySet().stream()
                .collect(
                        MoreCollectors.toImmutableMap(Map.Entry::getKey,
                                pathWithTargetNode -> aggregationModuleFactory.createAggregationModule(
                                        pathWithTargetNode.getKey(),
                                        ImmutableSet.copyOf(pathWithTargetNode.getValue()))));

        Path rootPath = Paths.get("");

        AggregationModule rootAggregationModule = pathToAggregationModuleMap.get(rootPath);
        if (rootAggregationModule == null) {
            rootAggregationModule = aggregationModuleFactory.createAggregationModule(rootPath, ImmutableSet.of());
        }

        AggregationTree aggregationTree = new AggregationTree(rootAggregationModule);

        pathToAggregationModuleMap.entrySet().stream().filter(e -> !rootPath.equals(e.getKey()))
                .forEach(e -> aggregationTree.addModule(e.getKey(), e.getValue()));

        return aggregationTree;
    }

    /**
     * @param projectConfig the project config used
     * @param targetGraph input graph.
     * @param libraryFactory library factory.
     * @param moduleFactory module factory.
     * @return module graph corresponding to the supplied {@link TargetGraph}. Multiple targets from
     *     the same base path are mapped to a single module, therefore an IjModuleGraph edge exists
     *     between two modules (Ma, Mb) if a TargetGraph edge existed between a pair of nodes (Ta, Tb)
     *     and Ma contains Ta and Mb contains Tb.
     */
    public static IjModuleGraph from(final ProjectFilesystem projectFilesystem, final IjProjectConfig projectConfig,
            final TargetGraph targetGraph, final IjLibraryFactory libraryFactory,
            final IjModuleFactory moduleFactory, final AggregationModuleFactory aggregationModuleFactory) {
        ImmutableSet<String> ignoredTargetLabels = projectConfig.getIgnoredTargetLabels();
        final ImmutableMap<BuildTarget, IjModule> rulesToModules = createModules(projectFilesystem, targetGraph,
                moduleFactory, aggregationModuleFactory,
                projectConfig.getAggregationMode().getGraphMinimumDepth(targetGraph.getNodes().size()),
                ignoredTargetLabels);
        final ExportedDepsClosureResolver exportedDepsClosureResolver = new ExportedDepsClosureResolver(targetGraph,
                ignoredTargetLabels);
        ImmutableMap.Builder<IjProjectElement, ImmutableMap<IjProjectElement, DependencyType>> depsBuilder = ImmutableMap
                .builder();
        final Set<IjLibrary> referencedLibraries = new HashSet<>();

        for (final IjModule module : ImmutableSet.copyOf(rulesToModules.values())) {
            Map<IjProjectElement, DependencyType> moduleDeps = new HashMap<>();

            for (Map.Entry<BuildTarget, DependencyType> entry : module.getDependencies().entrySet()) {
                BuildTarget depBuildTarget = entry.getKey();
                TargetNode<?, ?> depTargetNode = targetGraph.get(depBuildTarget);

                AbstractDescriptionArg arg = (AbstractDescriptionArg) depTargetNode.getConstructorArg();
                if (arg.labelsContainsAnyOf(ignoredTargetLabels)) {
                    continue;
                }

                DependencyType depType = entry.getValue();
                ImmutableSet<IjProjectElement> depElements;

                if (depType.equals(DependencyType.COMPILED_SHADOW)) {
                    Optional<IjLibrary> library = libraryFactory.getLibrary(depTargetNode);
                    if (library.isPresent()) {
                        depElements = ImmutableSet.of(library.get());
                    } else {
                        depElements = ImmutableSet.of();
                    }
                } else {
                    depElements = Stream
                            .concat(exportedDepsClosureResolver.getExportedDepsClosure(depBuildTarget).stream(),
                                    Stream.of(depBuildTarget))
                            .filter(input -> {
                                TargetNode<?, ?> targetNode = targetGraph.get(input);
                                // IntelliJ doesn't support referring to source files which aren't below the root of
                                // the project. Filter out those cases proactively, so that we don't try to resolve
                                // files relative to the wrong ProjectFilesystem.
                                // Maybe one day someone will fix this.
                                return isInRootCell(projectFilesystem, targetNode);
                            }).filter(input -> {
                                // The exported deps closure can contain references back to targets contained
                                // in the module, so filter those out.
                                return !module.getTargets().contains(input);
                            }).map(depTarget -> {
                                IjModule depModule = rulesToModules.get(depTarget);
                                if (depModule != null) {
                                    return depModule;
                                }
                                TargetNode<?, ?> targetNode = targetGraph.get(depTarget);
                                return libraryFactory.getLibrary(targetNode).orElse(null);
                            }).filter(Objects::nonNull).collect(MoreCollectors.toImmutableSet());
                }

                for (IjProjectElement depElement : depElements) {
                    Preconditions.checkState(!depElement.equals(module));
                    DependencyType.putWithMerge(moduleDeps, depElement, depType);
                }
            }

            if (!module.getExtraClassPathDependencies().isEmpty()) {
                IjLibrary extraClassPathLibrary = IjLibrary.builder()
                        .setClassPaths(module.getExtraClassPathDependencies()).setTargets(ImmutableSet.of())
                        .setName("library_" + module.getName() + "_extra_classpath").build();
                moduleDeps.put(extraClassPathLibrary, DependencyType.PROD);
            }

            moduleDeps.keySet().stream().filter(dep -> dep instanceof IjLibrary).map(library -> (IjLibrary) library)
                    .forEach(referencedLibraries::add);

            depsBuilder.put(module, ImmutableMap.copyOf(moduleDeps));
        }

        referencedLibraries.forEach(library -> depsBuilder.put(library, ImmutableMap.of()));

        return new IjModuleGraph(depsBuilder.build());
    }

    private static boolean isInRootCell(ProjectFilesystem projectFilesystem, TargetNode<?, ?> targetNode) {
        return targetNode.getFilesystem().equals(projectFilesystem);
    }

    private IjModuleGraphFactory() {
    }
}