EA-69262 - NPE: DebuggerTreeNodeExpression.substituteThis
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / ui / impl / watch / DebuggerTreeNodeExpression.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.debugger.ui.impl.watch;
17
18 import com.intellij.codeInsight.ChangeContextUtil;
19 import com.intellij.debugger.DebuggerBundle;
20 import com.intellij.debugger.codeinsight.RuntimeTypeEvaluator;
21 import com.intellij.debugger.engine.evaluation.EvaluateException;
22 import com.intellij.debugger.engine.evaluation.TextWithImports;
23 import com.intellij.debugger.engine.evaluation.TextWithImportsImpl;
24 import com.intellij.debugger.impl.DebuggerContextImpl;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.util.Computable;
28 import com.intellij.pom.java.LanguageLevel;
29 import com.intellij.psi.*;
30 import com.intellij.psi.search.GlobalSearchScope;
31 import com.intellij.psi.util.PsiTreeUtil;
32 import com.intellij.psi.util.PsiUtil;
33 import com.intellij.util.IncorrectOperationException;
34 import com.sun.jdi.ObjectReference;
35 import com.sun.jdi.ReferenceType;
36 import com.sun.jdi.Value;
37 import org.jetbrains.annotations.Nullable;
38
39 /**
40  * User: lex
41  * Date: Oct 29, 2003
42  * Time: 9:24:52 PM
43  */
44 public class DebuggerTreeNodeExpression {
45   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeExpression");
46
47 //  private static PsiExpression beautifyExpression(PsiExpression expression) throws IncorrectOperationException {
48 //    final PsiElementFactory elementFactory = expression.getManager().getElementFactory();
49 //    final PsiParenthesizedExpression utility = (PsiParenthesizedExpression)elementFactory.createExpressionFromText(
50 //      "(expr)", expression.getContext());
51 //    utility.getExpression().replace(expression);
52 //
53 //    PsiRecursiveElementVisitor visitor = new PsiRecursiveElementVisitor() {
54 //      @Override public void visitTypeCastExpression(PsiTypeCastExpression expression) {
55 //        try {
56 //          super.visitTypeCastExpression(expression);
57 //
58 //          PsiElement parent;
59 //          PsiElement toBeReplaced = expression;
60 //          for (parent = expression.getParent();
61 //               parent instanceof PsiParenthesizedExpression && parent != utility;
62 //               parent = parent.getParent()) {
63 //            toBeReplaced = parent;
64 //          }
65 //
66 //          if (parent instanceof PsiReferenceExpression) {
67 //            PsiReferenceExpression reference = ((PsiReferenceExpression)parent);
68 //            //((TypeCast)).member
69 //            PsiElement oldResolved = reference.resolve();
70 //
71 //            if (oldResolved != null) {
72 //              PsiReferenceExpression newReference = ((PsiReferenceExpression)reference.copy());
73 //              newReference.getQualifierExpression().replace(expression.getOperand());
74 //              PsiElement newResolved = newReference.resolve();
75 //
76 //              if (oldResolved == newResolved) {
77 //                toBeReplaced.replace(expression.getOperand());
78 //              }
79 //              else if (newResolved instanceof PsiMethod && oldResolved instanceof PsiMethod) {
80 //                if (isSuperMethod((PsiMethod)newResolved, (PsiMethod)oldResolved)) {
81 //                  toBeReplaced.replace(expression.getOperand());
82 //                }
83 //              }
84 //            }
85 //          }
86 //          else {
87 //            toBeReplaced.replace(expression.getOperand());
88 //          }
89 //        }
90 //        catch (IncorrectOperationException e) {
91 //          throw new IncorrectOperationRuntimeException(e);
92 //        }
93 //      }
94 //
95 //      @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
96 //        expression.acceptChildren(this);
97 //
98 //        try {
99 //          JavaResolveResult resolveResult = expression.advancedResolve(false);
100 //
101 //          PsiElement oldResolved = resolveResult.getElement();
102 //
103 //          if(oldResolved == null) return;
104 //
105 //          PsiReferenceExpression newReference;
106 //          if (expression instanceof PsiMethodCallExpression) {
107 //            int length = expression.getQualifierExpression().getTextRange().getLength();
108 //            PsiMethodCallExpression methodCall = (PsiMethodCallExpression)elementFactory.createExpressionFromText(
109 //              expression.getText().substring(length), expression.getContext());
110 //            newReference = methodCall.getMethodExpression();
111 //          }
112 //          else {
113 //            newReference =
114 //            (PsiReferenceExpression)elementFactory.createExpressionFromText(expression.getReferenceName(),
115 //                                                                            expression.getContext());
116 //          }
117 //
118 //          PsiElement newResolved = newReference.resolve();
119 //          if (oldResolved == newResolved) {
120 //            expression.replace(newReference);
121 //          }
122 //        }
123 //        catch (IncorrectOperationException e) {
124 //          LOG.debug(e);
125 //        }
126 //      }
127 //    };
128 //
129 //    try {
130 //      utility.accept(visitor);
131 //    }
132 //    catch (IncorrectOperationRuntimeException e) {
133 //      throw e.getException();
134 //    }
135 //    return utility.getExpression();
136 //  }
137
138   private static boolean isSuperMethod(PsiMethod superMethod, PsiMethod overridingMethod) {
139     PsiMethod[] superMethods = overridingMethod.findSuperMethods();
140       for (int i = 0; i < superMethods.length; i++) {
141         if (superMethods[i] == superMethod) {
142           return true;
143         }
144         else if (isSuperMethod(superMethod, superMethods[i])) {
145           return true;
146         }
147       }
148       return false;
149     }
150
151   @Nullable
152   public static PsiExpression substituteThis(@Nullable PsiExpression expressionWithThis, PsiExpression howToEvaluateThis, Value howToEvaluateThisValue)
153     throws EvaluateException {
154     if (expressionWithThis == null) return null;
155     PsiExpression result = (PsiExpression)expressionWithThis.copy();
156
157     PsiClass thisClass = PsiTreeUtil.getContextOfType(result, PsiClass.class, true);
158
159     boolean castNeeded = true;
160
161     if (thisClass != null) {
162       PsiType type = howToEvaluateThis.getType();
163       if(type != null) {
164         if(type instanceof PsiClassType) {
165           PsiClass psiClass = ((PsiClassType) type).resolve();
166           if(psiClass != null && (psiClass == thisClass || psiClass.isInheritor(thisClass, true))) {
167             castNeeded = false;
168           }
169         }
170         else if(type instanceof PsiArrayType) {
171           LanguageLevel languageLevel = PsiUtil.getLanguageLevel(expressionWithThis);
172           if(thisClass == JavaPsiFacade.getInstance(expressionWithThis.getProject()).getElementFactory().getArrayClass(languageLevel)) {
173             castNeeded = false;
174           }
175         }
176       }
177     }
178
179     if (castNeeded) {
180       howToEvaluateThis = castToRuntimeType(howToEvaluateThis, howToEvaluateThisValue, howToEvaluateThis.getContext());
181     }
182
183     ChangeContextUtil.encodeContextInfo(result, false);
184     PsiExpression psiExpression;
185     try {
186       psiExpression = (PsiExpression) ChangeContextUtil.decodeContextInfo(result, thisClass, howToEvaluateThis);
187     }
188     catch (IncorrectOperationException e) {
189       throw new EvaluateException(
190         DebuggerBundle.message("evaluation.error.invalid.this.expression", result.getText(), howToEvaluateThis.getText()), null);
191     }
192
193     try {
194       return JavaPsiFacade.getInstance(howToEvaluateThis.getProject()).getElementFactory()
195         .createExpressionFromText(psiExpression.getText(), howToEvaluateThis.getContext());
196     }
197     catch (IncorrectOperationException e) {
198       throw new EvaluateException(e.getMessage(), e);
199     }
200   }
201
202   public static PsiExpression castToRuntimeType(PsiExpression expression, Value value, PsiElement contextElement) throws EvaluateException {
203     if (!(value instanceof ObjectReference)) {
204       return expression;
205     }
206     
207     ReferenceType valueType = ((ObjectReference)value).referenceType();
208     if (valueType == null) {
209       return expression;
210     }
211     
212     Project project = expression.getProject();
213
214     PsiClass type = RuntimeTypeEvaluator.getCastableRuntimeType(project, value);
215     if (type == null) {
216       return expression;
217     }
218
219     PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
220     try {
221       PsiParenthesizedExpression parenthExpression = (PsiParenthesizedExpression)elementFactory.createExpressionFromText(
222         "((" + type.getQualifiedName() + ")expression)", null);
223       ((PsiTypeCastExpression)parenthExpression.getExpression()).getOperand().replace(expression);
224       return parenthExpression;
225     }
226     catch (IncorrectOperationException e) {
227       throw new EvaluateException(DebuggerBundle.message("error.invalid.type.name", type.getQualifiedName()), e);
228     }
229   }
230
231   /**
232    * @param qualifiedName the class qualified name to be resolved against the current execution context
233    * @return short name if the class could be resolved using short name,
234    * otherwise returns qualifiedName
235    */
236   public static String normalize(final String qualifiedName, PsiElement contextElement, Project project) {
237     if (contextElement == null) {
238       return qualifiedName;
239     }
240
241     final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
242     PsiClass aClass = facade.findClass(qualifiedName, GlobalSearchScope.allScope(project));
243     if (aClass != null) {
244       return normalizePsiClass(aClass, contextElement, facade.getResolveHelper());
245     }
246     return qualifiedName;
247   }
248
249   private static String normalizePsiClass(PsiClass psiClass, PsiElement contextElement, PsiResolveHelper helper) {
250     String name = psiClass.getName();
251     PsiClass aClass = helper.resolveReferencedClass(name, contextElement);
252     if (psiClass.equals(aClass)) {
253       return name;
254     }
255     PsiClass parentClass = psiClass.getContainingClass();
256     if (parentClass != null) {
257       return normalizePsiClass(parentClass, contextElement, helper) + "." + name;
258     }
259     return psiClass.getQualifiedName();
260   }
261
262   public static PsiExpression getEvaluationExpression(DebuggerTreeNodeImpl node, DebuggerContextImpl context) throws EvaluateException {
263     if(node.getDescriptor() instanceof ValueDescriptorImpl) {
264       throw new IllegalStateException("Not supported any more");
265       //return ((ValueDescriptorImpl)node.getDescriptor()).getTreeEvaluation(node, context);
266     }
267     else {
268       LOG.error(node.getDescriptor() != null ? node.getDescriptor().getClass().getName() : "null");
269       return null;
270     }
271   }
272
273   public static TextWithImports createEvaluationText(final DebuggerTreeNodeImpl node, final DebuggerContextImpl context) throws EvaluateException {
274     final EvaluateException[] ex = new EvaluateException[] {null};
275     final TextWithImports textWithImports = PsiDocumentManager.getInstance(context.getProject()).commitAndRunReadAction(new Computable<TextWithImports>() {
276       public TextWithImports compute() {
277         try {
278           final PsiExpression expressionText = getEvaluationExpression(node, context);
279           if (expressionText != null) {
280             return new TextWithImportsImpl(expressionText);
281           }
282         }
283         catch (EvaluateException e) {
284           ex[0] = e;
285         }
286         return null;
287       }
288     });
289     if (ex[0] != null) {
290       throw ex[0];
291     }
292     return textWithImports;
293   }
294
295   private static class IncorrectOperationRuntimeException extends RuntimeException {
296     private final IncorrectOperationException myException;
297
298     public IncorrectOperationRuntimeException(IncorrectOperationException exception) {
299       myException = exception;
300     }
301
302     public IncorrectOperationException getException() { return myException; }
303   }
304 }