com.android.tools.idea.gradle.dependencies.GradleDependencyManager.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.gradle.dependencies.GradleDependencyManager.java

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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.android.tools.idea.gradle.dependencies;

import com.android.SdkConstants;
import com.android.ide.common.repository.GradleCoordinate;
import com.android.tools.idea.gradle.dsl.model.GradleBuildModel;
import com.android.tools.idea.gradle.dsl.model.dependencies.ArtifactDependencyModel;
import com.android.tools.idea.gradle.dsl.model.dependencies.DependenciesModel;
import com.android.tools.idea.gradle.project.model.AndroidModuleModel;
import com.android.tools.idea.gradle.project.sync.GradleSyncInvoker;
import com.android.tools.idea.gradle.project.sync.GradleSyncListener;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.android.tools.idea.templates.RepositoryUrlManager;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

import static com.android.tools.idea.gradle.dsl.model.dependencies.CommonConfigurationNames.COMPILE;
import static com.intellij.openapi.util.text.StringUtil.pluralize;

public class GradleDependencyManager {
    private static final String ADD_DEPENDENCY = "Add Dependency";

    @NotNull
    public static GradleDependencyManager getInstance(@NotNull Project project) {
        return ServiceManager.getService(project, GradleDependencyManager.class);
    }

    /**
     * Returns {@code true} if the main artifact of the given Android model depends on the given artifact, which consists of a group id and an
     * artifact id, such as {@link SdkConstants#APPCOMPAT_LIB_ARTIFACT}.
     *
     * @param module the module to check
     * @param artifact the artifact
     * @return {@code true} if the module depends on the given artifact (including transitively)
     */
    public boolean dependsOn(@NotNull Module module, @NotNull String artifact) {
        AndroidModuleModel gradleModel = AndroidModuleModel.get(module);
        return gradleModel != null && GradleUtil.dependsOn(gradleModel, artifact);
    }

    /**
     * Returns the dependencies that are NOT included in the specified module.
     * Note: the version of the dependency is disregarded.
     *
     * @param module the module to check dependencies in
     * @param dependencies the dependencies of interest.
     * @return a list of the dependencies NOT included in the module
     */
    @NotNull
    public List<GradleCoordinate> findMissingDependencies(@NotNull Module module,
            @NotNull Iterable<GradleCoordinate> dependencies) {
        AndroidModuleModel gradleModel = AndroidModuleModel.get(module);
        GradleBuildModel buildModel = GradleBuildModel.get(module);

        if (gradleModel == null && buildModel == null) {
            return Collections.emptyList();
        }

        RepositoryUrlManager manager = RepositoryUrlManager.get();
        List<GradleCoordinate> missingLibraries = Lists.newArrayList();
        for (GradleCoordinate coordinate : dependencies) {
            GradleCoordinate resolvedCoordinate = manager.resolveDynamicCoordinate(coordinate, null);
            if (resolvedCoordinate == null) {
                // We don't have anything installed, but we can keep trying with the unresolved coordinate if we have enough info
                if (coordinate.getArtifactId() == null || coordinate.getGroupId() == null) {
                    // We don't have enough info to continue. Skip.
                    // TODO Should this be an error ?
                    continue;
                }
            } else {
                coordinate = resolvedCoordinate;
            }

            boolean dependencyFound = false;
            // First look in the model returned by Gradle.
            if (gradleModel != null && GradleUtil.dependsOn(gradleModel,
                    String.format("%s:%s", coordinate.getGroupId(), coordinate.getArtifactId()))) {
                // GradleUtil.dependsOn method only checks the android library dependencies.
                // TODO: Consider updating it to also check for java library dependencies.
                dependencyFound = true;
            } else if (buildModel != null) {
                // Now, check in the model obtained from the gradle files.
                for (ArtifactDependencyModel dependency : buildModel.dependencies().artifacts(COMPILE)) {
                    if (Objects.equal(coordinate.getGroupId(), dependency.group().value())
                            && Objects.equal(coordinate.getArtifactId(), dependency.name().value())) {
                        dependencyFound = true;
                        break;
                    }
                }
            }

            if (!dependencyFound) {
                missingLibraries.add(coordinate);
            }
        }

        return missingLibraries;
    }

    /**
     * Ensures that all the specified dependencies are included in the specified module.
     * <p/>
     * If some dependencies are missing a dialog is presented to the user if those dependencies should be added to the module.
     * If the user agrees the dependencies are added. The caller may supply a callback to determine when the requested dependencies
     * have been added (this make take several seconds).
     *
     * @param module       the module to add dependencies to
     * @param dependencies the dependencies of interest.
     * @param callback     an optional callback to signal to completion of the added dependencies
     * @return true if the dependencies were already present in the module or if the user requested adding them, and
     * false if the dependency was missing and the user declined adding them
     */
    public boolean ensureLibraryIsIncluded(@NotNull Module module, @NotNull Iterable<GradleCoordinate> dependencies,
            @Nullable Runnable callback) {
        List<GradleCoordinate> missing = findMissingDependencies(module, dependencies);
        if (missing.isEmpty()) {
            return true;
        }

        GradleBuildModel buildModel = GradleBuildModel.get(module);
        if (buildModel == null) {
            return false;
        }

        if (userWantToAddDependencies(module, missing)) {
            addDependenciesInTransaction(buildModel, module, missing, callback);
            return true;
        }

        return false;
    }

