Package de.plushnikov.intellij.lombok.processor.field

Source Code of de.plushnikov.intellij.lombok.processor.field.DelegateFieldProcessor

package de.plushnikov.intellij.lombok.processor.field;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiParameterList;
import com.intellij.psi.PsiSubstitutor;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeParameter;
import com.intellij.psi.impl.light.LightTypeParameter;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import de.plushnikov.intellij.lombok.UserMapKeys;
import de.plushnikov.intellij.lombok.problem.ProblemBuilder;
import de.plushnikov.intellij.lombok.psi.LombokLightMethodBuilder;
import de.plushnikov.intellij.lombok.psi.LombokPsiElementFactory;
import de.plushnikov.intellij.lombok.util.PsiAnnotationUtil;
import de.plushnikov.intellij.lombok.util.PsiElementUtil;
import lombok.Delegate;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

/**
* Inspect and validate @Delegate lombok annotation on a field
* Creates delegation methods for this field
*
* @author Plushnikov Michail
*/
public class DelegateFieldProcessor extends AbstractLombokFieldProcessor {

  public DelegateFieldProcessor() {
    super(Delegate.class, PsiMethod.class);
  }

  @Override
  protected boolean validate(@NotNull PsiAnnotation psiAnnotation, @NotNull PsiField psiField, @NotNull ProblemBuilder builder) {
    boolean result = true;

    final PsiClass psiClass = psiField.getContainingClass();
    if (null == psiClass) {
      result = false;
    }

    final Collection<PsiType> types = collectDelegateTypes(psiAnnotation, psiField);
    result &= validateTypes(types, builder);

    final Collection<PsiType> excludes = collectExcludeTypes(psiAnnotation);
    result &= validateTypes(excludes, builder);

    //TODO Error: delegation of methods that doesn't exists on type
    return result;
  }

  private boolean validateTypes(Collection<PsiType> excludes, ProblemBuilder builder) {
    boolean result = true;
    for (PsiType type : excludes) {
      if (!(type instanceof PsiClassType)) {
        builder.addError(String.format(
            "'@Delegate' can only use concrete class types, not wildcards, arrays, type variables, or primitives. '%s' is wrong class type",
            type.getCanonicalText()));
        result = false;
      }
    }
    return result;
  }

  protected void processIntern(@NotNull PsiField psiField, @NotNull PsiAnnotation psiAnnotation, @NotNull List<? super PsiElement> target) {
    final PsiClass psiClass = psiField.getContainingClass();
    final Project project = psiField.getProject();
    final PsiManager manager = psiField.getContainingFile().getManager();

    final Collection<Pair<PsiMethod, PsiSubstitutor>> includesMethods = new HashSet<Pair<PsiMethod, PsiSubstitutor>>();

    final Collection<PsiType> types = collectDelegateTypes(psiAnnotation, psiField);
    addMethodsOfTypes(types, includesMethods);

    final Collection<Pair<PsiMethod, PsiSubstitutor>> excludeMethods = new HashSet<Pair<PsiMethod, PsiSubstitutor>>();
    PsiClassType javaLangObjectType = PsiType.getJavaLangObject(manager, GlobalSearchScope.allScope(project));
    addMethodsOfType(javaLangObjectType, excludeMethods);

    final Collection<PsiType> excludes = collectExcludeTypes(psiAnnotation);
    addMethodsOfTypes(excludes, excludeMethods);

    final Collection<Pair<PsiMethod, PsiSubstitutor>> methodsToDelegate = findMethodsToDelegate(includesMethods, excludeMethods);
    if (!methodsToDelegate.isEmpty()) {
      for (Pair<PsiMethod, PsiSubstitutor> pair : methodsToDelegate) {
        target.add(generateDelegateMethod(psiClass, psiAnnotation, pair.getFirst(), pair.getSecond()));
      }
      UserMapKeys.addGeneralUsageFor(psiField);
      UserMapKeys.addReadUsageFor(psiField);
    }
  }

  private Collection<PsiType> collectDelegateTypes(PsiAnnotation psiAnnotation, PsiField psiField) {
    Collection<PsiType> types = PsiAnnotationUtil.getAnnotationValues(psiAnnotation, "types", PsiType.class);
    if (types.isEmpty()) {
      final PsiType psiType = psiField.getType();
      types = Collections.singletonList(psiType);
    }
    return types;
  }

  private Collection<PsiType> collectExcludeTypes(PsiAnnotation psiAnnotation) {
    return PsiAnnotationUtil.getAnnotationValues(psiAnnotation, "excludes", PsiType.class);
  }

  private void addMethodsOfTypes(Collection<PsiType> types, Collection<Pair<PsiMethod, PsiSubstitutor>> includesMethods) {
    for (PsiType type : types) {
      addMethodsOfType(type, includesMethods);
    }
  }

