com.android.tools.idea.gradle.project.GradleProjectImporter.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.gradle.project.GradleProjectImporter.java

Source

/*
 * Copyright (C) 2013 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.project;

import com.android.SdkConstants;
import com.android.tools.idea.gradle.GradleImportNotificationListener;
import com.android.tools.idea.gradle.util.LocalProperties;
import com.android.tools.idea.gradle.util.Projects;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.intellij.ProjectTopics;
import com.intellij.ide.impl.NewProjectUtil;
import com.intellij.ide.impl.ProjectUtil;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.externalSystem.model.DataNode;
import com.intellij.openapi.externalSystem.model.ExternalSystemDataKeys;
import com.intellij.openapi.externalSystem.model.ProjectKeys;
import com.intellij.openapi.externalSystem.model.ProjectSystemId;
import com.intellij.openapi.externalSystem.model.project.ModuleData;
import com.intellij.openapi.externalSystem.model.project.ProjectData;
import com.intellij.openapi.externalSystem.service.project.ExternalProjectRefreshCallback;
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager;
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
import com.intellij.openapi.externalSystem.util.ExternalSystemBundle;
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.CompilerProjectExtension;
import com.intellij.openapi.roots.ModuleRootAdapter;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.IdeFrameEx;
import com.intellij.openapi.wm.impl.IdeFrameImpl;
import com.intellij.util.SystemProperties;
import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
import org.jetbrains.plugins.gradle.settings.GradleSettings;
import org.jetbrains.plugins.gradle.util.GradleConstants;

import java.io.File;
import java.io.IOException;
import java.util.Collection;

/**
 * Imports an Android-Gradle project without showing the "Import Project" Wizard UI.
 */
public class GradleProjectImporter {
    private static final Logger LOG = Logger.getInstance(GradleProjectImporter.class);
    private static final ProjectSystemId SYSTEM_ID = GradleConstants.SYSTEM_ID;

    private final ImporterDelegate myDelegate;

    @NotNull
    public static GradleProjectImporter getInstance() {
        return ServiceManager.getService(GradleProjectImporter.class);
    }

    public GradleProjectImporter() {
        myDelegate = new ImporterDelegate();
    }

    @VisibleForTesting
    GradleProjectImporter(ImporterDelegate delegate) {
        myDelegate = delegate;
    }

    /**
     * Re-imports an existing Android-Gradle project.
     *
     * @param project the given project. This method does nothing if the project is not an Android-Gradle project.
     */
    public void reImportProject(@NotNull final Project project) throws ConfigurationException {
        if (Projects.isGradleProject(project)) {
            FileDocumentManager.getInstance().saveAllDocuments();
            doImport(project, false, false);
        }
    }

