com.facebook.buck.jvm.java.intellij.ModuleBuildContext.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.jvm.java.intellij.ModuleBuildContext.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.jvm.java.intellij;

import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.rules.TargetNode;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;

import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * Holds all of the mutable state required during {@link IjModule} creation.
 */
class ModuleBuildContext {
    private final ImmutableSet<BuildTarget> circularDependencyInducingTargets;

    private Optional<IjModuleAndroidFacet.Builder> androidFacetBuilder;
    private ImmutableSet.Builder<Path> extraClassPathDependenciesBuilder;
    private ImmutableSet.Builder<IjFolder> generatedSourceCodeFoldersBuilder;
    private Map<Path, IjFolder> sourceFoldersMergeMap;
    // See comment in getDependencies for these two member variables.
    private Map<BuildTarget, DependencyType> dependencyTypeMap;
    private Multimap<Path, BuildTarget> dependencyOriginMap;

    public ModuleBuildContext(ImmutableSet<BuildTarget> circularDependencyInducingTargets) {
        this.circularDependencyInducingTargets = circularDependencyInducingTargets;
        this.androidFacetBuilder = Optional.empty();
        this.extraClassPathDependenciesBuilder = new ImmutableSet.Builder<>();
        this.generatedSourceCodeFoldersBuilder = ImmutableSet.builder();
        this.sourceFoldersMergeMap = new HashMap<>();
        this.dependencyTypeMap = new HashMap<>();
        this.dependencyOriginMap = HashMultimap.create();
    }

    public void ensureAndroidFacetBuilder() {
        if (!androidFacetBuilder.isPresent()) {
            androidFacetBuilder = Optional.of(IjModuleAndroidFacet.builder());
        }
    }

    public IjModuleAndroidFacet.Builder getOrCreateAndroidFacetBuilder() {
        ensureAndroidFacetBuilder();
        return androidFacetBuilder.get();
    }

    public boolean isAndroidFacetBuilderPresent() {
        return androidFacetBuilder.isPresent();
    }

    public Optional<IjModuleAndroidFacet> getAndroidFacet() {
        return androidFacetBuilder.map(IjModuleAndroidFacet.Builder::build);
    }

    public ImmutableSet<IjFolder> getSourceFolders() {
        return ImmutableSet.copyOf(sourceFoldersMergeMap.values());
    }

    public void addExtraClassPathDependency(Path path) {
        extraClassPathDependenciesBuilder.add(path);
    }

    public ImmutableSet<Path> getExtraClassPathDependencies() {
        return extraClassPathDependenciesBuilder.build();
    }

    public void addGeneratedSourceCodeFolder(IjFolder generatedFolder) {
        generatedSourceCodeFoldersBuilder.add(generatedFolder);
    }

    public ImmutableSet<IjFolder> getGeneratedSourceCodeFolders() {
        return generatedSourceCodeFoldersBuilder.build();
    }

    /**
     * Adds a source folder to the context. If a folder with the same path has already been added
     * the types of the two folders will be merged.
     *
     * @param folder folder to add/merge.
     */
    public void addSourceFolder(IjFolder folder) {
        Path path = folder.getPath();
        IjFolder otherFolder = sourceFoldersMergeMap.get(path);
        if (otherFolder != null) {
            folder = mergeAllowingTestToBePromotedToSource(folder, otherFolder);
        }
        sourceFoldersMergeMap.put(path, folder);
    }

    private IjFolder mergeAllowingTestToBePromotedToSource(IjFolder from, IjFolder to) {
        if ((from instanceof TestFolder && to instanceof SourceFolder)
                || (to instanceof TestFolder && from instanceof SourceFolder)) {
            return new SourceFolder(to.getPath(), from.getWantsPackagePrefix() || to.getWantsPackagePrefix(),
                    IjFolder.combineInputs(from, to));
        }

        Preconditions.checkArgument(from.getClass() == to.getClass());

        return from.merge(to);
    }

    public void addDeps(ImmutableSet<BuildTarget> buildTargets, DependencyType dependencyType) {
        addDeps(ImmutableSet.of(), buildTargets, dependencyType);
    }

    public void addCompileShadowDep(BuildTarget buildTarget) {
        DependencyType.putWithMerge(dependencyTypeMap, buildTarget, DependencyType.COMPILED_SHADOW);
    }

    /**
     * Record a dependency on a {@link BuildTarget}. The dependency's type will be merged if
     * multiple {@link TargetNode}s refer to it or if multiple TargetNodes include sources from
     * the same directory.
     *
     * @param sourcePaths the {@link Path}s to sources which need this dependency to build.
     *                    Can be empty.
     * @param buildTargets the {@link BuildTarget}s to depend on
     * @param dependencyType what is the dependency needed for.
     */
    public void addDeps(ImmutableSet<Path> sourcePaths, ImmutableSet<BuildTarget> buildTargets,
            DependencyType dependencyType) {
        for (BuildTarget buildTarget : buildTargets) {
            if (circularDependencyInducingTargets.contains(buildTarget)) {
                continue;
            }
            if (sourcePaths.isEmpty()) {
                DependencyType.putWithMerge(dependencyTypeMap, buildTarget, dependencyType);
            } else {
                for (Path sourcePath : sourcePaths) {
                    dependencyOriginMap.put(sourcePath, buildTarget);
                }
            }
        }
    }

    public ImmutableMap<BuildTarget, DependencyType> getDependencies() {
        // Some targets may introduce dependencies without contributing to the IjFolder set. These
        // are recorded in the dependencyTypeMap.
        // Dependencies associated with source paths inherit the type from the folder. This is because
        // IntelliJ only operates on folders and so it is impossible to distinguish between test and
        // production code if it's in the same folder. That in turn means test-only dependencies need
        // to be "promoted" to production dependencies in the above scenario to keep code compiling.
        // It is also possible that a target is included in both maps, in which case the type gets
        // merged anyway.
        // Merging types does not back-propagate: if TargetA depends on TargetB and the type of
        // TargetB has been changed that does not mean the dependency type of TargetA is changed too.
        Map<BuildTarget, DependencyType> result = new HashMap<>(dependencyTypeMap);
        for (Path path : dependencyOriginMap.keySet()) {
            DependencyType dependencyType = Preconditions
                    .checkNotNull(sourceFoldersMergeMap.get(path)) instanceof TestFolder ? DependencyType.TEST
                            : DependencyType.PROD;
            for (BuildTarget buildTarget : dependencyOriginMap.get(path)) {
                DependencyType.putWithMerge(result, buildTarget, dependencyType);
            }
        }
        return ImmutableMap.copyOf(result);
    }
}