All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.plugins.groovy.lang.psi.impl.types.GrCodeReferenceElementImpl Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition groovy-psi library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * 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 org.jetbrains.plugins.groovy.lang.psi.impl.types;

import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocReferenceElement;
import org.jetbrains.plugins.groovy.lang.lexer.TokenSets;
import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.packaging.GrPackageDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrReferenceElementImpl;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyResolveResultImpl;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassResolverProcessor;
import org.jetbrains.plugins.groovy.lang.resolve.processors.ResolverProcessor;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

/**
 * @author: Dmitry.Krasilschikov
 * @date: 26.03.2007
 */
public class GrCodeReferenceElementImpl extends GrReferenceElementImpl implements GrCodeReferenceElement {
  private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.lang.psi.impl.types.GrCodeReferenceElementImpl");

  public GrCodeReferenceElementImpl(@NotNull ASTNode node) {
    super(node);
  }

  @Override
  public PsiElement handleElementRenameSimple(String newElementName) throws IncorrectOperationException {
    if (StringUtil.isJavaIdentifier(newElementName)) {
      return super.handleElementRenameSimple(newElementName);
    }
    else {
      throw new IncorrectOperationException("Cannot rename reference to '" + newElementName + "'");
    }
  }

  @Override
  protected GrCodeReferenceElement bindWithQualifiedRef(@NotNull String qName) {
    final GrCodeReferenceElement qualifiedRef = GroovyPsiElementFactory.getInstance(getProject()).createTypeOrPackageReference(qName);
    final PsiElement list = getTypeArgumentList();
    if (list != null) {
      qualifiedRef.getNode().addChild(list.copy().getNode());
    }
    getNode().getTreeParent().replaceChild(getNode(), qualifiedRef.getNode());
    return qualifiedRef;
  }

  @Override
  public void accept(GroovyElementVisitor visitor) {
    visitor.visitCodeReferenceElement(this);
  }

  public String toString() {
    return "Reference element";
  }

  @Override
  public GrCodeReferenceElement getQualifier() {
    return (GrCodeReferenceElement)findChildByType(GroovyElementTypes.REFERENCE_ELEMENT);
  }

  @Override
  public PsiElement getReferenceNameElement() {
    return findChildByType(TokenSets.CODE_REFERENCE_ELEMENT_NAME_TOKENS);
  }

  public enum ReferenceKind {
    CLASS,
    CLASS_OR_PACKAGE,
    PACKAGE_FQ,
    CLASS_FQ,
    CLASS_OR_PACKAGE_FQ,
    STATIC_MEMBER_FQ,
    CLASS_IN_QUALIFIED_NEW
  }

  @Override
  @Nullable
  public PsiElement resolve() {
    ResolveResult[] results = TypeInferenceHelper.getCurrentContext().multiResolve(this, false, RESOLVER);
    return results.length == 1 ? results[0].getElement() : null;
  }

  public ReferenceKind getKind(boolean forCompletion) {
    if (isClassReferenceForNew()) {
      return ReferenceKind.CLASS_OR_PACKAGE;
    }

    PsiElement parent = getParent();
    if (parent instanceof GrCodeReferenceElementImpl) {
      ReferenceKind parentKind = ((GrCodeReferenceElementImpl)parent).getKind(forCompletion);
      if (parentKind == ReferenceKind.CLASS) {
        return ReferenceKind.CLASS_OR_PACKAGE;
      }
      else if (parentKind == ReferenceKind.STATIC_MEMBER_FQ) {
        return isQualified() ? ReferenceKind.CLASS_FQ : ReferenceKind.CLASS;
      }
      else if (parentKind == ReferenceKind.CLASS_FQ) return ReferenceKind.CLASS_OR_PACKAGE_FQ;
      return parentKind;
    }
    else if (parent instanceof GrPackageDefinition) {
      return ReferenceKind.PACKAGE_FQ;
    }
    else if (parent instanceof GrDocReferenceElement) {
      return ReferenceKind.CLASS_OR_PACKAGE;
    }
    else if (parent instanceof GrImportStatement) {
      final GrImportStatement importStatement = (GrImportStatement)parent;
      if (importStatement.isStatic()) {
        return importStatement.isOnDemand() ? ReferenceKind.CLASS : ReferenceKind.STATIC_MEMBER_FQ;
      }
      else {
        return forCompletion || importStatement.isOnDemand() ? ReferenceKind.CLASS_OR_PACKAGE_FQ : ReferenceKind.CLASS_FQ;
      }
    }
    else if (parent instanceof GrNewExpression || parent instanceof GrAnonymousClassDefinition) {
      PsiElement newExpr = parent instanceof GrAnonymousClassDefinition ? parent.getParent() : parent;
      assert newExpr instanceof GrNewExpression;
      if (((GrNewExpression)newExpr).getQualifier() != null) return ReferenceKind.CLASS_IN_QUALIFIED_NEW;
    }

    return ReferenceKind.CLASS;
  }

