package fr.adrienbrault.idea.symfony2plugin.assistant.signature;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.jetbrains.php.PhpIndex;
import com.jetbrains.php.lang.psi.elements.*;
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider3;
import fr.adrienbrault.idea.symfony2plugin.Settings;
import fr.adrienbrault.idea.symfony2plugin.Symfony2InterfacesUtil;
import fr.adrienbrault.idea.symfony2plugin.extension.MethodSignatureTypeProviderExtension;
import fr.adrienbrault.idea.symfony2plugin.extension.MethodSignatureTypeProviderParameter;
import fr.adrienbrault.idea.symfony2plugin.util.PhpTypeProviderUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
/**
* @author Adrien Brault <adrien.brault@gmail.com>
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class MethodSignatureTypeProvider implements PhpTypeProvider3 {
final static char TRIM_KEY = '\u0181';
private static final ExtensionPointName<MethodSignatureTypeProviderExtension> EXTENSIONS = new ExtensionPointName<>("fr.adrienbrault.idea.symfony2plugin.extension.MethodSignatureTypeProviderExtension");
@Override
public char getKey() {
return '\u0160';
}
@Nullable
@Override
public PhpType getType(PsiElement e) {
if (!Settings.getInstance(e.getProject()).pluginEnabled || !(e instanceof MethodReference)) {
return null;
}
Collection<MethodSignatureSetting> signatures = getSignatureSettings(e);
if(signatures.size() == 0) {
return null;
}
MethodReference methodReference = (MethodReference) e;
Collection<MethodSignatureSetting> matchedSignatures = getSignatureSetting(methodReference.getName(), signatures);
if(matchedSignatures.size() == 0) {
return null;
}
String refSignature = ((MethodReference)e).getSignature();
if(StringUtil.isEmpty(refSignature)) {
return null;
}
// we need the param key on getBySignature(), since we are already in the resolved method there attach it to signature
// param can have dotted values split with \
PsiElement[] parameters = ((MethodReference)e).getParameters();
for(MethodSignatureSetting methodSignature: matchedSignatures) {
if (parameters.length - 1 >= methodSignature.getIndexParameter()) {
PsiElement parameter = parameters[methodSignature.getIndexParameter()];
if ((parameter instanceof StringLiteralExpression)) {
String param = ((StringLiteralExpression)parameter).getContents();
if (StringUtil.isNotEmpty(param)) {
return new PhpType().add("#" + this.getKey() + refSignature + TRIM_KEY + param);
}
}
String parameterSignature = PhpTypeProviderUtil.getReferenceSignatureByFirstParameter(methodReference, TRIM_KEY);
if(parameterSignature != null) {
return new PhpType().add("#" + this.getKey() + parameterSignature);
}
}
}
return null;
}
@NotNull
private Collection<MethodSignatureSetting> getSignatureSettings(@NotNull PsiElement psiElement) {
Collection<MethodSignatureSetting> signatures = new ArrayList<>();
// get user defined settings
if(Settings.getInstance(psiElement.getProject()).objectSignatureTypeProvider) {
Collection<MethodSignatureSetting> settingSignatures = Settings.getInstance(psiElement.getProject()).methodSignatureSettings;
if(settingSignatures != null) {
signatures.addAll(settingSignatures);
}
}
// load extension
MethodSignatureTypeProviderExtension[] extensions = EXTENSIONS.getExtensions();
if(extensions.length > 0) {
MethodSignatureTypeProviderParameter parameter = new MethodSignatureTypeProviderParameter(psiElement);
for(MethodSignatureTypeProviderExtension extension: extensions){
signatures.addAll(extension.getSignatures(parameter));
}
}
return signatures;
}
private Collection<MethodSignatureSetting> getSignatureSetting(String methodName, Collection<MethodSignatureSetting> methodSignatureSettingList) {
Collection<MethodSignatureSetting> matchedSignatures = new ArrayList<>();
for(MethodSignatureSetting methodSignatureSetting: methodSignatureSettingList) {
if(methodSignatureSetting.getMethodName().equals(methodName)) {
matchedSignatures.add(methodSignatureSetting);
}
}
return matchedSignatures;
}
@Override
public Collection<? extends PhpNamedElement> getBySignature(String expression, Set<String> visited, int depth, Project project) {
// get back our original call
int endIndex = expression.lastIndexOf(TRIM_KEY);
if(endIndex == -1) {
return null;
}
String originalSignature = expression.substring(0, endIndex);
String parameter = expression.substring(endIndex + 1);
PhpIndex phpIndex = PhpIndex.getInstance(project);
Collection<? extends PhpNamedElement> phpNamedElementCollections = PhpTypeProviderUtil.getTypeSignature(phpIndex, originalSignature);
if(phpNamedElementCollections.size() == 0) {
return null;
}
// get first matched item
PhpNamedElement phpNamedElement = phpNamedElementCollections.iterator().next();
if(!(phpNamedElement instanceof Method)) {
return null;
}
Collection<MethodSignatureSetting> signatures = getSignatureSettings(phpNamedElement);
if(signatures.size() == 0) {
return null;
}
parameter = PhpTypeProviderUtil.getResolvedParameter(phpIndex, parameter);
if(parameter == null) {
return null;
}
Collection<PhpNamedElement> phpNamedElements = new ArrayList<>();
for(MethodSignatureSetting matchedSignature: signatures) {
for(PhpTypeSignatureInterface signatureTypeProvider: PhpTypeSignatureTypes.DEFAULT_PROVIDER) {
if( signatureTypeProvider.getName().equals(matchedSignature.getReferenceProviderName()) && new Symfony2InterfacesUtil().isCallTo((Method) phpNamedElement, matchedSignature.getCallTo(), matchedSignature.getMethodName())) {
Collection<? extends PhpNamedElement> namedElements = signatureTypeProvider.getByParameter(project, parameter);
if(namedElements != null) {
phpNamedElements.addAll(namedElements);
}
}
}
}
// not good but we need return any previous types: null clears all types
return new ArrayList<>(phpNamedElements);
}
}