    /**
     * Updates any coordinates to the versions specified in the dependencies list.
     * For example, if you pass it [com.android.support.constraint:constraint-layout:1.0.0-alpha2],
     * it will find any constraint layout occurrences of 1.0.0-alpha1 and replace them with 1.0.0-alpha2.
     */
    public boolean updateLibrariesToVersion(@NotNull Module module, @NotNull List<GradleCoordinate> dependencies,
            @Nullable Runnable callback) {
        GradleBuildModel buildModel = GradleBuildModel.get(module);
        if (buildModel == null) {
            return false;
        }
        updateDependenciesInTransaction(buildModel, module, dependencies, callback);
        return true;
    }

    private static boolean userWantToAddDependencies(@NotNull Module module,
            @NotNull Collection<GradleCoordinate> missing) {
        String libraryNames = StringUtil.join(missing, GradleCoordinate::getArtifactId, ", ");
        String message = String.format(
                "This operation requires the %1$s %2$s. \n\nWould you like to add %3$s %1$s now?",
                pluralize("library", missing.size()), libraryNames, pluralize("this", missing.size()));
        Project project = module.getProject();
        return Messages.showOkCancelDialog(project, message, "Add Project Dependency",
                Messages.getErrorIcon()) == Messages.OK;
    }

    private static void addDependenciesInTransaction(@NotNull GradleBuildModel buildModel, @NotNull Module module,
            @NotNull List<GradleCoordinate> coordinates, @Nullable Runnable callback) {
        assert !coordinates.isEmpty();

        Project project = module.getProject();
        new WriteCommandAction(project, ADD_DEPENDENCY) {
            @Override
            protected void run(@NotNull Result result) throws Throwable {
                addDependencies(buildModel, module, coordinates);
                requestProjectSync(project, callback);
            }
        }.execute();
    }

    private static void addDependencies(@NotNull GradleBuildModel buildModel, @NotNull Module module,
            @NotNull List<GradleCoordinate> coordinates) {
        ModuleRootModificationUtil.updateModel(module, model -> {
            DependenciesModel dependenciesModel = buildModel.dependencies();
            for (GradleCoordinate coordinate : coordinates) {
                dependenciesModel.addArtifact(COMPILE, coordinate.toString());
            }
            buildModel.applyChanges();
        });
    }

    private static void updateDependenciesInTransaction(@NotNull GradleBuildModel buildModel,
            @NotNull Module module, @NotNull List<GradleCoordinate> coordinates, @Nullable Runnable callback) {
        assert !coordinates.isEmpty();

        Project project = module.getProject();
        new WriteCommandAction(project, ADD_DEPENDENCY) {
            @Override
            protected void run(@NotNull Result result) throws Throwable {
                updateDependencies(buildModel, module, coordinates);
                requestProjectSync(project, callback);
            }
        }.execute();
    }

    private static void requestProjectSync(@NotNull Project project, @Nullable Runnable callback) {
        GradleSyncInvoker.Request request = new GradleSyncInvoker.Request().setGenerateSourcesOnSuccess(true);
        GradleSyncInvoker.getInstance().requestProjectSync(project, request, createSyncListener(callback));
    }

    private static void updateDependencies(@NotNull GradleBuildModel buildModel, @NotNull Module module,
            @NotNull List<GradleCoordinate> coordinates) {
        ModuleRootModificationUtil.updateModel(module, model -> {
            DependenciesModel dependenciesModel = buildModel.dependencies();
            for (GradleCoordinate gc : coordinates) {
                List<ArtifactDependencyModel> artifacts = Lists.newArrayList(dependenciesModel.artifacts());
                for (ArtifactDependencyModel m : artifacts) {
                    if (gc.getGroupId() != null && gc.getGroupId().equals(m.group().value())
                            && gc.getArtifactId() != null && gc.getArtifactId().equals(m.name().value())
                            && !gc.getRevision().equals(m.version().value())) {
                        dependenciesModel.remove(m);
                        dependenciesModel.addArtifact(m.configurationName(), gc.toString());
                    }
                }
            }
            buildModel.applyChanges();
        });
    }

    @Nullable
    private static GradleSyncListener createSyncListener(@Nullable Runnable callback) {
        if (callback == null) {
            return null;
        }
        return new GradleSyncListener.Adapter() {
            @Override
            public void syncSucceeded(@NotNull Project project) {
                callback.run();
            }

            @Override
            public void syncSkipped(@NotNull Project project) {
                callback.run();
            }
        };
    }
}