blob: 7fc4a22459412e33aa85c90fba30b1ce8383b7a4 [file] [log] [blame]
/*
* 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;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.impl.file.PsiPackageImpl;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.NonClasspathDirectoriesScope;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author peter
*/
public abstract class NonClasspathClassFinder extends PsiElementFinder {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.NonClasspathClassFinder");
private final AtomicLong myLastStamp = new AtomicLong();
protected final Project myProject;
private volatile List<VirtualFile> myCache;
private final PsiManager myManager;
private final String[] myFileExtensions;
public NonClasspathClassFinder(Project project, String... fileExtensions) {
myProject = project;
myManager = PsiManager.getInstance(myProject);
myFileExtensions = ArrayUtil.append(fileExtensions, "class");
}
protected List<VirtualFile> getClassRoots(@Nullable GlobalSearchScope scope) {
return getClassRoots();
}
protected List<VirtualFile> getClassRoots() {
List<VirtualFile> cache = myCache;
long stamp = myManager.getModificationTracker().getModificationCount();
if (myLastStamp.get() != stamp) {
cache = null;
}
if (cache != null && !cache.isEmpty()) {
for (VirtualFile file : cache) {
if (!file.isValid()) {
cache = null;
break;
}
}
}
if (cache == null) {
myCache = cache = calcClassRoots();
myLastStamp.set(stamp);
}
return cache;
}
@Override
public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
final List<VirtualFile> classRoots = getClassRoots(scope);
if (classRoots.isEmpty()) {
return null;
}
final String relPath = qualifiedName.replace('.', '/');
for (final VirtualFile classRoot : classRoots) {
if (scope.contains(classRoot)) {
VirtualFile virtualFile = findFileByRelativePath(classRoot, relPath, myFileExtensions);
if (virtualFile != null) {
if (!virtualFile.isValid()) {
LOG.error(
"Invalid child of valid parent: " + virtualFile.getPath() + "; " + classRoot.isValid() + " path=" + classRoot.getPath());
return null;
}
final PsiFile file = myManager.findFile(virtualFile);
if (file instanceof PsiClassOwner) {
final PsiClass[] classes = ((PsiClassOwner)file).getClasses();
if (classes.length == 1) {
return classes[0];
}
}
}
}
}
return null;
}
protected abstract List<VirtualFile> calcClassRoots();
@NotNull
@Override
public PsiClass[] getClasses(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
final List<VirtualFile> classRoots = getClassRoots(scope);
if (classRoots.isEmpty()) {
return PsiClass.EMPTY_ARRAY;
}
List<PsiClass> result = new ArrayList<PsiClass>();
for (final VirtualFile classRoot : classRoots) {
if (scope.contains(classRoot)) {
final String pkgName = psiPackage.getQualifiedName();
final VirtualFile dir = classRoot.findFileByRelativePath(pkgName.replace('.', '/'));
if (dir != null && dir.isDirectory()) {
for (final VirtualFile file : dir.getChildren()) {
if (!file.isDirectory()) {
final PsiFile psi = myManager.findFile(file);
if (psi instanceof PsiClassOwner) {
ContainerUtil.addAll(result, ((PsiClassOwner)psi).getClasses());
}
}
}
}
}
}
return result.toArray(new PsiClass[result.size()]);
}
@NotNull
@Override
public Set<String> getClassNames(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
final List<VirtualFile> classRoots = getClassRoots(scope);
if (classRoots.isEmpty()) {
return Collections.emptySet();
}
final Set<String> result = new HashSet<String>();
for (final VirtualFile classRoot : classRoots) {
if (scope.contains(classRoot)) {
final String pkgName = psiPackage.getQualifiedName();
final VirtualFile dir = classRoot.findFileByRelativePath(pkgName.replace('.', '/'));
if (dir != null && dir.isDirectory()) {
for (final VirtualFile file : dir.getChildren()) {
if (!file.isDirectory() && ArrayUtil.contains(file.getExtension(), myFileExtensions)) {
result.add(file.getNameWithoutExtension());
}
}
}
}
}
return result;
}
@Override
public PsiPackage findPackage(@NotNull String qualifiedName) {
final List<VirtualFile> classRoots = getClassRoots();
if (classRoots.isEmpty()) {
return null;
}
for (final VirtualFile classRoot : classRoots) {
final VirtualFile dir = classRoot.findFileByRelativePath(qualifiedName.replace('.', '/'));
if (dir != null && dir.isDirectory()) {
return createPackage(qualifiedName);
}
}
return null;
}
private PsiPackageImpl createPackage(String qualifiedName) {
return new PsiPackageImpl(myManager, qualifiedName);
}
@Override
public boolean processPackageDirectories(@NotNull PsiPackage psiPackage,
@NotNull GlobalSearchScope scope,
@NotNull Processor<PsiDirectory> consumer,
boolean includeLibrarySources) {
final List<VirtualFile> classRoots = getClassRoots(scope);
if (classRoots.isEmpty()) {
return true;
}
final String qname = psiPackage.getQualifiedName();
final PsiManager psiManager = psiPackage.getManager();
for (final VirtualFile classRoot : classRoots) {
if (scope.contains(classRoot)) {
final VirtualFile dir = classRoot.findFileByRelativePath(qname.replace('.', '/'));
if (dir != null && dir.isDirectory()) {
final PsiDirectory psiDirectory = ApplicationManager.getApplication().runReadAction(new Computable<PsiDirectory>() {
@Override
@Nullable
public PsiDirectory compute() {
return dir.isValid() ? psiManager.findDirectory(dir) : null;
}
});
if (psiDirectory != null && !consumer.process(psiDirectory)) {
return false;
}
}
}
}
return true;
}
@NotNull
@Override
public PsiPackage[] getSubPackages(@NotNull PsiPackage psiPackage, @NotNull GlobalSearchScope scope) {
final List<VirtualFile> classRoots = getClassRoots(scope);
if (classRoots.isEmpty()) {
return super.getSubPackages(psiPackage, scope);
}
List<PsiPackage> result = new ArrayList<PsiPackage>();
for (final VirtualFile classRoot : classRoots) {
if (scope.contains(classRoot)) {
final String pkgName = psiPackage.getQualifiedName();
final VirtualFile dir = classRoot.findFileByRelativePath(pkgName.replace('.', '/'));
if (dir != null && dir.isDirectory()) {
for (final VirtualFile file : dir.getChildren()) {
if (file.isDirectory()) {
result.add(createPackage(pkgName + "." + file.getName()));
}
}
}
}
}
return result.toArray(new PsiPackage[result.size()]);
}
@NotNull
@Override
public PsiClass[] findClasses(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
final PsiClass psiClass = findClass(qualifiedName, scope);
return psiClass == null ? PsiClass.EMPTY_ARRAY : new PsiClass[]{psiClass};
}
@NotNull
public static GlobalSearchScope addNonClasspathScope(@NotNull Project project, @NotNull GlobalSearchScope base) {
GlobalSearchScope scope = base;
for (PsiElementFinder finder : Extensions.getExtensions(EP_NAME, project)) {
if (finder instanceof NonClasspathClassFinder) {
scope = scope.uniteWith(NonClasspathDirectoriesScope.compose(((NonClasspathClassFinder)finder).getClassRoots()));
}
}
return scope;
}
public PsiManager getPsiManager() {
return myManager;
}
@Nullable
private static VirtualFile findFileByRelativePath(@NotNull VirtualFile root,
@NotNull String relPath,
@NotNull String[] extensions) {
VirtualFile file = null;
for (String extension : extensions) {
file = root.findFileByRelativePath(relPath + '.' + extension);
if (file != null) break;
}
return file;
}
}