/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2014-2014 AS3Boyan
* Copyright 2014-2014 Elias Ku
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.plugins.haxe.lang.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.plugins.haxe.ide.HaxeLookupElement;
import com.intellij.plugins.haxe.ide.refactoring.move.HaxeFileMoveHandler;
import com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypes;
import com.intellij.plugins.haxe.lang.psi.*;
import com.intellij.plugins.haxe.util.*;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.impl.source.tree.JavaSourceUtil;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
abstract public class HaxeReferenceImpl extends HaxeExpressionImpl implements HaxeReference {
public static final HaxeDebugLogger LOG = HaxeDebugLogger.getLogger();
public HaxeReferenceImpl(ASTNode node) {
super(node);
}
@Override
public PsiElement getElement() {
return this;
}
@Override
public PsiReference getReference() {
return this;
}
@Override
public TextRange getRangeInElement() {
final TextRange textRange = getTextRange();
return new TextRange(0, textRange.getEndOffset() - textRange.getStartOffset());
}
@NotNull
@Override
public String getCanonicalText() {
return getText();
}
@Nullable
public HaxeGenericSpecialization getSpecialization() {
// CallExpressions need to resolve their child, rather than themselves.
HaxeExpression expression = this;
if (this instanceof HaxeCallExpression) {
expression = ((HaxeCallExpression)this).getExpression();
}
// The specialization for a reference comes from either the type of the left-hand side of the
// expression, or failing that, from the class in which the reference appears, which is
// exactly what tryGetLeftResolveResult() gives us.
final HaxeClassResolveResult result = tryGetLeftResolveResult(expression);
return result != null ? result.getSpecialization() : null;
}
@Override
public boolean isSoft() {
return false;
}
private List<? extends PsiElement> resolveNamesToParents(List<? extends PsiElement> nameList) {
if (nameList == null) {
return Collections.emptyList();
}
List<PsiElement> result = new ArrayList<PsiElement>();
for (PsiElement element : nameList) {
PsiElement elementToAdd = element;
if (element instanceof HaxeComponentName) {
PsiElement parent = element.getParent();
if (null != parent && parent.isValid()) {
// Don't look for package parents. It turns 'com' into 'com.xx'.
// XXX: May need to walk the tree until we get to the PACKAGE_STATEMENT
// element;
if (!(parent instanceof PsiPackage)) {
elementToAdd = parent;
}
}
}
result.add(elementToAdd);
}
return result;
}
@Override
public PsiElement resolve() {
return resolve(true);
}
public boolean resolveIsStaticExtension() {
// @TODO: DIRTY HACK! to avoid rewriting all the code!
HaxeResolver.INSTANCE.resolve(this, true);
return HaxeResolver.isExtension.get();
}
@NotNull
@Override
public JavaResolveResult advancedResolve(boolean incompleteCode) {
final PsiElement resolved = resolve(incompleteCode);
// TODO: Determine if we are using the right substitutor.
// ?? XXX: Is the internal element here supposed to be a PsiClass sub-class ??
return null != resolved ? new CandidateInfo(resolved, EmptySubstitutor.getInstance()) : JavaResolveResult.EMPTY;
}
@NotNull
private JavaResolveResult[] multiResolve(boolean incompleteCode, boolean resolveToParents) {
//
// Resolving through this.resolve, or through the ResolveCache.resolve,
// resolves to the *name* of the component. That's what is cached, that's
// what is returned. For the Java processing code, the various reference types
// are sub-classed, along with the base reference being aware of the type of the
// entity. Still, the base reference (PsiJavaReference) resolves to the
// COMPONENT_NAME element, NOT the element type. The various sub-classes
// of PsiJavaReference (and PsiJavaCodeReferenceElement) return the actual
// element type. For example, you have to have to use PsiClassType.resolve
// to get back a PsiClassType.
//
// For the Haxe code, we don't have a large number of reference sub-classes,
// so we have to figure out what the expected parent type is and return that.
// Luckily, most references have a COMPONENT_NAME element located immediately
// below the parent in the PSI tree. Therefore, when requested, we're going
// to return the parent type.
//
// The root of the problem appears to be that the Java language processing
// always expected the COMPONENT_NAME field. However, the Haxe processing
// (plugin) code was written to expect the type *containing* the
// COMPONENT_NAME element (e.g. the named element not the name of the element).
// Therefore, we now have an adapter, and have to tweak some things to make them
// compatible. Perhaps the proper answer is to make all of the plug-in code
// expect the COMPONENT_NAME field, to be consistent, and then we won't need
// the resolveToParents logic (here, at least).
//
// For the moment (while debugging the resolver) let's do this without caching.
boolean skipCaching = false;
List<? extends PsiElement> cachedNames
= skipCaching ? (HaxeResolver.INSTANCE).resolve(this, incompleteCode)
: ResolveCache.getInstance(getProject()).resolveWithCaching(this, HaxeResolver.INSTANCE, true, incompleteCode);
// CandidateInfo does some extra resolution work when checking validity, so
// the results have to be turned into a CandidateInfoArray, and not just passed
// around as the list that HaxeResolver returns.
JavaResolveResult [] result = toCandidateInfoArray(resolveToParents ? resolveNamesToParents(cachedNames) : cachedNames);
return result;
}
/**
* Resolve a reference, returning the COMPONENT_NAME field of the found
* PsiElement.
*
* @return the component name of the found element, or null if not (or
* more than one) found.
*/
@Nullable
public PsiElement resolveToComponentName() {
final ResolveResult[] resolveResults = multiResolve(true, false);
return resolveResults.length == 0 ||
resolveResults.length > 1 ||
!resolveResults[0].isValidResult() ? null : resolveResults[0].getElement();
}
/**
* Resolve a reference, returning a list of possible candidates.
*
* @param incompleteCode Whether to treat the code as a fragment or not.
* Usually, code is considered incomplete.
*
* @return a (possibly empty) list of candidates that this reference matches.
*/
@NotNull
@Override
public JavaResolveResult[] multiResolve(boolean incompleteCode) {
return multiResolve(incompleteCode, true);
}
/**
* Resolve this reference to a PsiElement -- *NOT* it's name.
*
* @param incompleteCode Whether to treat the code as a fragment or not.
* Usually, code is considered incomplete.
*
* @return the element this reference refers to, or null if none (or more
* than one) is found.
*/
@Nullable
public PsiElement resolve(boolean incompleteCode) {
final ResolveResult[] resolveResults = multiResolve(incompleteCode);
return resolveResults.length == 0 ||
resolveResults.length > 1 ||
!resolveResults[0].isValidResult() ? null : resolveResults[0].getElement();
}
@NotNull
@Override
public HaxeClassResolveResult resolveHaxeClass() {
if (this instanceof HaxeThisExpression) {
HaxeClass clazz = PsiTreeUtil.getParentOfType(this, HaxeClass.class);
// this has different semantics on abstracts
if (clazz != null && clazz.getModel().isAbstract()) {
HaxeTypeOrAnonymous type = clazz.getModel().getAbstractUnderlyingType();
if (type != null) {
return HaxeClassResolveResult.create(HaxeResolveUtil.tryResolveClassByQName(type));
}
}
return HaxeClassResolveResult.create(clazz);
}
if (this instanceof HaxeSuperExpression) {
final HaxeClass haxeClass = PsiTreeUtil.getParentOfType(this, HaxeClass.class);
assert haxeClass != null;
if (haxeClass.getHaxeExtendsList().isEmpty()) {
return HaxeClassResolveResult.create(null);
}
final HaxeExpression superExpression = haxeClass.getHaxeExtendsList().get(0).getReferenceExpression();
final HaxeClassResolveResult superClassResolveResult = superExpression instanceof HaxeReference
? ((HaxeReference)superExpression).resolveHaxeClass()
: HaxeClassResolveResult.create(null);
superClassResolveResult.specializeByParameters(haxeClass.getHaxeExtendsList().get(0).getTypeParam());
return superClassResolveResult;
}
if (this instanceof HaxeStringLiteralExpression) {
return HaxeClassResolveResult.create(HaxeResolveUtil.findClassByQName("String", this));
}
if (this instanceof HaxeLiteralExpression) {
final LeafPsiElement child = (LeafPsiElement)getFirstChild();
final IElementType childTokenType = child == null ? null : child.getElementType();
return HaxeClassResolveResult.create(HaxeResolveUtil.findClassByQName(getLiteralClassName(childTokenType), this));
}
if (this instanceof HaxeArrayLiteral) {
HaxeArrayLiteral haxeArrayLiteral = (HaxeArrayLiteral)this;
HaxeExpressionList expressionList = haxeArrayLiteral.getExpressionList();
boolean isMap = false;
boolean isString = false;
boolean sameClass = false;
boolean implementOrExtendSameClass = false;
HaxeClass haxeClass = null;
List<HaxeType> commonTypeList = new ArrayList<HaxeType>();
List<HaxeExpression> haxeExpressionList = expressionList != null ? expressionList.getExpressionList() : new ArrayList<HaxeExpression>();
if (!haxeExpressionList.isEmpty()) {
isMap = true;
isString = true;
sameClass = true;
for (HaxeExpression expression : haxeExpressionList) {
if (!(expression instanceof HaxeFatArrowExpression)) {
isMap = false;
}
if (!(expression instanceof HaxeStringLiteralExpression)) {
isString = false;
}
if (sameClass || implementOrExtendSameClass) {
HaxeReferenceExpression haxeReference = null;
if (expression instanceof HaxeNewExpression || expression instanceof HaxeCallExpression) {
haxeReference = PsiTreeUtil.findChildOfType(expression, HaxeReferenceExpression.class);
}
if (expression instanceof HaxeReferenceExpression) {
haxeReference = (HaxeReferenceExpression)expression;
}
HaxeClass haxeClassResolveResultHaxeClass = null;
if (haxeReference != null) {
HaxeClassResolveResult haxeClassResolveResult = haxeReference.resolveHaxeClass();
haxeClassResolveResultHaxeClass = haxeClassResolveResult.getHaxeClass();
if (haxeClassResolveResultHaxeClass != null) {
if (haxeClass == null) {
haxeClass = haxeClassResolveResultHaxeClass;
commonTypeList.addAll(haxeClass.getHaxeImplementsList());
commonTypeList.addAll(haxeClass.getHaxeExtendsList());
}
}
}
if (haxeClass != null && !haxeClass.equals(haxeClassResolveResultHaxeClass)) {
List<HaxeType> haxeTypeList = new ArrayList<HaxeType>();
haxeTypeList.addAll(haxeClass.getHaxeImplementsList());
haxeTypeList.addAll(haxeClass.getHaxeExtendsList());
commonTypeList.retainAll(haxeTypeList);
if (!commonTypeList.isEmpty()) {
implementOrExtendSameClass = true;
}
else {
implementOrExtendSameClass = false;
}
}
if (haxeClass == null || !haxeClass.equals(haxeClassResolveResultHaxeClass)) {
sameClass = false;
}
}
}
if (isMap) {
return HaxeClassResolveResult.create(HaxeResolveUtil.findClassByQName("Map", this));
}
}
HaxeClassResolveResult resolveResult =
HaxeClassResolveResult.create(HaxeResolveUtil.findClassByQName(getLiteralClassName(getTokenType()), this));
HaxeClass resolveResultHaxeClass = resolveResult.getHaxeClass();
HaxeGenericSpecialization specialization = resolveResult.getSpecialization();
if (resolveResultHaxeClass != null && specialization.get(resolveResultHaxeClass, "T") == null) {
if (isString) {
specialization.put(resolveResultHaxeClass, "T", HaxeClassResolveResult.create(HaxeResolveUtil.findClassByQName("String", this)));
}
else if (sameClass) {
specialization.put(resolveResultHaxeClass, "T", HaxeClassResolveResult.create(HaxeResolveUtil.findClassByQName(haxeClass.getQualifiedName(), this)));
}
else if (implementOrExtendSameClass) {
HaxeReferenceExpression haxeReferenceExpression = commonTypeList.get(commonTypeList.size() - 1).getReferenceExpression();
if (haxeReferenceExpression != null) {
HaxeClassResolveResult resolveHaxeClass = haxeReferenceExpression.resolveHaxeClass();
if (resolveHaxeClass != null) {
HaxeClass resolveHaxeClassHaxeClass = resolveHaxeClass.getHaxeClass();
if (resolveHaxeClassHaxeClass != null) {
specialization.put(resolveResultHaxeClass, "T", HaxeClassResolveResult.create(HaxeResolveUtil.findClassByQName(
resolveHaxeClassHaxeClass.getQualifiedName(), this)));
}
}
}
}
}
return resolveResult;
}
if (this instanceof HaxeNewExpression) {
final HaxeClassResolveResult result = HaxeClassResolveResult.create(HaxeResolveUtil.tryResolveClassByQName(
((HaxeNewExpression)this).getType()));
result.specialize(this);
return result;
}
if (this instanceof HaxeCallExpression) {
final HaxeExpression expression = ((HaxeCallExpression)this).getExpression();
final HaxeClassResolveResult leftResult = tryGetLeftResolveResult(expression);
if (expression instanceof HaxeReference) {
final HaxeClassResolveResult result =
HaxeResolveUtil.getHaxeClassResolveResult(((HaxeReference)expression).resolve(), leftResult.getSpecialization());
result.specialize(this);
return result;
}
}
if (this instanceof HaxeArrayAccessExpression) {
// wrong generation. see HaxeCallExpression
final HaxeReference reference = PsiTreeUtil.getChildOfType(this, HaxeReference.class);
if (reference != null) {
final HaxeClassResolveResult resolveResult = reference.resolveHaxeClass();
final HaxeClass resolveResultHaxeClass = resolveResult.getHaxeClass();
if (resolveResultHaxeClass == null) {
return resolveResult;
}
// std Array
if ("Array".equals(resolveResultHaxeClass.getQualifiedName())) {
HaxeClassResolveResult arrayResolveResult = resolveResult.getSpecialization().get(resolveResultHaxeClass, "T");
if (arrayResolveResult != null) {
return arrayResolveResult;
}
}
// __get method
return HaxeResolveUtil.getHaxeClassResolveResult(resolveResultHaxeClass.findHaxeMethodByName("__get"),
resolveResult.getSpecialization());
}
}
PsiElement resolve = resolve();
if (resolve instanceof PsiPackage) {
// Packages don't ever resolve to classes. (And they don't have children!)
return HaxeClassResolveResult.EMPTY;
}
if (resolve != null) {
PsiElement parent = resolve.getParent();
if (parent != null) {
if (parent instanceof HaxeFunctionDeclarationWithAttributes || parent instanceof HaxeExternFunctionDeclaration) {
return HaxeClassResolveResult.create(HaxeResolveUtil.findClassByQName("Dynamic", this));
}
HaxeTypeTag typeTag = PsiTreeUtil.getChildOfType(parent, HaxeTypeTag.class);
if (typeTag != null) {
HaxeFunctionType functionType = PsiTreeUtil.getChildOfType(typeTag, HaxeFunctionType.class);
if (functionType != null) {
return HaxeClassResolveResult.create(HaxeResolveUtil.findClassByQName("Dynamic", this));
}
}
}
}
HaxeClassResolveResult result = HaxeResolveUtil.getHaxeClassResolveResult(resolve(), tryGetLeftResolveResult(this).getSpecialization());
if (result.getHaxeClass() == null) {
result = HaxeClassResolveResult.create(HaxeResolveUtil.findClassByQName(getText(), this));
}
return result;
}
@NotNull
private static HaxeClassResolveResult tryGetLeftResolveResult(HaxeExpression expression) {
final HaxeReference[] childReferences = PsiTreeUtil.getChildrenOfType(expression, HaxeReference.class);
final HaxeReference leftReference = childReferences != null ? childReferences[0] : null;
return leftReference != null
? leftReference.resolveHaxeClass()
: HaxeClassResolveResult.create(PsiTreeUtil.getParentOfType(expression, HaxeClass.class));
}
@Nullable
private static String getLiteralClassName(IElementType type) {
if (type == HaxeTokenTypes.STRING_LITERAL_EXPRESSION) {
return "String";
}
else if (type == HaxeTokenTypes.ARRAY_LITERAL) {
return "Array";
}
else if (type == HaxeTokenTypes.LITFLOAT) {
return "Float";
}
else if (type == HaxeTokenTypes.REG_EXP) {
return "EReg";
}
else if (type == HaxeTokenTypes.LITHEX || type == HaxeTokenTypes.LITINT || type == HaxeTokenTypes.LITOCT) {
return "Int";
}
return null;
}
private static ResolveResult[] resolveByClassAndSymbol(@Nullable HaxeClassResolveResult resolveResult, @NotNull String symbolName) {
return resolveResult == null ? ResolveResult.EMPTY_ARRAY : resolveByClassAndSymbol(resolveResult.getHaxeClass(), symbolName);
}
private static ResolveResult[] resolveByClassAndSymbol(@Nullable HaxeClass referenceClass, @NotNull String symbolName) {
final HaxeNamedComponent namedSubComponent =
HaxeResolveUtil.findNamedSubComponent(referenceClass, symbolName);
final HaxeComponentName componentName = namedSubComponent == null ? null : namedSubComponent.getComponentName();
return toCandidateInfoArray(componentName);
}
@NotNull
private static JavaResolveResult[] toCandidateInfoArray(@Nullable PsiElement element) {
if (element == null) {
return JavaResolveResult.EMPTY_ARRAY;
}
return new JavaResolveResult[]{new CandidateInfo(element, null)};
}
@NotNull
private static JavaResolveResult[] toCandidateInfoArray(List<? extends PsiElement> elements) {
final JavaResolveResult[] result = new JavaResolveResult[elements.size()];
for (int i = 0, size = elements.size(); i < size; i++) {
result[i] = new CandidateInfo(elements.get(i), EmptySubstitutor.getInstance());
}
return result;
}
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
PsiElement element = this;
if (getText().indexOf('.') != -1) {
// qName
final PsiElement lastChild = getLastChild();
element = lastChild == null ? this : lastChild;
}
final HaxeIdentifier identifier = PsiTreeUtil.getChildOfType(element, HaxeIdentifier.class);
final HaxeIdentifier identifierNew = HaxeElementGenerator.createIdentifierFromText(getProject(), newElementName);
if (identifier != null && identifierNew != null) {
element.getNode().replaceChild(identifier.getNode(), identifierNew.getNode());
}
return this;
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
if (element instanceof HaxeFile) {
bindToFile(element);
}
else if (element instanceof PsiPackage) {
bindToPackage((PsiPackage)element);
}
else if (element instanceof PsiClass) {
bindToClass((PsiClass)element);
}
return this;
}
private void bindToClass(PsiClass element) {
handleElementRename(element.getName());
}
private void bindToPackage(PsiPackage element) {
final HaxeImportStatementRegular importStatement =
HaxeElementGenerator.createImportStatementFromPath(getProject(), element.getQualifiedName());
HaxeReferenceExpression referenceExpression = importStatement != null ? importStatement.getReferenceExpression() : null;
assert referenceExpression != null;
replace(referenceExpression);
}
private void bindToFile(PsiElement element) {
String destinationPackage = element.getUserData(HaxeFileMoveHandler.destinationPackageKey);
if (destinationPackage == null) {
destinationPackage = "";
}
final String importPath = (destinationPackage.isEmpty() ? "" : destinationPackage + ".") +
FileUtil.getNameWithoutExtension(((HaxeFile)element).getName());
if (resolve() == null) {
if (getParent() instanceof HaxeImportStatementRegular && !destinationPackage.isEmpty()) {
final HaxeImportStatementRegular importStatement =
HaxeElementGenerator.createImportStatementFromPath(getProject(), importPath);
assert importStatement != null;
getParent().replace(importStatement);
}
else if (getParent() instanceof HaxeImportStatementRegular && destinationPackage.isEmpty()) {
// need remove, empty destination
getParent().getParent().deleteChildRange(getParent(), getParent());
}
else if (getText().indexOf('.') != -1) {
// qName
final String newQName = destinationPackage.equals(HaxeResolveUtil.getPackageName(getContainingFile())) ?
FileUtil.getNameWithoutExtension(((HaxeFile)element).getName()) :
importPath;
final HaxeImportStatementRegular importStatement =
HaxeElementGenerator.createImportStatementFromPath(getProject(), newQName);
HaxeReferenceExpression referenceExpression = importStatement != null ? importStatement.getReferenceExpression() : null;
assert referenceExpression != null;
replace(referenceExpression);
}
else if (UsefulPsiTreeUtil.findImportByClassName(this, getText()) == null && UsefulPsiTreeUtil.findImportWithInByClassName(this, getText()) == null && !destinationPackage.isEmpty()) {
// need add import
HaxeAddImportHelper.addImport(importPath, getContainingFile());
}
}
else {
final HaxeImportStatementRegular importStatement = UsefulPsiTreeUtil.findImportByClassName(this, getText());
HaxeReferenceExpression referenceExpression = importStatement != null ? importStatement.getReferenceExpression() : null;
if (referenceExpression != null && !importPath.equals(referenceExpression.getText())) {
// need remove, cause can resolve without
importStatement.getParent().deleteChildRange(importStatement, importStatement);
}
}
}
@Override
public boolean isReferenceTo(PsiElement element) {
// Resolving is (relatively) expensive, so if we're going to ignore the answer anyway, then don't bother.
if (!(element instanceof HaxeFile)) {
final HaxeReference[] references = PsiTreeUtil.getChildrenOfType(this, HaxeReference.class);
final boolean chain = references != null && references.length == 2;
if (chain) return false;
}
final PsiElement resolve = element instanceof HaxeComponentName ? resolveToComponentName() : resolve();
if (element instanceof HaxeFile && resolve instanceof HaxeClass) {
return element == resolve.getContainingFile();
}
return resolve == element;
}
@NotNull
@Override
public Object[] getVariants() {
final Set<HaxeComponentName> suggestedVariants = new THashSet<HaxeComponentName>();
final Set<HaxeComponentName> suggestedVariantsExtensions = new THashSet<HaxeComponentName>();
// if not first in chain
// foo.bar.baz
final HaxeReference leftReference = HaxeResolveUtil.getLeftReference(this);
// TODO: This should use getName() instead of getQualifiedName(), but it isn't implemented properly and getName() NPEs.
HaxeClassResolveResult result = null;
HaxeClass haxeClass = null;
String name = null;
if (leftReference != null) {
result = leftReference.resolveHaxeClass();
if (result != null) {
haxeClass = result.getHaxeClass();
if (haxeClass != null) {
name = haxeClass.getName();
}
}
}
boolean isThis = leftReference instanceof HaxeThisExpression;
if (leftReference != null && getParent() instanceof HaxeReference && name != null &&
HaxeResolveUtil.splitQName(leftReference.getText()).getSecond().equals(name)) {
addClassStaticMembersVariants(suggestedVariants, result.getHaxeClass(),
!(isThis));
addChildClassVariants(suggestedVariants, result.getHaxeClass());
}
else if (leftReference != null && getParent() instanceof HaxeReference && !result.isFunctionType()) {
if (null == haxeClass) {
// TODO: fix haxeClass by type inference. Use compiler code assist?!
}
if (haxeClass != null) {
boolean isSuper = leftReference instanceof HaxeSuperExpression;
addClassNonStaticMembersVariants(suggestedVariants, haxeClass,
!(isThis || isSuper));
addUsingVariants(suggestedVariants, suggestedVariantsExtensions, haxeClass,
HaxeResolveUtil.findUsingClasses(getContainingFile()));
}
}
else {
// if chain
// node(foo.node(bar)).node(baz)
final HaxeReference[] childReferences = PsiTreeUtil.getChildrenOfType(this, HaxeReference.class);
final boolean isChain = childReferences != null && childReferences.length == 2;
if (!isChain) {
final boolean isElementInForwardMeta = HaxeAbstractForwardUtil.isElementInForwardMeta(this);
if (isElementInForwardMeta) {
addAbstractUnderlyingClassVariants(suggestedVariants, PsiTreeUtil.getParentOfType(this, HaxeClass.class), true);
} else {
PsiTreeUtil.treeWalkUp(new ComponentNameScopeProcessor(suggestedVariants), this, null, new ResolveState());
addClassVariants(suggestedVariants, PsiTreeUtil.getParentOfType(this, HaxeClass.class), false);
PsiFile psiFile = this.getContainingFile();
addImportStatementWithWildcardTypeClassVariants(suggestedVariants, psiFile);
}
}
}
Object[] variants = HaxeLookupElement.convert(result, suggestedVariants, suggestedVariantsExtensions).toArray();
PsiElement leftTarget = leftReference != null ? leftReference.resolve() : null;
if (leftTarget instanceof PsiPackage) {
return ArrayUtil.mergeArrays(variants, ((PsiPackage)leftTarget).getSubPackages());
}
else if (leftTarget instanceof HaxeFile) {
return ArrayUtil.mergeArrays(variants, ((HaxeFile)leftTarget).getClasses());
}
else if (leftReference == null) {
PsiPackage rootPackage = JavaPsiFacade.getInstance(getElement().getProject()).findPackage("");
return rootPackage == null ? variants : ArrayUtil.mergeArrays(variants, rootPackage.getSubPackages());
}
return variants;
}
private void addImportStatementWithWildcardTypeClassVariants(Set<HaxeComponentName> suggestedVariants, PsiFile psiFile) {
List<PsiElement> importStatementWithWildcardList = ContainerUtil.findAll(psiFile.getChildren(), new Condition<PsiElement>() {
@Override
public boolean value(PsiElement element) {
return element instanceof HaxeImportStatementWithWildcard;
}
});
for (PsiElement element : importStatementWithWildcardList) {
List<HaxeNamedComponent> namedSubComponents =
UsefulPsiTreeUtil.getImportStatementWithWildcardTypeNamedSubComponents((HaxeImportStatementWithWildcard)element, psiFile);
for (HaxeNamedComponent namedComponent : namedSubComponents) {
suggestedVariants.add(namedComponent.getComponentName());
}
}
}
private void addChildClassVariants(Set<HaxeComponentName> variants, HaxeClass haxeClass) {
if (haxeClass != null) {
PsiFile psiFile = haxeClass.getContainingFile();
VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile != null) {
String nameWithoutExtension = virtualFile.getNameWithoutExtension();
String name = haxeClass.getName();
if (name != null && name.equals(nameWithoutExtension)) {
List<HaxeClass> haxeClassList = HaxeResolveUtil.findComponentDeclarations(psiFile);
for (HaxeClass aClass : haxeClassList) {
if (!aClass.getName().equals(nameWithoutExtension)) {
variants.add(aClass.getComponentName());
}
}
}
}
}
}
private static void addUsingVariants(Set<HaxeComponentName> variants, Set<HaxeComponentName> variantsWithExtension, @Nullable HaxeClass ourClass, List<HaxeClass> classes) {
for (HaxeClass haxeClass : classes) {
for (HaxeNamedComponent haxeNamedComponent : HaxeResolveUtil.findNamedSubComponents(haxeClass)) {
if (haxeNamedComponent.isPublic() && haxeNamedComponent.isStatic() && haxeNamedComponent.getComponentName() != null) {
final HaxeClassResolveResult resolveResult = HaxeResolveUtil.findFirstParameterClass(haxeNamedComponent);
final HaxeClass resolvedClass = resolveResult.getHaxeClass();
final HashSet<HaxeClass> baseClassesSet = ourClass != null ? HaxeResolveUtil.getBaseClassesSet(ourClass) : null;
final boolean needToAdd = resolvedClass == null || resolvedClass == ourClass
|| (baseClassesSet != null && baseClassesSet.contains(resolvedClass));
if (needToAdd) {
variants.add(haxeNamedComponent.getComponentName());
variantsWithExtension.add(haxeNamedComponent.getComponentName());
}
}
}
}
}
private static void addClassVariants(Set<HaxeComponentName> suggestedVariants, @Nullable HaxeClass haxeClass, boolean filterByAccess) {
if (haxeClass == null) {
return;
}
for (HaxeNamedComponent namedComponent : HaxeResolveUtil.findNamedSubComponents(haxeClass)) {
final boolean needFilter = filterByAccess && !namedComponent.isPublic();
if (!needFilter && namedComponent.getComponentName() != null) {
suggestedVariants.add(namedComponent.getComponentName());
}
}
}
private static void addAbstractUnderlyingClassVariants(Set<HaxeComponentName> suggestedVariants, @Nullable HaxeClass haxeClass, boolean filterByAccess) {
final HaxeClass underlyingClass = HaxeAbstractUtil.getAbstractUnderlyingClass(haxeClass);
if (underlyingClass == null) {
return;
}
addClassVariants(suggestedVariants, underlyingClass, filterByAccess);
}
private static void addClassStaticMembersVariants(Set<HaxeComponentName> suggestedVariants, @Nullable HaxeClass haxeClass, boolean filterByAccess) {
if (haxeClass == null) {
return;
}
boolean extern = haxeClass.isExtern();
boolean isEnum = haxeClass instanceof HaxeEnumDeclaration;
boolean isAbstractEnum = HaxeAbstractEnumUtil.isAbstractEnum(haxeClass);
for (HaxeNamedComponent namedComponent : HaxeResolveUtil.findNamedSubComponents(haxeClass)) {
final boolean needFilter = filterByAccess && !namedComponent.isPublic();
final HaxeComponentName componentName = namedComponent.getComponentName();
if(componentName != null) {
if(isAbstractEnum && HaxeAbstractEnumUtil.couldBeAbstractEnumField(namedComponent)) {
suggestedVariants.add(componentName);
}
else if ((extern || !needFilter) && (namedComponent.isStatic() || isEnum)) {
suggestedVariants.add(componentName);
}
}
}
}
private static void addClassNonStaticMembersVariants(Set<HaxeComponentName> suggestedVariants, @Nullable HaxeClass haxeClass, boolean filterByAccess) {
if (haxeClass == null) {
return;
}
boolean extern = haxeClass.isExtern();
boolean isAbstractEnum = HaxeAbstractEnumUtil.isAbstractEnum(haxeClass);
boolean isAbstractForward = HaxeAbstractForwardUtil.isAbstractForward(haxeClass);
if (isAbstractForward) {
final List<HaxeNamedComponent> forwardingHaxeNamedComponents = HaxeAbstractForwardUtil.findAbstractForwardingNamedSubComponents(haxeClass);
if (forwardingHaxeNamedComponents != null) {
for (HaxeNamedComponent namedComponent : forwardingHaxeNamedComponents) {
final boolean needFilter = filterByAccess && !namedComponent.isPublic();
if ((extern || !needFilter) && !namedComponent.isStatic() && namedComponent.getComponentName() != null) {
suggestedVariants.add(namedComponent.getComponentName());
}
}
}
}
for (HaxeNamedComponent namedComponent : HaxeResolveUtil.findNamedSubComponents(haxeClass)) {
final boolean needFilter = filterByAccess && !namedComponent.isPublic();
if(isAbstractEnum && HaxeAbstractEnumUtil.couldBeAbstractEnumField(namedComponent)) {
continue;
}
if ((extern || !needFilter) && !namedComponent.isStatic() && namedComponent.getComponentName() != null) {
suggestedVariants.add(namedComponent.getComponentName());
}
}
}
@Nullable
@Override
public PsiElement getReferenceNameElement() {
PsiElement child = findChildByType(HaxeTokenTypes.REFERENCE_EXPRESSION); // REFERENCE_NAME in Java
return child;
}
@Nullable
@Override
public PsiReferenceParameterList getParameterList() {
// TODO: Unimplemented.
LOG.warn("getParameterList is unimplemented");
// REFERENCE_PARAMETER_LIST in Java
HaxeTypeParam child = (HaxeTypeParam) findChildByType(HaxeTokenTypes.TYPE_PARAM);
//return child == null ? null : child.getTypeList();
return null;
}
@NotNull
@Override
public PsiType[] getTypeParameters() {
// TODO: Unimplemented.
LOG.warn("getTypeParameters is unimplemented");
return new PsiType[0];
}
@Override
public boolean isQualified() {
return null != getQualifier();
}
@Override
public String getQualifiedName() {
return JavaSourceUtil.getReferenceText(this);
}
// PsiJavaReference overrides
@Override
public void processVariants(@NotNull PsiScopeProcessor processor) {
// TODO: Unimplemented.
LOG.warn("processVariants is unimplemented");
}
// PsiQualifiedReference overrides
@Nullable
@Override
public PsiElement getQualifier() {
// Package/class that this type is part of; the part before
// the last '.'. However, that may only be partial, so adding
// package information may also be necessary.
PsiElement left = UsefulPsiTreeUtil.getChildOfType(this, HaxeTokenTypes.REFERENCE_EXPRESSION);
boolean hasDot = nextSiblingIsADot(left);
return hasDot ? left : null;
}
/* Determine if the element to the right of the given element in the AST
* (at the same level) is a dot '.' separator.
* Workhorse for getQualifier().
* XXX: If we use this more than once, move it to a utility class, such as UsefulPsiTreeUtil.
*/
private static boolean nextSiblingIsADot(PsiElement element) {
if (null == element) return false;
PsiElement next = element.getNextSibling();
ASTNode node = ((null != next) ? next.getNode() : null);
IElementType type = ((null != node) ? node.getElementType() : null);
boolean ret = (null != type && type.equals(HaxeTokenTypes.ODOT));
return ret;
}
@Nullable
@Override
public String getReferenceName() {
// Unqualified name; the base name without any preceding
// package/class name.
// TODO: Figure out if this needs to split out any prefix.
return getText();
}
// PsiExpression implementations
@Nullable
public PsiType getPsiType() {
// XXX: EMB: Not sure about this. Does a reference really have a sub-node giving the type?
HaxeType ht = findChildByClass(HaxeType.class);
return ((null == ht) ? null : ht.getPsiType());
}
// PsiExpression implementations
//@Nullable
//public PsiExpression getQualifierExpression() {
// final PsiElement qualifier = getQualifier();
// return qualifier instanceof PsiExpression ? (PsiExpression)qualifier : null;
//}
@Override
public String toString() {
String ss = super.toString();
if (!ApplicationManager.getApplication().isUnitTestMode()) {
// Unit tests don't want the extra data. (Maybe we should fix the goldens?)
String clazzName = this.getClass().getSimpleName();
String text = getCanonicalText();
ss += ":" + (null == text ? "<no text>" : text);
ss += ":" + (null == clazzName ? "<anonymous>" : clazzName);
}
return ss;
}
}