  @Override
  @NotNull
  public String getCanonicalText() {
    final ReferenceKind kind = getKind(false);
    switch (kind) {
      case CLASS:
      case CLASS_IN_QUALIFIED_NEW:
      case CLASS_OR_PACKAGE:
        final PsiElement target = resolve();
        if (target instanceof PsiClass) {
          final PsiClass aClass = (PsiClass)target;
          String name = aClass.getQualifiedName();
          if (name == null) { //parameter types don't have qualified name
            name = aClass.getName();
          }
          final PsiType[] types = getTypeArguments();
          if (types.length == 0) return name;

          final StringBuilder buf = new StringBuilder();
          buf.append(name);
          buf.append('<');
          for (int i = 0; i < types.length; i++) {
            if (i > 0) buf.append(',');
            buf.append(types[i].getCanonicalText());
          }
          buf.append('>');

          return buf.toString();
        }
        else if (target instanceof PsiPackage) {
          return ((PsiPackage)target).getQualifiedName();
        }
        else {
          LOG.assertTrue(target == null);
          return getTextSkipWhiteSpaceAndComments();
        }

      case CLASS_FQ:
      case CLASS_OR_PACKAGE_FQ:
      case PACKAGE_FQ:
      case STATIC_MEMBER_FQ:
        return getTextSkipWhiteSpaceAndComments();
      default:
        LOG.assertTrue(false);
        return null;
    }
  }

  @Override
  protected boolean bindsCorrectly(PsiElement element) {
    if (super.bindsCorrectly(element)) return true;
    if (element instanceof PsiClass) {
      final PsiElement resolved = resolve();
      if (resolved instanceof PsiMethod) {
        final PsiMethod method = (PsiMethod)resolved;
        if (method.isConstructor() && getManager().areElementsEquivalent(element, method.getContainingClass())) {
          return true;
        }
      }
    }

    return false;
  }

  @Override
  public boolean isFullyQualified() {
    switch (getKind(false)) {
      case PACKAGE_FQ:
      case CLASS_FQ:
      case CLASS_OR_PACKAGE_FQ:
      case STATIC_MEMBER_FQ:
      case CLASS_OR_PACKAGE:
        if (resolve() instanceof PsiPackage) return true;
      case CLASS:
      case CLASS_IN_QUALIFIED_NEW:
    }
    final GrCodeReferenceElement qualifier = getQualifier();
    return qualifier != null && ((GrCodeReferenceElementImpl)qualifier).isFullyQualified();
  }

  @Override
  public boolean isReferenceTo(PsiElement element) {
    final PsiManager manager = getManager();
    if (element instanceof PsiNamedElement && getParent() instanceof GrImportStatement) {
      final GroovyResolveResult[] results = multiResolve(false);
      for (GroovyResolveResult result : results) {
        if (manager.areElementsEquivalent(result.getElement(), element)) return true;
      }
    }
    return manager.areElementsEquivalent(element, resolve());
  }

