diff --git a/platform/analysis-api/src/com/intellij/psi/search/GlobalSearchScopesCore.java b/platform/analysis-api/src/com/intellij/psi/search/GlobalSearchScopesCore.java index ffea51b3e79b..072e12114bc6 100644 --- a/platform/analysis-api/src/com/intellij/psi/search/GlobalSearchScopesCore.java +++ b/platform/analysis-api/src/com/intellij/psi/search/GlobalSearchScopesCore.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -26,8 +26,14 @@ import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.search.scope.packageSet.*; +import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; + public class GlobalSearchScopesCore { @NotNull public static GlobalSearchScope projectProductionScope(@NotNull Project project) { @@ -216,5 +222,131 @@ public class GlobalSearchScopesCore { //noinspection HardCodedStringLiteral return "directory scope: " + myDirectory + "; withSubdirs:"+myWithSubdirectories; } + + @Override + public int hashCode() { + return myDirectory.hashCode() *31 + (myWithSubdirectories?1:0); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof DirectoryScope && + myDirectory.equals(((DirectoryScope)obj).myDirectory) && + myWithSubdirectories == ((DirectoryScope)obj).myWithSubdirectories; + } + + @NotNull + @Override + public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) { + if (equals(scope)) return this; + if (scope instanceof DirectoryScope) { + DirectoryScope other = (DirectoryScope)scope; + VirtualFile otherDirectory = other.myDirectory; + if (myWithSubdirectories && VfsUtilCore.isAncestor(myDirectory, otherDirectory, false)) return this; + if (other.myWithSubdirectories && VfsUtilCore.isAncestor(otherDirectory, myDirectory, false)) return other; + BitSet newWithSubdirectories = new BitSet(); + newWithSubdirectories.set(0, myWithSubdirectories); + newWithSubdirectories.set(1, other.myWithSubdirectories); + return new DirectoriesScope(getProject(), new VirtualFile[]{myDirectory,otherDirectory}, newWithSubdirectories); + } + return super.uniteWith(scope); + } + } + + static class DirectoriesScope extends GlobalSearchScope { + private final VirtualFile[] myDirectories; + private final BitSet myWithSubdirectories; + + private DirectoriesScope(@NotNull Project project, @NotNull VirtualFile[] directories, @NotNull BitSet withSubdirectories) { + super(project); + myWithSubdirectories = withSubdirectories; + myDirectories = directories; + } + + @Override + public boolean contains(@NotNull VirtualFile file) { + VirtualFile parent = file.getParent(); + return parent != null && in(parent); + } + + private boolean in(@NotNull VirtualFile parent) { + for (int i = 0; i < myDirectories.length; i++) { + VirtualFile directory = myDirectories[i]; + boolean withSubdirectories = myWithSubdirectories.get(i); + if (withSubdirectories ? VfsUtilCore.isAncestor(directory, parent, false) : directory.equals(parent)) return true; + } + return false; + } + + @Override + public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) { + return 0; + } + + @Override + public boolean isSearchInModuleContent(@NotNull Module aModule) { + return true; + } + + @Override + public boolean isSearchInLibraries() { + return false; + } + + public String toString() { + //noinspection HardCodedStringLiteral + return "Directories scope: " + Arrays.asList(myDirectories); + } + + @Override + public int hashCode() { + int result = 1; + for (int i = 0; i < myDirectories.length; i++) { + VirtualFile directory = myDirectories[i]; + boolean withSubdirectories = myWithSubdirectories.get(i); + result = result*31 + directory.hashCode() *31 + (withSubdirectories?1:0); + } + return result; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof DirectoriesScope && + Arrays.equals(myDirectories, ((DirectoriesScope)obj).myDirectories) && + myWithSubdirectories.equals(((DirectoriesScope)obj).myWithSubdirectories); + } + + @NotNull + @Override + public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) { + if (equals(scope)) { + return this; + } + if (scope instanceof DirectoryScope) { + if (in(((DirectoryScope)scope).myDirectory)) { + return this; + } + VirtualFile[] newDirectories = ArrayUtil.append(myDirectories, ((DirectoryScope)scope).myDirectory, VirtualFile.class); + BitSet newWithSubdirectories = (BitSet)myWithSubdirectories.clone(); + newWithSubdirectories.set(myDirectories.length, ((DirectoryScope)scope).myWithSubdirectories); + return new DirectoriesScope(getProject(), newDirectories, newWithSubdirectories); + } + if (scope instanceof DirectoriesScope) { + DirectoriesScope other = (DirectoriesScope)scope; + List newDirectories = new ArrayList(myDirectories.length + other.myDirectories.length); + newDirectories.addAll(Arrays.asList(other.myDirectories)); + BitSet newWithSubdirectories = (BitSet)myWithSubdirectories.clone(); + VirtualFile[] directories = other.myDirectories; + for (int i = 0; i < directories.length; i++) { + VirtualFile otherDirectory = directories[i]; + if (!in(otherDirectory)) { + newWithSubdirectories.set(newDirectories.size(), other.myWithSubdirectories.get(i)); + newDirectories.add(otherDirectory); + } + } + return new DirectoriesScope(getProject(), newDirectories.toArray(new VirtualFile[newDirectories.size()]), newWithSubdirectories); + } + return super.uniteWith(scope); + } } } diff --git a/platform/platform-tests/testSrc/com/intellij/psi/search/GlobalSearchScopeTest.java b/platform/platform-tests/testSrc/com/intellij/psi/search/GlobalSearchScopeTest.java new file mode 100644 index 000000000000..418190f427b6 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/psi/search/GlobalSearchScopeTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2014 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.psi.search; + +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.PlatformTestCase; + +public class GlobalSearchScopeTest extends PlatformTestCase { + public void testUniteDirectorySearchScopeDoesNotSOE() throws Exception { + VirtualFile genRoot = getVirtualFile(createTempDir("genSrcRoot")); + VirtualFile srcRoot = getVirtualFile(createTempDir("srcRoot")); + VirtualFile child = srcRoot.createChildDirectory(this, "child"); + GlobalSearchScope childScope = GlobalSearchScopesCore.directoryScope(getProject(), child, true); + + GlobalSearchScope directoryScope = GlobalSearchScopesCore.directoryScope(getProject(), srcRoot, true); + GlobalSearchScope scope = GlobalSearchScope.EMPTY_SCOPE.uniteWith(directoryScope); + assertSame(scope, directoryScope); + scope = scope.uniteWith(directoryScope); + assertSame(scope, directoryScope); + + scope = scope.uniteWith(childScope); + assertSame(scope, directoryScope); + + GlobalSearchScope s = childScope; + int N = 1000; + VirtualFile[] d = new VirtualFile[N]; + for (int i=0; i< N;i++) { + d[i] = srcRoot.createChildDirectory(this, "d"+i); + GlobalSearchScope united = s.uniteWith(GlobalSearchScopesCore.directoryScope(getProject(), d[i], true)); + assertNotSame(s, united); + s = united; + assertTrue(s instanceof GlobalSearchScopesCore.DirectoriesScope); + } + for (VirtualFile file : d) { + VirtualFile f = createChildData(file, "f"); + assertTrue(s.contains(f)); + } + assertFalse(s.contains(genRoot)); + + assertSame(s.uniteWith(childScope), s); + assertSame(s.uniteWith(s), s); + } +} \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/ArrayUtil.java b/platform/util/src/com/intellij/util/ArrayUtil.java index 1e47adf8fcf1..5de68c11799b 100644 --- a/platform/util/src/com/intellij/util/ArrayUtil.java +++ b/platform/util/src/com/intellij/util/ArrayUtil.java @@ -78,6 +78,21 @@ public class ArrayUtil extends ArrayUtilRt { System.arraycopy(array, 0, result, 0, Math.min(oldSize, newSize)); return result; } + @NotNull + public static boolean[] realloc(@NotNull boolean[] array, final int newSize) { + if (newSize == 0) { + return EMPTY_BOOLEAN_ARRAY; + } + + final int oldSize = array.length; + if (oldSize == newSize) { + return array; + } + + boolean[] result = new boolean[newSize]; + System.arraycopy(array, 0, result, 0, Math.min(oldSize, newSize)); + return result; + } @NotNull public static int[] realloc(@NotNull int[] array, final int newSize) { @@ -131,6 +146,12 @@ public class ArrayUtil extends ArrayUtilRt { array[array.length - 1] = value; return array; } + @NotNull + public static boolean[] append(@NotNull boolean[] array, boolean value) { + array = realloc(array, array.length + 1); + array[array.length - 1] = value; + return array; + } @NotNull public static char[] realloc(@NotNull char[] array, final int newSize) {