package fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.util; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.lang.xml.XMLLanguage; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; 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.*; import com.intellij.psi.xml.XmlTag; import com.intellij.util.indexing.FileBasedIndex; import com.jetbrains.php.PhpIndex; import com.jetbrains.php.lang.psi.elements.PhpClass; import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineModelInterface; import fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.dict.DoctrineManagerEnum; import fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.dict.DoctrineMetadataModel; import fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.driver.*; import fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.lookup.DoctrineRepositoryLookupElement; import fr.adrienbrault.idea.symfony2plugin.stubs.cache.FileIndexCaches; import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.DoctrineMetadataFileStubIndex; import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.yaml.psi.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author Daniel Espendiller <daniel@espendiller.net> */ public class DoctrineMetadataUtil { private static final Key<CachedValue<Set<String>>> CLASS_KEYS = new Key<>("CLASS_KEYS"); private static DoctrineMappingDriverInterface[] MAPPING_DRIVERS = new DoctrineMappingDriverInterface[] { new DoctrineXmlMappingDriver(), new DoctrineYamlMappingDriver(), new DoctrinePhpMappingDriver(), }; @NotNull public static Collection<LookupElement> getObjectRepositoryLookupElements(@NotNull Project project) { return new ArrayList<>(DoctrineRepositoryLookupElement.create(PhpIndex.getInstance(project).getAllSubclasses("\\Doctrine\\Common\\Persistence\\ObjectRepository"))); } /** * Try to find repository class on models scope on its metadata definition */ @Nullable public static PhpClass getClassRepository(final @NotNull Project project, final @NotNull String className) { for (VirtualFile virtualFile : FileBasedIndex.getInstance().getContainingFiles(DoctrineMetadataFileStubIndex.KEY, className, GlobalSearchScope.allScope(project))) { final String[] phpClass = {null}; FileBasedIndex.getInstance().processValues(DoctrineMetadataFileStubIndex.KEY, className, virtualFile, (virtualFile1, model) -> { if (phpClass[0] != null || model == null || model.getRepositoryClass() == null) { return true; } // piping value out of this index thread phpClass[0] = model.getRepositoryClass(); return true; }, GlobalSearchScope.allScope(project)); if(phpClass[0] != null) { return PhpElementsUtil.getClassInsideNamespaceScope(project, className, phpClass[0]); } } return null; } @NotNull public static Collection<VirtualFile> findMetadataFiles(@NotNull Project project, @NotNull String className) { final Collection<VirtualFile> virtualFiles = new ArrayList<>(); FileBasedIndex.getInstance().getFilesWithKey(DoctrineMetadataFileStubIndex.KEY, new HashSet<>(Collections.singletonList(className)), virtualFile -> { virtualFiles.add(virtualFile); return true; }, GlobalSearchScope.allScope(project)); return virtualFiles; } @NotNull public static Collection<VirtualFile> findMetadataForRepositoryClass(@NotNull PhpClass phpClass) { return findMetadataForRepositoryClass( phpClass.getProject(), StringUtils.stripStart(phpClass.getPresentableFQN(), "\\") ); } private static final Key<CachedValue<Map<String, Collection<String>>>> DOCTRINE_REPOSITORY_CACHE; static { DOCTRINE_REPOSITORY_CACHE = new Key<>("DOCTRINE_REPOSITORY_CACHE"); } @NotNull public static Collection<VirtualFile> findMetadataForRepositoryClass(final @NotNull Project project, @NotNull String repositoryClass) { CachedValue<Map<String, Collection<String>>> cache = project.getUserData(DOCTRINE_REPOSITORY_CACHE); if(cache == null) { cache = CachedValuesManager.getManager(project).createCachedValue(() -> { Map<String, Collection<String>> repositoryMap = new HashMap<>(); for (String key : FileIndexCaches.getIndexKeysCache(project, CLASS_KEYS, DoctrineMetadataFileStubIndex.KEY)) { for (DoctrineModelInterface repositoryDefinition : FileBasedIndex.getInstance().getValues(DoctrineMetadataFileStubIndex.KEY, key, GlobalSearchScope.allScope(project))) { if(StringUtils.isBlank(repositoryDefinition.getRepositoryClass())) { continue; } PhpClass phpClass = PhpElementsUtil.getClassInsideNamespaceScope(project, key, repositoryDefinition.getRepositoryClass()); if(phpClass != null) { String presentableFQN = phpClass.getPresentableFQN(); if(!repositoryMap.containsKey(presentableFQN)) { repositoryMap.put(presentableFQN, new HashSet<>()); } repositoryMap.get(presentableFQN).add(key); } } } return CachedValueProvider.Result.create(repositoryMap, PsiModificationTracker.MODIFICATION_COUNT); }, false); project.putUserData(DOCTRINE_REPOSITORY_CACHE, cache); } repositoryClass = StringUtils.stripStart(repositoryClass,"\\"); if(!cache.getValue().containsKey(repositoryClass)) { return Collections.emptyList(); } Set<VirtualFile> virtualFiles = new HashSet<>(); for (String s : cache.getValue().get(repositoryClass)) { virtualFiles.addAll( FileBasedIndex.getInstance().getContainingFiles(DoctrineMetadataFileStubIndex.KEY, s, GlobalSearchScope.allScope(project)) ); } return virtualFiles; } /** * Find metadata model in which the given repository class is used * eg "@ORM\Entity(repositoryClass="FOOBAR")", xml or yaml */ @NotNull public static Collection<DoctrineModelInterface> findMetadataModelForRepositoryClass(final @NotNull Project project, @NotNull String repositoryClass) { repositoryClass = StringUtils.stripStart(repositoryClass,"\\"); Collection<DoctrineModelInterface> models = new ArrayList<>(); for (String key : FileIndexCaches.getIndexKeysCache(project, CLASS_KEYS, DoctrineMetadataFileStubIndex.KEY)) { for (DoctrineModelInterface repositoryDefinition : FileBasedIndex.getInstance().getValues(DoctrineMetadataFileStubIndex.KEY, key, GlobalSearchScope.allScope(project))) { String myRepositoryClass = repositoryDefinition.getRepositoryClass(); if(StringUtils.isBlank(myRepositoryClass) || !repositoryClass.equalsIgnoreCase(StringUtils.stripStart(myRepositoryClass, "\\"))) { continue; } models.add(repositoryDefinition); } } return models; } @NotNull public static Collection<Pair<String, PsiElement>> getTables(@NotNull Project project) { Collection<Pair<String, PsiElement>> pair = new ArrayList<>(); for (String key : FileIndexCaches.getIndexKeysCache(project, CLASS_KEYS, DoctrineMetadataFileStubIndex.KEY)) { for (VirtualFile virtualFile : FileBasedIndex.getInstance().getContainingFiles(DoctrineMetadataFileStubIndex.KEY, key, GlobalSearchScope.allScope(project))) { PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); if(psiFile == null) { continue; } DoctrineMappingDriverArguments arguments = new DoctrineMappingDriverArguments(project, psiFile, key); for (DoctrineMappingDriverInterface mappingDriver : MAPPING_DRIVERS) { DoctrineMetadataModel metadata = mappingDriver.getMetadata(arguments); if(metadata == null) { continue; } String table = metadata.getTable(); if(table == null) { continue; } // @TODO: add target pair.add(new Pair<>(table, psiFile)); } } } return pair; } @Nullable public static DoctrineMetadataModel getMetadataByTable(@NotNull Project project, @NotNull String tableName) { for (String key : FileIndexCaches.getIndexKeysCache(project, CLASS_KEYS, DoctrineMetadataFileStubIndex.KEY)) { for (VirtualFile virtualFile : FileBasedIndex.getInstance().getContainingFiles(DoctrineMetadataFileStubIndex.KEY, key, GlobalSearchScope.allScope(project))) { PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); if(psiFile == null) { continue; } DoctrineMappingDriverArguments arguments = new DoctrineMappingDriverArguments(project, psiFile, key); for (DoctrineMappingDriverInterface mappingDriver : MAPPING_DRIVERS) { DoctrineMetadataModel metadata = mappingDriver.getMetadata(arguments); if(metadata == null) { continue; } String table = metadata.getTable(); if(table != null && tableName.equals(table)) { return metadata; } } } } return null; } @Nullable public static DoctrineMetadataModel getModelFields(@NotNull Project project, @NotNull String className) { for (VirtualFile file : findMetadataFiles(project, className)) { PsiFile psiFile = PsiManager.getInstance(project).findFile(file); if(psiFile == null) { continue; } DoctrineMappingDriverArguments arguments = new DoctrineMappingDriverArguments(project, psiFile, className); for (DoctrineMappingDriverInterface mappingDriver : MAPPING_DRIVERS) { DoctrineMetadataModel metadata = mappingDriver.getMetadata(arguments); if(metadata != null) { return metadata; } } } return null; } @NotNull public static Collection<PhpClass> getModels(@NotNull Project project) { Collection<PhpClass> phpClasses = new ArrayList<>(); for (String key : FileIndexCaches.getIndexKeysCache(project, CLASS_KEYS, DoctrineMetadataFileStubIndex.KEY)) { PhpClass classInterface = PhpElementsUtil.getClassInterface(project, key); if(classInterface != null) { phpClasses.add(classInterface); } } return phpClasses; } @Nullable public static DoctrineManagerEnum findManagerByScope(@NotNull PsiElement psiElement) { String name = psiElement.getContainingFile().getName(); Matcher matcher = Pattern.compile(".(mongodb|couchdb|orm|document|odm).(xml|yaml|yml)$", Pattern.CASE_INSENSITIVE).matcher(name); if(matcher.find()) { DoctrineManagerEnum managerEnum = DoctrineManagerEnum.getEnumFromString(matcher.group(1)); if(managerEnum != null) { return managerEnum; } } // @TODO: implement psiElement position scope return null; } @Nullable public static String findModelNameInScope(@NotNull PsiElement psiElement) { if(psiElement.getLanguage().equals(XMLLanguage.INSTANCE)) { PsiElement firstParent = PsiTreeUtil.findFirstParent(psiElement, psiElement1 -> { if (!(psiElement1 instanceof XmlTag)) { return false; } String name = ((XmlTag) psiElement1).getName(); return name.equals("entity") || name.equals("document") || name.equals("embedded") || name.equals("embedded-document"); }); if(firstParent instanceof XmlTag) { String name = ((XmlTag) firstParent).getAttributeValue("name"); if(StringUtils.isNotBlank(name)) { return name; } } } else if(psiElement.getContainingFile() instanceof YAMLFile) { PsiFile psiFile = psiElement.getContainingFile(); YAMLDocument yamlDocument = PsiTreeUtil.getChildOfType(psiFile, YAMLDocument.class); if(yamlDocument != null) { YAMLValue topLevelValue = yamlDocument.getTopLevelValue(); if(topLevelValue instanceof YAMLMapping) { PsiElement firstChild = topLevelValue.getFirstChild(); if(firstChild instanceof YAMLKeyValue) { String keyText = ((YAMLKeyValue) firstChild).getKeyText(); if(StringUtils.isNotBlank(keyText)) { return keyText; } } } } } // @TODO: yml scope return null; } @NotNull public static Collection<PhpClass> getClassInsideScope(@NotNull PsiElement psiElement, @NotNull String originValue) { Collection<PhpClass> classesInterface = new ArrayList<>(); String modelNameInScope = DoctrineMetadataUtil.findModelNameInScope(psiElement); if(modelNameInScope != null) { PhpClass classInsideNamespaceScope = PhpElementsUtil.getClassInsideNamespaceScope(psiElement.getProject(), modelNameInScope, originValue); if(classInsideNamespaceScope != null) { classesInterface = Collections.singletonList(classInsideNamespaceScope); } } else { classesInterface = PhpElementsUtil.getClassesInterface(psiElement.getProject(), originValue); } // @TODO: multi classes return classesInterface; } }