constructor reference: don't ignore constructor parameters during method reference...
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / extractMethodObject / reflect / FieldDescriptor.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.refactoring.extractMethodObject.reflect;
3
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.openapi.util.text.StringUtil;
6 import com.intellij.psi.*;
7 import com.intellij.psi.util.ClassUtil;
8 import com.intellij.refactoring.extractMethodObject.ItemToReplaceDescriptor;
9 import org.jetbrains.annotations.NotNull;
10 import org.jetbrains.annotations.Nullable;
11
12 import java.lang.reflect.Array;
13 import java.util.Objects;
14
15 /**
16  * @author Vitaliy.Bibaev
17  */
18 public class FieldDescriptor implements ItemToReplaceDescriptor {
19   private static final Logger LOG = Logger.getInstance(FieldDescriptor.class);
20
21   private final PsiField myField;
22   private final PsiReferenceExpression myExpression;
23   private final String myAccessibleType;
24
25   private FieldDescriptor(@NotNull PsiField field, @NotNull PsiReferenceExpression expression) {
26     myField = field;
27     myExpression = expression;
28     String fieldType = PsiReflectionAccessUtil.getAccessibleReturnType(myExpression, resolveFieldType(myField, myExpression));
29     if (fieldType == null) {
30       LOG.warn("Could not resolve field type. java.lang.Object will be used instead");
31       fieldType = "java.lang.Object";
32     }
33     myAccessibleType = fieldType;
34   }
35
36   @Nullable
37   public static ItemToReplaceDescriptor createIfInaccessible(@NotNull PsiClass outerClass, @NotNull PsiReferenceExpression expression) {
38     final PsiElement resolved = expression.resolve();
39     if (resolved instanceof PsiField) {
40       final PsiField field = (PsiField)resolved;
41       PsiClass containingClass = field.getContainingClass();
42
43
44       if (!Objects.equals(containingClass, outerClass) && needReplace(field, expression)) {
45         Array.getLength(new int[3]);
46         return new FieldDescriptor(field, expression);
47       }
48     }
49
50     return null;
51   }
52
53   @Override
54   public void replace(@NotNull PsiClass outerClass,
55                       @NotNull PsiElementFactory elementFactory,
56                       @NotNull PsiMethodCallExpression callExpression) {
57     PsiElement parent = myExpression.getParent();
58     if (parent instanceof PsiAssignmentExpression &&
59         Objects.equals(myExpression, ((PsiAssignmentExpression)parent).getLExpression())) {
60       grantUpdateAccess((PsiAssignmentExpression)parent, outerClass, callExpression, elementFactory);
61     }
62     else {
63       grantReadAccess(outerClass, callExpression, elementFactory);
64     }
65   }
66
67   private void grantReadAccess(@NotNull PsiClass outerClass,
68                                @NotNull PsiMethodCallExpression generatedCall,
69                                @NotNull PsiElementFactory elementFactory) {
70     PsiMethod newMethod = createPsiMethod(FieldAccessType.GET, outerClass, elementFactory);
71     if (newMethod == null) return;
72
73     outerClass.add(newMethod);
74
75     String object = MemberQualifierUtil.findObjectExpression(myExpression, myField, outerClass, generatedCall, elementFactory);
76     String methodCall = newMethod.getName() + "(" + (object == null ? "null" : object) + ", null)";
77     myExpression.replace(elementFactory.createExpressionFromText(methodCall, myExpression));
78   }
79
80   private void grantUpdateAccess(@NotNull PsiAssignmentExpression assignmentExpression,
81                                  @NotNull PsiClass outerClass,
82                                  @NotNull PsiMethodCallExpression generatedCall,
83                                  @NotNull PsiElementFactory elementFactory) {
84     PsiMethod newMethod = createPsiMethod(FieldAccessType.SET, outerClass, elementFactory);
85     if (newMethod == null) return;
86
87     outerClass.add(newMethod);
88     PsiExpression rightExpression = assignmentExpression.getRExpression();
89     if (rightExpression == null) {
90       LOG.warn("Expression representing a new field value not found");
91       return;
92     }
93
94     String newValue = rightExpression.getText();
95     String objectForReference = MemberQualifierUtil.findObjectExpression(myExpression, myField, outerClass, generatedCall, elementFactory);
96     String args = (objectForReference == null ? "null" : objectForReference) + ", " + newValue;
97     String methodCallExpression = newMethod.getName() + "(" + args + ")";
98
99     PsiExpression newMethodCallExpression = elementFactory.createExpressionFromText(methodCallExpression, myExpression);
100     assignmentExpression.replace(newMethodCallExpression);
101   }
102
103   @Nullable
104   private PsiMethod createPsiMethod(FieldAccessType accessType, PsiClass outerClass, PsiElementFactory elementFactory) {
105     PsiClass containingClass = myField.getContainingClass();
106     String className = containingClass == null ? null : ClassUtil.getJVMClassName(containingClass);
107     String fieldName = myField.getName();
108     if (className == null || fieldName == null) {
109       LOG.warn("Code is incomplete. Class name or field name not found");
110       return null;
111     }
112
113     String methodName = PsiReflectionAccessUtil.getUniqueMethodName(outerClass, "accessToField" + StringUtil.capitalize(fieldName));
114     ReflectionAccessMethodBuilder methodBuilder = new ReflectionAccessMethodBuilder(methodName);
115     if (FieldAccessType.GET.equals(accessType)) {
116       methodBuilder.accessedField(className, fieldName).setReturnType(myAccessibleType);
117     }
118     else {
119       methodBuilder.updatedField(className, fieldName)
120                    .setReturnType("void");
121     }
122
123     methodBuilder.setStatic(outerClass.hasModifierProperty(PsiModifier.STATIC))
124                  .addParameter("java.lang.Object", "object")
125                  .addParameter("java.lang.Object", "value");
126
127     return methodBuilder.build(elementFactory, outerClass);
128   }
129
130   private static boolean needReplace(@NotNull PsiField field, @NotNull PsiReferenceExpression expression) {
131     return !PsiReflectionAccessUtil.isAccessibleMember(field) ||
132            !PsiReflectionAccessUtil.isQualifierAccessible(expression.getQualifierExpression());
133   }
134
135   @NotNull
136   private static PsiType resolveFieldType(@NotNull PsiField field, @NotNull PsiReferenceExpression referenceExpression) {
137     PsiType rawType = field.getType();
138     return referenceExpression.advancedResolve(false).getSubstitutor().substitute(rawType);
139   }
140 }