summaryrefslogtreecommitdiff
path: root/java/typeMigration/src/com/intellij/refactoring/typeMigration/intentions/ChangeClassParametersIntention.java
blob: b65e5364f57b0ee9198f2e89eaf57507195f6bb4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package com.intellij.refactoring.typeMigration.intentions;

import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.codeInsight.intention.impl.TypeExpression;
import com.intellij.codeInsight.template.*;
import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.typeMigration.TypeMigrationProcessor;
import com.intellij.refactoring.typeMigration.TypeMigrationRules;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;

/**
 * @author anna
 */
public class ChangeClassParametersIntention extends PsiElementBaseIntentionAction {

  private static final Logger LOG = Logger.getInstance("#" + ChangeClassParametersIntention.class);

  @NotNull
  @Override
  public String getText() {
    return getFamilyName();
  }

  @NotNull
  @Override
  public String getFamilyName() {
    return "Change class type parameter";
  }

  @Override
  public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
    final PsiTypeElement typeElement = PsiTreeUtil.getTopmostParentOfType(element, PsiTypeElement.class);
    final PsiElement parent = typeElement != null ? typeElement.getParent() : null;
    final PsiReferenceParameterList parameterList = parent instanceof PsiReferenceParameterList ? (PsiReferenceParameterList)parent : null;
    if (parameterList != null && parameterList.getTypeArguments().length > 0) {
      final PsiMember member = PsiTreeUtil.getParentOfType(parameterList, PsiMember.class);
      if (member instanceof PsiAnonymousClass) {
        final PsiClassType.ClassResolveResult result = ((PsiAnonymousClass)member).getBaseClassType().resolveGenerics();
        final PsiClass baseClass = result.getElement();
        return baseClass != null && baseClass.getTypeParameters().length == parameterList.getTypeParameterElements().length &&
               ((PsiAnonymousClass)member).getBaseClassReference().getParameterList() == parameterList;
      }
    }
    return false;
  }

  @Override
  public void invoke(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) throws IncorrectOperationException {
    if (!FileModificationService.getInstance().preparePsiElementsForWrite(element)) return;

    final PsiTypeElement typeElement = PsiTreeUtil.getTopmostParentOfType(element, PsiTypeElement.class);
    final PsiReferenceParameterList parameterList = PsiTreeUtil.getParentOfType(element, PsiReferenceParameterList.class);
    if (parameterList != null && typeElement != null) {
      final PsiClass aClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
      if (aClass instanceof PsiAnonymousClass) {
        editor.getCaretModel().moveToOffset(aClass.getTextOffset());
        final PsiTypeElement[] typeElements = parameterList.getTypeParameterElements();
        final int changeIdx = ArrayUtil.find(typeElements, typeElement);

        final PsiClassType.ClassResolveResult result = ((PsiAnonymousClass)aClass).getBaseClassType().resolveGenerics();
        final PsiClass baseClass = result.getElement();
        LOG.assertTrue(baseClass != null);
        final PsiTypeParameter typeParameter = baseClass.getTypeParameters()[changeIdx];

        final TemplateBuilderImpl templateBuilder = (TemplateBuilderImpl)TemplateBuilderFactory.getInstance().createTemplateBuilder(aClass);

        final String oldTypeText = typeElement.getText();
        final String varName = "param";
        templateBuilder.replaceElement(typeElement, varName, new TypeExpression(project, new PsiType[]{typeElement.getType()}), true);

        final Template template = templateBuilder.buildInlineTemplate();
        TemplateManager.getInstance(project).startTemplate(editor, template, false, null, new TemplateEditingAdapter() {
          private String myNewType;

          @Override
          public void beforeTemplateFinished(TemplateState state, Template template) {
            final TextResult value = state.getVariableValue(varName);
            myNewType = value != null ? value.getText() : "";
            final int segmentsCount = state.getSegmentsCount();
            final Document document = state.getEditor().getDocument();
            for (int i = 0; i < segmentsCount; i++) {
              final TextRange segmentRange = state.getSegmentRange(i);
              document.replaceString(segmentRange.getStartOffset(), segmentRange.getEndOffset(), oldTypeText);
            }
          }

          @Override
          public void templateFinished(Template template, boolean brokenOff) {
            if (!brokenOff) {
              final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
              try {
                final PsiType targetParam = elementFactory.createTypeFromText(myNewType, aClass);
                final TypeMigrationRules myRules = new TypeMigrationRules(((PsiAnonymousClass)aClass).getBaseClassType());
                final PsiSubstitutor substitutor = result.getSubstitutor().put(typeParameter, targetParam);
                final PsiType targetClassType = elementFactory.createType(baseClass, substitutor);
                myRules.setMigrationRootType(targetClassType);
                myRules.setBoundScope(new LocalSearchScope(aClass));
                new TypeMigrationProcessor(project, ((PsiAnonymousClass)aClass).getBaseClassReference().getParameterList(), myRules).run();
              }
              catch (IncorrectOperationException e) {
                HintManager.getInstance().showErrorHint(editor, "Incorrect type");
              }
            }
          }
        });
      }
    }
  }

  @Override
  public boolean startInWriteAction() {
    return true;
  }
}