  @Override
  @NotNull
  public Object[] getVariants() {
    return ArrayUtil.EMPTY_OBJECT_ARRAY;
  }

  private boolean isClassReferenceForNew() {
    PsiElement parent = getParent();
    while (parent instanceof GrCodeReferenceElement) parent = parent.getParent();
    return parent instanceof GrNewExpression;
  }

  @Override
  public boolean isSoft() {
    return false;
  }

  private static class OurResolver implements ResolveCache.PolyVariantResolver {

    @Override
    @NotNull
    public GroovyResolveResult[] resolve(@NotNull GrCodeReferenceElementImpl reference, boolean incompleteCode) {
      if (reference.getReferenceName() == null) return GroovyResolveResult.EMPTY_ARRAY;
      final GroovyResolveResult[] results = _resolve(reference, reference.getManager(), reference.getKind(false));
      if (results.length == 0) return results;

      List imported = new ArrayList();
      final PsiType[] args = reference.getTypeArguments();
      for (int i = 0; i < results.length; i++) {
        GroovyResolveResult result = results[i];
        final PsiElement element = result.getElement();
        if (element instanceof PsiClass) {
          final PsiSubstitutor substitutor = result.getSubstitutor();
          final PsiSubstitutor newSubstitutor = substitutor.putAll((PsiClass)element, args);
          PsiElement context = result.getCurrentFileResolveContext();
          GroovyResolveResultImpl newResult =
            new GroovyResolveResultImpl(element, context, null, newSubstitutor, result.isAccessible(), result.isStaticsOK());
          results[i] = newResult;
          if (context instanceof GrImportStatement) {
            imported.add(newResult);
          }
        }
      }
      if (!imported.isEmpty()) {
        return imported.toArray(new GroovyResolveResult[imported.size()]);
      }

      return results;
    }

