blob: 6a3bb5ae28f26286b44e76679f25cb2617634ce2 [file] [log] [blame]
package com.intellij.openapi.externalSystem.service.project.wizard;
import com.intellij.ide.util.projectWizard.WizardContext;
import com.intellij.openapi.components.PersistentStateComponent;
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.ProjectSystemId;
import com.intellij.openapi.externalSystem.model.project.ProjectData;
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener;
import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode;
import com.intellij.openapi.externalSystem.service.internal.ExternalSystemResolveProjectTask;
import com.intellij.openapi.externalSystem.service.project.ExternalProjectRefreshCallback;
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager;
import com.intellij.openapi.externalSystem.service.settings.AbstractImportFromExternalSystemControl;
import com.intellij.openapi.externalSystem.settings.AbstractExternalSystemSettings;
import com.intellij.openapi.externalSystem.settings.ExternalProjectSettings;
import com.intellij.openapi.externalSystem.util.DisposeAwareProjectChange;
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.module.ModifiableModuleModel;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.packaging.artifacts.ModifiableArtifactModel;
import com.intellij.projectImport.ProjectImportBuilder;
import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
/**
* GoF builder for gradle-backed projects.
*
* @author Denis Zhdanov
* @since 8/1/11 1:29 PM
*/
@SuppressWarnings("MethodMayBeStatic")
public abstract class AbstractExternalProjectImportBuilder<C extends AbstractImportFromExternalSystemControl>
extends ProjectImportBuilder<DataNode<ProjectData>>
{
private static final Logger LOG = Logger.getInstance("#" + AbstractExternalProjectImportBuilder.class.getName());
@NotNull private final ProjectDataManager myProjectDataManager;
@NotNull private final C myControl;
@NotNull private final ProjectSystemId myExternalSystemId;
private DataNode<ProjectData> myExternalProjectNode;
public AbstractExternalProjectImportBuilder(@NotNull ProjectDataManager projectDataManager,
@NotNull C control,
@NotNull ProjectSystemId externalSystemId)
{
myProjectDataManager = projectDataManager;
myControl = control;
myExternalSystemId = externalSystemId;
}
@Override
public List<DataNode<ProjectData>> getList() {
return Arrays.asList(myExternalProjectNode);
}
@Override
public boolean isMarked(DataNode<ProjectData> element) {
return true;
}
@Override
public void setList(List<DataNode<ProjectData>> gradleProjects) {
}
@Override
public void setOpenProjectSettingsAfter(boolean on) {
}
@NotNull
public C getControl(@Nullable Project currentProject) {
myControl.setCurrentProject(currentProject);
return myControl;
}
public void prepare(@NotNull WizardContext context) {
myControl.reset();
String pathToUse = getFileToImport();
myControl.setLinkedProjectPath(pathToUse);
doPrepare(context);
}
protected abstract void doPrepare(@NotNull WizardContext context);
@Override
public List<Module> commit(final Project project,
ModifiableModuleModel model,
ModulesProvider modulesProvider,
ModifiableArtifactModel artifactModel)
{
project.putUserData(ExternalSystemDataKeys.NEWLY_IMPORTED_PROJECT, Boolean.TRUE);
final DataNode<ProjectData> externalProjectNode = getExternalProjectNode();
if (externalProjectNode != null) {
beforeCommit(externalProjectNode, project);
}
StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
AbstractExternalSystemSettings systemSettings = ExternalSystemApiUtil.getSettings(project, myExternalSystemId);
final ExternalProjectSettings projectSettings = getCurrentExternalProjectSettings();
Set<ExternalProjectSettings> projects = ContainerUtilRt.newHashSet(systemSettings.getLinkedProjectsSettings());
// add current importing project settings to linked projects settings or replace if similar already exist
projects.remove(projectSettings);
projects.add(projectSettings);
systemSettings.copyFrom(myControl.getSystemSettings());
systemSettings.setLinkedProjectsSettings(projects);
if (externalProjectNode != null) {
ExternalSystemUtil.ensureToolWindowInitialized(project, myExternalSystemId);
ExternalSystemApiUtil.executeProjectChangeAction(new DisposeAwareProjectChange(project) {
@Override
public void execute() {
ProjectRootManagerEx.getInstanceEx(project).mergeRootsChangesDuring(new Runnable() {
@Override
public void run() {
myProjectDataManager.importData(externalProjectNode.getKey(), Collections.singleton(externalProjectNode), project, true);
myExternalProjectNode = null;
}
});
}
});
final Runnable resolveDependenciesTask = new Runnable() {
@Override
public void run() {
String progressText = ExternalSystemBundle.message("progress.resolve.libraries", myExternalSystemId.getReadableName());
ProgressManager.getInstance().run(
new Task.Backgroundable(project, progressText, false) {
@Override
public void run(@NotNull final ProgressIndicator indicator) {
if(project.isDisposed()) return;
ExternalSystemResolveProjectTask task
= new ExternalSystemResolveProjectTask(myExternalSystemId, project, projectSettings.getExternalProjectPath(), false);
task.execute(indicator, ExternalSystemTaskNotificationListener.EP_NAME.getExtensions());
DataNode<ProjectData> projectWithResolvedLibraries = task.getExternalProject();
if (projectWithResolvedLibraries == null) {
return;
}
setupLibraries(projectWithResolvedLibraries, project);
}
});
}
};
UIUtil.invokeLaterIfNeeded(resolveDependenciesTask);
}
}
});
return Collections.emptyList();
}
@NotNull
private ExternalProjectSettings getCurrentExternalProjectSettings() {
ExternalProjectSettings result = myControl.getProjectSettings().clone();
File externalProjectConfigFile = getExternalProjectConfigToUse(new File(result.getExternalProjectPath()));
final String linkedProjectPath = FileUtil.toCanonicalPath(externalProjectConfigFile.getPath());
assert linkedProjectPath != null;
result.setExternalProjectPath(linkedProjectPath);
return result;
}
protected abstract void beforeCommit(@NotNull DataNode<ProjectData> dataNode, @NotNull Project project);
/**
* The whole import sequence looks like below:
* <p/>
* <pre>
* <ol>
* <li>Get project view from the gradle tooling api without resolving dependencies (downloading libraries);</li>
* <li>Allow to adjust project settings before importing;</li>
* <li>Create IJ project and modules;</li>
* <li>Ask gradle tooling api to resolve library dependencies (download the if necessary);</li>
* <li>Configure libraries used by the gradle project at intellij;</li>
* <li>Configure library dependencies;</li>
* </ol>
* </pre>
* <p/>
*
* @param projectWithResolvedLibraries gradle project with resolved libraries (libraries have already been downloaded and
* are available at file system under gradle service directory)
* @param project current intellij project which should be configured by libraries and module library
* dependencies information available at the given gradle project
*/
private void setupLibraries(@NotNull final DataNode<ProjectData> projectWithResolvedLibraries, final Project project) {
ExternalSystemApiUtil.executeProjectChangeAction(new DisposeAwareProjectChange(project) {
@Override
public void execute() {
ProjectRootManagerEx.getInstanceEx(project).mergeRootsChangesDuring(new Runnable() {
@Override
public void run() {
if (ExternalSystemApiUtil.isNewProjectConstruction()) {
// Clean existing libraries (if any).
LibraryTable projectLibraryTable = ProjectLibraryTable.getInstance(project);
if (projectLibraryTable == null) {
LOG.warn(
"Can't resolve external dependencies of the target gradle project (" + project + "). Reason: project "
+ "library table is undefined"
);
return;
}
LibraryTable.ModifiableModel model = projectLibraryTable.getModifiableModel();
try {
for (Library library : model.getLibraries()) {
model.removeLibrary(library);
}
}
finally {
model.commit();
}
}
// Register libraries.
myProjectDataManager.importData(Collections.<DataNode<?>>singletonList(projectWithResolvedLibraries), project, false);
}
});
}
});
}
@Nullable
private File getProjectFile() {
String path = myControl.getProjectSettings().getExternalProjectPath();
return path == null ? null : new File(path);
}
/**
* Asks current builder to ensure that target external project is defined.
*
* @param wizardContext current wizard context
* @throws ConfigurationException if gradle project is not defined and can't be constructed
*/
@SuppressWarnings("unchecked")
public void ensureProjectIsDefined(@NotNull WizardContext wizardContext) throws ConfigurationException {
final String externalSystemName = myExternalSystemId.getReadableName();
File projectFile = getProjectFile();
if (projectFile == null) {
throw new ConfigurationException(ExternalSystemBundle.message("error.project.undefined"));
}
projectFile = getExternalProjectConfigToUse(projectFile);
final Ref<ConfigurationException> error = new Ref<ConfigurationException>();
final ExternalProjectRefreshCallback callback = new ExternalProjectRefreshCallback() {
@Override
public void onSuccess(@Nullable DataNode<ProjectData> externalProject) {
myExternalProjectNode = externalProject;
}
@Override
public void onFailure(@NotNull String errorMessage, @Nullable String errorDetails) {
if (!StringUtil.isEmpty(errorDetails)) {
LOG.warn(errorDetails);
}
error.set(new ConfigurationException(ExternalSystemBundle.message("error.resolve.with.reason", errorMessage),
ExternalSystemBundle.message("error.resolve.generic")));
}
};
final Project project = getProject(wizardContext);
final File finalProjectFile = projectFile;
final String externalProjectPath = FileUtil.toCanonicalPath(finalProjectFile.getAbsolutePath());
final Ref<ConfigurationException> exRef = new Ref<ConfigurationException>();
executeAndRestoreDefaultProjectSettings(project, new Runnable() {
@Override
public void run() {
try {
ExternalSystemUtil.refreshProject(
project,
myExternalSystemId,
externalProjectPath,
callback,
true,
ProgressExecutionMode.MODAL_SYNC
);
}
catch (IllegalArgumentException e) {
exRef.set(
new ConfigurationException(e.getMessage(), ExternalSystemBundle.message("error.cannot.parse.project", externalSystemName)));
}
}
});
ConfigurationException ex = exRef.get();
if (ex != null) {
throw ex;
}
if (myExternalProjectNode == null) {
ConfigurationException exception = error.get();
if (exception != null) {
throw exception;
}
}
else {
applyProjectSettings(wizardContext);
}
}
@SuppressWarnings("unchecked")
private void executeAndRestoreDefaultProjectSettings(@NotNull Project project, @NotNull Runnable task) {
if (!project.isDefault()) {
task.run();
return;
}
AbstractExternalSystemSettings systemSettings = ExternalSystemApiUtil.getSettings(project, myExternalSystemId);
Object systemStateToRestore = null;
if (systemSettings instanceof PersistentStateComponent) {
systemStateToRestore = ((PersistentStateComponent)systemSettings).getState();
}
systemSettings.copyFrom(myControl.getSystemSettings());
Collection projectSettingsToRestore = systemSettings.getLinkedProjectsSettings();
systemSettings.setLinkedProjectsSettings(Collections.singleton(getCurrentExternalProjectSettings()));
try {
task.run();
}
finally {
if (systemStateToRestore != null) {
((PersistentStateComponent)systemSettings).loadState(systemStateToRestore);
}
else {
systemSettings.setLinkedProjectsSettings(projectSettingsToRestore);
}
}
}
/**
* Allows to adjust external project config file to use on the basis of the given value.
* <p/>
* Example: a user might choose a directory which contains target config file and particular implementation expands
* that to a particular file under the directory.
*
* @param file base external project config file
* @return external project config file to use
*/
@NotNull
protected abstract File getExternalProjectConfigToUse(@NotNull File file);
@Nullable
public DataNode<ProjectData> getExternalProjectNode() {
return myExternalProjectNode;
}
/**
* Applies external system-specific settings like project files location etc to the given context.
*
* @param context storage for the project/module settings.
*/
public void applyProjectSettings(@NotNull WizardContext context) {
if (myExternalProjectNode == null) {
assert false;
return;
}
context.setProjectName(myExternalProjectNode.getData().getInternalName());
context.setProjectFileDirectory(myExternalProjectNode.getData().getIdeProjectFileDirectoryPath());
applyExtraSettings(context);
}
protected abstract void applyExtraSettings(@NotNull WizardContext context);
/**
* Allows to get {@link Project} instance to use. Basically, there are two alternatives -
* {@link WizardContext#getProject() project from the current wizard context} and
* {@link ProjectManager#getDefaultProject() default project}.
*
* @param wizardContext current wizard context
* @return {@link Project} instance to use
*/
@NotNull
public Project getProject(@NotNull WizardContext wizardContext) {
Project result = wizardContext.getProject();
if (result == null) {
result = ProjectManager.getInstance().getDefaultProject();
}
return result;
}
}