From 5ae1e907d7fc675314d0f57183f22e7a1a40328f Mon Sep 17 00:00:00 2001 From: Sergey Simonchik Date: Tue, 31 Jan 2017 19:19:55 +0300 Subject: [PATCH] IDEA-160131 Consider adding roots provided by AdditionalLibraryRootsProvider to External Libraries --- .../roots/impl/DirectoryIndexTest.java | 6 +- .../impl/nodes/ExternalLibrariesNode.java | 35 ++++--- .../nodes/SyntheticLibraryElementNode.java | 92 +++++++++++++++++++ ...leBasedIndexScanRunnableCollectorImpl.java | 5 +- .../src/com/intellij/openapi/vfs/VfsUtil.java | 27 ++++++ .../roots/AdditionalLibraryRootsProvider.java | 17 +++- .../openapi/roots/SyntheticLibrary.java | 65 +++++++++++++ .../openapi/roots/impl/RootIndex.java | 11 ++- 8 files changed, 237 insertions(+), 21 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/SyntheticLibraryElementNode.java create mode 100644 platform/projectModel-impl/src/com/intellij/openapi/roots/SyntheticLibrary.java diff --git a/java/java-tests/testSrc/com/intellij/openapi/roots/impl/DirectoryIndexTest.java b/java/java-tests/testSrc/com/intellij/openapi/roots/impl/DirectoryIndexTest.java index 8eb8271c0ae1..090106340027 100644 --- a/java/java-tests/testSrc/com/intellij/openapi/roots/impl/DirectoryIndexTest.java +++ b/java/java-tests/testSrc/com/intellij/openapi/roots/impl/DirectoryIndexTest.java @@ -158,8 +158,10 @@ public class DirectoryIndexTest extends IdeaTestCase { PlatformTestUtil.registerExtension(AdditionalLibraryRootsProvider.EP_NAME, new AdditionalLibraryRootsProvider() { @NotNull @Override - public Collection getAdditionalProjectLibrarySourceRoots(@NotNull Project project) { - return myProject == project ? Collections.singletonList(myLibAdditionalSrcDir) : Collections.emptyList(); + public Collection getAdditionalProjectLibraries(@NotNull Project project) { + return myProject == project ? Collections.singletonList( + SyntheticLibrary.newFixedLibrary(null, Collections.singletonList(myLibAdditionalSrcDir)) + ) : Collections.emptyList(); } }, getTestRootDisposable()); diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ExternalLibrariesNode.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ExternalLibrariesNode.java index 03c4db01a728..d94b57436688 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ExternalLibrariesNode.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ExternalLibrariesNode.java @@ -38,10 +38,7 @@ import com.intellij.util.PlatformIcons; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Set; +import java.util.*; public class ExternalLibrariesNode extends ProjectViewNode { public ExternalLibrariesNode(Project project, ViewSettings viewSettings) { @@ -50,7 +47,11 @@ public class ExternalLibrariesNode extends ProjectViewNode { @Override public boolean contains(@NotNull VirtualFile file) { - ProjectFileIndex index = ProjectRootManager.getInstance(getProject()).getFileIndex(); + Project project = getProject(); + if (project == null) { + return false; + } + ProjectFileIndex index = ProjectFileIndex.getInstance(project); if (!index.isInLibrarySource(file) && !index.isInLibraryClasses(file)) return false; return someChildContainsFile(file, false); @@ -59,9 +60,13 @@ public class ExternalLibrariesNode extends ProjectViewNode { @NotNull @Override public Collection getChildren() { - final List children = new ArrayList<>(); - ProjectFileIndex fileIndex = ProjectRootManager.getInstance(getProject()).getFileIndex(); - Module[] modules = ModuleManager.getInstance(getProject()).getModules(); + Project project = getProject(); + if (project == null) { + return Collections.emptyList(); + } + List children = new ArrayList<>(); + ProjectFileIndex fileIndex = ProjectFileIndex.getInstance(project); + Module[] modules = ModuleManager.getInstance(project).getModules(); Set processedLibraries = new THashSet<>(); Set processedSdk = new THashSet<>(); @@ -80,10 +85,10 @@ public class ExternalLibrariesNode extends ProjectViewNode { final String libraryName = library.getName(); if (libraryName == null || libraryName.length() == 0) { - addLibraryChildren(libraryOrderEntry, children, getProject(), this); + addLibraryChildren(libraryOrderEntry, children, project, this); } else { - children.add(new NamedLibraryElementNode(getProject(), new NamedLibraryElement(null, libraryOrderEntry), getSettings())); + children.add(new NamedLibraryElementNode(project, new NamedLibraryElement(null, libraryOrderEntry), getSettings())); } } else if (orderEntry instanceof JdkOrderEntry) { @@ -92,11 +97,19 @@ public class ExternalLibrariesNode extends ProjectViewNode { if (jdk != null) { if (processedSdk.contains(jdk)) continue; processedSdk.add(jdk); - children.add(new NamedLibraryElementNode(getProject(), new NamedLibraryElement(null, jdkOrderEntry), getSettings())); + children.add(new NamedLibraryElementNode(project, new NamedLibraryElement(null, jdkOrderEntry), getSettings())); } } } } + for (AdditionalLibraryRootsProvider provider : AdditionalLibraryRootsProvider.EP_NAME.getExtensions()) { + Collection libraries = provider.getAdditionalProjectLibraries(project); + for (SyntheticLibrary library : libraries) { + if (library.getName() != null) { + children.add(new SyntheticLibraryElementNode(project, library, getSettings())); + } + } + } return children; } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/SyntheticLibraryElementNode.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/SyntheticLibraryElementNode.java new file mode 100644 index 000000000000..fb286b57666c --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/SyntheticLibraryElementNode.java @@ -0,0 +1,92 @@ +/* + * Copyright 2000-2017 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.projectView.impl.nodes; + +import com.intellij.icons.AllIcons; +import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.projectView.ProjectViewNode; +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.SyntheticLibrary; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +public class SyntheticLibraryElementNode extends ProjectViewNode { + public SyntheticLibraryElementNode(@NotNull Project project, @NotNull SyntheticLibrary library, ViewSettings settings) { + super(project, library, settings); + } + + @Override + public boolean contains(@NotNull VirtualFile file) { + SyntheticLibrary library = getLibrary(); + return VfsUtil.contains(file, library.getSourceRoots()); + } + + @NotNull + @Override + public Collection getChildren() { + List children = new ArrayList<>(); + SyntheticLibrary library = getLibrary(); + Project project = getProject(); + if (project != null) { + PsiManager psiManager = PsiManager.getInstance(project); + for (VirtualFile file : library.getSourceRoots()) { + if (!file.isValid()) continue; + if (file.isDirectory()) { + PsiDirectory psiDir = psiManager.findDirectory(file); + if (psiDir != null) { + children.add(new PsiDirectoryNode(project, psiDir, getSettings())); + } + } + else { + PsiFile psiFile = psiManager.findFile(file); + if (psiFile != null) { + children.add(new PsiFileNode(project, psiFile, getSettings())); + } + } + } + } + return children; + } + + @Override + public String getName() { + SyntheticLibrary library = getLibrary(); + return StringUtil.notNullize(library.getName()); + } + + @NotNull + private SyntheticLibrary getLibrary() { + return Objects.requireNonNull(getValue()); + } + + @Override + protected void update(PresentationData presentation) { + presentation.setPresentableText(getName()); + presentation.setIcon(AllIcons.Nodes.PpLibFolder); + } +} diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexScanRunnableCollectorImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexScanRunnableCollectorImpl.java index 34e01af60a9b..f57feb234e93 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexScanRunnableCollectorImpl.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexScanRunnableCollectorImpl.java @@ -87,7 +87,10 @@ public class FileBasedIndexScanRunnableCollectorImpl extends FileBasedIndexScanR if (myProject.isDisposed()) { return tasks; } - contributedRoots = contributedRoots.append(provider.getAdditionalProjectLibrarySourceRoots(myProject)); + contributedRoots = contributedRoots.append( + provider.getAdditionalProjectLibraries(myProject), + descriptor -> descriptor.getSourceRoots() + ); } for (VirtualFile root : contributedRoots) { if (visitedRoots.add(root)) { diff --git a/platform/platform-api/src/com/intellij/openapi/vfs/VfsUtil.java b/platform/platform-api/src/com/intellij/openapi/vfs/VfsUtil.java index 17c897d80cc3..9e55e72d8079 100644 --- a/platform/platform-api/src/com/intellij/openapi/vfs/VfsUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/vfs/VfsUtil.java @@ -151,6 +151,33 @@ public class VfsUtil extends VfsUtilCore { return toVirtualFileArray(ancestorsList); } + public static boolean contains(@NotNull VirtualFile file, @NotNull Collection roots) { + Set dirs = null; + for (VirtualFile root : roots) { + if (file.getFileSystem().equals(root.getFileSystem())) { + if (root.isDirectory()) { + if (dirs == null) { + dirs = new HashSet<>(); + } + dirs.add(root); + } + else if (root.equals(file)) { + return true; + } + } + } + if (dirs != null) { + VirtualFile f = file; + while (f != null) { + if (dirs.contains(f)) { + return true; + } + f = f.getParent(); + } + } + return false; + } + /** * Gets the common ancestor for passed files, or {@code null} if the files do not have common ancestors. */ diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/AdditionalLibraryRootsProvider.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/AdditionalLibraryRootsProvider.java index c957ed043587..b9ac28a356b7 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/AdditionalLibraryRootsProvider.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/AdditionalLibraryRootsProvider.java @@ -38,17 +38,28 @@ public abstract class AdditionalLibraryRootsProvider { public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.additionalLibraryRootsProvider"); /** - * Returns library source roots (analogous to {@code library.getFiles(OrderRootType.SOURCES)} for a given project. + * Returns of collection of {@link SyntheticLibrary}. * This method is suitable when it's easier to collect all additional library roots associated with {@code Project}, * instead of {@code Module}. E.g. JavaScript libraries can be associated with files or folders allowing more * fine-grained control. - * Files contained in the returned roots are considered as library source files: + * Files contained in the returned libraries roots are considered as library source files: * {@link ProjectFileIndex#isInLibrarySource(VirtualFile)} should return {@code true} for them. * * @param project Project instance - * @return a collection of library source roots + * @return a collection of {@link SyntheticLibrary} */ @NotNull + public Collection getAdditionalProjectLibraries(@NotNull Project project) { + //noinspection deprecation + Collection roots = getAdditionalProjectLibrarySourceRoots(project); + if (roots.isEmpty()) { + return Collections.emptyList(); + } + return Collections.singletonList(SyntheticLibrary.newFixedLibrary(null, roots)); + } + + @SuppressWarnings("DeprecatedIsStillUsed") + @NotNull public Collection getAdditionalProjectLibrarySourceRoots(@NotNull Project project) { return Collections.emptyList(); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/SyntheticLibrary.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/SyntheticLibrary.java new file mode 100644 index 000000000000..38648a527a49 --- /dev/null +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/SyntheticLibrary.java @@ -0,0 +1,65 @@ +/* + * Copyright 2000-2017 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.roots; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +/** + * A lightweight library definition comparing to {@link com.intellij.openapi.roots.libraries.Library}. + * When provided by {@link AdditionalLibraryRootsProvider}, a library of this type contributes the followings: + *
    + *
  • Source libraries roots ({@link #getSourceRoots()}) extends {@link com.intellij.psi.search.GlobalSearchScope#allScope(Project)} + * (in UI, "Project and Libraries" scope). + * Files contained in the returned roots are considered as library source files: + * {@link ProjectFileIndex#isInLibrarySource(VirtualFile)} returns {@code true} for them. + *
    + * Unlike to {@code library.getFiles(OrderRootType.SOURCES)}, these source roots are not indexed and + * are not included in the classpath.
  • + *
  • An item in "External Libraries" in Project view if {@link #getName()} is not-null
  • + *
+ * @see AdditionalLibraryRootsProvider + */ +@ApiStatus.Experimental +public abstract class SyntheticLibrary { + @Nullable + public abstract String getName(); + + @NotNull + public abstract Collection getSourceRoots(); + + @NotNull + public static SyntheticLibrary newFixedLibrary(@Nullable String name, @NotNull Collection sourceRoots) { + return new SyntheticLibrary() { + @Nullable + @Override + public String getName() { + return name; + } + + @NotNull + @Override + public Collection getSourceRoots() { + return sourceRoots; + } + }; + } +} diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java index e0587269baa5..b67d9d1ad7c1 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java @@ -168,10 +168,13 @@ public class RootIndex { } for (AdditionalLibraryRootsProvider provider : Extensions.getExtensions(AdditionalLibraryRootsProvider.EP_NAME)) { - Collection roots = ContainerUtil.filter(provider.getAdditionalProjectLibrarySourceRoots(project), - file -> ensureValid(file, provider)); - info.libraryOrSdkSources.addAll(roots); - info.classAndSourceRoots.addAll(roots); + Collection libraries = provider.getAdditionalProjectLibraries(project); + for (SyntheticLibrary descriptor : libraries) { + Collection roots = ContainerUtil.filter(descriptor.getSourceRoots(), + file -> ensureValid(file, project)); + info.libraryOrSdkSources.addAll(roots); + info.classAndSourceRoots.addAll(roots); + } } for (DirectoryIndexExcludePolicy policy : Extensions.getExtensions(DirectoryIndexExcludePolicy.EP_NAME, project)) { info.excludedFromProject.addAll(ContainerUtil.filter(policy.getExcludeRootsForProject(), file -> ensureValid(file, policy)));