package fr.adrienbrault.idea.symfony2plugin;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.PsiReference;
import com.intellij.psi.ResolveResult;
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 com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Adrien Brault <adrien.brault@gmail.com>
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class Symfony2InterfacesUtil {
public boolean isContainerGetCall(PsiElement e) {
return isCallTo(e, createExpectedContainerMethods(e));
}
public boolean isContainerGetCall(Method e) {
return isCallTo(e, createExpectedContainerMethods(e));
}
@NotNull
private Method[] createExpectedContainerMethods(PsiElement e) {
return new Method[] {
getInterfaceMethod(e.getProject(), "\\Symfony\\Component\\DependencyInjection\\ContainerInterface", "get"),
getInterfaceMethod(e.getProject(), "\\Psr\\Container\\ContainerInterface", "get"),
getClassMethod(e.getProject(), "\\Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller", "get"),
};
}
public boolean isTemplatingRenderCall(PsiElement e) {
return isCallTo(e, new Method[] {
getInterfaceMethod(e.getProject(), "\\Symfony\\Component\\Templating\\EngineInterface", "render"),
getInterfaceMethod(e.getProject(), "\\Symfony\\Component\\Templating\\StreamingEngineInterface", "stream"),
getInterfaceMethod(e.getProject(), "\\Symfony\\Bundle\\FrameworkBundle\\Templating\\EngineInterface", "renderResponse"),
getClassMethod(e.getProject(), "\\Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller", "render"),
getClassMethod(e.getProject(), "\\Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller", "renderView"),
getClassMethod(e.getProject(), "\\Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller", "stream"),
});
}
public boolean isTranslatorCall(PsiElement e) {
return isCallTo(e, new Method[] {
getInterfaceMethod(e.getProject(), "\\Symfony\\Component\\Translation\\TranslatorInterface", "trans"),
getInterfaceMethod(e.getProject(), "\\Symfony\\Component\\Translation\\TranslatorInterface", "transChoice"),
});
}
public boolean isGetRepositoryCall(Method e) {
return isCallTo(e, new Method[] {
getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ManagerRegistry", "getRepository"),
getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectManager", "getRepository"),
});
}
public boolean isObjectRepositoryCall(Method e) {
return isCallTo(e, new Method[] {
getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectRepository", "find"),
getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectRepository", "findOneBy"),
getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectRepository", "findAll"),
getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectRepository", "findBy"),
});
}
public boolean isFormBuilderFormTypeCall(PsiElement e) {
List<Method> methods = getCallToSignatureInterfaceMethods(e, getFormBuilderInterface());
return isCallTo(e, methods.toArray( new Method[methods.size()]));
}
protected boolean isCallTo(PsiElement e, Method expectedMethod) {
return isCallTo(e, new Method[] { expectedMethod });
}
protected boolean isCallTo(PsiElement e, Method[] expectedMethods) {
return isCallTo(e, expectedMethods, 1);
}
protected boolean isCallTo(Method e, Method[] expectedMethods) {
PhpClass methodClass = e.getContainingClass();
if(methodClass == null) {
return false;
}
for (Method expectedMethod : expectedMethods) {
// @TODO: its stuff from beginning times :)
if(expectedMethod == null) {
continue;
}
PhpClass containingClass = expectedMethod.getContainingClass();
if (containingClass != null && expectedMethod.getName().equals(e.getName()) && isInstanceOf(methodClass, containingClass)) {
return true;
}
}
return false;
}
protected boolean isCallTo(PsiElement e, Method[] expectedMethods, int deepness) {
if (!(e instanceof MethodReference)) {
return false;
}
MethodReference methodRef = (MethodReference) e;
// resolve is also called on invalid php code like "use <xxx>"
// so double check the method name before resolve the method
if(!isMatchingMethodName(methodRef, expectedMethods)) {
return false;
}
PsiReference psiReference = methodRef.getReference();
if (null == psiReference) {
return false;
}
Method[] multiResolvedMethod = getMultiResolvedMethod(psiReference);
if(multiResolvedMethod == null) {
return false;
}
for (Method method : multiResolvedMethod) {
PhpClass methodClass = method.getContainingClass();
if(methodClass == null) {
continue;
}
for (Method expectedMethod : expectedMethods) {
// @TODO: its stuff from beginning times :)
if(expectedMethod == null) {
continue;
}
PhpClass containingClass = expectedMethod.getContainingClass();
if (null != containingClass && expectedMethod.getName().equals(method.getName()) && isInstanceOf(methodClass, containingClass)) {
return true;
}
}
}
return false;
}
/**
* Single resolve doesnt work if we have non unique class names in project context,
* so try a multiResolve
*/
@Nullable
public static Method[] getMultiResolvedMethod(PsiReference psiReference) {
// class be unique in normal case, so try this first
PsiElement resolvedReference = psiReference.resolve();
if (resolvedReference instanceof Method) {
return new Method[] { (Method) resolvedReference };
}
// try multiResolve if class exists twice in project
if(psiReference instanceof PsiPolyVariantReference) {
Collection<Method> methods = new HashSet<>();
for(ResolveResult resolveResult : ((PsiPolyVariantReference) psiReference).multiResolve(false)) {
PsiElement element = resolveResult.getElement();
if(element instanceof Method) {
methods.add((Method) element);
}
}
if(methods.size() > 0) {
return methods.toArray(new Method[methods.size()]);
}
}
return null;
}
protected boolean isMatchingMethodName(MethodReference methodRef, Method[] expectedMethods) {
for (Method expectedMethod : Arrays.asList(expectedMethods)) {
if(expectedMethod != null && expectedMethod.getName().equals(methodRef.getName())) {
return true;
}
}
return false;
}
@Deprecated
@Nullable
public static String getFirstArgumentStringValue(MethodReference e) {
String stringValue = null;
PsiElement[] parameters = e.getParameters();
if (parameters.length > 0 && parameters[0] instanceof StringLiteralExpression) {
stringValue = ((StringLiteralExpression) parameters[0]).getContents();
}
return stringValue;
}
@Nullable
protected Method getInterfaceMethod(@NotNull Project project, @NotNull String interfaceFQN, @NotNull String methodName) {
Collection<PhpClass> interfaces = PhpIndex.getInstance(project).getInterfacesByFQN(interfaceFQN);
if (interfaces.size() < 1) {
return null;
}
return interfaces.iterator().next().findMethodByName(methodName);
}
@Nullable
protected Method getClassMethod(Project project, String classFQN, String methodName) {
return PhpElementsUtil.getClassMethod(project, classFQN, methodName);
}
@Deprecated
public boolean isInstanceOf(PhpClass subjectClass, String expectedClass) {
return PhpElementsUtil.isInstanceOf(subjectClass, expectedClass);
}
@Deprecated
public boolean isInstanceOf(Project project, String subjectClass, String expectedClass) {
return PhpElementsUtil.isInstanceOf(project, subjectClass, expectedClass);
}
@Deprecated
public boolean isInstanceOf(@NotNull PhpClass subjectClass, @NotNull PhpClass expectedClass) {
return PhpElementsUtil.isInstanceOf(subjectClass, expectedClass);
}
public boolean isCallTo(MethodReference e, String ClassInterfaceName, String methodName) {
return isCallTo((PsiElement) e, ClassInterfaceName, methodName);
}
/**
* @deprecated isCallTo with MethodReference
*/
public boolean isCallTo(PsiElement e, String ClassInterfaceName, String methodName) {
// we need a full fqn name
if(ClassInterfaceName.contains("\\") && !ClassInterfaceName.startsWith("\\")) {
ClassInterfaceName = "\\" + ClassInterfaceName;
}
return isCallTo(e, new Method[] {
getInterfaceMethod(e.getProject(), ClassInterfaceName, methodName),
getClassMethod(e.getProject(), ClassInterfaceName, methodName),
});
}
public boolean isCallTo(Method e, String ClassInterfaceName, String methodName) {
// we need a full fqn name
if(ClassInterfaceName.contains("\\") && !ClassInterfaceName.startsWith("\\")) {
ClassInterfaceName = "\\" + ClassInterfaceName;
}
return isCallTo(e, new Method[] {
getInterfaceMethod(e.getProject(), ClassInterfaceName, methodName),
getClassMethod(e.getProject(), ClassInterfaceName, methodName),
});
}
private List<Method> getCallToSignatureInterfaceMethods(PsiElement e, Collection<MethodMatcher.CallToSignature> signatures) {
List<Method> methods = new ArrayList<>();
for(MethodMatcher.CallToSignature signature: signatures) {
Method method = getInterfaceMethod(e.getProject(), signature.getInstance(), signature.getMethod());
if(method != null) {
methods.add(method);
}
}
return methods;
}
public static Collection<MethodMatcher.CallToSignature> getFormBuilderInterface() {
Collection<MethodMatcher.CallToSignature> signatures = new ArrayList<>();
signatures.add(new MethodMatcher.CallToSignature("\\Symfony\\Component\\Form\\FormBuilderInterface", "add"));
signatures.add(new MethodMatcher.CallToSignature("\\Symfony\\Component\\Form\\FormBuilderInterface", "create"));
signatures.add(new MethodMatcher.CallToSignature("\\Symfony\\Component\\Form\\FormInterface", "add"));
signatures.add(new MethodMatcher.CallToSignature("\\Symfony\\Component\\Form\\FormInterface", "create"));
return signatures;
}
}