/*
* Copyright 2000-2016 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 com.intellij.openapi.externalSystem.service.project;
import com.intellij.facet.Facet;
import com.intellij.facet.FacetModel;
import com.intellij.facet.FacetTypeId;
import com.intellij.facet.ModifiableFacetModel;
import com.intellij.ide.highlighter.ModuleFileType;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.externalSystem.model.project.LibraryData;
import com.intellij.openapi.externalSystem.model.project.ModuleData;
import com.intellij.openapi.module.ModifiableModuleModel;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleWithNameAlreadyExists;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.roots.impl.ModifiableModelCommitter;
import com.intellij.openapi.roots.impl.libraries.LibraryEx;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
import com.intellij.openapi.roots.ui.configuration.FacetsProvider;
import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.packaging.artifacts.ArtifactModel;
import com.intellij.packaging.artifacts.ModifiableArtifactModel;
import com.intellij.packaging.elements.ManifestFileProvider;
import com.intellij.packaging.elements.PackagingElementResolvingContext;
import com.intellij.packaging.impl.artifacts.DefaultManifestFileProvider;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.graph.CachingSemiGraph;
import com.intellij.util.graph.Graph;
import com.intellij.util.graph.GraphGenerator;
import com.intellij.util.graph.InboundSemiGraph;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.isRelated;
import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.toCanonicalPath;
public abstract class AbstractIdeModifiableModelsProvider extends IdeModelsProviderImpl implements IdeModifiableModelsProvider {
private static final Logger LOG = Logger.getInstance(AbstractIdeModifiableModelsProvider.class);
private ModifiableModuleModel myModifiableModuleModel;
private Map<Module, ModifiableRootModel> myModifiableRootModels = new THashMap<>();
private Map<Module, ModifiableFacetModel> myModifiableFacetModels = new THashMap<>();
private Map<Module, String> myProductionModulesForTestModules = new THashMap<>();
private Map<Library, Library.ModifiableModel> myModifiableLibraryModels = new IdentityHashMap<>();
private ModifiableArtifactModel myModifiableArtifactModel;
private AbstractIdeModifiableModelsProvider.MyPackagingElementResolvingContext myPackagingElementResolvingContext;
private final ArtifactExternalDependenciesImporter myArtifactExternalDependenciesImporter;
public AbstractIdeModifiableModelsProvider(@NotNull Project project) {
super(project);
myArtifactExternalDependenciesImporter = new ArtifactExternalDependenciesImporterImpl();
}
protected abstract ModifiableArtifactModel doGetModifiableArtifactModel();
protected abstract ModifiableModuleModel doGetModifiableModuleModel();
protected abstract ModifiableRootModel doGetModifiableRootModel(Module module);
protected abstract ModifiableFacetModel doGetModifiableFacetModel(Module module);
protected abstract Library.ModifiableModel doGetModifiableLibraryModel(Library library);
@NotNull
@Override
public abstract LibraryTable.ModifiableModel getModifiableProjectLibrariesModel();
@NotNull
@Override
public Module[] getModules() {
return getModifiableModuleModel().getModules();
}
protected void processExternalArtifactDependencies() {
myArtifactExternalDependenciesImporter.applyChanges(getModifiableArtifactModel(), getPackagingElementResolvingContext());
}
@Override
public PackagingElementResolvingContext getPackagingElementResolvingContext() {
if (myPackagingElementResolvingContext == null) {
myPackagingElementResolvingContext = new MyPackagingElementResolvingContext();
}
return myPackagingElementResolvingContext;
}
@NotNull
@Override
public OrderEntry[] getOrderEntries(@NotNull Module module) {
return getRootModel(module).getOrderEntries();
}
@NotNull
@Override
public Module newModule(@NotNull final String filePath, final String moduleTypeId) {
Module module = getModifiableModuleModel().newModule(filePath, moduleTypeId);
final String moduleName = FileUtil.getNameWithoutExtension(new File(filePath));
if (!module.getName().equals(moduleName)) {
try {
getModifiableModuleModel().renameModule(module, moduleName);
}
catch (ModuleWithNameAlreadyExists exists) {
LOG.warn(exists);
}
}
// set module type id explicitly otherwise it can not be set if there is an existing module (with the same filePath) and w/o 'type' attribute
module.setOption(Module.ELEMENT_TYPE, moduleTypeId);
return module;
}
@NotNull
@Override
public Module newModule(@NotNull ModuleData moduleData) {
String filePath = moduleData.getModuleFilePath();
String moduleTypeId = moduleData.getModuleTypeId();
for (String candidate : suggestModuleNameCandidates(moduleData)) {
Module module = findIdeModule(candidate);
if (module == null) {
filePath = toCanonicalPath(moduleData.getModuleFileDirectoryPath() + "/" + candidate + ModuleFileType.DOT_DEFAULT_EXTENSION);
break;
}
}
return newModule(filePath, moduleTypeId);
}
@Nullable
@Override
public Module findIdeModule(@NotNull String ideModuleName) {
Module module = getModifiableModuleModel().findModuleByName(ideModuleName);
return module == null ? getModifiableModuleModel().getModuleToBeRenamed(ideModuleName) : module;
}
@Nullable
@Override
public Library findIdeLibrary(@NotNull LibraryData libraryData) {
final LibraryTable.ModifiableModel libraryTable = getModifiableProjectLibrariesModel();
for (Library ideLibrary : libraryTable.getLibraries()) {
if (isRelated(ideLibrary, libraryData)) return ideLibrary;
}
return null;
}
@Override
@NotNull
public VirtualFile[] getContentRoots(Module module) {
return getRootModel(module).getContentRoots();
}
@NotNull
@Override
public VirtualFile[] getSourceRoots(Module module) {
return getRootModel(module).getSourceRoots();
}
@NotNull
@Override
public VirtualFile[] getSourceRoots(Module module, boolean includingTests) {
return getRootModel(module).getSourceRoots(includingTests);
}
@NotNull
@Override
public ModifiableModuleModel getModifiableModuleModel() {
if (myModifiableModuleModel == null) {
myModifiableModuleModel = doGetModifiableModuleModel();
}
return myModifiableModuleModel;
}
@Override
@NotNull
public ModifiableRootModel getModifiableRootModel(Module module) {
return (ModifiableRootModel)getRootModel(module);
}
@NotNull
private ModuleRootModel getRootModel(Module module) {
return myModifiableRootModels.computeIfAbsent(module, k -> doGetModifiableRootModel(module));
}
@Override
@NotNull
public ModifiableFacetModel getModifiableFacetModel(Module module) {
return myModifiableFacetModels.computeIfAbsent(module, k -> doGetModifiableFacetModel(module));
}
@Override
@NotNull
public ModifiableArtifactModel getModifiableArtifactModel() {
if (myModifiableArtifactModel == null) {
myModifiableArtifactModel = doGetModifiableArtifactModel();
}
return myModifiableArtifactModel;
}
@Override
@NotNull
public Library[] getAllLibraries() {
return getModifiableProjectLibrariesModel().getLibraries();
}
@Override
@Nullable
public Library getLibraryByName(String name) {
return getModifiableProjectLibrariesModel().getLibraryByName(name);
}
@Override
public Library createLibrary(String name) {
return getModifiableProjectLibrariesModel().createLibrary(name);
}
@Override
public Library createLibrary(String name, @Nullable ProjectModelExternalSource externalSource) {
return getModifiableProjectLibrariesModel().createLibrary(name, null, externalSource);
}
@Override
public void removeLibrary(Library library) {
getModifiableProjectLibrariesModel().removeLibrary(library);
}
@Override
public Library.ModifiableModel getModifiableLibraryModel(Library library) {
return myModifiableLibraryModels.computeIfAbsent(library, k -> doGetModifiableLibraryModel(library));
}
@NotNull
@Override
public String[] getLibraryUrls(@NotNull Library library, @NotNull OrderRootType type) {
final Library.ModifiableModel model = myModifiableLibraryModels.get(library);
if (model != null) {
return model.getUrls(type);
}
return library.getUrls(type);
}
@Override
public ModalityState getModalityStateForQuestionDialogs() {
return ModalityState.NON_MODAL;
}
@Override
public ArtifactExternalDependenciesImporter getArtifactExternalDependenciesImporter() {
return myArtifactExternalDependenciesImporter;
}
@NotNull
@Override
public List<Module> getAllDependentModules(@NotNull Module module) {
final ArrayList<Module> list = new ArrayList<>();
final Graph<Module> graph = getModuleGraph();
for (Iterator<Module> i = graph.getOut(module); i.hasNext();) {
list.add(i.next());
}
return list;
}
private Graph<Module> getModuleGraph() {
return GraphGenerator.generate(CachingSemiGraph.cache(new InboundSemiGraph<Module>() {
@Override
public Collection<Module> getNodes() {
return ContainerUtil.list(getModules());
}
@Override
public Iterator<Module> getIn(Module m) {
Module[] dependentModules = getModifiableRootModel(m).getModuleDependencies(true);
return Arrays.asList(dependentModules).iterator();
}
}));
}
private class MyPackagingElementResolvingContext implements PackagingElementResolvingContext {
private final ModulesProvider myModulesProvider = new MyModulesProvider();
private final MyFacetsProvider myFacetsProvider = new MyFacetsProvider();
private final ManifestFileProvider myManifestFileProvider = new DefaultManifestFileProvider(this);
@NotNull
public Project getProject() {
return myProject;
}
@NotNull
public ArtifactModel getArtifactModel() {
return AbstractIdeModifiableModelsProvider.this.getModifiableArtifactModel();
}
@NotNull
public ModulesProvider getModulesProvider() {
return myModulesProvider;
}
@NotNull
public FacetsProvider getFacetsProvider() {
return myFacetsProvider;
}
public Library findLibrary(@NotNull String level, @NotNull String libraryName) {
if (level.equals(LibraryTablesRegistrar.PROJECT_LEVEL)) {
return getLibraryByName(libraryName);
}
final LibraryTable table = LibraryTablesRegistrar.getInstance().getLibraryTableByLevel(level, myProject);
return table != null ? table.getLibraryByName(libraryName) : null;
}
@NotNull
@Override
public ManifestFileProvider getManifestFileProvider() {
return myManifestFileProvider;
}
}
private class MyModulesProvider implements ModulesProvider {
@NotNull
public Module[] getModules() {
return AbstractIdeModifiableModelsProvider.this.getModules();
}
public Module getModule(String name) {
return AbstractIdeModifiableModelsProvider.this.findIdeModule(name);
}
public ModuleRootModel getRootModel(@NotNull Module module) {
return AbstractIdeModifiableModelsProvider.this.getModifiableRootModel(module);
}
public FacetModel getFacetModel(@NotNull Module module) {
return AbstractIdeModifiableModelsProvider.this.getModifiableFacetModel(module);
}
}
private class MyFacetsProvider implements FacetsProvider {
@NotNull
public Facet[] getAllFacets(Module module) {
return getModifiableFacetModel(module).getAllFacets();
}
@NotNull
public <F extends Facet> Collection<F> getFacetsByType(Module module, FacetTypeId<F> type) {
return getModifiableFacetModel(module).getFacetsByType(type);
}
public <F extends Facet> F findFacet(Module module, FacetTypeId<F> type, String name) {
return getModifiableFacetModel(module).findFacet(type, name);
}
}
@Override
public void commit() {
ProjectRootManagerEx.getInstanceEx(myProject).mergeRootsChangesDuring(() -> {
processExternalArtifactDependencies();
for (Library.ModifiableModel each : myModifiableLibraryModels.values()) {
each.commit();
}
getModifiableProjectLibrariesModel().commit();
Collection<ModifiableRootModel> rootModels = myModifiableRootModels.values();
ModifiableRootModel[] rootModels1 = rootModels.toArray(new ModifiableRootModel[rootModels.size()]);
for (ModifiableRootModel model : rootModels1) {
assert !model.isDisposed() : "Already disposed: " + model;
}
if (myModifiableModuleModel != null) {
ModifiableModelCommitter.multiCommit(rootModels1, myModifiableModuleModel);
} else {
for (ModifiableRootModel model : rootModels1) {
model.commit();
}
}
for (Map.Entry<Module, String> entry : myProductionModulesForTestModules.entrySet()) {
TestModuleProperties.getInstance(entry.getKey()).setProductionModuleName(entry.getValue());
}
for (Map.Entry<Module, ModifiableFacetModel> each : myModifiableFacetModels.entrySet()) {
if(!each.getKey().isDisposed()) {
each.getValue().commit();
}
}
if (myModifiableArtifactModel != null) {
myModifiableArtifactModel.commit();
}
});
}
@Override
public void dispose() {
for (ModifiableRootModel each : myModifiableRootModels.values()) {
if (each.isDisposed()) continue;
each.dispose();
}
Disposer.dispose(getModifiableProjectLibrariesModel());
for (Library.ModifiableModel each : myModifiableLibraryModels.values()) {
if (each instanceof LibraryEx && ((LibraryEx)each).isDisposed()) continue;
Disposer.dispose(each);
}
if(myModifiableModuleModel != null) {
myModifiableModuleModel.dispose();
}
if (myModifiableArtifactModel != null) {
myModifiableArtifactModel.dispose();
}
myModifiableRootModels.clear();
myModifiableFacetModels.clear();
myModifiableLibraryModels.clear();
}
@Override
public void setTestModuleProperties(Module testModule, String productionModuleName) {
myProductionModulesForTestModules.put(testModule, productionModuleName);
}
@Nullable
@Override
public String getProductionModuleName(Module module) {
return myProductionModulesForTestModules.get(module);
}
}