package fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dbal;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.util.Pair;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.jetbrains.php.lang.PhpLanguage;
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrar;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrarParameter;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.utils.GotoCompletionUtil;
import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineModelField;
import fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.dict.DoctrineMetadataModel;
import fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.util.DoctrineMetadataUtil;
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class DoctrineDbalQbGotoCompletionRegistrar implements GotoCompletionRegistrar {
@Override
public void register(GotoCompletionRegistrarParameter registrar) {
// table name completion on method eg:
// Doctrine\DBAL\Connection::insert
// Doctrine\DBAL\Query\QueryBuilder::update
registrar.register(PhpElementsUtil.getParameterInsideMethodReferencePattern(), psiElement -> {
PsiElement context = psiElement.getContext();
if (!(context instanceof StringLiteralExpression)) {
return null;
}
if (!isTableNameRegistrar(context)) {
return null;
}
return new DbalTableGotoCompletionProvider(context);
});
// simple flat field names eg:
// Doctrine\DBAL\Connection::update('foo', ['<caret>'])
registrar.register(PlatformPatterns.psiElement().withParent(StringLiteralExpression.class).withLanguage(PhpLanguage.INSTANCE), psiElement -> {
PsiElement context = psiElement.getContext();
if (!(context instanceof StringLiteralExpression)) {
return null;
}
MethodMatcher.MethodMatchParameter methodMatchParameter = new MethodMatcher.ArrayParameterMatcher(context, 1)
.withSignature("\\Doctrine\\DBAL\\Connection", "insert")
.withSignature("\\Doctrine\\DBAL\\Connection", "update")
.match();
if (methodMatchParameter == null) {
return null;
}
PsiElement[] parameters = methodMatchParameter.getParameters();
if(parameters.length < 2) {
return null;
}
String stringValue = PhpElementsUtil.getStringValue(parameters[0]);
if(StringUtils.isBlank(stringValue)) {
return null;
}
return new DbalFieldGotoCompletionProvider(context, stringValue);
});
// simple word alias completion
// join('foo', 'foo', 'bar')
registrar.register(PhpElementsUtil.getParameterInsideMethodReferencePattern(), psiElement -> {
PsiElement context = psiElement.getContext();
if (!(context instanceof StringLiteralExpression)) {
return null;
}
MethodMatcher.MethodMatchParameter methodMatchParameter = new MethodMatcher.StringParameterRecursiveMatcher(context, 2)
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "innerJoin")
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "leftJoin")
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "join")
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "rightJoin")
.match();
if (methodMatchParameter == null) {
return null;
}
PsiElement[] parameters = methodMatchParameter.getParameters();
if(parameters.length < 2) {
return null;
}
String stringValue = PhpElementsUtil.getStringValue(parameters[1]);
if(StringUtils.isBlank(stringValue)) {
return null;
}
return new MyDbalAliasGotoCompletionProvider(context, stringValue, PhpElementsUtil.getStringValue(parameters[0]));
});
}
private static class MyDbalAliasGotoCompletionProvider extends GotoCompletionProvider {
@NotNull
private final String value;
@Nullable
private final String fromAlias;
public MyDbalAliasGotoCompletionProvider(PsiElement psiElement, @NotNull String value, @Nullable String fromAlias) {
super(psiElement);
this.value = value;
this.fromAlias = fromAlias;
}
@NotNull
@Override
public Collection<LookupElement> getLookupElements() {
Set<String> aliasSet = new HashSet<>();
aliasSet.add(value);
aliasSet.add(fr.adrienbrault.idea.symfony2plugin.util.StringUtils.camelize(value, true));
String underscore = fr.adrienbrault.idea.symfony2plugin.util.StringUtils.underscore(value);
aliasSet.add(underscore);
if(value.length() > 0) {
aliasSet.add(value.substring(0, 1));
}
if(underscore.contains("_")) {
String[] split = underscore.split("_");
if(split.length > 1) {
aliasSet.add(split[0].substring(0, 1) + split[1].substring(0, 1));
}
List<String> i = new ArrayList<>();
for (String s : split) {
i.add(s.substring(0, 1));
}
aliasSet.add(StringUtils.join(i, ""));
}
if(StringUtils.isNotBlank(this.fromAlias)) {
aliasSet.add(fr.adrienbrault.idea.symfony2plugin.util.StringUtils.camelize(this.fromAlias + "_" + value, true));
aliasSet.add(fr.adrienbrault.idea.symfony2plugin.util.StringUtils.underscore(this.fromAlias + "_" + value));
}
Collection<LookupElement> lookupElements = new ArrayList<>();
for (String s : aliasSet) {
lookupElements.add(LookupElementBuilder.create(s).withIcon(Symfony2Icons.DOCTRINE));
}
return lookupElements;
}
@NotNull
@Override
public Collection<PsiElement> getPsiTargets(PsiElement element) {
return Collections.emptyList();
}
}
private boolean isTableNameRegistrar(PsiElement context) {
MethodMatcher.MethodMatchParameter methodMatchParameter = new MethodMatcher.StringParameterRecursiveMatcher(context, 0)
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "update")
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "insert")
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "from")
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "delete")
.withSignature("Doctrine\\DBAL\\Connection", "insert")
.withSignature("Doctrine\\DBAL\\Connection", "update")
.match();
if(methodMatchParameter != null) {
return true;
}
methodMatchParameter = new MethodMatcher.StringParameterRecursiveMatcher(context, 1)
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "innerJoin")
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "leftJoin")
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "join")
.withSignature("Doctrine\\DBAL\\Query\\QueryBuilder", "rightJoin")
.match();
return methodMatchParameter != null;
}
private static class DbalTableGotoCompletionProvider extends GotoCompletionProvider {
public DbalTableGotoCompletionProvider(PsiElement element) {
super(element);
}
@NotNull
@Override
public Collection<LookupElement> getLookupElements() {
Collection<LookupElement> elements = new ArrayList<>();
for (Pair<String, PsiElement> pair : DoctrineMetadataUtil.getTables(getProject())) {
elements.add(LookupElementBuilder.create(pair.getFirst()).withIcon(Symfony2Icons.DOCTRINE));
}
return elements;
}
@NotNull
@Override
public Collection<PsiElement> getPsiTargets(PsiElement element) {
String contents = GotoCompletionUtil.getStringLiteralValue(element);
if(contents == null) {
return Collections.emptyList();
}
Collection<PsiElement> psiElements = new ArrayList<>();
for (Pair<String, PsiElement> pair : DoctrineMetadataUtil.getTables(getProject())) {
if(!contents.equals(pair.getFirst())) {
continue;
}
PsiElement second = pair.getSecond();
if(second == null) {
continue;
}
psiElements.add(second);
}
return psiElements;
}
}
private static class DbalFieldGotoCompletionProvider extends GotoCompletionProvider {
private final String stringValue;
public DbalFieldGotoCompletionProvider(PsiElement element, String stringValue) {
super(element);
this.stringValue = stringValue;
}
@NotNull
@Override
public Collection<LookupElement> getLookupElements() {
DoctrineMetadataModel model = DoctrineMetadataUtil.getMetadataByTable(getProject(), this.stringValue);
if(model == null) {
return Collections.emptyList();
}
Collection<LookupElement> elements = new ArrayList<>();
for (DoctrineModelField field : model.getFields()) {
String column = field.getColumn();
// use "column" else fallback to field name
if(column != null && StringUtils.isNotBlank(column)) {
elements.add(LookupElementBuilder.create(column).withIcon(Symfony2Icons.DOCTRINE));
} else {
String name = field.getName();
if(StringUtils.isNotBlank(name)) {
elements.add(LookupElementBuilder.create(name).withIcon(Symfony2Icons.DOCTRINE));
}
}
}
return elements;
}
@NotNull
@Override
public Collection<PsiElement> getPsiTargets(PsiElement element) {
String contents = GotoCompletionUtil.getStringLiteralValue(element);
if(contents == null) {
return Collections.emptyList();
}
DoctrineMetadataModel model = DoctrineMetadataUtil.getMetadataByTable(getProject(), this.stringValue);
if(model == null) {
return Collections.emptyList();
}
Collection<PsiElement> elements = new ArrayList<>();
for (DoctrineModelField field : model.getFields()) {
if(contents.equals(field.getColumn()) || contents.equals(field.getName())) {
elements.addAll(field.getTargets());
}
}
return elements;
}
}
}