package fr.adrienbrault.idea.symfony2plugin.stubs.indexes; import com.intellij.patterns.PlatformPatterns; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiRecursiveElementVisitor; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.indexing.*; import com.intellij.util.io.DataExternalizer; import com.intellij.util.io.EnumeratorStringDescriptor; import com.intellij.util.io.KeyDescriptor; import com.jetbrains.php.lang.PhpFileType; import com.jetbrains.php.lang.documentation.phpdoc.parser.PhpDocElementTypes; import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment; import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag; import com.jetbrains.php.lang.parser.PhpElementTypes; import com.jetbrains.php.lang.psi.elements.PhpPsiElement; import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; import com.jetbrains.php.lang.psi.elements.impl.ClassConstImpl; import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; import fr.adrienbrault.idea.symfony2plugin.stubs.dict.DispatcherEvent; import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.externalizer.ObjectStreamDataExternalizer; import fr.adrienbrault.idea.symfony2plugin.stubs.util.EventDispatcherUtil; import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import java.util.HashMap; import java.util.Map; /** * @author Daniel Espendiller <daniel@espendiller.net> */ public class EventAnnotationStubIndex extends FileBasedIndexExtension<String, DispatcherEvent> { public static final ID<String, DispatcherEvent> KEY = ID.create("fr.adrienbrault.idea.symfony2plugin.events_annotation"); private final KeyDescriptor<String> myKeyDescriptor = new EnumeratorStringDescriptor(); private static ObjectStreamDataExternalizer<DispatcherEvent> EXTERNALIZER = new ObjectStreamDataExternalizer<>(); @NotNull @Override public ID<String, DispatcherEvent> getName() { return KEY; } @NotNull @Override public DataIndexer<String, DispatcherEvent, FileContent> getIndexer() { return inputData -> { Map<String, DispatcherEvent> map = new HashMap<>(); PsiFile psiFile = inputData.getPsiFile(); if(!Symfony2ProjectComponent.isEnabledForIndex(psiFile.getProject())) { return map; } psiFile.accept(new MyPsiRecursiveElementWalkingVisitor(map)); return map; }; } @NotNull @Override public KeyDescriptor<String> getKeyDescriptor() { return this.myKeyDescriptor; } @NotNull @Override public DataExternalizer<DispatcherEvent> getValueExternalizer() { return EXTERNALIZER; } @NotNull @Override public FileBasedIndex.InputFilter getInputFilter() { return file -> file.getFileType() == PhpFileType.INSTANCE; } @Override public boolean dependsOnFileContent() { return true; } @Override public int getVersion() { return 2; } private class MyPsiRecursiveElementWalkingVisitor extends PsiRecursiveElementVisitor { private final Map<String, DispatcherEvent> map; MyPsiRecursiveElementWalkingVisitor(Map<String, DispatcherEvent> map) { this.map = map; } @Override public void visitElement(PsiElement element) { if ((element instanceof PhpDocTag)) { visitPhpDocTag((PhpDocTag) element); } super.visitElement(element); } private void visitPhpDocTag(PhpDocTag element) { String name = StringUtils.stripStart(element.getName(), "@"); if(!"Event".equalsIgnoreCase(name)) { return; } PhpDocComment phpDocComment = ObjectUtils.tryCast(element.getParent(), PhpDocComment.class); if(phpDocComment == null) { return; } PhpPsiElement nextPsiSibling = phpDocComment.getNextPsiSibling(); if(nextPsiSibling == null || nextPsiSibling.getNode().getElementType() != PhpElementTypes.CLASS_CONSTANTS) { return; } ClassConstImpl childOfAnyType = PsiTreeUtil.findChildOfAnyType(nextPsiSibling, ClassConstImpl.class); if(childOfAnyType == null) { return; } PsiElement defaultValue = childOfAnyType.getDefaultValue(); if(!(defaultValue instanceof StringLiteralExpression)) { return; } String contents = ((StringLiteralExpression) defaultValue).getContents(); String fqn = StringUtils.stripStart(childOfAnyType.getFQN(), "\\"); map.put(contents, new DispatcherEvent( fqn, findClassInstance(phpDocComment, element)) ); } private String findClassInstance(@NotNull PhpDocComment phpDocComment, @NotNull PhpDocTag phpDocTag) { PsiElement phpDocAttributeList = PsiElementUtils.getChildrenOfType(phpDocTag, PlatformPatterns.psiElement(PhpDocElementTypes.phpDocAttributeList)); if(phpDocAttributeList instanceof PhpPsiElement) { PsiElement childrenOfType = PsiElementUtils.getChildrenOfType(phpDocAttributeList, PlatformPatterns.psiElement(PhpDocElementTypes.phpDocString)); if(childrenOfType instanceof StringLiteralExpression) { String contents = StringUtils.stripStart(((StringLiteralExpression) childrenOfType).getContents(), "\\"); if(StringUtils.isNotBlank(contents) && contents.length() < 350) { return contents; } } } return EventDispatcherUtil.extractEventClassInstance(phpDocComment.getText()); } } }