  private PsiSubstitutor getSubstitutor(PsiType type) {
    final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(type);
    PsiSubstitutor psiSubstitutor = resolveResult.getSubstitutor();
    if (type instanceof PsiClassType) {
      final PsiClass psiClass = resolveResult.getElement();
      if (psiClass instanceof PsiTypeParameter) {
        for (PsiClass aClass : psiClass.getSupers()) {
          psiSubstitutor = psiSubstitutor.putAll(TypeConversionUtil.getSuperClassSubstitutor(aClass, (PsiClassType) type));
        }
      }
    }
    return psiSubstitutor;
  }

  private void addMethodsOfType(PsiType psiType, Collection<Pair<PsiMethod, PsiSubstitutor>> allMethods) {
    final PsiSubstitutor classSubstitutor = getSubstitutor(psiType);

    PsiClass psiClass = PsiUtil.resolveClassInType(psiType);
    if (null != psiClass) {
      List<Pair<PsiMethod, PsiSubstitutor>> acceptedMethods = psiClass.getAllMethodsAndTheirSubstitutors();
      for (Pair<PsiMethod, PsiSubstitutor> pair : acceptedMethods) {
        PsiMethod psiMethod = pair.getFirst();
        if (!psiMethod.isConstructor() && psiMethod.hasModifierProperty(PsiModifier.PUBLIC) && !psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
          PsiSubstitutor combinedSubstitutor = pair.getSecond().putAll(classSubstitutor);
          allMethods.add(new Pair<PsiMethod, PsiSubstitutor>(psiMethod, combinedSubstitutor));
        }
      }
    }
  }

  private void removeDuplicateMethods(Collection<Pair<PsiMethod, PsiSubstitutor>> allMethods) {
    Collection<Pair<PsiMethod, PsiSubstitutor>> processedMethods = new ArrayList<Pair<PsiMethod, PsiSubstitutor>>();
    Iterator<Pair<PsiMethod, PsiSubstitutor>> iterator = allMethods.iterator();
    while (iterator.hasNext()) {
      Pair<PsiMethod, PsiSubstitutor> pair = iterator.next();
      boolean acceptMethod = true;
      for (Pair<PsiMethod, PsiSubstitutor> uniquePair : processedMethods) {
        if (PsiElementUtil.methodMatches(pair, uniquePair)) {
          acceptMethod = false;
          break;
        }
      }
      if (acceptMethod) {
        processedMethods.add(pair);
      } else {
        iterator.remove();
      }
    }
  }

  private Collection<Pair<PsiMethod, PsiSubstitutor>> findMethodsToDelegate(Collection<Pair<PsiMethod, PsiSubstitutor>> includesMethods, Collection<Pair<PsiMethod, PsiSubstitutor>> excludeMethods) {
    removeDuplicateMethods(includesMethods);
    removeDuplicateMethods(excludeMethods);

    if (excludeMethods.isEmpty()) {
      return includesMethods;
    }

    Collection<Pair<PsiMethod, PsiSubstitutor>> result = new ArrayList<Pair<PsiMethod, PsiSubstitutor>>();
    for (Pair<PsiMethod, PsiSubstitutor> includesMethodPair : includesMethods) {
      boolean acceptMethod = true;
      for (Pair<PsiMethod, PsiSubstitutor> excludeMethodPair : excludeMethods) {
        if (PsiElementUtil.methodMatches(includesMethodPair, excludeMethodPair)) {
          acceptMethod = false;
          break;
        }
      }
      if (acceptMethod) {
        result.add(includesMethodPair);
      }
    }
    return result;
  }

  @NotNull
  private PsiMethod generateDelegateMethod(@NotNull PsiClass psiClass, @NotNull PsiAnnotation psiAnnotation, @NotNull PsiMethod psiMethod, @Nullable PsiSubstitutor psiSubstitutor) {
    final PsiType returnType = null == psiSubstitutor ? psiMethod.getReturnType() : psiSubstitutor.substitute(psiMethod.getReturnType());

    LombokLightMethodBuilder methodBuilder = LombokPsiElementFactory.getInstance().
        createLightMethod(psiClass.getManager(), psiMethod.getName())
        .withModifier(PsiModifier.PUBLIC)
        .withMethodReturnType(returnType)
        .withContainingClass(psiClass)
        .withNavigationElement(psiAnnotation);

    for (PsiTypeParameter typeParameter : psiMethod.getTypeParameters()) {
      methodBuilder.withTypeParameter(new LightTypeParameter(typeParameter));
    }

    final PsiParameterList parameterList = psiMethod.getParameterList();

    int parameterIndex = 0;
    for (PsiParameter psiParameter : parameterList.getParameters()) {
      final PsiType psiParameterType = null == psiSubstitutor ? psiParameter.getType() : psiSubstitutor.substitute(psiParameter.getType());
      String psiParameterName = psiParameter.getName();
      methodBuilder.withParameter(StringUtils.defaultIfEmpty(psiParameterName, "p" + parameterIndex), psiParameterType);
      parameterIndex++;
    }

    return methodBuilder;
  }
}
TOP

Related Classes of de.plushnikov.intellij.lombok.processor.field.DelegateFieldProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.