    /**
     * Imports and opens the newly created Android project.
     *
     * @param projectName    name of the project.
     * @param projectRootDir root directory of the project.
     * @param androidSdk     Android SDK to set.
     * @param callback       called after the project has been imported.
     * @throws IOException            if any file I/O operation fails (e.g. creating the '.idea' directory.)
     * @throws ConfigurationException if any required configuration option is missing (e.g. Gradle home directory path.)
     */
    public void importProject(@NotNull String projectName, @NotNull File projectRootDir, @NotNull Sdk androidSdk,
            @Nullable final Callback callback) throws IOException, ConfigurationException {
        GradleImportNotificationListener.attachToManager();

        createTopLevelBuildFile(projectRootDir);
        createIdeaProjectDir(projectRootDir);

        final Project newProject = createProject(projectName, projectRootDir.getPath());

        setUpProject(newProject, androidSdk);

        if (!ApplicationManager.getApplication().isUnitTestMode()) {
            newProject.save();
        }

        LocalProperties.createFile(newProject, androidSdk);
        Projects.setProjectBuildAction(newProject, Projects.BuildAction.REBUILD);

        doImport(newProject, true, true);

        // Since importing is synchronous we should have modules now. Notify callback.
        if (notifyCallback(newProject, callback)) {
            configureGradleProject(newProject);
            return;
        }

        // If we got here, there is some bad timing and the module creation got delayed somehow. Notify callback as soon as the project roots
        // are created.
        final MessageBusConnection connection = newProject.getMessageBus().connect();
        connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
            @Override
            public void rootsChanged(ModuleRootEvent event) {
                Module[] modules = ModuleManager.getInstance(newProject).getModules();
                if (modules.length > 0) {
                    connection.disconnect();
                    // TODO: Consider moving callback to AndroidProjectDataService. It can reliably notify when a project has modules.
                    notifyCallback(newProject, callback);
                    configureGradleProject(newProject);
                }
            }
        });
    }

    private static void createTopLevelBuildFile(@NotNull File projectRootDir) throws IOException {
        File projectFile = new File(projectRootDir, SdkConstants.FN_BUILD_GRADLE);
        FileUtilRt.createIfNotExists(projectFile);
        String contents = "// Top-level build file where you can add configuration options common to all sub-projects/modules."
                + SystemProperties.getLineSeparator();
        FileUtil.writeToFile(projectFile, contents);
    }

    private static void createIdeaProjectDir(@NotNull File projectRootDir) throws IOException {
        File ideaDir = new File(projectRootDir, Project.DIRECTORY_STORE_FOLDER);
        FileUtil.ensureExists(ideaDir);
    }

    @NotNull
    private static Project createProject(@NotNull String projectName, @NotNull String projectPath)
            throws ConfigurationException {
        ProjectManager projectManager = ProjectManager.getInstance();
        Project newProject = projectManager.createProject(projectName, projectPath);
        if (newProject == null) {
            throw new NullPointerException("Failed to create a new IDEA project");
        }
        return newProject;
    }

    private static void setUpProject(@NotNull final Project newProject, @NotNull final Sdk androidSdk) {
        CommandProcessor.getInstance().executeCommand(newProject, new Runnable() {
            @Override
            public void run() {
                ApplicationManager.getApplication().runWriteAction(new Runnable() {
                    @Override
                    public void run() {
                        NewProjectUtil.applyJdkToProject(newProject, androidSdk);
                        // In practice, it really does not matter where the compiler output folder is. Gradle handles that. This is done just to please
                        // IDEA.
                        String compileOutputUrl = VfsUtilCore
                                .pathToUrl(newProject.getBasePath() + "/build/classes");
                        CompilerProjectExtension compilerProjectExt = CompilerProjectExtension
                                .getInstance(newProject);
                        assert compilerProjectExt != null;
                        compilerProjectExt.setCompilerOutputUrl(compileOutputUrl);
                        setUpGradleSettings(newProject);
                    }
                });
            }
        }, null, null);
    }

    private static void setUpGradleSettings(@NotNull Project newProject) {
        GradleProjectSettings projectSettings = new GradleProjectSettings();
        projectSettings.setPreferLocalInstallationToWrapper(false);
        projectSettings.setExternalProjectPath(newProject.getBasePath());
        projectSettings.setUseAutoImport(true);

        GradleSettings gradleSettings = GradleSettings.getInstance(newProject);
        gradleSettings.setLinkedProjectsSettings(ImmutableList.of(projectSettings));
    }

    private void doImport(@NotNull final Project project, final boolean openProject, boolean modal)
            throws ConfigurationException {
        final Ref<ConfigurationException> errorRef = new Ref<ConfigurationException>();
        myDelegate.importProject(project, new ExternalProjectRefreshCallback() {
            @Override
            public void onSuccess(@Nullable final DataNode<ProjectData> projectInfo) {
                assert projectInfo != null;
                final Application application = ApplicationManager.getApplication();
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        populateProject(project, projectInfo);
                        if (openProject) {
                            open(project);
                        }

                        if (!application.isUnitTestMode()) {
                            project.save();
                        }
                    }
                };
                if (application.isUnitTestMode()) {
                    runnable.run();
                } else {
                    application.invokeLater(runnable);
                }
            }

            @Override
            public void onFailure(@NotNull final String errorMessage, @Nullable String errorDetails) {
                ConfigurationException error = handleImportFailure(errorMessage, errorDetails);
                errorRef.set(error);
            }
        }, modal);

        ConfigurationException errorCause = errorRef.get();
        if (errorCause != null) {
            throw errorCause;
        }
    }

    @NotNull
    private static ConfigurationException handleImportFailure(@NotNull String errorMessage,
            @Nullable String errorDetails) {
        if (errorDetails != null) {
            LOG.warn(errorDetails);
        }
        String reason = "Failed to import Gradle project: " + errorMessage;
        return new ConfigurationException(ExternalSystemBundle.message("error.resolve.with.reason", reason),
                ExternalSystemBundle.message("error.resolve.generic"));
    }

    private static void populateProject(@NotNull final Project newProject,
            @NotNull final DataNode<ProjectData> projectInfo) {
        newProject.putUserData(ExternalSystemDataKeys.NEWLY_IMPORTED_PROJECT, Boolean.TRUE);
        StartupManager.getInstance(newProject).runWhenProjectIsInitialized(new Runnable() {
            @Override
            public void run() {
                ExternalSystemApiUtil.executeProjectChangeAction(new Runnable() {
                    @Override
                    public void run() {
                        ProjectRootManagerEx.getInstanceEx(newProject).mergeRootsChangesDuring(new Runnable() {
                            @Override
                            public void run() {
                                Collection<DataNode<ModuleData>> modules = ExternalSystemApiUtil
                                        .findAll(projectInfo, ProjectKeys.MODULE);
                                ProjectDataManager dataManager = ServiceManager
                                        .getService(ProjectDataManager.class);
                                dataManager.importData(ProjectKeys.MODULE, modules, newProject, true);
                            }
                        });
                    }
                });
            }
        });
    }

    private static void open(@NotNull final Project newProject) {
        ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx();
        ProjectUtil.updateLastProjectLocation(newProject.getBasePath());
        if (WindowManager.getInstance().isFullScreenSupportedInCurrentOS()) {
            IdeFocusManager instance = IdeFocusManager.findInstance();
            IdeFrame lastFocusedFrame = instance.getLastFocusedFrame();
            if (lastFocusedFrame instanceof IdeFrameEx) {
                boolean fullScreen = ((IdeFrameEx) lastFocusedFrame).isInFullScreen();
                if (fullScreen) {
                    newProject.putUserData(IdeFrameImpl.SHOULD_OPEN_IN_FULL_SCREEN, Boolean.TRUE);
                }
            }
        }
        projectManager.openProject(newProject);
    }

    private static boolean notifyCallback(@NotNull Project project, @Nullable Callback callback) {
        Module[] modules = ModuleManager.getInstance(project).getModules();
        if (modules.length == 0) {
            return false;
        }
        if (callback != null) {
            callback.projectImported(project);
        }
        return true;
    }

    private static void configureGradleProject(@NotNull Project project) {
        // We need to do this because AndroidGradleProjectComponent#projectOpened is being called when the project is created, instead of when
        // the project is opened. When 'projectOpened' is called, the project is not fully configured, and it does not looks like it is
        // Gradle-based, resulting in listeners (e.g. modules added events) not being registered. Here we force the listeners to be registered.
        AndroidGradleProjectComponent projectComponent = ServiceManager.getService(project,
                AndroidGradleProjectComponent.class);
        projectComponent.configureGradleProject(false);
    }

    // Makes it possible to mock invocations to the Gradle Tooling API.
    static class ImporterDelegate {
        void importProject(@NotNull Project project, @NotNull ExternalProjectRefreshCallback callback,
                boolean modal) throws ConfigurationException {
            try {
                ExternalSystemUtil.refreshProject(project, SYSTEM_ID, project.getBasePath(), callback, true, modal);
            } catch (RuntimeException e) {
                String externalSystemName = SYSTEM_ID.getReadableName();
                throw new ConfigurationException(e.getMessage(),
                        ExternalSystemBundle.message("error.cannot.parse.project", externalSystemName));
            }
        }
    }

    public interface Callback {
        /**
         * Invoked when a Gradle project has been imported. It is not guaranteed that the created IDEA project has been compiled.
         *
         * @param project the IDEA project created from the Gradle one.
         */
        void projectImported(@NotNull Project project);
    }
}