fix groovy evaluation of c, res and other unfortunately-named variables
[fedora-idea.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / debugger / GroovyCodeFragmentFactory.java
blobd20e6053370e90407508d7674106a7122cda80d8
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.jetbrains.plugins.groovy.debugger;
18 import com.intellij.debugger.engine.evaluation.CodeFragmentFactory;
19 import com.intellij.debugger.engine.evaluation.TextWithImports;
20 import com.intellij.openapi.fileTypes.LanguageFileType;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.util.Pair;
23 import com.intellij.openapi.util.TextRange;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.psi.*;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.util.Function;
28 import com.intellij.util.containers.ContainerUtil;
29 import gnu.trove.THashMap;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.plugins.groovy.GroovyFileType;
32 import org.jetbrains.plugins.groovy.debugger.fragments.GroovyCodeFragment;
33 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
34 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
35 import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor;
36 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
37 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableBase;
38 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
39 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
40 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
41 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrSuperReferenceExpression;
42 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrThisReferenceExpression;
43 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
44 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
45 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
46 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.ClosureSyntheticParameter;
47 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Random;
54 /**
55 * @author ven
57 public class GroovyCodeFragmentFactory implements CodeFragmentFactory {
58 private static final String EVAL_NAME = "_JETGROOVY_EVAL_";
60 private static String createProperty(String text, String imports) {
61 String classText = "\"" +
62 imports +
63 "class DUMMY { " +
64 "public groovy.lang.Closure " +
65 EVAL_NAME + " = {" + text + "}}\"";
67 return "final java.lang.ClassLoader parentLoader = clazz.getClassLoader();\n" +
68 " final groovy.lang.GroovyClassLoader loader = new groovy.lang.GroovyClassLoader(parentLoader);\n" +
69 " final java.lang.Class c = loader.parseClass(" +
70 classText +
71 ", \"DUMMY.groovy\");\n" +
72 " int i;\n" +
73 " java.lang.reflect.Field[] fields = c.getFields();\n" +
74 " for (int j = 0; j < fields.length; j++) if (fields[j].getName().equals(\"_JETGROOVY_EVAL_\")) {i = j; break;}\n" +
75 " final java.lang.reflect.Field field = fields[i];\n" +
76 " final java.lang.Object closure = field.get(c.newInstance());\n";
79 private static String unwrapVals(List<String> vals) {
80 return "java.lang.Object[] vals = new java.lang.Object[]{" + StringUtil.join(vals, ",") + "};\n" +
81 "java.lang.Object[] resVals = new java.lang.Object[" + vals.size() + "];\n" +
82 "for (int iii =0; iii<vals.length; iii++){java.lang.Object o = vals[iii];\n" +
83 "if (o instanceof groovy.lang.Reference) {o = ((groovy.lang.Reference)o).get();}\n" +
84 "resVals[iii] = o;" +
85 "}\n";
88 public JavaCodeFragment createCodeFragment(TextWithImports textWithImports, PsiElement context, Project project) {
89 String text = textWithImports.getText();
90 String imports = textWithImports.getImports();
93 final Pair<Map<String, String>, GroovyFile> pair = externalParameters(text, context);
94 GroovyFile toEval = pair.second;
95 final Map<String, String> parameters = pair.first;
97 List<String> names = new ArrayList<String>(parameters.keySet());
98 List<String> values = ContainerUtil.map(names, new Function<String, String>() {
99 public String fun(String name) {
100 return parameters.get(name);
105 text = toEval.getText();
107 PsiClass contextClass = PsiUtil.getContextClass(context);
108 boolean isStatic = isStaticContext(context);
109 StringBuffer javaText = new StringBuffer();
111 javaText.append("groovy.lang.MetaClass mc;\n");
112 javaText.append("java.lang.Class clazz;\n");
113 if (!isStatic) {
114 javaText.append("clazz = ((java.lang.Object)this).getClass();\n");
115 javaText.append("mc = ((groovy.lang.GroovyObject)this).getMetaClass();\n");
116 } else {
117 javaText.append("clazz = java.lang.Class.forName(\"").append(contextClass.getQualifiedName()).append("\");\n");
118 javaText.append("mc = groovy.lang.GroovySystem.getMetaClassRegistry().getMetaClass(clazz);\n");
121 javaText.append(createProperty(StringUtil.join(names, ", ") + "->" + stripImports(text, toEval), imports));
123 javaText.append("groovy.lang.ExpandoMetaClass emc = new groovy.lang.ExpandoMetaClass(clazz);\n");
124 if (!isStatic) {
125 javaText.append("emc.setProperty(\"").append(EVAL_NAME).append("\", closure);\n");
126 javaText.append("((groovy.lang.GroovyObject)this).setMetaClass(emc);\n");
127 } else {
128 javaText.append("((groovy.lang.GroovyObject)emc.getProperty(\"static\")).setProperty(\"").append(EVAL_NAME).append("\", closure);\n");
129 javaText.append("groovy.lang.GroovySystem.getMetaClassRegistry().setMetaClass(clazz, emc);\n");
131 javaText.append("emc.initialize();\n");
132 javaText.append(unwrapVals(values));
133 if (!isStatic) {
134 javaText.append("java.lang.Object res = ((groovy.lang.MetaClassImpl)emc).invokeMethod(this, \"").append(EVAL_NAME).append("\", ").
135 append("resVals").append(");\n");
136 javaText.append("((groovy.lang.GroovyObject)this).setMetaClass(mc);"); //try/finally is not supported
137 } else {
138 javaText.append("java.lang.Object res = ((groovy.lang.MetaClassImpl)emc).invokeStaticMethod(clazz, \"").append(EVAL_NAME)
139 .append("\", ").
140 append("resVals").append(");\n");
141 javaText.append("groovy.lang.GroovySystem.getMetaClassRegistry().setMetaClass(clazz, mc);\n");
143 javaText.append("res");
145 final PsiElementFactory factory = JavaPsiFacade.getInstance(toEval.getProject()).getElementFactory();
146 JavaCodeFragment result = factory.createCodeBlockCodeFragment(javaText.toString(), null, true);
147 hideInternalJavaVariables(factory, result);
148 if (contextClass != null) {
149 result.setThisType(factory.createType(contextClass));
151 return result;
154 private static void hideInternalJavaVariables(final PsiElementFactory factory, JavaCodeFragment result) {
155 final String varPrefix = "_$$_$$$_$$$$$$$$$_" + new Random().nextInt(42);
156 result.accept(new PsiRecursiveElementWalkingVisitor() {
157 @Override
158 public void visitElement(PsiElement element) {
159 if (element instanceof PsiReferenceExpression && ((PsiReferenceExpression)element).resolve() instanceof PsiLocalVariable) {
160 element.replace(factory.createExpressionFromText(varPrefix + element.getText(), element));
162 super.visitElement(element);
165 result.accept(new PsiRecursiveElementWalkingVisitor() {
166 @Override
167 public void visitElement(PsiElement element) {
168 if (element instanceof PsiLocalVariable) {
169 ((PsiLocalVariable)element).setName(varPrefix + ((PsiLocalVariable)element).getName());
171 super.visitElement(element);
176 public static Pair<Map<String, String>, GroovyFile> externalParameters(String text, @NotNull final PsiElement context) {
177 final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(context.getProject());
178 final GroovyFile toEval = factory.createGroovyFile(text, false, context);
180 final GrClosableBlock closure = PsiTreeUtil.getParentOfType(context, GrClosableBlock.class);
181 final Map<String, String> parameters = new THashMap<String, String>();
182 toEval.accept(new GroovyRecursiveElementVisitor() {
183 public void visitReferenceExpression(GrReferenceExpression referenceExpression) {
184 super.visitReferenceExpression(referenceExpression);
185 PsiElement resolved = referenceExpression.resolve();
187 if (resolved instanceof PsiMethod && "getDelegate".equals(((PsiMethod) resolved).getName()) && closure != null) {
188 replaceWithReference(referenceExpression, "owner");
189 return;
192 if (resolved instanceof GrField && !referenceExpression.isQualified()) {
193 replaceWithReference(referenceExpression, (closure == null ? "delegate" : "owner") + "." + referenceExpression.getReferenceName());
194 return;
197 if (resolved instanceof GrVariableBase && !(resolved instanceof GrField) && !PsiTreeUtil.isAncestor(toEval, resolved, false)) {
198 final String name = ((GrVariableBase)resolved).getName();
199 if (resolved instanceof ClosureSyntheticParameter && PsiTreeUtil.isAncestor(toEval, ((ClosureSyntheticParameter) resolved).getClosure(), false)) {
200 return;
202 String value;
203 if (closure != null &&
204 PsiTreeUtil.findCommonParent(resolved, closure) != closure &&
205 !(resolved instanceof ClosureSyntheticParameter)) {
206 // Evaluating inside closure for outer variable definitions
207 // All non-local variables are accessed by references
208 value = "this." + name;
209 } else {
210 value = name;
212 parameters.put(name, value);
216 @Override
217 public void visitThisExpression(final GrThisReferenceExpression thisExpression) {
218 super.visitThisExpression(thisExpression);
219 replaceWithReference(thisExpression, closure == null ? "delegate" : "owner");
222 @Override
223 public void visitSuperExpression(final GrSuperReferenceExpression superExpression) {
224 super.visitSuperExpression(superExpression);
225 replaceWithReference(superExpression, closure == null ? "delegate" : "owner");
228 private void replaceWithReference(GrExpression expr, final String exprText) {
229 final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(expr.getProject());
230 visitReferenceExpression((GrReferenceExpression)expr.replaceWithExpression(factory.createExpressionFromText(exprText), false));
233 public void visitCodeReferenceElement(GrCodeReferenceElement refElement) {
234 if (refElement.getQualifier() != null) {
235 super.visitCodeReferenceElement(refElement);
236 } else {
237 PsiElement resolved = refElement.resolve();
238 if (resolved instanceof PsiClass) {
239 String qName = ((PsiClass)resolved).getQualifiedName();
240 if (qName != null) {
241 int dotIndex = qName.lastIndexOf(".");
242 if (dotIndex < 0) return;
243 String packageName = qName.substring(0, dotIndex);
244 refElement.setQualifier(factory.createReferenceElementFromText(packageName));
250 return Pair.create(parameters, toEval);
253 private static String stripImports(String text, GroovyFile toEval) {
254 GrImportStatement[] imports = toEval.getImportStatements();
255 for (int i = imports.length - 1; i >= 0; i--) {
256 TextRange range = imports[i].getTextRange();
257 text = text.substring(0, range.getStartOffset()) + text.substring(range.getEndOffset(), text.length());
259 return StringUtil.escapeStringCharacters(text);
262 public JavaCodeFragment createPresentationCodeFragment(TextWithImports item, PsiElement context, Project project) {
263 GroovyCodeFragment result = new GroovyCodeFragment(project, item.getText());
264 result.setContext(context);
265 return result;
268 private static boolean isStaticContext(PsiElement context) {
269 PsiElement parent = context;
270 while (parent != null) {
271 if (parent instanceof PsiModifierListOwner && ((PsiModifierListOwner)parent).hasModifierProperty(PsiModifier.STATIC)) return true;
272 if (parent instanceof GrTypeDefinition || parent instanceof GroovyFile) return false;
273 parent = parent.getParent();
276 return false;
279 public boolean isContextAccepted(PsiElement context) {
280 return context != null && context.getLanguage().equals(GroovyFileType.GROOVY_FILE_TYPE.getLanguage());
283 public String getDisplayName() {
284 return "Groovy";
287 public LanguageFileType getFileType() {
288 return GroovyFileType.GROOVY_FILE_TYPE;