package net.sf.ivyide.idea;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.ModifiableModuleModel;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import net.sf.ivyide.*;
import java.io.File;
import java.util.*;
public class UpdateModule
implements ProgressAwareRunnable {
private static final Logger LOGGER = Logger.getInstance(UpdateModule.class.getName());
private ModuleManager m_moduleManager;
private Map<String, Library> m_usedProjectLibraries;
private final ModifiableRootModel m_rootModel;
private ResolvedModule m_resolvedModule;
private SyncContext syncContext;
public UpdateModule(ModuleManager settings, ResolvedModule resolvedModule, ModifiableRootModel rootModel, Map<String, Library> usedProjectLibraries, ModifiableModuleModel modulesModel, SyncContext syncContext) {
m_rootModel = rootModel;
m_resolvedModule = resolvedModule;
m_moduleManager = settings;
m_usedProjectLibraries = usedProjectLibraries;
this.syncContext = syncContext;
}
private Library synchronizeProjectLib(final Dependency libraryId, ProgressIndicator progressIndicator) {
String name = (new StringBuilder()).append(libraryId.getName()).append("-").append(libraryId.getRevision()).toString();
return synchronizeProjectLib(name, new LibraryDescriptorProvider() {
public LibraryDescriptor getDescriptor() {
return m_moduleManager.getLibraryDescriptor(m_resolvedModule, libraryId);
}
}, progressIndicator);
}
private static interface LibraryDescriptorProvider {
LibraryDescriptor getDescriptor();
}
private Library synchronizeProjectLib(final String libraryName, LibraryDescriptorProvider libraryDescriptorProvider, ProgressIndicator progressIndicator) {
String name = libraryName;
Library cachedLibrary = m_usedProjectLibraries.get(name);
if (cachedLibrary == null) { //no yet synced (same artifact can be synced by different modules)
LibraryDescriptor descriptor = libraryDescriptorProvider.getDescriptor();
Library library = synchronizeLib(syncContext.getProjectLibraryTableModel(), name, descriptor, progressIndicator);
m_usedProjectLibraries.put(name, library);
return library;
} else {
return cachedLibrary;
}
}
private Library synchronizeLib(final LibraryTable.ModifiableModel libraryTable, final String name, final LibraryDescriptor descriptor, ProgressIndicator progressIndicator) {
progressIndicator.setText2("Synchronizing library " + name);
boolean modified = false;
Library libraryByName = libraryTable.getLibraryByName(name);
if (libraryByName == null) {
LOGGER.debug("New library " + name);
modified = true;
libraryByName = libraryTable.createLibrary(name);
}
Library.ModifiableModel libModifiableModel = syncContext.getLibraryModel(libraryByName);
VirtualFile[] classFiles = UpdateModule.this.createVirtualFiles(descriptor.getClasses(), progressIndicator);
VirtualFile[] sourceFiles = UpdateModule.this.createVirtualFiles(descriptor.getSources(), progressIndicator);
modified = modified | UpdateModule.syncRoots(classFiles, libModifiableModel, OrderRootType.CLASSES);
modified = modified | UpdateModule.syncRoots(sourceFiles, libModifiableModel, OrderRootType.SOURCES);
LOGGER.debug("Library modified " + name + ": " + modified);
return libraryByName;
}
private static boolean syncRoots(VirtualFile[] classes, Library.ModifiableModel libModifiableModel, OrderRootType rootType) {
boolean modified = false;
List<String> oldUrls = new LinkedList<String>(Arrays.asList(libModifiableModel.getUrls(rootType)));
for (VirtualFile virtualFile : classes) {
boolean alreadyAvailable = false;
for (String oldUrl : oldUrls) {
if (oldUrl.equals(virtualFile.getUrl())) {
alreadyAvailable = true;
oldUrls.remove(oldUrl);
break;
}
}
if (!alreadyAvailable) {
modified = true;
libModifiableModel.addRoot(virtualFile, rootType);
}
}
//Remove old stuff that is not available in the new stuff
for (String oldUrl : oldUrls) {
modified = true;
UpdateModule.LOGGER.info("Removing " + oldUrl + " from " + libModifiableModel.getName());
libModifiableModel.removeRoot(oldUrl, rootType);
}
return modified;
}
private ContentEntry getContentEntry(VirtualFile myContentRoot) {
ContentEntry[] contentEntries = m_rootModel.getContentEntries();
for (ContentEntry contentEntry : contentEntries) {
if (myContentRoot.equals(contentEntry.getFile())) {
return contentEntry;
}
}
return m_rootModel.addContentEntry(myContentRoot);
}
private void addSource(final Collection<SourceFolder> unusedSourceFolders, final Collection<ExcludeFolder> unusedExcludes, final Set<ContentEntry> unusedEntries, final ContentRoot[] contentRoots, ProgressIndicator progressIndicator) {
for (ContentRoot contentRoot : contentRoots) {
VirtualFile virtualRootDir = LocalFileSystem.getInstance().findFileByIoFile(contentRoot.getDir());
if (virtualRootDir == null) {
UpdateModule.LOGGER.error("Unexisting root: " + virtualRootDir);
} else {
ContentEntry contentEntry = getContentEntry(virtualRootDir);
unusedEntries.remove(contentEntry);
File[] mainJavaDirs = contentRoot.getMainJavaDirs();
for (File mainJavaDir : mainJavaDirs) {
syncSource(contentEntry, mainJavaDir, unusedSourceFolders, false);
}
File[] testJavaDirs = contentRoot.getTestJavaDirs();
for (File testJavaDir : testJavaDirs) {
syncSource(contentEntry, testJavaDir, unusedSourceFolders, true);
}
File[] excludedDirs = contentRoot.getExcludedDirs();
for (File excludedDir : excludedDirs) {
VirtualFile fileByIoFile = LocalFileSystem.getInstance().findFileByIoFile(excludedDir);
if (fileByIoFile != null) {
Iterator<ExcludeFolder> unusedIterator = unusedExcludes.iterator();
while (unusedIterator.hasNext()) {
ExcludeFolder excludeFolder = unusedIterator.next();
if (fileByIoFile.equals(excludeFolder.getFile())) {
unusedIterator.remove();
}
}
contentEntry.addExcludeFolder(fileByIoFile);
}
}
}
}
}
private void syncSource(ContentEntry contentEntry, File mainJavaDir, Collection<SourceFolder> unusedSourceFolders, boolean test) {
VirtualFile fileByIoFile = LocalFileSystem.getInstance().findFileByIoFile(mainJavaDir);
if (fileByIoFile != null) {
Iterator<SourceFolder> iterator = unusedSourceFolders.iterator();
while (iterator.hasNext()) {
SourceFolder sourceFolder = iterator.next();
if (fileByIoFile.equals(sourceFolder.getFile())) {
if (!(sourceFolder.isTestSource() ^ test)) {
iterator.remove();
return;
}
}
}
contentEntry.addSourceFolder(fileByIoFile, test);
}
}
public void run(final ProgressIndicator progressIndicator) {
try {
LOGGER.debug("Updating " + m_resolvedModule.getModuleId());
progressIndicator.setText2("");
progressIndicator.setText("Synchronizing module " + m_resolvedModule.getModuleId());
final List<String> dependantModules = new LinkedList<String>();
final Map<ModuleId, File> modules = m_moduleManager.getModules();
ModuleSourceDescriptor rootModuleDescriptor = m_moduleManager.getModuleSource(modules.get(m_resolvedModule.getModuleId()));
File compilerOutputPath = rootModuleDescriptor.getCompilerOutputPath();
CompilerModuleExtension compilerModule = m_rootModel.getModuleExtension(CompilerModuleExtension.class);
if (compilerOutputPath != null) {
String path = compilerOutputPath.getPath();
String url = VirtualFileManager.constructUrl("file", path.replace(File.separatorChar, '/'));
compilerModule.inheritCompilerOutputPath(false);
compilerModule.setCompilerOutputPath(url);
}
File compilerOutputPathForTests = rootModuleDescriptor.getCompilerOutputPathForTests();
if (compilerOutputPathForTests != null) {
compilerModule.setCompilerOutputPathForTests(VirtualFileManager.constructUrl("file", compilerOutputPathForTests.getPath().replace(File.separatorChar, '/')));
}
compilerModule.setExcludeOutput(true);
//clean (non syntetic) order entries
for (OrderEntry orderEntry : m_rootModel.getOrderEntries()) {
if (!orderEntry.isSynthetic())
m_rootModel.removeOrderEntry(orderEntry);
}
m_rootModel.inheritSdk();
final Map<Library, DependencyScope> librariesUseByThisModule = new LinkedHashMap<Library, DependencyScope>();
Map<ModuleId, DependencyType> internalModules = m_moduleManager.getDependantModules(m_resolvedModule);
final Set<SourceFolder> unusedSourceFolders = new HashSet<SourceFolder>();
final Set<ContentEntry> unusedContentEntries = new HashSet<ContentEntry>();
final ContentEntry[] contentEntries = m_rootModel.getContentEntries();
final Collection<ExcludeFolder> unusedExcludes = new LinkedList<ExcludeFolder>();
ProgressUtilities.executeAsReadAction(new ProgressAwareCallable<Object>() {
public Object call(ProgressIndicator indicator) throws Exception {
for (ContentEntry contentEntry : contentEntries) {
unusedContentEntries.add(contentEntry);
SourceFolder[] sourceFolders = contentEntry.getSourceFolders();
unusedSourceFolders.addAll(Arrays.asList(sourceFolders));
//getexcluded folders requires read access !!
unusedExcludes.addAll(Arrays.asList(contentEntry.getExcludeFolders()));
}
return null;
}
}, progressIndicator);
addModuleContentRoots(librariesUseByThisModule, unusedSourceFolders, unusedExcludes, unusedContentEntries, rootModuleDescriptor, progressIndicator);
final File[] extraRuntimeLibrary = rootModuleDescriptor.getExtraRuntimeLibary();
if (extraRuntimeLibrary.length > 0) {
synchronizeProjectLib(extraRuntimeLibrary[0].getPath(), new LibraryDescriptorProvider() {
public LibraryDescriptor getDescriptor() {
return new LibraryDescriptorImpl(extraRuntimeLibrary);
}
}, progressIndicator);
}
addFilesAsLibrary(librariesUseByThisModule, progressIndicator, syncContext.getProjectLibraryTableModel(), DependencyScope.RUNTIME, extraRuntimeLibrary);
Collection<Dependency> dependencies = m_resolvedModule.getDependencies();
for (final Dependency dependency : dependencies) {
DependencyType dependencyType = internalModules.get(dependency.getModuleId());
UpdateModule.LOGGER.debug("DependencyType for " + dependency + " " + dependencyType);
if (dependencyType == null || dependencyType.equals(DependencyType.LIBRARY)) {
Library library = synchronizeProjectLib(dependency, progressIndicator);
librariesUseByThisModule.put(library, dependency.getScope());
} else {
String dependantModuleName = dependency.getModuleId().getName();
//m_rootModel.addInvalidModuleEntry(dependantModuleName); adding invalid module required read access.. Don't use it..
//this only works when committing models at the end...
Module dependantModule = syncContext.getModulesModel().findModuleByName(dependantModuleName);
// LOGGER.debug("Adding module dependency to module " + m_rootModel.getModule().getName() + ": " + dependantModule);
m_rootModel.addModuleOrderEntry(dependantModule).setScope(dependency.getScope());
dependantModules.add(dependantModuleName);
//ModuleSourceDescriptor moduleSourceDescriptor = m_moduleManager.getModuleSource(file);
//addModuleSource(librariesUseByThisModule, unusedSourceFolders, unusedContentEntries, moduleSourceDescriptor);
}
}
progressIndicator.setText2("");
LibraryTable.ModifiableModel moduleLibraryTableModel = m_rootModel.getModuleLibraryTable().getModifiableModel();
synchronizeUsedLibraries(librariesUseByThisModule, moduleLibraryTableModel, m_rootModel, true);
synchronizeUsedLibraries(librariesUseByThisModule, syncContext.getProjectLibraryTableModel(), m_rootModel, false);
cleanUnusedLibraries(librariesUseByThisModule.keySet(), moduleLibraryTableModel);
List<String> libraryNamesUsedByThisModule = new LinkedList<String>();
for (Library library : librariesUseByThisModule.keySet()) {
libraryNamesUsedByThisModule.add(library.getName());
}
LOGGER.debug("Used libraries: " + libraryNamesUsedByThisModule);
cleanUnusedFolders(unusedSourceFolders, unusedExcludes, unusedContentEntries);
//remove unused and duplicate module dependencies
Set<String> orderEntries = new LinkedHashSet<String>();
progressIndicator.setText2("");
LOGGER.debug("Updating " + m_resolvedModule.getModuleId() + " done");
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private VirtualFile[] createVirtualFiles(File[] files, ProgressIndicator progressIndicator) {
Collection<VirtualFile> libDirs = new LinkedList<VirtualFile>();
for (final File file : files) {
final VirtualFile virtualFile;
if (file.isDirectory()) {
virtualFile = LocalFileSystem.getInstance().findFileByIoFile(file);
} else {//for jar files, special care is needed..
//both jar and zip files should be handled like this..
final String urlString = VirtualFileManager.constructUrl("jar", (new StringBuilder()).append(file.getAbsolutePath().replaceAll("\\\\", "/")).append("!/").toString());
virtualFile = VirtualFileManager.getInstance().findFileByUrl(urlString);
}
if (virtualFile != null) {
libDirs.add(virtualFile);
} else {
LOGGER.debug("Unable to find virtual file for " + file);
}
}
return libDirs.toArray(new VirtualFile[libDirs.size()]);
}
private void checkDuplicates(Set<String> orderEntries, OrderEntry orderEntry) {
if (orderEntries.contains(orderEntry.getPresentableName())) {
//duplicate
LOGGER.debug("Removing duplicate order entry: " + orderEntry.getPresentableName());
m_rootModel.removeOrderEntry(orderEntry);
}
orderEntries.add(orderEntry.getPresentableName());
}
private void cleanUnusedFolders(Collection<SourceFolder> unusedSourceFolders, Collection<ExcludeFolder> unusedExcludes, Set<ContentEntry> unusedEntries) {
ContentEntry[] contentEntries = m_rootModel.getContentEntries();
for (ContentEntry contentEntry : contentEntries) {
for (SourceFolder sourceFolder : unusedSourceFolders) {
if (contentEntry.equals(sourceFolder.getContentEntry())) {
contentEntry.removeSourceFolder(sourceFolder);
}
}
if (unusedEntries.contains(contentEntry)) {
m_rootModel.removeContentEntry(contentEntry);
}
for (ExcludeFolder unusedExclude : unusedExcludes) {
if (!unusedExclude.isSynthetic()) {
if (contentEntry.equals(unusedExclude.getContentEntry())) {
LOGGER.debug("Removing unused exclude " + unusedExclude.getUrl() + " " + unusedExclude.isSynthetic());
contentEntry.removeExcludeFolder(unusedExclude);
}
}
}
}
}
private void synchronizeUsedLibraries(final Map<Library, DependencyScope> usedLibraries, final LibraryTable.ModifiableModel libraryTable, final ModifiableRootModel rootModel, final boolean export) {
//for all entries in the model that are not used, remove order entry..
Collection<Library> libraryIterator = new LinkedList<Library>(Arrays.asList(libraryTable.getLibraries()));
for (Library library : libraryIterator) {
DependencyScope scope = usedLibraries.get(library);
if (scope == null) {
LibraryOrderEntry libraryOrderEntry = rootModel.findLibraryOrderEntry(library);
if (libraryOrderEntry != null) {
rootModel.removeOrderEntry(libraryOrderEntry);
}
} else {
LibraryOrderEntry libraryOrderEntry = rootModel.findLibraryOrderEntry(library);
if (libraryOrderEntry == null) {
//Altough the library is valid, we add an invalid library here...
//that works better on intellij 7. If we add the library directly,
//the returned order entry always gives back 'invalid' (for new libraries at least),
//and intellij get's messed up internally (red lines where there should not be..)
libraryOrderEntry = rootModel.addInvalidLibrary(library.getName(), library.getTable().getTableLevel());
}
libraryOrderEntry.setExported(export);
libraryOrderEntry.setScope(scope);
}
}
}
static void cleanUnusedLibraries(Collection<Library> usedLibraries, LibraryTable.ModifiableModel libraryTable) {
Collection<Library> libraries = new LinkedList<Library>(Arrays.asList(libraryTable.getLibraries()));
List<String> usedLibraryNames = new LinkedList<String>();
for (Library library : libraries) {
if (!usedLibraries.contains(library)) {
//remove
LOGGER.debug("Removing unused library: " + library.getName() + " " + library);
libraryTable.removeLibrary(library);
} else if (usedLibraryNames.contains(library.getName())) {
//removing duplicate..
LOGGER.debug("Removing duplicate library: " + library.getName() + " " + library);
libraryTable.removeLibrary(library);
}
usedLibraryNames.add(library.getName());
}
}
private void addModuleContentRoots(Map<Library, DependencyScope> librariesUseByThisModule, Collection<SourceFolder> unusedSourceFolders, Collection<ExcludeFolder> unusedExcludes, Set<ContentEntry> unusedEntries, ModuleSourceDescriptor rootModuleDescriptor, ProgressIndicator progressIndicator) {
ContentRoot[] contentRoots = rootModuleDescriptor.getContentRoots();
addSource(unusedSourceFolders, unusedExcludes, unusedEntries, contentRoots, progressIndicator);
LibraryTable.ModifiableModel moduleLibraryTable = m_rootModel.getModuleLibraryTable().getModifiableModel();
for (ContentRoot contentRoot : contentRoots) {
addFilesAsLibrary(librariesUseByThisModule, progressIndicator, moduleLibraryTable, DependencyScope.COMPILE, contentRoot.getMainResourceDirs());
addFilesAsLibrary(librariesUseByThisModule, progressIndicator, moduleLibraryTable, DependencyScope.TEST, contentRoot.getTestResourceDirs());
}
}
private void addFilesAsLibrary(Map<Library, DependencyScope> librariesUseByThisModule, ProgressIndicator progressIndicator, LibraryTable.ModifiableModel libraryTable, DependencyScope scope, File... files) {
final List<File> libDirs = new LinkedList<File>();
libDirs.addAll(Arrays.asList(files));
if (libDirs.size() > 0) {
Library library = synchronizeLib(libraryTable, libDirs.get(0).getPath(), new LibraryDescriptorImpl(libDirs.toArray(new File[libDirs.size()])), progressIndicator);
librariesUseByThisModule.put(library, scope);
}
}
}
|