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

org.jetbrains.plugins.groovy.lang.resolve.ast.DelegatedMethodsContributor 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-2015 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.resolve.ast;

import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiSuperMethodImplUtil;
import com.intellij.psi.impl.light.LightMethodBuilder;
import com.intellij.psi.impl.light.LightParameter;
import com.intellij.psi.impl.source.tree.java.PsiCompositeModifierList;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.HashSet;
import gnu.trove.THashMap;
import icons.JetgroovyIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyLanguage;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrExtendsClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrImplementsClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GrClassImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;

import java.util.*;

/**
 * @author Max Medvedev
 */
public class DelegatedMethodsContributor extends AstTransformContributor {
  @Override
  public void collectMethods(@NotNull final GrTypeDefinition clazz, @NotNull Collection collector) {
    Set processed = new HashSet();

    if (!checkForDelegate(clazz)) return;
    
    Map signatures = new THashMap(MethodSignatureUtil.METHOD_PARAMETERS_ERASURE_EQUALITY);
    initializeSignatures(clazz, PsiSubstitutor.EMPTY, signatures, processed);

    List methods = new ArrayList();
    process(clazz, PsiSubstitutor.EMPTY, true, new HashSet(), processed, methods, clazz, false);

    final Set result = new LinkedHashSet();
    for (PsiMethod method : methods) {
      addMethodChecked(signatures, method, PsiSubstitutor.EMPTY, result);
    }

    collector.addAll(result);
  }

  private static boolean checkForDelegate(GrTypeDefinition clazz) {
    for (GrField field : clazz.getFields()) {
      if (PsiImplUtil.getAnnotation(field, GroovyCommonClassNames.GROOVY_LANG_DELEGATE) != null) return true;
    }
    return false;
  }

  /**
   * Adds 'method' to 'signatures' if it doesn't yet contain any method with the same signature or replaces abstract methods
   */
  private static void addMethodChecked(Map signatures,
                                       PsiMethod method,
                                       PsiSubstitutor substitutor,
                                       @Nullable Set resultSet) {
    if (method.isConstructor()) return;
    if (method.hasModifierProperty(PsiModifier.STATIC)) return;

    final MethodSignature signature = method.getSignature(substitutor);
    final PsiMethod old = signatures.get(signature);

    if (old != null) {
      //if (method.hasModifierProperty(PsiModifier.ABSTRACT)) return;
      if (!old.hasModifierProperty(PsiModifier.ABSTRACT)) return;

      if (resultSet != null) resultSet.remove(old);
    }

    signatures.put(signature, method);
    if (resultSet != null) resultSet.add(method);
  }

  /**
   * Adds all code methods of clazz add its super classes to signatures. Doesn't walk into interfaces because all methods from them will be overloaded in any case.
   * Besides Some of interfaces came from delegates and they should be visited during the following processing.
   *
   * @param clazz current class
   * @param substitutor super class substitutor of clazz
   * @param signatures map to initialize
   * @param classes already visited classes
   */
  private static void initializeSignatures(PsiClass clazz, PsiSubstitutor substitutor, Map signatures, Set classes) {
    if (clazz.isInterface()) return;

    if (classes.add(clazz)) {
      final List methods;
      if (clazz instanceof GrTypeDefinition) {
        methods = new ArrayList();
        GrClassImplUtil.collectMethodsFromBody((GrTypeDefinition)clazz, methods);
      }
      else {
        methods = Arrays.asList(clazz.getMethods());
      }

      for (PsiMethod method : methods) {
        addMethodChecked(signatures, method, substitutor, null);
      }

      for (PsiClassType type : getSuperTypes(clazz)) {
        final PsiClassType.ClassResolveResult result = type.resolveGenerics();
        final PsiClass superClass = result.getElement();
        if (superClass == null) continue;
        final PsiSubstitutor superClassSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(superClass, clazz, substitutor);
        initializeSignatures(superClass, superClassSubstitutor, signatures, classes);
      }
    }
  }

