package org.fandev.lang.fan.psi.impl.statements.expressions;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.util.IncorrectOperationException;
import org.fandev.lang.fan.psi.api.FanResolveResult;
import org.fandev.lang.fan.psi.api.statements.expressions.*;
import org.fandev.lang.fan.psi.api.statements.typeDefs.FanTypeDefinition;
import org.fandev.lang.fan.psi.api.statements.typeDefs.FanEnumDefinition;
import org.fandev.lang.fan.psi.api.statements.typeDefs.members.*;
import org.fandev.lang.fan.psi.api.statements.FanVariable;
import org.fandev.lang.fan.psi.api.statements.params.FanFormals;
import org.fandev.lang.fan.psi.impl.*;
import org.fandev.lang.fan.psi.FanFile;
import org.fandev.lang.fan.psi.stubs.index.FanShortClassNameIndex;
import org.fandev.utils.PsiUtil;
import org.fandev.utils.FanUtil;
import org.fandev.index.FanIndex;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Date: Jun 24, 2009
* Time: 11:48:37 PM
*
* @author Dror Bereznitsky
*/
public class FanReferenceExpressionImpl extends FanReferenceElementImpl implements FanReferenceExpression {
private static final OurResolver RESOLVER = new OurResolver();
private static final Logger logger = Logger.getInstance(FanReferenceExpressionImpl.class.getName());
public FanReferenceExpressionImpl(final StubElement stubElement, @NotNull final IStubElementType iStubElementType) {
super(stubElement, iStubElementType);
}
public FanReferenceExpressionImpl(final ASTNode astNode) {
super(astNode);
}
public PsiElement getQualifier() {
return this;
}
public boolean isReferenceTo(final PsiElement psiElement) {
for (final Object obj : getVariants()) {
if (psiElement.equals(obj)) {
return true;
}
}
return false;
}
public PsiElement setName(@NonNls final String s) throws IncorrectOperationException {
//TODO [Dror] implement
return null;
}
public Object[] getVariants() {
final List<PsiElement> result = new ArrayList<PsiElement>();
final FanReferenceExpression qualifier = (FanReferenceExpression) getQualifier();
if (qualifier != null) {
final ResolveResult[] results = qualifier.multiResolve(true);
if (results.length > 0) {
for (final ResolveResult tmp : results) {
result.add(tmp.getElement());
}
return result.toArray(new PsiElement[0]);
}
}
return new Object[0];
}
public boolean isSoft() {
return false;
}
public String getCanonicalText() {
return null;
}
@NotNull
//incomplete means we do not take arguments into consideration
public ResolveResult[] multiResolve(final boolean incompleteCode) {
return getManager().getResolveCache().resolveWithCaching(this, RESOLVER, false, incompleteCode);
}
public PsiElement resolve() {
final ResolveResult[] results = getManager().getResolveCache().resolveWithCaching(this, RESOLVER, false, false);
return results.length == 1 ? results[0].getElement() : null;
}
public FanExpression getQualifierExpression() {
return findChildByClass(FanExpression.class);
}
@NotNull
public FanResolveResult[] getSameNameVariants() {
return RESOLVER.resolve(this, true);
}
private static class OurResolver implements ResolveCache.PolyVariantResolver<FanReferenceExpressionImpl> {
//TODO [Dror] refactor this complex, ugly, HUGE method !!!
public FanResolveResult[] resolve(final FanReferenceExpressionImpl refExpr, final boolean incompleteCode) {
final List<FanResolveResult> results = new ArrayList<FanResolveResult>();
final ResolveHint hint = new ResolveHint(refExpr);
FanTypeDefinition typeDefinition = null;
FanClassReferenceType referenceType = null;
if (hint.isSuperRefrenceExpression() || hint.isThisRefrenceExpression()) {
// "super" or "this" reference
if (hint.gotoNonFieldOrMethodDecleration()) {
return FanResolveResult.EMPTY_ARRAY;
}
typeDefinition = ((FanTypeReferenceExpression)hint.idElement).getReferencedType();
} else if (hint.someTypeFieldOrMethodRef()) {
typeDefinition = resolveTypeDefinition(hint, typeDefinition);
} else {
// methods and fields
if (hint.containingTypeFieldOrMethodRef()) {
final PsiElement maybeClazz = PsiTreeUtil.getParentOfType(refExpr, FanTypeDefinition.class, FanFile.class);
if (FanUtil.isFanType(maybeClazz)) {
resolveByType(results, hint, (FanTypeDefinition) maybeClazz);
}
}
// Find other options in the containing block
PsiElement containingBlock = refExpr.getParent();
while (!(FanUtil.isFanFile(containingBlock))) {
if (FanUtil.isFanClosure(containingBlock)) {
final FanFormals fanFormals = ((FanClosureExpression) containingBlock).getFunction().getFormals();
final String toMatch = stripRefElement(hint.idElement);
for (final PsiParameter formal : fanFormals.getParameters()) {
if (toMatch.equals(formal.getName())) {
if (hint.gotoNonFieldOrMethodDecleration()) {
// Goto formal decleration
return new FanResolveResult[]{new FanResolveResultImpl(formal, true)};
} else {
referenceType = (FanClassReferenceType) formal.getType();
break;
}
} else if (hint.completeIdentifier() && formal.getName().startsWith(toMatch)) {
results.add(new FanResolveResultImpl(formal, true));
}
}
} else if (FanUtil.isFanMethod(containingBlock)) {
final PsiParameterList params = ((FanMethod) containingBlock).getParameterList();
for (final PsiParameter param : params.getParameters()) {
String elementText = hint.idElement.getText();
if (elementText.indexOf("(") != -1) {
elementText = elementText.substring(0, elementText.indexOf("("));
}
if (param.getName().equals(elementText)) {
if (hint.gotoNonFieldOrMethodDecleration()) {
// Goto parameter decleration
return new FanResolveResult[]{new FanResolveResultImpl(param, true)};
} else {
referenceType = (FanClassReferenceType) param.getType();
break;
}
}
}
} else if (FanUtil.isPsiCodeBlock(containingBlock)) {
// Look for a local variable inside code block
List<PsiVariable> variables;
if (hint.gotoDecleration && !hint.fieldOrMethodeRef && !hint.isMethodRef) {
variables = findVariablesByName(hint.idElement, containingBlock, true);
// Goto variable decleration
if (variables.size() > 0) {
return new FanResolveResult[]{new FanResolveResultImpl(variables.get(0), true)};
}
} else if(hint.gotoDecleration && !hint.fieldOrMethodeRef && hint.isMethodRef) {
// Function call return type
variables = findVariablesByName(hint.idElement, containingBlock, true);
if (variables.size() > 0) {
final PsiType type = variables.get(0).getType();
if (type instanceof FanFuncType) {
final PsiType returnType = ((FanFuncType) type).getReturnType();
return new FanResolveResult[]{new FanResolveResultImpl(((FanClassReferenceType) returnType).resolveFanType(), true)};
}
//break;
}
} else if (!hint.gotoDecleration && !hint.fieldOrMethodeRef) {
variables = findVariablesByName(hint.idElement, containingBlock, false);
if (variables.size() > 0) {
results.addAll(Arrays.asList(createResults(refExpr, variables.toArray(new PsiVariable[0]))));
//break;
}
} else {
// Variable type
variables = findVariablesByName(hint.idElement, containingBlock, true);
if (variables.size() > 0) {
final PsiType type = variables.get(0).getType();
if (type instanceof FanClassReferenceType) {
referenceType = (FanClassReferenceType) type;
}
break;
}
}
} else if (FanUtil.isFanType(containingBlock)) {
// Look for a field inside the Fan type hierarchy
FanTypeDefinition myClass = (FanTypeDefinition) containingBlock;
while (myClass instanceof PsiClass) {
if (hint.isMethodRef && !hint.fieldOrMethodeRef) {
final FanMethod method = myClass.getMethodByName(hint.toMatch);
if (method != null && hint.gotoDecleration) {
return createResults(refExpr, method);
}
} else {
final FanField field = myClass.getFieldByName(hint.idElement.getText());
if (field != null) {
// If we need to goto the reference
if (hint.gotoNonFieldOrMethodDecleration()) {
return createResults(refExpr, field);
}
// Else we are looking for this field type
referenceType = (FanClassReferenceType) field.getType();
break;
} else {
final FanMethod method = myClass.getMethodByName(hint.toMatch);
if (method != null && hint.gotoDecleration) {
return createResults(refExpr, method);
}
}
}
myClass = myClass.getSuperType(); // super class of Obj will is null
}
}
containingBlock = containingBlock.getParent();
}
if (referenceType != null) {
typeDefinition = referenceType.resolveFanType();
}
}
if (typeDefinition != null) {
resolveByType(results, hint, typeDefinition);
} else if (referenceType != null) {
logger.warn("Could not resolve type " + referenceType.getPresentableText());
} else if (results.size() == 0 && !hint.fieldOrMethodeRef) {
resolveTypes(results, hint);
}
return results.toArray(new FanResolveResult[0]);
}
private FanTypeDefinition resolveTypeDefinition(final ResolveHint hint, FanTypeDefinition typeDefinition) {
if (!(hint.idElement instanceof FanReferenceExpression))
throw new IllegalArgumentException("hint idElement must be instance of FanReferenceExpression");
final PsiElement typeProvider = ((FanReferenceExpression) hint.idElement).resolve();
PsiType type = null;
if (FanUtil.isFanMethod(typeProvider)) {
type = ((FanMethod) typeProvider).getReturnType();
} else if (FanUtil.isFanField(typeProvider)) {
type = ((FanField) typeProvider).getType();
} else if (FanUtil.isFanVariable(typeProvider)) {
type = ((FanVariable) typeProvider).getType();
} else if (FanUtil.isFanTypeDefinition(typeProvider)) {
// If the provider is not the return value of a function call
if (!hint.idElement.getText().contains("(")) {
hint.isStatic = true;
}
typeDefinition = (FanTypeDefinition) typeProvider;
}
if (type instanceof FanClassReferenceType) {
typeDefinition = ((FanClassReferenceType) type).resolveFanType();
} else if (type instanceof FanFuncType) {
// if this is a function call than use the function return type
if (hint.idElement.getText().contains("(")) {
final PsiType returnType = ((FanFuncType) type).getReturnType();
typeDefinition = ((FanClassReferenceType) returnType).resolveFanType();
} else {
// Treat is as a Fan Func type
typeDefinition = ((FanFuncType) type).getFuncType();
}
} else if (FanUtil.isFanMapType(type)) {
typeDefinition = ((FanMapType) type).getMapType();
} else if (FanUtil.isFanListType(type)) {
if (hint.isIndex) {
typeDefinition = (FanTypeDefinition) ((FanListReferenceType) type).getTypeElement().getReferenceElement().resolve();
} else {
typeDefinition = ((FanListReferenceType) type).getListType();
}
}
return typeDefinition;
}
private FanResolveResult[] createResults(final FanReferenceExpressionImpl refExpr, final PsiElement... element) {
final List<FanResolveResult> results = new ArrayList<FanResolveResult>();
for (final PsiElement elem : element) {
boolean isAccessible = true;
if (PsiMember.class.isAssignableFrom(elem.getClass())) {
isAccessible = PsiUtil.isAccessible(refExpr, (PsiMember) elem);
}
results.add(new FanResolveResultImpl(elem, isAccessible));
}
return results.toArray(new FanResolveResult[0]);
}
/**
* Resolve slots in type 'typeDefinition' which match 'toMatch'
*/
private void resolveByType(final List<FanResolveResult> results, final ResolveHint hint, FanTypeDefinition typeDefinition) {
final FanReferenceExpressionImpl context = hint.refExpr;
boolean isSuperType = false;
while (typeDefinition != null) {
final FanMethod[] methods = hint.isStatic ? typeDefinition.getFanMethods(PsiModifier.STATIC) : typeDefinition.getFanMethods();
for (final PsiMethod method : methods) {
if ((!hint.gotoDecleration && method.getName().startsWith(hint.toMatch)) ||
(hint.gotoDecleration && method.getName().equals(hint.toMatch))) {
if (isSuperType && method instanceof FanConstructor) {
continue;
}
final boolean isAccessible = PsiUtil.isAccessible(context, method);
results.add(new FanResolveResultImpl(method, isAccessible));
}
}
if (!hint.isMethodRef) {
final FanField[] fields = hint.isStatic ? typeDefinition.getFanFields(PsiModifier.STATIC) : typeDefinition.getFanFields();
for (final PsiField psiField : fields) {
if ((!hint.gotoDecleration && psiField.getName().startsWith(hint.toMatch)) ||
(hint.gotoDecleration && psiField.getName().equals(hint.toMatch))) {
final boolean isAccessible = PsiUtil.isAccessible(context, psiField);
results.add(new FanResolveResultImpl(psiField, isAccessible));
}
}
}
if (hint.isStatic && FanUtil.isFanEnumDefinition(typeDefinition)) {
final FanEnumValue[] enumValues = ((FanEnumDefinition) typeDefinition).getEnumValues();
for (final FanEnumValue enumValue : enumValues) {
if ((!hint.gotoDecleration && enumValue.getName().startsWith(hint.toMatch)) ||
(hint.gotoDecleration && enumValue.getName().equals(hint.toMatch))) {
final boolean isAccessible = PsiUtil.isAccessible(context, enumValue);
results.add(new FanResolveResultImpl(enumValue, isAccessible));
}
}
}
typeDefinition = (FanTypeDefinition) typeDefinition.getSuperClass();
isSuperType = true;
}
}
private List<PsiVariable> findVariablesByName(final PsiElement idElement, final PsiElement containingBlock, final boolean exectMatch) {
final List<PsiVariable> variables = new ArrayList<PsiVariable>();
final String toMatch = stripRefElement(idElement);
for (final PsiElement psiElement : containingBlock.getChildren()) {
if (psiElement instanceof PsiVariable) {
final String name = ((PsiVariable) psiElement).getName();
if (name != null) {
if (name.equals(toMatch)) {
if (variables.size() > 0) {
variables.set(0, (PsiVariable) psiElement);
} else {
variables.add((PsiVariable) psiElement);
}
} else if (name.startsWith(toMatch) && !exectMatch) {
variables.add((PsiVariable) psiElement);
}
}
}
}
return variables;
}
private String stripRefElement(final PsiElement idElement) {
int idx = idElement.getText().indexOf("IntellijIdeaRulezzz");
int idx2 = idElement.getText().indexOf("(");
if (idx2 >= 0 && idx2 > idx) {
idx = idx2;
}
final String toMatch = idx >= 0 ? idElement.getText().substring(0, idx) : idElement.getText();
return toMatch;
}
private void resolvePodTypes(final FanReferenceExpressionImpl refExpr, final String podName, final String toMatch, boolean gotoDecleration, List<FanResolveResult> results) {
final FanIndex index = refExpr.getProject().getComponent(FanIndex.class);
final Set<FanTypeDefinition> typesStartingWith = index.getPodTypesStartingWith(podName, toMatch);
for (final FanTypeDefinition def : typesStartingWith) {
if (!gotoDecleration || (gotoDecleration && def.getName().equals(refExpr.getText()))) {
final boolean isAccessible = PsiUtil.isAccessible(refExpr, def);
results.add(new FanResolveResultImpl(def, isAccessible));
}
}
}
private void resolveTypes(final List<FanResolveResult> results, final ResolveHint hint) {
final FanIndex index = hint.refExpr.getProject().getComponent(FanIndex.class);
// try to match pod names
if (!hint.isFqn()) {
final Set<FanTypeDefinition> podDefinitions = index.getPodStartingWith(hint.toMatch);
for (final FanTypeDefinition def : podDefinitions) {
if (!hint.gotoDecleration || (hint.gotoDecleration && def.getName().equals(hint.refExpr.getText()))) {
results.add(new FanResolveResultImpl(def, true));
}
}
}
// Try to match type names
final Collection<FanTypeDefinition> typesStartingWith =
StubIndex.getInstance().get(FanShortClassNameIndex.KEY, hint.toMatch, hint.refExpr.getProject(), null);
if (hint.isFqn()) {
final String podName = ((PodReferenceExpression) hint.idElement).getPodName();
typesStartingWith.addAll(index.getPodTypesStartingWith(podName, hint.toMatch));
} else {
typesStartingWith.addAll(index.getAllTypesStartingWith(hint.toMatch));
}
for (final FanTypeDefinition def : typesStartingWith) {
if (!hint.gotoDecleration || (hint.gotoDecleration && def.getName().equals(hint.refExpr.getText()))) {
final boolean isAccessible = PsiUtil.isAccessible(hint.refExpr, def);
results.add(new FanResolveResultImpl(def, isAccessible));
}
}
}
}
private static class ResolveHint {
final boolean gotoDecleration;
final boolean isMethodRef;
final boolean fieldOrMethodeRef;
final boolean isIndex;
boolean isStatic;
final String toMatch;
final PsiElement idElement;
final FanReferenceExpressionImpl refExpr;
ResolveHint(final FanReferenceExpressionImpl refExpr) {
final int idx = refExpr.getText().indexOf("IntellijIdeaRulezzz");
// If we are not trying to complete but just return the PsiElement which is referenced (Go to declaration)
gotoDecleration = idx == -1;
String toMatch = idx >= 0 ? refExpr.getText().substring(0, idx) : refExpr.getText();
isMethodRef = toMatch.contains("(");
if (isMethodRef) {
toMatch = toMatch.substring(0, toMatch.indexOf("("));
}
final PsiElement parent = refExpr.getParent();
PsiElement isTheIdElem = refExpr;
int index = -1;
int idxExpIndex = -1;
for (final PsiElement child : parent.getChildren()) {
index++;
if (child == refExpr){
break;
}
if (FanUtil.isOfType(child, FanIndexExpression.class)) {
idxExpIndex = index;
continue;
}
isTheIdElem = child;
}
idElement = isTheIdElem instanceof FanReferenceExpression ||
isTheIdElem instanceof FanTypeReferenceExpression ||
isTheIdElem instanceof PodReferenceExpression ? isTheIdElem : refExpr;
fieldOrMethodeRef = !refExpr.equals(idElement) && !FanUtil.isOfType(idElement, PodReferenceExpression.class); // obj.fieldOrMethodeRef
isIndex = index == idxExpIndex + 1;
this.toMatch = toMatch;
this.refExpr = refExpr;
this.isStatic = false;
}
boolean isThisRefrenceExpression() {
return idElement instanceof FanThisReferenceExpression;
}
boolean isSuperRefrenceExpression() {
return idElement instanceof FanSuperReferenceExpression;
}
boolean gotoNonFieldOrMethodDecleration() {
return gotoDecleration && !fieldOrMethodeRef;
}
boolean completeIdentifier() {
return !gotoDecleration && !fieldOrMethodeRef;
}
// variable.fieldOrMethod
boolean someTypeFieldOrMethodRef() {
return idElement instanceof FanReferenceExpression && fieldOrMethodeRef;
}
boolean containingTypeFieldOrMethodRef() {
return idElement instanceof FanReferenceExpression && !fieldOrMethodeRef;
}
boolean isFqn() {
return FanUtil.isOfType(idElement, PodReferenceExpression.class);
}
}
}