package fr.adrienbrault.idea.symfony2plugin.stubs;
import com.intellij.ide.highlighter.XmlFileType;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.indexing.FileBasedIndexImpl;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import fr.adrienbrault.idea.symfony2plugin.config.xml.XmlHelper;
import fr.adrienbrault.idea.symfony2plugin.dic.ClassServiceDefinitionTargetLazyValue;
import fr.adrienbrault.idea.symfony2plugin.dic.ContainerService;
import fr.adrienbrault.idea.symfony2plugin.extension.ServiceDefinitionLocator;
import fr.adrienbrault.idea.symfony2plugin.extension.ServiceDefinitionLocatorParameter;
import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.ServicesDefinitionStubIndex;
import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.yaml.YAMLFileType;
import org.jetbrains.yaml.psi.YAMLFile;
import java.util.*;
/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class ServiceIndexUtil {
private static final Key<CachedValue<Map<String, Collection<ContainerService>>>> SERVICE_DECORATION_CACHE = new Key<>("SERVICE_DECORATION");
private static final ExtensionPointName<ServiceDefinitionLocator> EXTENSIONS = new ExtensionPointName<>(
"fr.adrienbrault.idea.symfony2plugin.extension.ServiceDefinitionLocator"
);
private static VirtualFile[] findServiceDefinitionFiles(@NotNull Project project, @NotNull String serviceName) {
final List<VirtualFile> virtualFiles = new ArrayList<>();
FileBasedIndexImpl.getInstance().getFilesWithKey(ServicesDefinitionStubIndex.KEY, new HashSet<>(Collections.singletonList(serviceName.toLowerCase())), virtualFile -> {
virtualFiles.add(virtualFile);
return true;
}, GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), XmlFileType.INSTANCE, YAMLFileType.YML));
return virtualFiles.toArray(new VirtualFile[virtualFiles.size()]);
}
public static List<PsiElement> findServiceDefinitions(@NotNull Project project, @NotNull String serviceName) {
List<PsiElement> items = new ArrayList<>();
VirtualFile[] twigVirtualFiles = ServiceIndexUtil.findServiceDefinitionFiles(project, serviceName);
for (VirtualFile twigVirtualFile : twigVirtualFiles) {
PsiFile psiFile = PsiManager.getInstance(project).findFile(twigVirtualFile);
if(psiFile instanceof YAMLFile) {
PsiElement servicePsiElement = YamlHelper.getLocalServiceName(psiFile, serviceName);
if(servicePsiElement != null) {
items.add(servicePsiElement);
}
}
if(psiFile instanceof XmlFile) {
PsiElement servicePsiElement = XmlHelper.getLocalServiceName(psiFile, serviceName);
if(servicePsiElement != null) {
items.add(servicePsiElement);
}
}
}
// extension points
ServiceDefinitionLocator[] extensions = EXTENSIONS.getExtensions();
if(extensions.length > 0) {
ServiceDefinitionLocatorParameter parameter = new ServiceDefinitionLocatorParameter(project, items);
for (ServiceDefinitionLocator locator : extensions) {
locator.locate(serviceName, parameter);
}
}
return items;
}
public static List<PsiElement> findParameterDefinitions(@NotNull PsiFile psiFile, @NotNull String parameterName) {
List<PsiElement> items = new ArrayList<>();
if(psiFile instanceof YAMLFile) {
PsiElement servicePsiElement = YamlHelper.getLocalParameterMap(psiFile, parameterName);
if(servicePsiElement != null) {
items.add(servicePsiElement);
}
}
if(psiFile instanceof XmlFile) {
PsiElement localParameterName = XmlHelper.getLocalParameterName(psiFile, parameterName);
if(localParameterName != null) {
items.add(localParameterName);
}
}
return items;
}
public static PsiElement[] findServiceDefinitions(@Nullable PhpClass phpClass) {
if(phpClass == null) {
return new PsiElement[0];
}
String phpClassName = phpClass.getPresentableFQN();
Set<String> serviceNames = ContainerCollectionResolver.ServiceCollector.create(phpClass.getProject()).convertClassNameToServices(phpClassName);
if(serviceNames.size() == 0) {
return new PsiElement[0];
}
List<PsiElement> psiElements = new ArrayList<>();
for(String serviceName: serviceNames) {
psiElements.addAll(findServiceDefinitions(phpClass.getProject(), serviceName));
}
return psiElements.toArray(new PsiElement[psiElements.size()]);
}
@Nullable
public static ClassServiceDefinitionTargetLazyValue findServiceDefinitionsLazy(@Nullable PhpClass phpClass) {
if(phpClass == null) {
return null;
}
String phpClassName = phpClass.getPresentableFQN();
Set<String> serviceNames = ContainerCollectionResolver.ServiceCollector.create(phpClass.getProject()).convertClassNameToServices(phpClassName);
if(serviceNames.size() == 0) {
return null;
}
return new ClassServiceDefinitionTargetLazyValue(phpClass.getProject(), phpClassName);
}
/**
* Lazy values for linemarker
*/
@Nullable
public static NotNullLazyValue<Collection<? extends PsiElement>> getServiceIdDefinitionLazyValue(@NotNull Project project, @NotNull Collection<String> ids) {
return new MyServiceIdLazyValue(project, ids);
}
/**
* So support only some file types, so we can filter them and xml and yaml for now
*/
public static GlobalSearchScope getRestrictedFileTypesScope(@NotNull Project project) {
return GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.allScope(project), XmlFileType.INSTANCE, YAMLFileType.YML);
}
@NotNull
public static Map<String, Collection<ContainerService>> getDecoratedServices(@NotNull Project project) {
CachedValue<Map<String, Collection<ContainerService>>> cache = project.getUserData(SERVICE_DECORATION_CACHE);
if (cache == null) {
cache = CachedValuesManager.getManager(project).createCachedValue(() ->
CachedValueProvider.Result.create(getDecoratedServicesInner(project), PsiModificationTracker.MODIFICATION_COUNT)
, false);
project.putUserData(SERVICE_DECORATION_CACHE, cache);
}
return cache.getValue();
}
@NotNull
private static Map<String, Collection<ContainerService>> getDecoratedServicesInner(@NotNull Project project) {
Map<String, Collection<ContainerService>> services = new HashMap<>();
for (ContainerService containerService : ContainerCollectionResolver.getServices(project).values()) {
if(containerService.getService() == null) {
continue;
}
String decorates = containerService.getService().getDecorates();
if(decorates == null) {
continue;
}
if(!services.containsKey(decorates)) {
services.put(decorates, new ArrayList<>());
}
services.get(decorates).add(containerService);
}
return services;
}
private static class MyServiceIdLazyValue extends NotNullLazyValue<Collection<? extends PsiElement>> {
@NotNull
private final Project project;
@NotNull
private final Collection<String> ids;
MyServiceIdLazyValue(@NotNull Project project, @NotNull Collection<String> ids) {
this.project = project;
this.ids = ids;
}
@NotNull
@Override
protected Collection<? extends PsiElement> compute() {
Collection<PsiElement> psiElements = new HashSet<>();
for (String id : new HashSet<>(this.ids)) {
psiElements.addAll(findServiceDefinitions(this.project, id));
}
return psiElements;
}
}
}