/*
* Copyright 2013-2016 consulo.io
*
* 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 consulo.psi.impl;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.roots.impl.DirectoryIndex;
import com.intellij.openapi.util.LowMemoryWatcher;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.Query;
import com.intellij.util.containers.ConcurrentHashMap;
import com.intellij.util.messages.MessageBus;
import consulo.module.extension.ModuleExtension;
import consulo.psi.PsiPackage;
import consulo.psi.PsiPackageManager;
import consulo.psi.PsiPackageSupportProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.annotations.RequiredReadAction;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
/**
* @author VISTALL
* @since 8:05/20.05.13
*/
public class PsiPackageManagerImpl extends PsiPackageManager {
private final Project myProject;
private final DirectoryIndex myDirectoryIndex;
@SuppressWarnings("UnusedDeclaration")
private final LowMemoryWatcher myLowMemoryWatcher = LowMemoryWatcher.register(new Runnable() {
@Override
public void run() {
myPackageCache.clear();
}
});
private Map<Class<? extends ModuleExtension>, ConcurrentMap<String, PsiPackage>> myPackageCache =
new ConcurrentHashMap<Class<? extends ModuleExtension>, ConcurrentMap<String, PsiPackage>>();
public PsiPackageManagerImpl(Project project, PsiManager psiManager, DirectoryIndex directoryIndex, MessageBus bus) {
myProject = project;
myDirectoryIndex = directoryIndex;
final PsiModificationTracker modificationTracker = psiManager.getModificationTracker();
if (bus != null) {
bus.connect().subscribe(PsiModificationTracker.TOPIC, new PsiModificationTracker.Listener() {
private long lastTimeSeen = -1L;
@Override
public void modificationCountChanged() {
final long now = modificationTracker.getJavaStructureModificationCount();
if (lastTimeSeen != now) {
lastTimeSeen = now;
myPackageCache.clear();
}
}
});
}
}
@Override
public void dropCache(@NotNull Class<? extends ModuleExtension> extensionClass) {
myPackageCache.remove(extensionClass);
}
@RequiredReadAction
@Nullable
@Override
public PsiPackage findPackage(@NotNull String qualifiedName, @NotNull Class<? extends ModuleExtension> extensionClass) {
ConcurrentMap<String, PsiPackage> map = myPackageCache.get(extensionClass);
if (map != null) {
final PsiPackage psiPackage = map.get(qualifiedName);
if (psiPackage != null) {
return psiPackage;
}
}
final PsiPackage newPackage = createPackage(qualifiedName, extensionClass);
if (newPackage != null) {
if (map == null) {
myPackageCache.put(extensionClass, map = new ConcurrentHashMap<String, PsiPackage>());
}
ConcurrencyUtil.cacheOrGet(map, qualifiedName, newPackage);
}
return newPackage;
}
@Nullable
private PsiPackage createPackage(@NotNull String qualifiedName, @NotNull Class<? extends ModuleExtension> extensionClass) {
Query<VirtualFile> dirs = myDirectoryIndex.getDirectoriesByPackageName(qualifiedName, true);
if (dirs.findFirst() == null) {
return null;
}
for (VirtualFile next : dirs) {
PsiPackage packageFromProviders = createPackageFromProviders(next, extensionClass, qualifiedName);
if (packageFromProviders != null) {
return packageFromProviders;
}
PsiPackage packageFromLibrary = createPackageFromLibrary(next, extensionClass, qualifiedName);
if (packageFromLibrary != null) {
return packageFromLibrary;
}
}
return null;
}
@Nullable
private PsiPackage createPackageFromProviders(@NotNull VirtualFile virtualFile,
@NotNull Class<? extends ModuleExtension> extensionClass,
@NotNull String qualifiedName) {
final Module moduleForFile = ModuleUtil.findModuleForFile(virtualFile, myProject);
if (moduleForFile == null) {
return null;
}
ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(moduleForFile);
final ModuleExtension extension = moduleRootManager.getExtension(extensionClass);
if (extension == null) {
return null;
}
PsiManager psiManager = PsiManager.getInstance(myProject);
for (PsiPackageSupportProvider p : PsiPackageSupportProvider.EP_NAME.getExtensions()) {
if (p.isSupported(extension)) {
return p.createPackage(psiManager, this, extensionClass, qualifiedName);
}
}
return null;
}
private PsiPackage createPackageFromLibrary(@NotNull VirtualFile virtualFile,
@NotNull Class<? extends ModuleExtension> extensionClass,
@NotNull String qualifiedName) {
ProjectFileIndex fileIndexFacade = ProjectFileIndex.SERVICE.getInstance(myProject);
PsiManager psiManager = PsiManager.getInstance(myProject);
if (fileIndexFacade.isInLibraryClasses(virtualFile)) {
List<OrderEntry> orderEntriesForFile = fileIndexFacade.getOrderEntriesForFile(virtualFile);
for (OrderEntry orderEntry : orderEntriesForFile) {
Module ownerModule = orderEntry.getOwnerModule();
ModuleExtension extension = ModuleUtilCore.getExtension(ownerModule, extensionClass);
if (extension != null) {
for (PsiPackageSupportProvider p : PsiPackageSupportProvider.EP_NAME.getExtensions()) {
if (p.isSupported(extension)) {
return p.createPackage(psiManager, this, extensionClass, qualifiedName);
}
}
}
}
}
return null;
}
@RequiredReadAction
@Nullable
@Override
public PsiPackage findPackage(@NotNull PsiDirectory directory, @NotNull Class<? extends ModuleExtension> extensionClass) {
ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(directory.getProject()).getFileIndex();
String packageName = projectFileIndex.getPackageNameByDirectory(directory.getVirtualFile());
if (packageName == null) {
return null;
}
return findPackage(packageName, extensionClass);
}
@RequiredReadAction
@Override
public PsiPackage findAnyPackage(@NotNull PsiDirectory directory) {
ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(directory.getProject()).getFileIndex();
String packageName = projectFileIndex.getPackageNameByDirectory(directory.getVirtualFile());
if (packageName == null) {
return null;
}
Module module = ModuleUtilCore.findModuleForPsiElement(directory);
if (module == null) {
return null;
}
ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
for (ModuleExtension<?> moduleExtension : rootManager.getExtensions()) {
final PsiPackage aPackage = findPackage(packageName, moduleExtension.getClass());
if (aPackage != null) {
return aPackage;
}
}
return null;
}
@RequiredReadAction
@Override
public PsiPackage findAnyPackage(@NotNull String packageName) {
for (ConcurrentMap<String, PsiPackage> map : myPackageCache.values()) {
PsiPackage psiPackage = map.get(packageName);
if (psiPackage != null) {
return psiPackage;
}
}
return null;
}
@RequiredReadAction
@Override
public boolean isValidPackageName(@NotNull Module module, @NotNull String packageName) {
PsiPackageSupportProvider[] extensions = PsiPackageSupportProvider.EP_NAME.getExtensions();
ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
for (ModuleExtension<?> moduleExtension : rootManager.getExtensions()) {
for (PsiPackageSupportProvider provider : extensions) {
if (provider.isSupported(moduleExtension)) {
return provider.isValidPackageName(module, packageName);
}
}
}
return true;
}
}