    @NotNull
    private static GroovyResolveResult[] _resolve(@NotNull GrCodeReferenceElementImpl ref,
                                                  @NotNull PsiManager manager,
                                                  @NotNull ReferenceKind kind) {
      final String refName = ref.getReferenceName();
      if (refName == null) {
        return GroovyResolveResult.EMPTY_ARRAY;
      }

      switch (kind) {
        case CLASS_OR_PACKAGE_FQ:
        case CLASS_FQ:
        case PACKAGE_FQ:
          String qName = PsiUtil.getQualifiedReferenceText(ref);
          LOG.assertTrue(qName != null, ref.getText());

          JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
          if (kind == ReferenceKind.CLASS_OR_PACKAGE_FQ || kind == ReferenceKind.CLASS_FQ) {
            final PsiFile file = ref.getContainingFile();
            if (qName.indexOf('.') > 0 || file instanceof GroovyFile && ((GroovyFile)file).getPackageName().isEmpty()) {
              PsiClass aClass = facade.findClass(qName, ref.getResolveScope());
              if (aClass != null) {
                boolean isAccessible = PsiUtil.isAccessible(ref, aClass);
                return new GroovyResolveResult[]{new GroovyResolveResultImpl(aClass, isAccessible)};
              }
            }
          }

          if (kind == ReferenceKind.CLASS_OR_PACKAGE_FQ || kind == ReferenceKind.PACKAGE_FQ) {
            PsiPackage aPackage = facade.findPackage(qName);
            if (aPackage != null) {
              return new GroovyResolveResult[]{new GroovyResolveResultImpl(aPackage, true)};
            }
          }

          break;

        case CLASS: {
          EnumSet kinds = kind == ReferenceKind.CLASS
                                                 ? ClassHint.RESOLVE_KINDS_CLASS : ClassHint.RESOLVE_KINDS_CLASS_PACKAGE;
          ResolverProcessor processor = new ClassResolverProcessor(refName, ref, kinds);
          GrCodeReferenceElement qualifier = ref.getQualifier();
          if (qualifier != null) {
            PsiElement qualifierResolved = qualifier.resolve();
            if (qualifierResolved instanceof PsiPackage || qualifierResolved instanceof PsiClass) {
              qualifierResolved.processDeclarations(processor, ResolveState.initial(), null, ref);
              return processor.getCandidates();
            }
          }
          else {
            // if ref is an annotation name reference we should not process declarations of annotated elements
            // because inner annotations are not permitted and it can cause infinite recursion
            PsiElement placeToStartWalking = isAnnotationRef(ref) ? getContainingFileSkippingStubFiles(ref) : ref;
            if (placeToStartWalking != null) {
              ResolveUtil.treeWalkUp(placeToStartWalking, processor, false);
              GroovyResolveResult[] candidates = processor.getCandidates();
              if (candidates.length > 0) return candidates;
            }

            if (kind == ReferenceKind.CLASS_OR_PACKAGE) {
              PsiPackage pkg = JavaPsiFacade.getInstance(ref.getProject()).findPackage(refName);
              if (pkg != null) {
                return new GroovyResolveResult[]{new GroovyResolveResultImpl(pkg, true)};
              }
            }
          }

          break;
        }

        case CLASS_OR_PACKAGE: {
          GroovyResolveResult[] classResult = _resolve(ref, manager, ReferenceKind.CLASS);

          if (classResult.length == 1 && !classResult[0].isAccessible()) {
            GroovyResolveResult[] packageResult = _resolve(ref, manager, ReferenceKind.PACKAGE_FQ);
            if (packageResult.length != 0) {
              return packageResult;
            }
          }
          else if (classResult.length == 0) {
            return _resolve(ref, manager, ReferenceKind.PACKAGE_FQ);
          }
          
          return classResult;
        }
        case STATIC_MEMBER_FQ: {
          final GrCodeReferenceElement qualifier = ref.getQualifier();
          if (qualifier != null) {
            final PsiElement resolved = qualifier.resolve();
            if (resolved instanceof PsiClass) {
              final PsiClass clazz = (PsiClass)resolved;
              PsiResolveHelper helper = JavaPsiFacade.getInstance(clazz.getProject()).getResolveHelper();
              List result = new ArrayList();

              processFields(ref, refName, clazz, helper, result);
              processMethods(ref, refName, clazz, helper, result);
              processInnerClasses(ref, refName, clazz, helper, result);

              if (result.isEmpty()) {
                processAccessors(ref, refName, clazz, helper, result);
              }

              return result.toArray(new GroovyResolveResult[result.size()]);
            }
          }
          break;
        }
        case CLASS_IN_QUALIFIED_NEW: {
          if (ref.getParent() instanceof GrCodeReferenceElement) return GroovyResolveResult.EMPTY_ARRAY;
          final GrNewExpression newExpression = PsiTreeUtil.getParentOfType(ref, GrNewExpression.class);
          assert newExpression != null;
          final GrExpression qualifier = newExpression.getQualifier();
          assert qualifier != null;

          final PsiType type = qualifier.getType();
          if (!(type instanceof PsiClassType)) break;

          final PsiClassType classType = (PsiClassType)type;
          final PsiClass psiClass = classType.resolve();
          if (psiClass == null) break;

          final PsiClass[] allInnerClasses = psiClass.getAllInnerClasses();
          ArrayList result = new ArrayList();
          PsiResolveHelper helper = JavaPsiFacade.getInstance(ref.getProject()).getResolveHelper();

          for (final PsiClass innerClass : allInnerClasses) {
            if (refName.equals(innerClass.getName())) {
              result.add(new GroovyResolveResultImpl(innerClass, helper.isAccessible(innerClass, ref, null)));
            }
          }
          return result.toArray(new GroovyResolveResult[result.size()]);
        }
      }

      return GroovyResolveResult.EMPTY_ARRAY;
    }

    private static PsiFile getContainingFileSkippingStubFiles(GrCodeReferenceElementImpl ref) {
      PsiFile file = ref.getContainingFile();
      while (file != null && !file.isPhysical() && file.getContext() != null) {
        PsiElement context = file.getContext();
        file = context.getContainingFile();
      }
      return file;
    }