  /**
   *  The key method of contributor. It collects all delegating methods of clazz
   *
   * @param clazz class to process
   * @param processedWithoutDeprecated already visited classes which deprecated methods were not processsed
   * @param processedAll already visited classes which all methods were processed
   * @param collector result collection
   */
  private static void process(PsiClass clazz,
                              PsiSubstitutor superClassSubstitutor,
                              boolean shouldProcessDeprecated,
                              Set processedWithoutDeprecated,
                              Set processedAll,
                              List collector,
                              GrTypeDefinition classToDelegateTo,
                              boolean keepParameterAnnotations) {
    final List result = new ArrayList();

    //process super methods before delegated methods
    for (PsiClassType superType : getSuperTypes(clazz)) {
      processClassInner(superType, superClassSubstitutor, shouldProcessDeprecated, result, classToDelegateTo, processedWithoutDeprecated,
                        processedAll, keepParameterAnnotations);
    }

    if (clazz instanceof GrTypeDefinition) {
      //search for @Delegate fields and collect methods from them
      for (GrField field : ((GrTypeDefinition)clazz).getFields()) {
        final PsiAnnotation delegate = PsiImplUtil.getAnnotation(field, GroovyCommonClassNames.GROOVY_LANG_DELEGATE);
        if (delegate == null) continue;

        final PsiType type = field.getDeclaredType();
        if (!(type instanceof PsiClassType)) continue;

        processClassInner((PsiClassType)type, superClassSubstitutor, shouldDelegateDeprecated(delegate), result, classToDelegateTo,
                          processedWithoutDeprecated, processedAll, shouldKeepParameterAnnotations(delegate));
      }
    }

    collector.addAll(result);
  }

  private static List getSuperTypes(PsiClass clazz) {
    if (clazz instanceof GrTypeDefinition) {
      final GrExtendsClause elist = ((GrTypeDefinition)clazz).getExtendsClause();
      final GrImplementsClause ilist = ((GrTypeDefinition)clazz).getImplementsClause();

      if (elist == null && ilist == null) return ContainerUtil.emptyList();

      final ArrayList types = new ArrayList();
      if (elist != null) ContainerUtil.addAll(types, elist.getReferencedTypes());
      if (ilist != null) ContainerUtil.addAll(types, ilist.getReferencedTypes());
      return types;
    }
    else {
      final PsiReferenceList elist = clazz.getExtendsList();
      final PsiReferenceList ilist = clazz.getImplementsList();

      if (elist == null && ilist == null) return ContainerUtil.emptyList();

      final ArrayList types = new ArrayList();
      if (elist != null) ContainerUtil.addAll(types, elist.getReferencedTypes());
      if (ilist != null) ContainerUtil.addAll(types, ilist.getReferencedTypes());
      return types;
    }
  }

  private static void processClassInner(PsiClassType type,
                                        PsiSubstitutor superClassSubstitutor,
                                        boolean shouldProcessDeprecated,
                                        List result,
                                        GrTypeDefinition classToDelegateTo,
                                        Set processedWithoutDeprecated,
                                        Set processedAll, boolean keepParameterAnnotationsNew) {
    final PsiClassType.ClassResolveResult resolveResult = type.resolveGenerics();
    final PsiClass psiClass = resolveResult.getElement();
    if (psiClass == null) return;

    final String qname = psiClass.getQualifiedName();
    if (CommonClassNames.JAVA_LANG_OBJECT.equals(qname)) return;
    if (GroovyCommonClassNames.GROOVY_OBJECT.equals(qname)) return;
    if (GroovyCommonClassNames.GROOVY_OBJECT_SUPPORT.equals(qname)) return;

    final PsiSubstitutor substitutor = TypesUtil.composeSubstitutors(resolveResult.getSubstitutor(), superClassSubstitutor);

    if (processedAll.contains(psiClass)) return;
    if (!shouldProcessDeprecated && processedWithoutDeprecated.contains(psiClass)) return;

    if (shouldProcessDeprecated) {
      processedAll.add(psiClass);
    }
    else {
      processedWithoutDeprecated.add(psiClass);
    }

    collectMethods(psiClass, substitutor, shouldProcessDeprecated, classToDelegateTo, result, keepParameterAnnotationsNew);
    process(psiClass, substitutor, shouldProcessDeprecated, processedWithoutDeprecated, processedAll, result, classToDelegateTo, keepParameterAnnotationsNew);
  }

