package fr.adrienbrault.idea.symfony2plugin.config.utils; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.psi.PsiElement; import com.intellij.psi.util.*; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.php.PhpIndex; import com.jetbrains.php.lang.psi.elements.Method; import com.jetbrains.php.lang.psi.elements.MethodReference; import com.jetbrains.php.lang.psi.elements.PhpClass; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import java.util.*; import java.util.function.Consumer; /** * @author Daniel Espendiller <daniel@espendiller.net> */ public class ConfigUtil { private static final Key<CachedValue<Map<String, Collection<String>>>> TREE_SIGNATURE_CACHE = new Key<>("TREE_SIGNATURE_CACHE"); /** * Search for all "Symfony\Component\Config\Definition\Builder\TreeBuilder::root" elements */ @NotNull public static Map<String, Collection<String>> getTreeSignatures(@NotNull Project project) { CachedValue<Map<String, Collection<String>>> cache = project.getUserData(TREE_SIGNATURE_CACHE); if (cache == null) { cache = CachedValuesManager.getManager(project).createCachedValue(() -> CachedValueProvider.Result.create(visitTreeSignatures(project), PsiModificationTracker.MODIFICATION_COUNT) , false); project.putUserData(TREE_SIGNATURE_CACHE, cache); } return cache.getValue(); } @NotNull public static Collection<PsiElement> getTreeSignatureTargets(@NotNull Project project, @NotNull String key) { Map<String, Collection<String>> signatures = getTreeSignatures(project); if(!signatures.containsKey(key)) { return Collections.emptyList(); } Collection<PhpClass> phpClasses = new HashSet<>(); for (String aClass : signatures.get(key)) { ContainerUtil.addIfNotNull(phpClasses, PhpElementsUtil.getClass(project, aClass)); } Collection<PsiElement> psiElements = new ArrayList<>(); visitTreeSignatures(phpClasses, treeVisitor -> { if(key.equalsIgnoreCase(treeVisitor.contents)) { psiElements.add(treeVisitor.psiElement); } }); return psiElements; } @NotNull public static Collection<PsiElement> getTreeSignatureTargets(@NotNull Project project, @NotNull String key, @NotNull Collection<String> classes) { Collection<PsiElement> psiElements = new ArrayList<>(); Collection<PhpClass> phpClasses = new HashSet<>(); for (String aClass : classes) { ContainerUtil.addIfNotNull(phpClasses, PhpElementsUtil.getClass(project, aClass)); } visitTreeSignatures(phpClasses, treeVisitor -> { if(key.equalsIgnoreCase(treeVisitor.contents)) { psiElements.add(treeVisitor.psiElement); } }); return psiElements; } @NotNull private static Map<String, Collection<String>> visitTreeSignatures(@NotNull Project project) { Map<String, Collection<String>> signatures = new HashMap<>(); visitTreeSignatures(PhpIndex.getInstance(project).getAllSubclasses("Symfony\\Component\\Config\\Definition\\ConfigurationInterface"), treeVisitor -> { if(!signatures.containsKey(treeVisitor.contents)) { signatures.put(treeVisitor.contents, new HashSet<>()); } signatures.get(treeVisitor.contents).add(treeVisitor.phpClass.getFQN()); }); return signatures; } private static void visitTreeSignatures(@NotNull Collection<PhpClass> classes, @NotNull Consumer<TreeVisitor> consumer) { for (PhpClass phpClass : classes) { Method method = phpClass.findOwnMethodByName("getConfigTreeBuilder"); if(method == null) { continue; } for(MethodReference methodReference: PsiTreeUtil.findChildrenOfType(method, MethodReference.class)) { if(!PhpElementsUtil.isMethodReferenceInstanceOf(methodReference, "Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder", "root")) { continue; } PsiElement[] parameters = methodReference.getParameters(); if (parameters.length == 0) { continue; } String contents = PhpElementsUtil.getStringValue(parameters[0]); if(contents == null || StringUtils.isBlank(contents)) { continue; } consumer.accept(new TreeVisitor(phpClass, parameters[0], contents)); } } } private static class TreeVisitor { @NotNull private final PhpClass phpClass; @NotNull private final PsiElement psiElement; @NotNull private final String contents; public TreeVisitor(@NotNull PhpClass phpClass, @NotNull PsiElement psiElement, @NotNull String contents) { this.phpClass = phpClass; this.psiElement = psiElement; this.contents = contents; } } }