blob: ed0cbe93c0bfe4611a80d604a83237d1507e5251 [file] [log] [blame]
/*
* Copyright 2000-2013 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.manage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.externalSystem.model.DataNode;
import com.intellij.openapi.externalSystem.model.Key;
import com.intellij.openapi.externalSystem.model.ProjectKeys;
import com.intellij.openapi.externalSystem.model.project.*;
import com.intellij.openapi.externalSystem.service.project.PlatformFacade;
import com.intellij.openapi.externalSystem.service.project.ProjectStructureHelper;
import com.intellij.openapi.externalSystem.util.DisposeAwareProjectChange;
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
import com.intellij.openapi.externalSystem.util.ExternalSystemConstants;
import com.intellij.openapi.externalSystem.util.Order;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.impl.ModuleLibraryOrderEntryImpl;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.BooleanFunction;
import com.intellij.util.containers.ContainerUtilRt;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.*;
import static com.intellij.openapi.externalSystem.model.ProjectKeys.MODULE;
/**
* @author Denis Zhdanov
* @since 4/12/13 6:19 PM
*/
@Order(ExternalSystemConstants.BUILTIN_SERVICE_ORDER)
public class LibraryDependencyDataService extends AbstractDependencyDataService<LibraryDependencyData, LibraryOrderEntry> {
private static final Logger LOG = Logger.getInstance("#" + LibraryDependencyDataService.class.getName());
@NotNull private final PlatformFacade myPlatformFacade;
@NotNull private final ProjectStructureHelper myProjectStructureHelper;
@NotNull private final ModuleDataService myModuleManager;
@NotNull private final LibraryDataService myLibraryManager;
public LibraryDependencyDataService(@NotNull PlatformFacade platformFacade,
@NotNull ProjectStructureHelper helper,
@NotNull ModuleDataService moduleManager,
@NotNull LibraryDataService libraryManager)
{
myPlatformFacade = platformFacade;
myProjectStructureHelper = helper;
myModuleManager = moduleManager;
myLibraryManager = libraryManager;
}
@NotNull
@Override
public Key<LibraryDependencyData> getTargetDataKey() {
return ProjectKeys.LIBRARY_DEPENDENCY;
}
@Override
public void importData(@NotNull Collection<DataNode<LibraryDependencyData>> toImport, @NotNull Project project, boolean synchronous) {
if (toImport.isEmpty()) {
return;
}
Map<DataNode<ModuleData>, List<DataNode<LibraryDependencyData>>> byModule = ExternalSystemApiUtil.groupBy(toImport, MODULE);
for (Map.Entry<DataNode<ModuleData>, List<DataNode<LibraryDependencyData>>> entry : byModule.entrySet()) {
Module module = myProjectStructureHelper.findIdeModule(entry.getKey().getData(), project);
if (module == null) {
myModuleManager.importData(Collections.singleton(entry.getKey()), project, true);
module = myProjectStructureHelper.findIdeModule(entry.getKey().getData(), project);
if (module == null) {
LOG.warn(String.format(
"Can't import library dependencies %s. Reason: target module (%s) is not found at the ide and can't be imported",
entry.getValue(), entry.getKey()
));
continue;
}
}
importData(entry.getValue(), module, synchronous);
}
}
public void importData(@NotNull final Collection<DataNode<LibraryDependencyData>> nodesToImport,
@NotNull final Module module,
final boolean synchronous)
{
ExternalSystemApiUtil.executeProjectChangeAction(synchronous, new DisposeAwareProjectChange(module) {
@Override
public void execute() {
importMissingProjectLibraries(module, nodesToImport, synchronous);
// The general idea is to import all external project library dependencies and module libraries which don't present at the
// ide side yet and remove all project library dependencies and module libraries which present at the ide but not at
// the given collection.
// The trick is that we should perform module settings modification inside try/finally block against target root model.
// That means that we need to prepare all necessary data, obtain a model and modify it as necessary.
Map<Set<String>/* library paths */, LibraryDependencyData> moduleLibrariesToImport = ContainerUtilRt.newHashMap();
Map<String/* library name + scope */, LibraryDependencyData> projectLibrariesToImport = ContainerUtilRt.newHashMap();
Set<LibraryDependencyData> toImport = ContainerUtilRt.newLinkedHashSet();
boolean hasUnresolved = false;
for (DataNode<LibraryDependencyData> dependencyNode : nodesToImport) {
LibraryDependencyData dependencyData = dependencyNode.getData();
LibraryData libraryData = dependencyData.getTarget();
hasUnresolved |= libraryData.isUnresolved();
switch (dependencyData.getLevel()) {
case MODULE:
if (!libraryData.isUnresolved()) {
Set<String> paths = ContainerUtilRt.newHashSet();
for (String path : libraryData.getPaths(LibraryPathType.BINARY)) {
paths.add(ExternalSystemApiUtil.toCanonicalPath(path) + dependencyData.getScope().name());
}
moduleLibrariesToImport.put(paths, dependencyData);
toImport.add(dependencyData);
}
break;
case PROJECT:
projectLibrariesToImport.put(libraryData.getInternalName() + dependencyData.getScope().name(), dependencyData);
toImport.add(dependencyData);
}
}
ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
final ModifiableRootModel moduleRootModel = moduleRootManager.getModifiableModel();
LibraryTable moduleLibraryTable = moduleRootModel.getModuleLibraryTable();
LibraryTable libraryTable = myPlatformFacade.getProjectLibraryTable(module.getProject());
try {
syncExistingAndRemoveObsolete(moduleLibrariesToImport, projectLibrariesToImport, toImport, moduleRootModel, hasUnresolved);
// Import missing library dependencies.
if (!toImport.isEmpty()) {
importMissing(toImport, moduleRootModel, moduleLibraryTable, libraryTable, module);
}
}
finally {
moduleRootModel.commit();
}
}
});
}
private void importMissing(@NotNull Set<LibraryDependencyData> toImport,
@NotNull ModifiableRootModel moduleRootModel,
@NotNull LibraryTable moduleLibraryTable,
@NotNull LibraryTable libraryTable,
@NotNull Module module)
{
for (final LibraryDependencyData dependencyData : toImport) {
final LibraryData libraryData = dependencyData.getTarget();
final String libraryName = libraryData.getInternalName();
switch (dependencyData.getLevel()) {
case MODULE:
final Library moduleLib = moduleLibraryTable.createLibrary(libraryName);
syncExistingLibraryDependency(dependencyData, moduleLib, moduleRootModel, module);
break;
case PROJECT:
final Library projectLib = libraryTable.getLibraryByName(libraryName);
if (projectLib == null) {
assert false;
continue;
}
LibraryOrderEntry orderEntry = moduleRootModel.addLibraryEntry(projectLib);
setLibraryScope(orderEntry, projectLib, module, dependencyData);
}
}
}
private static void setLibraryScope(@NotNull LibraryOrderEntry orderEntry,
@NotNull Library lib,
@NotNull Module module,
@NotNull LibraryDependencyData dependencyData) {
LOG.info(String.format("Adding library dependency '%s' to module '%s'", lib.getName(), module.getName()));
orderEntry.setExported(dependencyData.isExported());
orderEntry.setScope(dependencyData.getScope());
LOG.info(String.format(
"Configuring library dependency '%s' of module '%s' to be%s exported and have scope %s",
lib.getName(), module.getName(), dependencyData.isExported() ? " not" : "", dependencyData.getScope()
));
}
private void syncExistingAndRemoveObsolete(@NotNull Map<Set<String>, LibraryDependencyData> moduleLibrariesToImport,
@NotNull Map<String, LibraryDependencyData> projectLibrariesToImport,
@NotNull Set<LibraryDependencyData> toImport,
@NotNull ModifiableRootModel moduleRootModel,
boolean hasUnresolvedLibraries)
{
Set<String> moduleLibraryKey = ContainerUtilRt.newHashSet();
for (OrderEntry entry : moduleRootModel.getOrderEntries()) {
if (entry instanceof ModuleLibraryOrderEntryImpl) {
ModuleLibraryOrderEntryImpl moduleLibraryOrderEntry = (ModuleLibraryOrderEntryImpl)entry;
Library library = moduleLibraryOrderEntry.getLibrary();
if (library == null) {
LOG.warn("Skipping module-level library entry because it doesn't have backing Library object. Entry: " + entry);
continue;
}
moduleLibraryKey.clear();
for (VirtualFile file : library.getFiles(OrderRootType.CLASSES)) {
moduleLibraryKey.add(ExternalSystemApiUtil.getLocalFileSystemPath(file) + moduleLibraryOrderEntry.getScope().name());
}
LibraryDependencyData existing = moduleLibrariesToImport.remove(moduleLibraryKey);
if (existing == null) {
moduleRootModel.removeOrderEntry(entry);
}
else {
syncExistingLibraryDependency(existing, library, moduleRootModel, moduleLibraryOrderEntry.getOwnerModule());
toImport.remove(existing);
}
}
else if (entry instanceof LibraryOrderEntry) {
final LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry)entry;
final String libraryName = libraryOrderEntry.getLibraryName();
final LibraryDependencyData existing = projectLibrariesToImport.remove(libraryName + libraryOrderEntry.getScope().name());
if (existing != null) {
toImport.remove(existing);
}
else if (!hasUnresolvedLibraries) {
// There is a possible case that a project has been successfully imported from external model and after
// that network/repo goes down. We don't want to drop existing binary mappings then.
moduleRootModel.removeOrderEntry(entry);
}
}
}
}
private void syncExistingLibraryDependency(@NotNull LibraryDependencyData libraryDependencyData,
@NotNull Library library,
@NotNull ModifiableRootModel moduleRootModel,
@NotNull Module module) {
final Library.ModifiableModel libModel = library.getModifiableModel();
try {
final String libraryName = libraryDependencyData.getInternalName();
Map<OrderRootType, Collection<File>> files = myLibraryManager.prepareLibraryFiles(libraryDependencyData.getTarget());
myLibraryManager.registerPaths(files, libModel, libraryName);
LibraryOrderEntry orderEntry = moduleRootModel.findLibraryOrderEntry(library);
assert orderEntry != null;
setLibraryScope(orderEntry, library, module, libraryDependencyData);
}
finally {
libModel.commit();
}
}
private void importMissingProjectLibraries(@NotNull Module module,
@NotNull Collection<DataNode<LibraryDependencyData>> nodesToImport,
boolean synchronous)
{
LibraryTable libraryTable = myPlatformFacade.getProjectLibraryTable(module.getProject());
List<DataNode<LibraryData>> librariesToImport = ContainerUtilRt.newArrayList();
for (DataNode<LibraryDependencyData> dataNode : nodesToImport) {
final LibraryDependencyData dependencyData = dataNode.getData();
if (dependencyData.getLevel() != LibraryLevel.PROJECT) {
continue;
}
final Library library = libraryTable.getLibraryByName(dependencyData.getInternalName());
if (library == null) {
DataNode<ProjectData> projectNode = dataNode.getDataNode(ProjectKeys.PROJECT);
if (projectNode != null) {
DataNode<LibraryData> libraryNode =
ExternalSystemApiUtil.find(projectNode, ProjectKeys.LIBRARY, new BooleanFunction<DataNode<LibraryData>>() {
@Override
public boolean fun(DataNode<LibraryData> node) {
return node.getData().equals(dependencyData.getTarget());
}
});
if (libraryNode != null) {
librariesToImport.add(libraryNode);
}
}
}
}
if (!librariesToImport.isEmpty()) {
myLibraryManager.importData(librariesToImport, module.getProject(), synchronous);
}
}
}