  private static void collectMethods(PsiClass currentClass,
                                     PsiSubstitutor currentClassSubstitutor,
                                     boolean shouldProcessDeprecated,
                                     GrTypeDefinition classToDelegateTo,
                                     Collection collector,
                                     boolean keepParameterAnnotations) {
    final List methods;
    if (currentClass instanceof GrTypeDefinition) {
      methods = new ArrayList();
      GrClassImplUtil.collectMethodsFromBody((GrTypeDefinition)currentClass, methods);
    }
    else {
      methods = Arrays.asList(currentClass.getMethods());
    }

    for (PsiMethod method : methods) {
      if (method.isConstructor() || method.hasModifierProperty(PsiModifier.STATIC)) continue;
      if (overridesObjectOrGroovyObject(method)) continue;
      if (!shouldProcessDeprecated && PsiImplUtil.getAnnotation(method, CommonClassNames.JAVA_LANG_DEPRECATED) != null) continue;
      collector.add(generateDelegateMethod(method, classToDelegateTo, currentClassSubstitutor, keepParameterAnnotations));
    }
  }

  private static boolean overridesObjectOrGroovyObject(PsiMethod method) {
    final String name = method.getName();
    if (!OBJECT_METHODS.contains(name) && !GROOVY_OBJECT_METHODS.contains(name)) return false;

    final PsiMethod superMethod = PsiSuperMethodImplUtil.findDeepestSuperMethod(method);
    if (superMethod == null) return false;

    final PsiClass superClass = superMethod.getContainingClass();
    if (superClass == null) return false;

    final String qname = superClass.getQualifiedName();
    return CommonClassNames.JAVA_LANG_OBJECT.equals(qname) || GroovyCommonClassNames.GROOVY_OBJECT.equals(qname);
  }

  private static boolean shouldDelegateDeprecated(PsiAnnotation delegate) {
    final Boolean result = GrAnnotationUtil.inferBooleanAttribute(delegate, "deprecated");
    return result != null && result.booleanValue();
  }

  private static boolean shouldKeepParameterAnnotations(PsiAnnotation delegate) {
    final Boolean keepParameterAnnotations = GrAnnotationUtil.inferBooleanAttribute(delegate, "parameterAnnotations");
    return keepParameterAnnotations != null && keepParameterAnnotations.booleanValue();
  }


  private static PsiMethod generateDelegateMethod(PsiMethod method,
                                                  PsiClass superClass,
                                                  PsiSubstitutor substitutor,
                                                  boolean keepParameterAnnotations) {
    final LightMethodBuilder builder = new LightMethodBuilder(superClass.getManager(), GroovyLanguage.INSTANCE, method.getName());
    builder.setContainingClass(superClass);
    builder.setMethodReturnType(substitutor.substitute(method.getReturnType()));
    builder.setNavigationElement(method);
    builder.addModifier(PsiModifier.PUBLIC);

    final PsiTypeParameter[] typeParameters = method.getTypeParameters();

    final PsiClass containingClass = method.getContainingClass();
    boolean isRaw = containingClass != null && PsiUtil.isRawSubstitutor(containingClass, substitutor);
    if (isRaw) {
      substitutor = JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createRawSubstitutor(substitutor, typeParameters);
    }

    if (!isRaw) {
      for (PsiTypeParameter typeParameter : typeParameters) {
        builder.addTypeParameter(typeParameter);
      }
    }

    final PsiParameter[] originalParameters = method.getParameterList().getParameters();

    for (int i = 0; i < originalParameters.length; i++) {
      PsiParameter originalParameter = originalParameters[i];
      PsiType type;
      if (isRaw) {
        type = TypeConversionUtil.erasure(substitutor.substitute(originalParameter.getType()));
      }
      else {
        type = substitutor.substitute(originalParameter.getType());
      }
      if (type == null) {
        type = TypesUtil.getJavaLangObject(superClass);
      }
      final LightParameter lightParameter = new LightParameter(StringUtil.notNullize(originalParameter.getName(), "p" + i), type, builder, JavaLanguage.INSTANCE);
      if (keepParameterAnnotations) {
        final PsiCompositeModifierList delegatingModifierList = new PsiCompositeModifierList(method.getManager(), Collections.singletonList(originalParameter.getModifierList()));
        lightParameter.setModifierList(delegatingModifierList);
      }
      builder.addParameter(lightParameter);
    }
    builder.setBaseIcon(JetgroovyIcons.Groovy.Method);

    return new DelegatedMethod(builder, method);
  }

  private static final Set OBJECT_METHODS = ContainerUtil.newHashSet("equals", "hashCode", "getClass", "clone", "toString", "notify", "notifyAll", "wait", "finalize");
  private static final Set GROOVY_OBJECT_METHODS = ContainerUtil.newHashSet("invokeMethod", "getProperty", "setProperty", "getMetaClass", "setMetaClass");
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy