added Maven's implementation for ProjectModelModificationService so various 'add missing dependency' actions will modify pom.xml files in case of Maven projects

This commit is contained in:
nik
2015-08-27 13:00:54 +03:00
parent e1146c697b
commit b7d98e501e
10 changed files with 463 additions and 71 deletions
@@ -15,6 +15,8 @@
*/
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.roots.ExternalLibraryDescriptor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
@@ -23,6 +25,7 @@ import com.intellij.openapi.roots.DependencyScope;
import com.intellij.openapi.roots.ProjectModelModificationService;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.util.Consumer;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -66,9 +69,18 @@ class AddExternalLibraryToDependenciesQuickFix extends OrderEntryFix {
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
public void invoke(@NotNull Project project, final Editor editor, PsiFile file) throws IncorrectOperationException {
DependencyScope scope = suggestScopeByLocation(myCurrentModule, myReference.getElement());
ProjectModelModificationService.getInstance(project).addDependency(myCurrentModule, myLibraryDescriptor, scope);
importClass(myCurrentModule, editor, myReference, myQualifiedClassName);
ProjectModelModificationService.getInstance(project).addDependency(myCurrentModule, myLibraryDescriptor, scope).done(
new Consumer<Void>() {
@Override
public void consume(Void aVoid) {
new WriteAction() {
protected void run(@NotNull final Result result) {
importClass(myCurrentModule, editor, myReference, myQualifiedClassName);
}
}.execute();
}
});
}
}
@@ -20,11 +20,17 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.libraries.Library;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.concurrency.Promise;
import java.util.Collection;
import java.util.Collections;
/**
* Provides methods to perform high-level modifications of project configuration accordingly with dependency management system used in the
* project. E.g. if the project is imported from Maven the methods will modify pom.xml files and invoke reimporting to update IDEA's
* project model. Since importing the changes to IDEA's project model may take a while the method work asynchronously and returns
* {@link Promise} objects which may be used to be notified when the project configuration is finally updated.
*
* @author nik
*/
public abstract class ProjectModelModificationService {
@@ -32,22 +38,22 @@ public abstract class ProjectModelModificationService {
return ServiceManager.getService(project, ProjectModelModificationService.class);
}
public void addDependency(@NotNull Module from, @NotNull Module to) {
addDependency(from, to, DependencyScope.COMPILE);
public Promise<Void> addDependency(@NotNull Module from, @NotNull Module to) {
return addDependency(from, to, DependencyScope.COMPILE);
}
public abstract void addDependency(@NotNull Module from, @NotNull Module to, @NotNull DependencyScope scope);
public abstract Promise<Void> addDependency(@NotNull Module from, @NotNull Module to, @NotNull DependencyScope scope);
public void addDependency(@NotNull Module from, @NotNull ExternalLibraryDescriptor libraryDescriptor) {
addDependency(from, libraryDescriptor, DependencyScope.COMPILE);
public Promise<Void> addDependency(@NotNull Module from, @NotNull ExternalLibraryDescriptor libraryDescriptor) {
return addDependency(from, libraryDescriptor, DependencyScope.COMPILE);
}
public void addDependency(Module from, ExternalLibraryDescriptor descriptor, DependencyScope scope) {
addDependency(Collections.singletonList(from), descriptor, scope);
public Promise<Void> addDependency(@NotNull Module from, @NotNull ExternalLibraryDescriptor descriptor, @NotNull DependencyScope scope) {
return addDependency(Collections.singletonList(from), descriptor, scope);
}
public abstract void addDependency(@NotNull Collection<Module> from, @NotNull ExternalLibraryDescriptor libraryDescriptor,
@NotNull DependencyScope scope);
public abstract Promise<Void> addDependency(@NotNull Collection<Module> from, @NotNull ExternalLibraryDescriptor libraryDescriptor,
@NotNull DependencyScope scope);
public abstract void addDependency(@NotNull Module from, @NotNull Library library, @NotNull DependencyScope scope);
public abstract Promise<Void> addDependency(@NotNull Module from, @NotNull Library library, @NotNull DependencyScope scope);
}
@@ -19,20 +19,53 @@ import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.roots.libraries.Library;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;
import java.util.Collection;
/**
* Register implementation of this extension to support custom dependency management system for {@link ProjectModelModificationService}.
*
* @author nik
*/
public abstract class ProjectModelModifier {
public static final ExtensionPointName<ProjectModelModifier> EP_NAME = ExtensionPointName.create("com.intellij.projectModelModifier");
public abstract boolean addModuleDependency(@NotNull Module from, @NotNull Module to, @NotNull DependencyScope scope);
/**
* Implementation of this method should add dependency from module {@code from} to module {@code to} with scope {@code scope} accordingly
* to this dependencies management system. If it takes some time to propagate changes in the external project configuration to IDEA's
* project model the method may schedule this work for asynchronous execution and return {@link Promise} instance which will be fulfilled
* when the work is done.
* @return {@link Promise} instance if dependencies between these modules can be handled by this dependencies management system or
* {@code null} otherwise
*/
@Nullable
public abstract Promise<Void> addModuleDependency(@NotNull Module from, @NotNull Module to, @NotNull DependencyScope scope);
public abstract boolean addExternalLibraryDependency(@NotNull Collection<Module> modules,
@NotNull ExternalLibraryDescriptor descriptor,
@NotNull DependencyScope scope);
/**
* Implementation of this method should add dependency from modules {@code modules} to an external library with scope {@code scope} accordingly
* to this dependencies management system. If it takes some time to propagate changes in the external project configuration to IDEA's
* project model the method may schedule this work for asynchronous execution and return {@link Promise} instance which will be fulfilled
* when the work is done.
*
* @return {@link Promise} instance if dependencies of these modules can be handled by this dependencies management system or
* {@code null} otherwise
*/
@Nullable
public abstract Promise<Void> addExternalLibraryDependency(@NotNull Collection<Module> modules,
@NotNull ExternalLibraryDescriptor descriptor,
@NotNull DependencyScope scope);
public abstract boolean addLibraryDependency(@NotNull Module from, @NotNull Library library, @NotNull DependencyScope scope);
/**
* Implementation of this method should add dependency from module {@code from} to {@code library} with scope {@code scope} accordingly
* to this dependencies management system. If it takes some time to propagate changes in the external project configuration to IDEA's
* project model the method may schedule this work for asynchronous execution and return {@link Promise} instance which will be fulfilled
* when the work is done.
*
* @return {@link Promise} instance if dependencies between these modules can be handled by this dependencies management system or
* {@code null} otherwise
*/
@Nullable
public abstract Promise<Void> addLibraryDependency(@NotNull Module from, @NotNull Library library, @NotNull DependencyScope scope);
}
@@ -28,6 +28,7 @@ import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
import com.intellij.openapi.roots.libraries.LibraryUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.concurrency.Promise;
import java.util.Collection;
import java.util.Collections;
@@ -45,15 +46,15 @@ public class IdeaProjectModelModifier extends ProjectModelModifier {
}
@Override
public boolean addModuleDependency(@NotNull Module from, @NotNull Module to, @NotNull DependencyScope scope) {
public Promise<Void> addModuleDependency(@NotNull Module from, @NotNull Module to, @NotNull DependencyScope scope) {
ModuleRootModificationUtil.addDependency(from, to, scope, false);
return true;
return Promise.DONE;
}
@Override
public boolean addExternalLibraryDependency(@NotNull final Collection<Module> modules,
@NotNull final ExternalLibraryDescriptor descriptor,
@NotNull final DependencyScope scope) {
public Promise<Void> addExternalLibraryDependency(@NotNull final Collection<Module> modules,
@NotNull final ExternalLibraryDescriptor descriptor,
@NotNull final DependencyScope scope) {
List<String> defaultRoots = descriptor.getLibraryClassesRoots();
Module firstModule = ContainerUtil.getFirstItem(modules);
LOG.assertTrue(firstModule != null);
@@ -82,12 +83,12 @@ public class IdeaProjectModelModifier extends ProjectModelModifier {
}.execute();
}
}
return true;
return Promise.DONE;
}
@Override
public boolean addLibraryDependency(@NotNull Module from, @NotNull Library library, @NotNull DependencyScope scope) {
public Promise<Void> addLibraryDependency(@NotNull Module from, @NotNull Library library, @NotNull DependencyScope scope) {
OrderEntryUtil.addLibraryToRoots(from, library);
return true;
return Promise.DONE;
}
}
@@ -23,6 +23,7 @@ import com.intellij.openapi.roots.ProjectModelModificationService;
import com.intellij.openapi.roots.ProjectModelModifier;
import com.intellij.openapi.roots.libraries.Library;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.concurrency.Promise;
import java.util.Collection;
@@ -37,30 +38,36 @@ public class ProjectModelModificationServiceImpl extends ProjectModelModificatio
}
@Override
public void addDependency(@NotNull Module from, @NotNull Module to, @NotNull DependencyScope scope) {
public Promise<Void> addDependency(@NotNull Module from, @NotNull Module to, @NotNull DependencyScope scope) {
for (ProjectModelModifier modifier : getModelModifiers()) {
if (modifier.addModuleDependency(from, to, scope)) {
return;
Promise<Void> promise = modifier.addModuleDependency(from, to, scope);
if (promise != null) {
return promise;
}
}
return Promise.REJECTED;
}
@Override
public void addDependency(@NotNull Collection<Module> from, @NotNull ExternalLibraryDescriptor libraryDescriptor, @NotNull DependencyScope scope) {
public Promise<Void> addDependency(@NotNull Collection<Module> from, @NotNull ExternalLibraryDescriptor libraryDescriptor, @NotNull DependencyScope scope) {
for (ProjectModelModifier modifier : getModelModifiers()) {
if (modifier.addExternalLibraryDependency(from, libraryDescriptor, scope)) {
return;
Promise<Void> promise = modifier.addExternalLibraryDependency(from, libraryDescriptor, scope);
if (promise != null) {
return promise;
}
}
return Promise.REJECTED;
}
@Override
public void addDependency(@NotNull Module from, @NotNull Library library, @NotNull DependencyScope scope) {
public Promise<Void> addDependency(@NotNull Module from, @NotNull Library library, @NotNull DependencyScope scope) {
for (ProjectModelModifier modifier : getModelModifiers()) {
if (modifier.addLibraryDependency(from, library, scope)) {
return;
Promise<Void> promise = modifier.addLibraryDependency(from, library, scope);
if (promise != null) {
return promise;
}
}
return Promise.REJECTED;
}
@NotNull
@@ -0,0 +1,165 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.idea.maven.importing;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.DependencyScope;
import com.intellij.openapi.roots.ExternalLibraryDescriptor;
import com.intellij.openapi.roots.ProjectModelModifier;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.text.VersionComparatorUtil;
import com.intellij.util.xml.DomUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.idea.maven.dom.MavenDomUtil;
import org.jetbrains.idea.maven.dom.model.MavenDomDependency;
import org.jetbrains.idea.maven.dom.model.MavenDomProjectModel;
import org.jetbrains.idea.maven.indices.MavenProjectIndicesManager;
import org.jetbrains.idea.maven.model.MavenArtifact;
import org.jetbrains.idea.maven.model.MavenConstants;
import org.jetbrains.idea.maven.model.MavenId;
import org.jetbrains.idea.maven.project.MavenProject;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.utils.library.RepositoryAttachHandler;
import java.util.*;
/**
* @author nik
*/
public class MavenProjectModelModifier extends ProjectModelModifier {
private final Project myProject;
private final MavenProjectsManager myProjectsManager;
private MavenProjectIndicesManager myIndicesManager;
public MavenProjectModelModifier(Project project, MavenProjectsManager projectsManager, MavenProjectIndicesManager manager) {
myProject = project;
myProjectsManager = projectsManager;
myIndicesManager = manager;
}
@Nullable
@Override
public Promise<Void> addModuleDependency(@NotNull Module from, @NotNull Module to, @NotNull final DependencyScope scope) {
final MavenProject toProject = myProjectsManager.findProject(to);
if (toProject == null) return null;
MavenId mavenId = toProject.getMavenId();
return addDependency(Collections.singletonList(from), mavenId, scope);
}
private Promise<Void> addDependency(@NotNull Collection<Module> fromModules, @NotNull final MavenId mavenId, @NotNull final DependencyScope scope) {
final List<MavenDomProjectModel> models = new ArrayList<MavenDomProjectModel>(fromModules.size());
List<XmlFile> files = new ArrayList<XmlFile>(fromModules.size());
List<MavenProject> projectToUpdate = new ArrayList<MavenProject>(fromModules.size());
for (Module from : fromModules) {
if (!myProjectsManager.isMavenizedModule(from)) return null;
MavenProject fromProject = myProjectsManager.findProject(from);
if (fromProject == null) return null;
final MavenDomProjectModel model = MavenDomUtil.getMavenDomProjectModel(myProject, fromProject.getFile());
if (model == null) return null;
models.add(model);
files.add(DomUtil.getFile(model));
projectToUpdate.add(fromProject);
}
new WriteCommandAction(myProject, "Add Maven Dependency", PsiUtilCore.toPsiFileArray(files)) {
@Override
protected void run(@NotNull Result result) throws Throwable {
for (MavenDomProjectModel model : models) {
MavenDomDependency dependency = MavenDomUtil.createDomDependency(model, null, mavenId);
String mavenScope = getMavenScope(scope);
if (mavenScope != null) {
dependency.getScope().setStringValue(mavenScope);
}
Document document = PsiDocumentManager.getInstance(myProject).getDocument(DomUtil.getFile(model));
if (document != null) {
FileDocumentManager.getInstance().saveDocument(document);
}
}
}
}.execute();
return myProjectsManager.forceUpdateProjects(projectToUpdate);
}
@Nullable
@Override
public Promise<Void> addExternalLibraryDependency(@NotNull Collection<Module> modules,
@NotNull ExternalLibraryDescriptor descriptor,
@NotNull DependencyScope scope) {
for (Module module : modules) {
if (!myProjectsManager.isMavenizedModule(module)) {
return null;
}
}
String version = selectVersion(descriptor);
MavenId mavenId = new MavenId(descriptor.getLibraryGroupId(), descriptor.getLibraryArtifactId(), version);
return addDependency(modules, mavenId, scope);
}
@NotNull
private String selectVersion(@NotNull ExternalLibraryDescriptor descriptor) {
Set<String> versions = myIndicesManager.getVersions(descriptor.getLibraryGroupId(), descriptor.getLibraryArtifactId());
List<String> suitableVersions = new ArrayList<String>();
String minVersion = descriptor.getMinVersion();
String maxVersion = descriptor.getMaxVersion();
for (String version : versions) {
if ((minVersion == null || VersionComparatorUtil.compare(minVersion, version) <= 0)
&& (maxVersion == null || VersionComparatorUtil.compare(version, maxVersion) < 0)) {
suitableVersions.add(version);
}
}
return Collections.max(suitableVersions, VersionComparatorUtil.COMPARATOR);
}
@Nullable
@Override
public Promise<Void> addLibraryDependency(@NotNull Module from, @NotNull Library library, @NotNull DependencyScope scope) {
String name = library.getName();
if (name != null && name.startsWith(MavenArtifact.MAVEN_LIB_PREFIX)) {
//it would be better to use RepositoryLibraryType for libraries imported from Maven and fetch mavenId from the library properties instead
String mavenCoordinates = StringUtil.trimStart(name, MavenArtifact.MAVEN_LIB_PREFIX);
return addDependency(Collections.singletonList(from), RepositoryAttachHandler.getMavenId(mavenCoordinates), scope);
}
return null;
}
@Nullable
private static String getMavenScope(DependencyScope scope) {
switch (scope) {
case RUNTIME:
return MavenConstants.SCOPE_RUNTIME;
case TEST:
return MavenConstants.SCOPE_TEST;
case PROVIDED:
return MavenConstants.SCOPE_PROVIDED;
default:
return null;
}
}
}
@@ -53,6 +53,8 @@ import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.concurrency.AsyncPromise;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.idea.maven.importing.MavenDefaultModifiableModelsProvider;
import org.jetbrains.idea.maven.importing.MavenFoldersImporter;
import org.jetbrains.idea.maven.importing.MavenModifiableModelsProvider;
@@ -780,8 +782,8 @@ public class MavenProjectsManager extends MavenSimpleProjectComponent
doScheduleUpdateProjects(null, false, forceImportAndResolve);
}
public void forceUpdateProjects(@NotNull Collection<MavenProject> projects) {
doScheduleUpdateProjects(projects, true, true);
public AsyncPromise<Void> forceUpdateProjects(@NotNull Collection<MavenProject> projects) {
return doScheduleUpdateProjects(projects, true, true);
}
public void forceUpdateAllProjectsOrFindAllAvailablePomFiles() {
@@ -791,30 +793,34 @@ public class MavenProjectsManager extends MavenSimpleProjectComponent
doScheduleUpdateProjects(null, true, true);
}
private void doScheduleUpdateProjects(final Collection<MavenProject> projects,
final boolean forceUpdate,
final boolean forceImportAndResolve) {
private AsyncPromise<Void> doScheduleUpdateProjects(final Collection<MavenProject> projects,
final boolean forceUpdate,
final boolean forceImportAndResolve) {
final AsyncPromise<Void> promise = new AsyncPromise<Void>();
MavenUtil.runWhenInitialized(myProject, new DumbAwareRunnable() {
public void run() {
if (projects == null) {
myWatcher.scheduleUpdateAll(forceUpdate, forceImportAndResolve);
myWatcher.scheduleUpdateAll(forceUpdate, forceImportAndResolve).processed(promise);
}
else {
myWatcher.scheduleUpdate(MavenUtil.collectFiles(projects),
Collections.<VirtualFile>emptyList(),
forceUpdate,
forceImportAndResolve);
forceImportAndResolve).processed(promise);
}
}
});
return promise;
}
public void scheduleImportAndResolve() {
scheduleResolve(); // scheduleImport will be called after the scheduleResolve process has finished
public Promise<List<Module>> scheduleImportAndResolve() {
AsyncPromise<List<Module>> promise = scheduleResolve();// scheduleImport will be called after the scheduleResolve process has finished
fireImportAndResolveScheduled();
return promise;
}
private void scheduleResolve() {
private AsyncPromise<List<Module>> scheduleResolve() {
final AsyncPromise<List<Module>> result = new AsyncPromise<List<Module>>();
runWhenFullyOpen(new Runnable() {
public void run() {
LinkedHashSet<MavenProject> toResolve;
@@ -830,7 +836,9 @@ public class MavenProjectsManager extends MavenSimpleProjectComponent
Runnable onCompletion = it.hasNext() ? null : new Runnable() {
@Override
public void run() {
if (hasScheduledProjects()) scheduleImport();
if (hasScheduledProjects()) {
scheduleImport().processed(result);
}
}
};
@@ -839,6 +847,7 @@ public class MavenProjectsManager extends MavenSimpleProjectComponent
}
}
});
return result;
}
public void evaluateEffectivePom(@NotNull final MavenProject mavenProject, @NotNull final NullableConsumer<String> consumer) {
@@ -949,16 +958,18 @@ public class MavenProjectsManager extends MavenSimpleProjectComponent
scheduleImport();
}
private void scheduleImport() {
private Promise<List<Module>> scheduleImport() {
final AsyncPromise<List<Module>> result = new AsyncPromise<List<Module>>();
runWhenFullyOpen(new Runnable() {
public void run() {
myImportingQueue.queue(new Update(MavenProjectsManager.this) {
public void run() {
importProjects();
result.setResult(importProjects());
}
});
}
});
return result;
}
@TestOnly
@@ -42,6 +42,7 @@ import com.intellij.openapi.vfs.pointers.VirtualFilePointer;
import com.intellij.openapi.vfs.pointers.VirtualFilePointerListener;
import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.util.Consumer;
import com.intellij.util.PathUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.MessageBusConnection;
@@ -50,6 +51,8 @@ import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.concurrency.AsyncPromise;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.idea.maven.model.MavenConstants;
import org.jetbrains.idea.maven.model.MavenExplicitProfiles;
import org.jetbrains.idea.maven.utils.MavenMergingUpdateQueue;
@@ -270,38 +273,45 @@ public class MavenProjectsManagerWatcher {
scheduleUpdateAll(false, false);
}
public void scheduleUpdateAll(boolean force, final boolean forceImportAndResolve) {
Runnable onCompletion = new Runnable() {
@Override
public void run() {
if (myProject.isDisposed()) return;
if (forceImportAndResolve || myManager.getImportingSettings().isImportAutomatically()) {
myManager.scheduleImportAndResolve();
}
}
};
public Promise<Void> scheduleUpdateAll(boolean force, final boolean forceImportAndResolve) {
final AsyncPromise<Void> promise = new AsyncPromise<Void>();
Runnable onCompletion = createScheduleImportAction(forceImportAndResolve, promise);
myReadingProcessor.scheduleTask(new MavenProjectsProcessorReadingTask(force, myProjectsTree, myGeneralSettings, onCompletion));
return promise;
}
public void scheduleUpdate(List<VirtualFile> filesToUpdate,
List<VirtualFile> filesToDelete,
boolean force,
final boolean forceImportAndResolve) {
Runnable onCompletion = new Runnable() {
@Override
public void run() {
if (forceImportAndResolve || myManager.getImportingSettings().isImportAutomatically()) {
myManager.scheduleImportAndResolve();
}
}
};
public Promise<Void> scheduleUpdate(List<VirtualFile> filesToUpdate,
List<VirtualFile> filesToDelete,
boolean force,
final boolean forceImportAndResolve) {
final AsyncPromise<Void> promise = new AsyncPromise<Void>();
Runnable onCompletion = createScheduleImportAction(forceImportAndResolve, promise);
myReadingProcessor.scheduleTask(new MavenProjectsProcessorReadingTask(filesToUpdate,
filesToDelete,
force,
myProjectsTree,
myGeneralSettings,
onCompletion));
return promise;
}
@NotNull
private Runnable createScheduleImportAction(final boolean forceImportAndResolve, final AsyncPromise<Void> promise) {
return new Runnable() {
@Override
public void run() {
if (myProject.isDisposed()) return;
if (forceImportAndResolve || myManager.getImportingSettings().isImportAutomatically()) {
myManager.scheduleImportAndResolve().done(new Consumer<List<Module>>() {
@Override
public void consume(List<Module> modules) {
promise.setResult(null);
}
});
}
}
};
}
private void onSettingsChange() {
@@ -180,6 +180,7 @@
<postStartupActivity implementation="org.jetbrains.idea.maven.utils.MavenRehighlighter$MavenRehighlighterPostStartupActivity"/>
<projectService serviceImplementation="org.jetbrains.idea.maven.utils.MavenRehighlighter"/>
<projectModelModifier implementation="org.jetbrains.idea.maven.importing.MavenProjectModelModifier"/>
<library.dependencyScopeSuggester implementation="org.jetbrains.idea.maven.utils.library.RepositoryLibraryDependencyScopeSuggester"/>
</extensions>
@@ -0,0 +1,146 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* 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 org.jetbrains.idea.maven.actions;
import com.intellij.openapi.roots.DependencyScope;
import com.intellij.openapi.roots.ExternalLibraryDescriptor;
import com.intellij.openapi.roots.ProjectModelModifier;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.idea.maven.dom.MavenDomWithIndicesTestCase;
import org.jetbrains.idea.maven.importing.MavenProjectModelModifier;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author nik
*/
public class MavenProjectModelModifierTest extends MavenDomWithIndicesTestCase {
public void testAddExternalLibraryDependency() throws IOException {
importProject("<groupId>test</groupId>" +
"<artifactId>project</artifactId>" +
"<version>1</version>");
Promise<Void> result =
getExtension().addExternalLibraryDependency(Collections.singletonList(getModule("project")), new CommonsIoLibraryDescriptor(),
DependencyScope.COMPILE);
assertNotNull(result);
String version = assertHasDependency(myProjectPom, "junit", "junit");
waitUntilImported(result);
assertModuleLibDep("project", "Maven: junit:junit:" + version);
}
public void testAddModuleDependency() throws IOException {
createTwoModulesPom("m1", "m2");
VirtualFile m1 = createModulePom("m1", "<groupId>test</groupId>" +
"<artifactId>m1</artifactId>" +
"<version>1</version>");
createModulePom("m2", "<groupId>test</groupId>" +
"<artifactId>m2</artifactId>" +
"<version>1</version>");
importProject();
Promise<Void> result = getExtension().addModuleDependency(getModule("m1"), getModule("m2"), DependencyScope.COMPILE);
assertNotNull(result);
assertHasDependency(m1, "test", "m2");
waitUntilImported(result);
assertModuleModuleDeps("m1", "m2");
}
public void testAddLibraryDependency() throws IOException {
createTwoModulesPom("m1", "m2");
VirtualFile m1 = createModulePom("m1", "<groupId>test</groupId>" +
"<artifactId>m1</artifactId>" +
"<version>1</version>");
createModulePom("m2", "<groupId>test</groupId>" +
"<artifactId>m2</artifactId>" +
"<version>1</version>" +
"<dependencies>" +
" <dependency>" +
" <groupId>junit</groupId>" +
" <artifactId>junit</artifactId>" +
" <version>4.0</version>" +
" <scope>test</scope>" +
" </dependency>" +
"</dependencies>");
importProject();
String libName = "Maven: junit:junit:4.0";
assertModuleLibDep("m2", libName);
Library library = LibraryTablesRegistrar.getInstance().getLibraryTable(myProject).getLibraryByName(libName);
assertNotNull(library);
Promise<Void> result = getExtension().addLibraryDependency(getModule("m1"), library, DependencyScope.COMPILE);
assertNotNull(result);
assertHasDependency(m1, "junit", "junit");
waitUntilImported(result);
assertModuleLibDep("m1", libName);
}
private void createTwoModulesPom(final String m1, final String m2) throws IOException {
createProjectPom("<groupId>test</groupId>" +
"<artifactId>project</artifactId>" +
"<packaging>pom</packaging>" +
"<version>1</version>" +
"<modules>" +
" <module>" + m1 + "</module>" +
" <module>" + m2 + "</module>" +
"</modules>");
}
private String assertHasDependency(VirtualFile pom, final String groupId, final String artifactId) {
String pomText = PsiManager.getInstance(myProject).findFile(pom).getText();
Pattern
pattern = Pattern.compile("(?s).*<dependency>\\s*<groupId>" + groupId + "</groupId>\\s*<artifactId>" +
artifactId + "</artifactId>\\s*<version>(.*)</version>\\s*</dependency>.*");
Matcher matcher = pattern.matcher(pomText);
assertTrue(matcher.matches());
return matcher.group(1);
}
private void waitUntilImported(Promise<Void> result) {
waitForReadingCompletion();
myProjectsManager.waitForResolvingCompletion();
myProjectsManager.waitForArtifactsDownloadingCompletion();
performPostImportTasks();
myProjectsManager.performScheduledImportInTests();
assertSame(Promise.State.FULFILLED, result.getState());
}
private MavenProjectModelModifier getExtension() {
return ContainerUtil.findInstance(ProjectModelModifier.EP_NAME.getExtensions(myProject), MavenProjectModelModifier.class);
}
private static class CommonsIoLibraryDescriptor extends ExternalLibraryDescriptor {
public CommonsIoLibraryDescriptor() {
super("junit", "junit");
}
@NotNull
@Override
public List<String> getLibraryClassesRoots() {
return Collections.emptyList();
}
}
}