| /* |
| * Copyright 2000-2009 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.ide.util.importProject; |
| |
| import com.intellij.ide.util.projectWizard.importSources.DetectedProjectRoot; |
| import com.intellij.ide.util.projectWizard.importSources.DetectedSourceRoot; |
| import com.intellij.ide.util.projectWizard.importSources.impl.ProjectFromSourcesBuilderImpl; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.progress.ProcessCanceledException; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.StringBuilderSpinAllocator; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.StringInterner; |
| import com.intellij.util.text.StringFactory; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.*; |
| |
| /** |
| * @author Eugene Zhuravlev |
| * Date: Jul 3, 2007 |
| */ |
| public abstract class ModuleInsight { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.importProject.ModuleInsight"); |
| @NotNull private final ProgressIndicatorWrapper myProgress; |
| |
| private final Set<File> myEntryPointRoots = new HashSet<File>(); |
| private final List<DetectedSourceRoot> mySourceRoots = new ArrayList<DetectedSourceRoot>(); |
| private final Set<String> myIgnoredNames = new HashSet<String>(); |
| |
| private final Map<File, Set<String>> mySourceRootToReferencedPackagesMap = new HashMap<File, Set<String>>(); |
| private final Map<File, Set<String>> mySourceRootToPackagesMap = new HashMap<File, Set<String>>(); |
| private final Map<File, Set<String>> myJarToPackagesMap = new HashMap<File, Set<String>>(); |
| private final StringInterner myInterner = new StringInterner(); |
| |
| private List<ModuleDescriptor> myModules; |
| private List<LibraryDescriptor> myLibraries; |
| private final Set<String> myExistingModuleNames; |
| private final Set<String> myExistingProjectLibraryNames; |
| |
| public ModuleInsight(@Nullable final ProgressIndicator progress, Set<String> existingModuleNames, Set<String> existingProjectLibraryNames) { |
| myExistingModuleNames = existingModuleNames; |
| myExistingProjectLibraryNames = existingProjectLibraryNames; |
| myProgress = new ProgressIndicatorWrapper(progress); |
| setRoots(Collections.<File>emptyList(), Collections.<DetectedSourceRoot>emptyList(), Collections.<String>emptySet()); |
| } |
| |
| public final void setRoots(final List<File> contentRoots, final List<? extends DetectedSourceRoot> sourceRoots, final Set<String> ignoredNames) { |
| myModules = null; |
| myLibraries = null; |
| |
| myEntryPointRoots.clear(); |
| myEntryPointRoots.addAll(contentRoots); |
| |
| mySourceRoots.clear(); |
| mySourceRoots.addAll(sourceRoots); |
| |
| myIgnoredNames.clear(); |
| myIgnoredNames.addAll(ignoredNames); |
| |
| myJarToPackagesMap.clear(); |
| myInterner.clear(); |
| } |
| |
| @Nullable |
| public List<LibraryDescriptor> getSuggestedLibraries() { |
| return myLibraries; |
| } |
| |
| @Nullable |
| public List<ModuleDescriptor> getSuggestedModules() { |
| return myModules; |
| } |
| |
| public void scanModules() { |
| myProgress.setIndeterminate(true); |
| final Map<File, ModuleDescriptor> contentRootToModules = new HashMap<File, ModuleDescriptor>(); |
| |
| try { |
| myProgress.pushState(); |
| |
| List<DetectedSourceRoot> processedRoots = new ArrayList<DetectedSourceRoot>(); |
| for (DetectedSourceRoot root : mySourceRoots) { |
| final File sourceRoot = root.getDirectory(); |
| if (myIgnoredNames.contains(sourceRoot.getName())) { |
| continue; |
| } |
| myProgress.setText("Scanning " + sourceRoot.getPath()); |
| |
| final HashSet<String> usedPackages = new HashSet<String>(); |
| mySourceRootToReferencedPackagesMap.put(sourceRoot, usedPackages); |
| |
| final HashSet<String> selfPackages = new HashSet<String>(); |
| mySourceRootToPackagesMap.put(sourceRoot, selfPackages); |
| |
| scanSources(sourceRoot, ProjectFromSourcesBuilderImpl.getPackagePrefix(root), usedPackages, selfPackages) ; |
| usedPackages.removeAll(selfPackages); |
| processedRoots.add(root); |
| } |
| myProgress.popState(); |
| |
| myProgress.pushState(); |
| myProgress.setText("Building modules layout..."); |
| for (DetectedSourceRoot sourceRoot : processedRoots) { |
| final File srcRoot = sourceRoot.getDirectory(); |
| final File moduleContentRoot = myEntryPointRoots.contains(srcRoot)? srcRoot : srcRoot.getParentFile(); |
| ModuleDescriptor moduleDescriptor = contentRootToModules.get(moduleContentRoot); |
| if (moduleDescriptor != null) { |
| moduleDescriptor.addSourceRoot(moduleContentRoot, sourceRoot); |
| } |
| else { |
| moduleDescriptor = createModuleDescriptor(moduleContentRoot, Collections.singletonList(sourceRoot)); |
| contentRootToModules.put(moduleContentRoot, moduleDescriptor); |
| } |
| } |
| |
| buildModuleDependencies(contentRootToModules); |
| |
| myProgress.popState(); |
| } |
| catch (ProcessCanceledException ignored) { |
| } |
| |
| myModules = new ArrayList<ModuleDescriptor>(contentRootToModules.values()); |
| final Set<String> moduleNames = new HashSet<String>(myExistingModuleNames); |
| for (ModuleDescriptor module : myModules) { |
| final String suggested = suggestUniqueName(moduleNames, module.getName()); |
| module.setName(suggested); |
| moduleNames.add(suggested); |
| } |
| } |
| |
| protected abstract ModuleDescriptor createModuleDescriptor(final File moduleContentRoot, Collection<DetectedSourceRoot> sourceRoots); |
| |
| private void buildModuleDependencies(final Map<File, ModuleDescriptor> contentRootToModules) { |
| final Set<File> moduleContentRoots = contentRootToModules.keySet(); |
| |
| for (File contentRoot : moduleContentRoots) { |
| final ModuleDescriptor checkedModule = contentRootToModules.get(contentRoot); |
| myProgress.setText2("Building library dependencies for module " + checkedModule.getName()); |
| buildJarDependencies(checkedModule); |
| |
| myProgress.setText2("Building module dependencies for module " + checkedModule.getName()); |
| for (File aContentRoot : moduleContentRoots) { |
| final ModuleDescriptor aModule = contentRootToModules.get(aContentRoot); |
| if (checkedModule.equals(aModule)) { |
| continue; // avoid self-dependencies |
| } |
| final Collection<? extends DetectedProjectRoot> aModuleRoots = aModule.getSourceRoots(); |
| checkModules: |
| for (DetectedProjectRoot srcRoot: checkedModule.getSourceRoots()) { |
| final Set<String> referencedBySourceRoot = mySourceRootToReferencedPackagesMap.get(srcRoot.getDirectory()); |
| for (DetectedProjectRoot aSourceRoot : aModuleRoots) { |
| if (ContainerUtil.intersects(referencedBySourceRoot, mySourceRootToPackagesMap.get(aSourceRoot.getDirectory()))) { |
| checkedModule.addDependencyOn(aModule); |
| break checkModules; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void buildJarDependencies(final ModuleDescriptor module) { |
| for (File jarFile : myJarToPackagesMap.keySet()) { |
| final Set<String> jarPackages = myJarToPackagesMap.get(jarFile); |
| for (DetectedProjectRoot srcRoot : module.getSourceRoots()) { |
| if (ContainerUtil.intersects(mySourceRootToReferencedPackagesMap.get(srcRoot.getDirectory()), jarPackages)) { |
| module.addLibraryFile(jarFile); |
| break; |
| } |
| } |
| } |
| } |
| |
| public void scanLibraries() { |
| myProgress.setIndeterminate(true); |
| myProgress.pushState(); |
| try { |
| try { |
| for (File root : myEntryPointRoots) { |
| myProgress.setText("Scanning for libraries " + root.getPath()); |
| scanRootForLibraries(root); |
| } |
| } |
| catch (ProcessCanceledException ignored) { |
| } |
| myProgress.setText("Building initial libraries layout..."); |
| final List<LibraryDescriptor> libraries = buildInitialLibrariesLayout(myJarToPackagesMap.keySet()); |
| // correct library names so that there are no duplicates |
| final Set<String> libNames = new HashSet<String>(myExistingProjectLibraryNames); |
| for (LibraryDescriptor library : libraries) { |
| final Collection<File> libJars = library.getJars(); |
| final String newName = suggestUniqueName(libNames, libJars.size() == 1? FileUtil.getNameWithoutExtension(libJars.iterator().next()) : library.getName()); |
| library.setName(newName); |
| libNames.add(newName); |
| } |
| myLibraries = libraries; |
| } |
| finally { |
| myProgress.popState(); |
| } |
| } |
| |
| public abstract boolean isApplicableRoot(final DetectedProjectRoot root); |
| |
| private static String suggestUniqueName(Set<String> existingNames, String baseName) { |
| String name = baseName; |
| int index = 1; |
| while (existingNames.contains(name)) { |
| name = baseName + (index++); |
| } |
| return name; |
| } |
| |
| public void merge(final ModuleDescriptor mainModule, final ModuleDescriptor module) { |
| for (File contentRoot : module.getContentRoots()) { |
| final File _contentRoot = appendContentRoot(mainModule, contentRoot); |
| final Collection<DetectedSourceRoot> sources = module.getSourceRoots(contentRoot); |
| for (DetectedSourceRoot source : sources) { |
| mainModule.addSourceRoot(_contentRoot, source); |
| } |
| } |
| for (File jar : module.getLibraryFiles()) { |
| mainModule.addLibraryFile(jar); |
| } |
| // fix forward dependencies |
| for (ModuleDescriptor dependency : module.getDependencies()) { |
| if (!mainModule.equals(dependency)) { // avoid self-dependencies |
| mainModule.addDependencyOn(dependency); |
| } |
| } |
| |
| myModules.remove(module); |
| // fix back dependencies |
| for (ModuleDescriptor moduleDescr : myModules) { |
| if (moduleDescr.getDependencies().contains(module)) { |
| moduleDescr.removeDependencyOn(module); |
| if (!moduleDescr.equals(mainModule)) { // avoid self-dependencies |
| moduleDescr.addDependencyOn(mainModule); |
| } |
| } |
| } |
| } |
| |
| public LibraryDescriptor splitLibrary(LibraryDescriptor library, String newLibraryName, final Collection<File> jarsToExtract) { |
| final LibraryDescriptor newLibrary = new LibraryDescriptor(newLibraryName, jarsToExtract); |
| myLibraries.add(newLibrary); |
| library.removeJars(jarsToExtract); |
| if (library.getJars().size() == 0) { |
| removeLibrary(library); |
| } |
| return newLibrary; |
| } |
| |
| @Nullable |
| public ModuleDescriptor splitModule(final ModuleDescriptor descriptor, String newModuleName, final Collection<File> contentsToExtract) { |
| ModuleDescriptor newModule = null; |
| for (File root : contentsToExtract) { |
| final Collection<DetectedSourceRoot> sources = descriptor.removeContentRoot(root); |
| if (newModule == null) { |
| newModule = createModuleDescriptor(root, sources != null ? sources : new HashSet<DetectedSourceRoot>()); |
| } |
| else { |
| if (sources != null && sources.size() > 0) { |
| for (DetectedSourceRoot source : sources) { |
| newModule.addSourceRoot(root, source); |
| } |
| } |
| else { |
| newModule.addContentRoot(root); |
| } |
| } |
| } |
| |
| if (newModule != null) { |
| newModule.setName(newModuleName); |
| myModules.add(newModule); |
| } |
| else { |
| return null; |
| } |
| |
| final Map<File, ModuleDescriptor> contentRootToModule = new HashMap<File, ModuleDescriptor>(); |
| for (ModuleDescriptor module : myModules) { |
| final Set<File> roots = module.getContentRoots(); |
| for (File root : roots) { |
| contentRootToModule.put(root, module); |
| } |
| module.clearModuleDependencies(); |
| module.clearLibraryFiles(); |
| } |
| |
| buildModuleDependencies(contentRootToModule); |
| return newModule; |
| } |
| |
| public void removeLibrary(LibraryDescriptor lib) { |
| myLibraries.remove(lib); |
| } |
| |
| public void moveJarsToLibrary(final LibraryDescriptor from, Collection<File> files, LibraryDescriptor to) { |
| to.addJars(files); |
| from.removeJars(files); |
| // remove the library if it became empty |
| if (from.getJars().size() == 0) { |
| removeLibrary(from); |
| } |
| } |
| |
| public Collection<LibraryDescriptor> getLibraryDependencies(ModuleDescriptor module) { |
| return getLibraryDependencies(module, myLibraries); |
| } |
| |
| public static Collection<LibraryDescriptor> getLibraryDependencies(ModuleDescriptor module, |
| final List<LibraryDescriptor> allLibraries) { |
| final Set<LibraryDescriptor> libs = new HashSet<LibraryDescriptor>(); |
| for (LibraryDescriptor library : allLibraries) { |
| if (ContainerUtil.intersects(library.getJars(), module.getLibraryFiles())) { |
| libs.add(library); |
| } |
| } |
| return libs; |
| } |
| |
| private static File appendContentRoot(final ModuleDescriptor module, final File contentRoot) { |
| final Set<File> moduleRoots = module.getContentRoots(); |
| for (File moduleRoot : moduleRoots) { |
| if (FileUtil.isAncestor(moduleRoot, contentRoot, false)) { |
| return moduleRoot; // no need to include a separate root |
| } |
| if (FileUtil.isAncestor(contentRoot, moduleRoot, true)) { |
| final Collection<DetectedSourceRoot> currentSources = module.getSourceRoots(moduleRoot); |
| module.removeContentRoot(moduleRoot); |
| module.addContentRoot(contentRoot); |
| for (DetectedSourceRoot source : currentSources) { |
| module.addSourceRoot(contentRoot, source); |
| } |
| return contentRoot; // no need to include a separate root |
| } |
| } |
| module.addContentRoot(contentRoot); |
| return contentRoot; |
| } |
| |
| |
| private static List<LibraryDescriptor> buildInitialLibrariesLayout(final Set<File> jars) { |
| final Map<File, LibraryDescriptor> rootToLibraryMap = new HashMap<File, LibraryDescriptor>(); |
| for (File jar : jars) { |
| final File parent = jar.getParentFile(); |
| LibraryDescriptor lib = rootToLibraryMap.get(parent); |
| if (lib == null) { |
| lib = new LibraryDescriptor(parent.getName(), new HashSet<File>()); |
| rootToLibraryMap.put(parent, lib); |
| } |
| lib.addJars(Collections.singleton(jar)); |
| } |
| return new ArrayList<LibraryDescriptor>(rootToLibraryMap.values()); |
| } |
| |
| private void scanSources(final File fromRoot, final String parentPackageName, final Set<String> usedPackages, final Set<String> selfPackages) { |
| if (myIgnoredNames.contains(fromRoot.getName())) { |
| return; |
| } |
| final File[] files = fromRoot.listFiles(); |
| if (files != null) { |
| myProgress.checkCanceled(); |
| boolean includeParentName = false; |
| for (File file : files) { |
| if (file.isDirectory()) { |
| final String subPackageName; |
| final StringBuilder builder = StringBuilderSpinAllocator.alloc(); |
| try { |
| builder.append(parentPackageName); |
| if (builder.length() > 0) { |
| builder.append("."); |
| } |
| builder.append(file.getName()); |
| subPackageName = builder.toString(); |
| } |
| finally { |
| StringBuilderSpinAllocator.dispose(builder); |
| } |
| scanSources(file, subPackageName, usedPackages, selfPackages); |
| } |
| else { |
| if (isSourceFile(file)) { |
| includeParentName = true; |
| scanSourceFile(file, usedPackages); |
| } |
| } |
| } |
| if (includeParentName) { |
| selfPackages.add(myInterner.intern(parentPackageName)); |
| } |
| } |
| } |
| |
| protected abstract boolean isSourceFile(final File file); |
| |
| private void scanSourceFile(File file, final Set<String> usedPackages) { |
| myProgress.setText2(file.getName()); |
| try { |
| final char[] chars = FileUtil.loadFileText(file); |
| scanSourceFileForImportedPackages(StringFactory.createShared(chars), new Consumer<String>() { |
| public void consume(final String s) { |
| usedPackages.add(myInterner.intern(s)); |
| } |
| }); |
| } |
| catch (IOException e) { |
| LOG.info(e); |
| } |
| } |
| |
| protected abstract void scanSourceFileForImportedPackages(final CharSequence chars, Consumer<String> result); |
| |
| private void scanRootForLibraries(File fromRoot) { |
| if (myIgnoredNames.contains(fromRoot.getName())) { |
| return; |
| } |
| final File[] files = fromRoot.listFiles(); |
| if (files != null) { |
| myProgress.checkCanceled(); |
| for (File file : files) { |
| if (file.isDirectory()) { |
| scanRootForLibraries(file); |
| } |
| else { |
| final String fileName = file.getName(); |
| if (isLibraryFile(fileName)) { |
| if (!myJarToPackagesMap.containsKey(file)) { |
| final HashSet<String> libraryPackages = new HashSet<String>(); |
| myJarToPackagesMap.put(file, libraryPackages); |
| |
| myProgress.pushState(); |
| myProgress.setText2(file.getName()); |
| try { |
| scanLibraryForDeclaredPackages(file, new Consumer<String>() { |
| public void consume(final String s) { |
| if (!libraryPackages.contains(s)) { |
| libraryPackages.add(myInterner.intern(s)); |
| } |
| } |
| }); |
| } |
| catch (IOException e) { |
| LOG.info(e); |
| } |
| catch (InternalError e) { // indicates that file is somehow damaged and cannot be processed |
| LOG.info(e); |
| } |
| finally { |
| myProgress.popState(); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| protected abstract boolean isLibraryFile(final String fileName); |
| |
| protected abstract void scanLibraryForDeclaredPackages(File file, Consumer<String> result) throws IOException; |
| |
| } |