    private static boolean isAnnotationRef(GrCodeReferenceElement ref) {
      final PsiElement parent = ref.getParent();
      return parent instanceof GrAnnotation || parent instanceof GrCodeReferenceElement && isAnnotationRef((GrCodeReferenceElement)parent);
    }

    private static void processAccessors(GrCodeReferenceElementImpl ref,
                                         String refName,
                                         PsiClass clazz,
                                         PsiResolveHelper helper,
                                         List result) {
      final String booleanGetter = GroovyPropertyUtils.getGetterNameBoolean(refName);
      final String nonBooleanGetter = GroovyPropertyUtils.getGetterNameNonBoolean(refName);
      final String setter = GroovyPropertyUtils.getSetterName(refName);

      processMethods(ref, booleanGetter, clazz, helper, result);
      if (!result.isEmpty()) return;
      processMethods(ref, nonBooleanGetter, clazz, helper, result);
      if (!result.isEmpty()) return;
      processMethods(ref, setter, clazz, helper, result);
    }

    private static void processInnerClasses(GrCodeReferenceElementImpl ref,
                                            String refName,
                                            PsiClass clazz,
                                            PsiResolveHelper helper,
                                            List result) {
      final PsiClass innerClass = clazz.findInnerClassByName(refName, true);
      if (innerClass != null && innerClass.hasModifierProperty(PsiModifier.STATIC)) {
        result.add(new GroovyResolveResultImpl(innerClass, helper.isAccessible(innerClass, ref, null)));
      }
    }

    private static void processFields(GrCodeReferenceElementImpl ref,
                                      String refName,
                                      PsiClass clazz,
                                      PsiResolveHelper helper,
                                      List result) {
      final PsiField field = clazz.findFieldByName(refName, true);
      if (field != null && field.hasModifierProperty(PsiModifier.STATIC)) {
        result.add(new GroovyResolveResultImpl(field, helper.isAccessible(field, ref, null)));
      }
    }

    private static void processMethods(GrCodeReferenceElementImpl ref,
                                       String refName,
                                       PsiClass clazz,
                                       PsiResolveHelper helper,
                                       List result) {
      final PsiMethod[] methods = clazz.findMethodsByName(refName, true);
      for (PsiMethod method : methods) {
        result.add(new GroovyResolveResultImpl(method, helper.isAccessible(method, ref, null)));
      }
    }
  }

  private static final OurResolver RESOLVER = new OurResolver();

  @Override
  public GroovyResolveResult advancedResolve() {
    ResolveResult[] results = TypeInferenceHelper.getCurrentContext().multiResolve(this, false, RESOLVER);
    return results.length == 1 ? (GroovyResolveResult)results[0] : GroovyResolveResult.EMPTY_RESULT;
  }

  @Override
  @NotNull
  public GroovyResolveResult[] multiResolve(boolean incompleteCode) {
    final ResolveResult[] results = TypeInferenceHelper.getCurrentContext().multiResolve(this, incompleteCode, RESOLVER);
    if (results.length == 0) {
      return GroovyResolveResult.EMPTY_ARRAY;
    }

    return (GroovyResolveResult[])results;
  }

  @NotNull
  @Override
  public PsiType[] getTypeArguments() {
    GrTypeArgumentList typeArgumentList = getTypeArgumentList();
    if (typeArgumentList != null && typeArgumentList.isDiamond()) {
      return inferDiamondTypeArguments();
    }
    else {
      return super.getTypeArguments();
    }
  }

  private PsiType[] inferDiamondTypeArguments() {
    PsiElement parent = getParent();
    if (!(parent instanceof GrNewExpression)) return PsiType.EMPTY_ARRAY;

    PsiType lType = PsiImplUtil.inferExpectedTypeForDiamond((GrNewExpression)parent);

    if (lType instanceof PsiClassType) {
      return ((PsiClassType)lType).getParameters();
    }

    return PsiType.EMPTY_ARRAY;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy