fixing possible stack overflow in case of a cyclic constructor invocation in groovy...
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / compiler / generator / GroovyToJavaGenerator.java
1 /*
2  * Copyright 2000-2009 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 org.jetbrains.plugins.groovy.compiler.generator;
17
18 import com.intellij.compiler.CompilerConfiguration;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.compiler.CompileContext;
21 import com.intellij.openapi.compiler.CompilerManager;
22 import com.intellij.openapi.compiler.TimestampValidityState;
23 import com.intellij.openapi.compiler.ValidityState;
24 import com.intellij.openapi.compiler.options.ExcludedEntriesConfiguration;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.module.Module;
27 import com.intellij.openapi.progress.ProgressIndicator;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.util.Computable;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.openapi.vfs.VfsUtil;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.psi.*;
34 import com.intellij.psi.util.MethodSignature;
35 import com.intellij.psi.util.MethodSignatureUtil;
36 import com.intellij.util.ArrayUtil;
37 import com.intellij.util.containers.CollectionFactory;
38 import com.intellij.util.containers.HashSet;
39 import gnu.trove.THashSet;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42 import org.jetbrains.plugins.groovy.GroovyFileType;
43 import org.jetbrains.plugins.groovy.compiler.GroovyCompilerConfiguration;
44 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
45 import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
46 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
47 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
48 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrConstructorInvocation;
49 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
50 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
51 import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
52 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.*;
53 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrConstructor;
54 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant;
55 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMembersDeclaration;
56 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.GrTopStatement;
57 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
58 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.packaging.GrPackageDefinition;
59 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
60 import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
61 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrSyntheticMethodImplementation;
62 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
63 import org.jetbrains.plugins.groovy.util.GroovyUtils;
64 import org.jetbrains.plugins.groovy.util.containers.CharTrie;
65
66 import java.io.*;
67 import java.util.*;
68
69 /**
70  * @author: Dmitry.Krasilschikov
71  * @date: 03.05.2007
72  */
73 public class GroovyToJavaGenerator {
74   private static final Map<String, String> typesToInitialValues = new HashMap<String, String>();
75   private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.compiler.generator.GroovyToJavaGenerator");
76
77   static {
78     typesToInitialValues.put("boolean", "false");
79     typesToInitialValues.put("int", "0");
80     typesToInitialValues.put("short", "0");
81     typesToInitialValues.put("long", "0L");
82     typesToInitialValues.put("byte", "0");
83     typesToInitialValues.put("char", "'c'");
84     typesToInitialValues.put("double", "0D");
85     typesToInitialValues.put("float", "0F");
86     typesToInitialValues.put("void", "");
87   }
88
89   private static final String[] JAVA_MODIFIERS = new String[]{
90       PsiModifier.PUBLIC,
91       PsiModifier.PROTECTED,
92       PsiModifier.PRIVATE,
93       PsiModifier.PACKAGE_LOCAL,
94       PsiModifier.STATIC,
95       PsiModifier.ABSTRACT,
96       PsiModifier.FINAL,
97       PsiModifier.NATIVE,
98       PsiModifier.SYNCHRONIZED,
99       PsiModifier.STRICTFP,
100       PsiModifier.TRANSIENT,
101       PsiModifier.VOLATILE
102   };
103
104   private static final CharSequence PREFIX_SEPARATOR = "/";
105   private final CompileContext myContext;
106   private final Project myProject;
107
108   public GroovyToJavaGenerator(Project project, CompileContext context) {
109     myProject = project;
110     myContext = context;
111   }
112
113   public GenerationItem[] getGenerationItems(CompileContext context) {
114     if (GroovyCompilerConfiguration.getInstance(myProject).isUseGroovycStubs()) {
115       return new GenerationItem[0];
116     }
117
118     List<GenerationItem> generationItems = new ArrayList<GenerationItem>();
119     GenerationItem item;
120     final CompilerManager compilerManager = CompilerManager.getInstance(myProject);
121     final CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(myProject);
122     final ExcludedEntriesConfiguration excluded = GroovyCompilerConfiguration.getExcludeConfiguration(myProject);
123     for (VirtualFile file : getGroovyFilesToGenerate(context)) {
124       if (compilerManager.isExcludedFromCompilation(file)) continue;
125       if (excluded.isExcluded(file)) continue;
126       if (compilerConfiguration.isResourceFile(file)) continue;
127
128       final Module module = getModuleByFile(context, file);
129       if (!GroovyUtils.isSuitableModule(module)) {
130         continue;
131       }
132
133       final GroovyFileBase psiFile = findPsiFile(file);
134       GrTopStatement[] statements = getTopStatementsInReadAction(psiFile);
135
136       boolean needCreateTopLevelClass = !needsCreateClassFromFileName(statements);
137
138       String prefix = "";
139       if (statements.length > 0 && statements[0] instanceof GrPackageDefinition) {
140         prefix = getJavaClassPackage((GrPackageDefinition) statements[0]);
141       }
142
143       //top level class
144       if (needCreateTopLevelClass) {
145         generationItems.add(new GenerationItem(prefix + file.getNameWithoutExtension() + "." + "java", module, new TimestampValidityState(file.getTimeStamp()),
146                                                file));
147       }
148
149       GrTypeDefinition[] typeDefinitions = ApplicationManager.getApplication().runReadAction(new Computable<GrTypeDefinition[]>() {
150         public GrTypeDefinition[] compute() {
151           return psiFile.getTypeDefinitions();
152         }
153       });
154
155       for (GrTypeDefinition typeDefinition : typeDefinitions) {
156         item = new GenerationItem(prefix + typeDefinition.getName() + "." + "java", module, new TimestampValidityState(file.getTimeStamp()),
157                                   file);
158         generationItems.add(item);
159       }
160     }
161     return generationItems.toArray(new GenerationItem[generationItems.size()]);
162   }
163
164   protected Module getModuleByFile(CompileContext context, VirtualFile file) {
165     return context.getModuleByFile(file);
166   }
167
168   protected VirtualFile[] getGroovyFilesToGenerate(final CompileContext context) {
169     final HashSet<VirtualFile> set = new HashSet<VirtualFile>();
170     ApplicationManager.getApplication().runReadAction(new Runnable() {
171       public void run() {
172         set.addAll(Arrays.asList(context.getProjectCompileScope().getFiles(GroovyFileType.GROOVY_FILE_TYPE, true)));
173       }
174     });
175
176     return VfsUtil.toVirtualFileArray(set);
177   }
178
179   public GenerationItem[] generate(GenerationItem[] itemsToGenerate, VirtualFile outputRootDirectory) {
180     List<GenerationItem> generatedItems = new ArrayList<GenerationItem>();
181     Map<String, GenerationItem> pathsToItemsMap = new HashMap<String, GenerationItem>();
182
183     //puts items witch can be generated
184     for (GenerationItem item : itemsToGenerate) {
185       pathsToItemsMap.put(item.getPath(), item);
186     }
187
188     Set<VirtualFile> vFiles = new HashSet<VirtualFile>();
189     for (GenerationItem item : itemsToGenerate) {
190       vFiles.add(item.getVFile());
191     }
192
193     for (VirtualFile vFile : vFiles) {
194       //generate java classes form groovy source files
195       List<String> generatedJavaFilesRelPaths = generateItems(vFile, outputRootDirectory);
196       for (String relPath : generatedJavaFilesRelPaths) {
197         GenerationItem generationItem = pathsToItemsMap.get(relPath);
198         if (generationItem != null)
199           generatedItems.add(generationItem);
200       }
201     }
202
203     return generatedItems.toArray(new GenerationItem[generatedItems.size()]);
204   }
205
206   private GroovyFile findPsiFile(final VirtualFile virtualFile) {
207     final GroovyFile[] myFindPsiFile = new GroovyFile[1];
208
209     ApplicationManager.getApplication().runReadAction(new Runnable() {
210       public void run() {
211         myFindPsiFile[0] = (GroovyFile) PsiManager.getInstance(myProject).findFile(virtualFile);
212       }
213     });
214
215     assert myFindPsiFile[0] != null;
216     return myFindPsiFile[0];
217   }
218
219   //virtualFile -> PsiFile
220   public List<String> generateItems(final VirtualFile item, final VirtualFile outputRootDirectory) {
221     ProgressIndicator indicator = getProcessIndicator();
222     if (indicator != null) indicator.setText("Generating stubs for " + item.getName() + "...");
223
224     if (LOG.isDebugEnabled()) {
225       LOG.debug("Generating stubs for " + item.getName() + "...");
226     }
227
228     final GroovyFile file = findPsiFile(item);
229
230     List<String> generatedJavaFilesRelPaths = ApplicationManager.getApplication().runReadAction(new Computable<List<String>>() {
231       public List<String> compute() {
232         return generate(file, outputRootDirectory);
233       }
234     });
235
236     assert generatedJavaFilesRelPaths != null;
237
238     return generatedJavaFilesRelPaths;
239   }
240
241   protected ProgressIndicator getProcessIndicator() {
242     return myContext.getProgressIndicator();
243   }
244
245   private List<String> generate(final GroovyFile file, VirtualFile outputRootDirectory) {
246     List<String> generatedItemsRelativePaths = new ArrayList<String>();
247
248     GrTopStatement[] statements = getTopStatementsInReadAction(file);
249
250     GrPackageDefinition packageDefinition = null;
251     if (statements.length > 0 && statements[0] instanceof GrPackageDefinition) {
252       packageDefinition = (GrPackageDefinition) statements[0];
253     }
254
255     Set<String> classNames = new THashSet<String>();
256     for (final GrTypeDefinition typeDefinition : file.getTypeDefinitions()) {
257       classNames.add(typeDefinition.getName());
258     }
259
260     if (file.isScript()) {
261       VirtualFile virtualFile = file.getVirtualFile();
262       assert virtualFile != null;
263       String fileDefinitionName = virtualFile.getNameWithoutExtension();
264       if (!classNames.contains(StringUtil.capitalize(fileDefinitionName)) &&
265           !classNames.contains(StringUtil.decapitalize(fileDefinitionName))) {
266         final PsiClass scriptClass = file.getScriptClass();
267         if (scriptClass != null) {
268           generatedItemsRelativePaths.add(createJavaSourceFile(outputRootDirectory, scriptClass, packageDefinition));
269         }
270       }
271     }
272
273     for (final GrTypeDefinition typeDefinition : file.getTypeDefinitions()) {
274       generatedItemsRelativePaths.add(createJavaSourceFile(outputRootDirectory, typeDefinition, packageDefinition));
275     }
276
277     return generatedItemsRelativePaths;
278   }
279
280   private static String getJavaClassPackage(@Nullable GrPackageDefinition packageDefinition) {
281     if (packageDefinition == null) return "";
282
283     String prefix = packageDefinition.getPackageName();
284     prefix = prefix.replace(".", PREFIX_SEPARATOR);
285     prefix += PREFIX_SEPARATOR;
286
287     return prefix;
288   }
289
290   private String createJavaSourceFile(VirtualFile outputRootDirectory, @NotNull PsiClass typeDefinition, GrPackageDefinition packageDefinition) {
291     StringBuffer text = new StringBuffer();
292     writeTypeDefinition(text, typeDefinition, packageDefinition, true);
293
294     String prefix = getJavaClassPackage(packageDefinition);
295     String outputDir = outputRootDirectory.getPath();
296     String fileName = typeDefinition.getName() + "." + "java";
297
298     String prefixWithoutSeparator = prefix;
299
300     if (!"".equals(prefix)) {
301       prefixWithoutSeparator = prefix.substring(0, prefix.length() - PREFIX_SEPARATOR.length());
302       new File(outputDir, prefixWithoutSeparator).mkdirs();
303     }
304
305     File myFile;
306     if (!"".equals(prefix))
307       myFile = new File(outputDir + File.separator + prefixWithoutSeparator, fileName);
308     else
309       myFile = new File(outputDir, fileName);
310
311     BufferedWriter writer = null;
312     try {
313       Writer fileWriter = new FileWriter(myFile);
314       writer = new BufferedWriter(fileWriter);
315       writer.write(text.toString());
316     } catch (IOException e) {
317       LOG.error(e);
318     } finally {
319       try {
320         assert writer != null;
321         writer.close();
322       } catch (IOException e) {
323         LOG.error(e);
324       }
325     }
326     return prefix + fileName;
327   }
328
329   private static GrTopStatement[] getTopStatementsInReadAction(final GroovyFileBase myPsiFile) {
330     if (myPsiFile == null) return new GrTopStatement[0];
331
332     return ApplicationManager.getApplication().runReadAction(new Computable<GrTopStatement[]>() {
333       public GrTopStatement[] compute() {
334         return myPsiFile.getTopStatements();
335       }
336     });
337   }
338
339   private static boolean needsCreateClassFromFileName(GrTopStatement[] statements) {
340     boolean isOnlyInnerTypeDef = true;
341     for (GrTopStatement statement : statements) {
342       if (!(statement instanceof GrTypeDefinition || statement instanceof GrImportStatement || statement instanceof GrPackageDefinition)) {
343         isOnlyInnerTypeDef = false;
344         break;
345       }
346     }
347     return isOnlyInnerTypeDef;
348   }
349
350   private void writeTypeDefinition(StringBuffer text, @NotNull PsiClass typeDefinition,
351                                    @Nullable GrPackageDefinition packageDefinition, boolean toplevel) {
352     final boolean isScript = typeDefinition instanceof GroovyScriptClass;
353
354     writePackageStatement(text, packageDefinition);
355
356     GrMembersDeclaration[] membersDeclarations = typeDefinition instanceof GrTypeDefinition ? ((GrTypeDefinition) typeDefinition).getMemberDeclarations() : GrMembersDeclaration.EMPTY_ARRAY; //todo
357
358     boolean isClassDef = typeDefinition instanceof GrClassDefinition;
359     boolean isInterface = typeDefinition instanceof GrInterfaceDefinition;
360     boolean isEnum = typeDefinition instanceof GrEnumTypeDefinition;
361     boolean isAtInterface = typeDefinition instanceof GrAnnotationTypeDefinition;
362
363     writeClassModifiers(text, typeDefinition.getModifierList(), typeDefinition.isInterface(), toplevel);
364
365     if (isInterface) text.append("interface");
366     else if (isEnum) text.append("enum");
367     else if (isAtInterface) text.append("@interface");
368     else text.append("class");
369
370     text.append(" ").append(typeDefinition.getName());
371
372     appendTypeParameters(text, typeDefinition);
373
374     text.append(" ");
375
376     if (isScript) {
377       text.append("extends groovy.lang.Script ");
378     } else if (!isEnum && !isAtInterface) {
379       final PsiClassType[] extendsClassesTypes = typeDefinition.getExtendsListTypes();
380
381       if (extendsClassesTypes.length > 0) {
382         text.append("extends ").append(getTypeText(extendsClassesTypes[0], typeDefinition, false)).append(" ");
383       }
384       PsiClassType[] implementsTypes = typeDefinition.getImplementsListTypes();
385
386       if (implementsTypes.length > 0) {
387         text.append(isInterface ? "extends " : "implements ");
388         int i = 0;
389         while (i < implementsTypes.length) {
390           if (i > 0) text.append(", ");
391           text.append(getTypeText(implementsTypes[i], typeDefinition, false)).append(" ");
392           i++;
393         }
394       }
395     }
396
397     text.append("{");
398
399     if (isEnum) {
400       writeEnumConstants(text, (GrEnumTypeDefinition) typeDefinition);
401     }
402
403     Set<MethodSignature> methodSignatures = new HashSet<MethodSignature>();
404
405
406     List<PsiMethod> methods = new ArrayList<PsiMethod>();
407     methods.addAll(Arrays.asList(typeDefinition.getMethods()));
408     if (isClassDef) {
409       final PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory();
410       methods.add(factory.createMethodFromText("public groovy.lang.MetaClass getMetaClass() {}", null));
411       methods.add(factory.createMethodFromText("public void setMetaClass(groovy.lang.MetaClass mc) {}", null));
412       methods.add(factory.createMethodFromText("public Object invokeMethod(String name, Object args) {}", null));
413       methods.add(factory.createMethodFromText("public Object getProperty(String propertyName) {}", null));
414       methods.add(factory.createMethodFromText("public void setProperty(String propertyName, Object newValue) {}", null));
415     }
416
417
418     for (PsiMethod method : methods) {
419       if (method instanceof GrSyntheticMethodImplementation) {
420         continue;
421       }
422
423       if (method instanceof GrConstructor) {
424         writeConstructor(text, (GrConstructor) method, isEnum);
425         continue;
426       }
427
428       PsiParameter[] parameters = method.getParameterList().getParameters();
429       if (parameters.length > 0) {
430         PsiParameter[] parametersCopy = new PsiParameter[parameters.length];
431         PsiType[] parameterTypes = new PsiType[parameters.length];
432         for (int i = 0; i < parameterTypes.length; i++) {
433           parametersCopy[i] = parameters[i];
434           parameterTypes[i] = parameters[i].getType();
435         }
436
437         for (int i = parameters.length - 1; i >= 0; i--) {
438           MethodSignature signature = MethodSignatureUtil.createMethodSignature(method.getName(), parameterTypes, method.getTypeParameters(), PsiSubstitutor.EMPTY);
439           if (methodSignatures.add(signature)) {
440             writeMethod(text, method, parametersCopy);
441           }
442
443           PsiParameter parameter = parameters[i];
444           if (!(parameter instanceof GrParameter) || !((GrParameter) parameter).isOptional()) break;
445           parameterTypes = ArrayUtil.remove(parameterTypes, parameterTypes.length - 1);
446           parametersCopy = ArrayUtil.remove(parametersCopy, parametersCopy.length - 1);
447         }
448       } else {
449         MethodSignature signature = method.getSignature(PsiSubstitutor.EMPTY);
450         if (methodSignatures.add(signature)) {
451           writeMethod(text, method, parameters);
452         }
453       }
454     }
455
456     for (GrMembersDeclaration declaration : membersDeclarations) {
457       if (declaration instanceof GrVariableDeclaration) {
458         writeVariableDeclarations(text, (GrVariableDeclaration) declaration);
459       }
460     }
461     for (PsiClass inner : typeDefinition.getInnerClasses()) {
462       writeTypeDefinition(text, inner, null, false);
463       text.append("\n");
464     }
465
466     text.append("}");
467   }
468
469   private static void appendTypeParameters(StringBuffer text, PsiTypeParameterListOwner typeParameterListOwner) {
470     if (typeParameterListOwner.hasTypeParameters()) {
471       text.append("<");
472       PsiTypeParameter[] parameters = typeParameterListOwner.getTypeParameters();
473       for (int i = 0; i < parameters.length; i++) {
474         if (i > 0) text.append(", ");
475         PsiTypeParameter parameter = parameters[i];
476         text.append(parameter.getName());
477         PsiClassType[] extendsListTypes = parameter.getExtendsListTypes();
478         if (extendsListTypes.length > 0) {
479           text.append(" extends ");
480           for (int j = 0; j < extendsListTypes.length; j++) {
481             if (j > 0) text.append(" & ");
482             text.append(getTypeText(extendsListTypes[j], typeParameterListOwner, false));
483           }
484         }
485       }
486       text.append(">");
487     }
488   }
489
490   private static void writeEnumConstants(StringBuffer text, GrEnumTypeDefinition enumDefinition) {
491     text.append("\n  ");
492     GrEnumConstant[] enumConstants = enumDefinition.getEnumConstants();
493     for (int i = 0; i < enumConstants.length; i++) {
494       if (i > 0) text.append(", ");
495       GrEnumConstant enumConstant = enumConstants[i];
496       text.append(enumConstant.getName());
497       PsiMethod constructor = enumConstant.resolveConstructor();
498       if (constructor != null) {
499         text.append("(");
500         writeStubConstructorInvocation(text, constructor, PsiSubstitutor.EMPTY);
501         text.append(")");
502       }
503
504       GrTypeDefinitionBody block = enumConstant.getAnonymousBlock();
505       if (block != null) {
506         text.append("{\n");
507         for (PsiMethod method : block.getMethods()) {
508           writeMethod(text, method, method.getParameterList().getParameters());
509         }
510         text.append("}");
511       }
512     }
513     text.append(";");
514   }
515
516   private static void writeStubConstructorInvocation(StringBuffer text, PsiMethod constructor, PsiSubstitutor substitutor) {
517     final PsiParameter[] superParams = constructor.getParameterList().getParameters();
518     for (int j = 0; j < superParams.length; j++) {
519       if (j > 0) text.append(", ");
520       String typeText = getTypeText(substitutor.substitute(superParams[j].getType()), null, false);
521       text.append("(").append(typeText).append(")").append(getDefaultValueText(typeText));
522     }
523   }
524
525   private static void writePackageStatement(StringBuffer text, GrPackageDefinition packageDefinition) {
526     if (packageDefinition != null) {
527       text.append("package ");
528       text.append(packageDefinition.getPackageName());
529       text.append(";");
530       text.append("\n");
531       text.append("\n");
532     }
533   }
534
535   private static void writeConstructor(final StringBuffer text, final GrConstructor constructor, boolean isEnum) {
536     text.append("\n");
537     text.append("  ");
538     if (!isEnum) {
539       text.append("public ");
540       //writeMethodModifiers(text, constructor.getModifierList(), JAVA_MODIFIERS);
541     }
542
543     /************* name **********/
544     //append constructor name
545     text.append(constructor.getName());
546
547     /************* parameters **********/
548     GrParameter[] parameterList = constructor.getParameters();
549
550     text.append("(");
551
552     for (int i = 0; i < parameterList.length; i++) {
553       if (i > 0) text.append(", ");
554
555       GrParameter parameter = parameterList[i];
556
557       text.append(getTypeText(parameter.getTypeElementGroovy())).append(" ").append(parameter.getName());
558     }
559
560     text.append(") ");
561
562     final Set<String> throwsTypes = collectThrowsTypes(constructor, new THashSet<PsiMethod>());
563     if (!throwsTypes.isEmpty()) {
564       text.append("throws ").append(StringUtil.join(throwsTypes, ", ")).append(" ");
565     }
566
567     /************* body **********/
568
569     text.append("{\n");
570     final GrConstructorInvocation invocation = constructor.getChainingConstructorInvocation();
571     if (invocation != null) {
572       final GroovyResolveResult resolveResult = resolveChainingConstructor(constructor);
573       if (resolveResult != null) {
574         text.append("    ");
575         text.append(invocation.isSuperCall() ? "super(" : "this(");
576         writeStubConstructorInvocation(text, (PsiMethod) resolveResult.getElement(), resolveResult.getSubstitutor());
577         text.append(");");
578       }
579     }
580
581     text.append("\n  }\n");
582   }
583
584   private static Set<String> collectThrowsTypes(GrConstructor constructor, Set<PsiMethod> visited) {
585     final GroovyResolveResult resolveResult = resolveChainingConstructor(constructor);
586     if (resolveResult == null) {
587       return Collections.emptySet();
588     }
589
590
591     final PsiSubstitutor substitutor = resolveResult.getSubstitutor();
592     final PsiMethod chainedConstructor = (PsiMethod)resolveResult.getElement();
593     assert chainedConstructor != null;
594
595     if (!visited.add(chainedConstructor)) {
596       return Collections.emptySet();
597     }
598
599     final Set<String> result = CollectionFactory.newTroveSet();
600     for (PsiClassType type : chainedConstructor.getThrowsList().getReferencedTypes()) {
601       result.add(getTypeText(substitutor.substitute(type), null, false));
602     }
603
604     if (chainedConstructor instanceof GrConstructor) {
605       result.addAll(collectThrowsTypes((GrConstructor)chainedConstructor, visited));
606     }
607     return result;
608   }
609
610   @Nullable
611   private static GroovyResolveResult resolveChainingConstructor(GrConstructor constructor) {
612     final GrConstructorInvocation constructorInvocation = constructor.getChainingConstructorInvocation();
613     if (constructorInvocation == null) {
614       return null;
615     }
616
617     GroovyResolveResult resolveResult = constructorInvocation.resolveConstructorGenerics();
618     if (resolveResult.getElement() != null) {
619       return resolveResult;
620     }
621
622     final GroovyResolveResult[] results = constructorInvocation.multiResolveConstructor();
623     if (results.length > 0) {
624       int i = 0;
625       while (results.length > i+1) {
626         final PsiMethod candidate = (PsiMethod)results[i].getElement();
627         final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(constructor.getProject()).getResolveHelper();
628         if (candidate != null && candidate != constructor && resolveHelper.isAccessible(candidate, constructorInvocation, null)) {
629           break;
630         }
631         i++;
632       }
633       return results[i];
634     }
635     return null;
636   }
637
638   private static String getDefaultValueText(String typeCanonicalText) {
639     final String result = typesToInitialValues.get(typeCanonicalText);
640     if (result == null) return "null";
641     return result;
642   }
643
644   private static void writeVariableDeclarations(StringBuffer text, GrVariableDeclaration variableDeclaration) {
645     final String type = getTypeText(variableDeclaration.getTypeElementGroovy());
646     final String initializer = getDefaultValueText(type);
647
648     final GrModifierList modifierList = variableDeclaration.getModifierList();
649     final PsiNameHelper nameHelper = JavaPsiFacade.getInstance(variableDeclaration.getProject()).getNameHelper();
650     for (final GrVariable variable : variableDeclaration.getVariables()) {
651       String name = variable.getName();
652       if (!nameHelper.isIdentifier(name)) {
653         continue; //does not have a java image
654       }
655
656       text.append("\n  ");
657       writeFieldModifiers(text, modifierList, JAVA_MODIFIERS);
658
659       //type
660       text.append(type).append(" ").append(name).append(" = ").append(initializer).append(";\n");
661     }
662   }
663
664   private static void writeMethod(StringBuffer text, PsiMethod method, final PsiParameter[] parameters) {
665     if (method == null) return;
666     String name = method.getName();
667     if (!JavaPsiFacade.getInstance(method.getProject()).getNameHelper().isIdentifier(name))
668       return; //does not have a java image
669
670     boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT);
671
672     PsiModifierList modifierList = method.getModifierList();
673
674     text.append("\n");
675     text.append("  ");
676     writeMethodModifiers(text, modifierList, JAVA_MODIFIERS);
677     if (method.hasTypeParameters()) {
678       appendTypeParameters(text, method);
679       text.append(" ");
680     }
681
682     //append return type
683     PsiType retType = method.getReturnType();
684     if (retType == null) retType = TypesUtil.getJavaLangObject(method);
685
686     text.append(getTypeText(retType, method, false));
687     text.append(" ");
688
689     //append method name
690     text.append(name);
691
692     /************* parameters **********/
693
694     text.append("(");
695
696     //writes myParameters
697     int i = 0;
698     while (i < parameters.length) {
699       PsiParameter parameter = parameters[i];
700       if (parameter == null) continue;
701
702       if (i > 0) text.append(", ");  //append ','
703
704       text.append(getTypeText(parameter.getType(), parameter, i == parameters.length - 1));
705       text.append(" ");
706       text.append(parameter.getName());
707
708       i++;
709     }
710     text.append(")");
711     text.append(" ");
712
713     if (!isAbstract) {
714       /************* body **********/
715       text.append("{\n");
716       text.append("    return ");
717
718       text.append(getDefaultValueText(getTypeText(retType, method, false)));
719
720       text.append(";");
721
722       text.append("\n  }");
723     } else {
724       text.append(";");
725     }
726     text.append("\n");
727   }
728
729   private static boolean writeMethodModifiers(StringBuffer text, PsiModifierList modifierList, String[] modifiers) {
730     boolean wasAddedModifiers = false;
731     for (String modifierType : modifiers) {
732       if (modifierList.hasModifierProperty(modifierType)) {
733         text.append(modifierType);
734         text.append(" ");
735         wasAddedModifiers = true;
736       }
737     }
738     return wasAddedModifiers;
739   }
740
741   private static void writeFieldModifiers(StringBuffer text, GrModifierList modifierList, String[] modifiers) {
742     for (String modifierType : modifiers) {
743       if (modifierList.hasModifierProperty(modifierType)) {
744         text.append(modifierType);
745         text.append(" ");
746       }
747     }
748   }
749
750   private static void writeClassModifiers(StringBuffer text,
751                                              @Nullable PsiModifierList modifierList, boolean isInterface, boolean toplevel) {
752     if (modifierList == null || modifierList.hasModifierProperty(PsiModifier.PUBLIC)) {
753       text.append("public ");
754     }
755
756     if (modifierList != null) {
757       List<String> allowedModifiers = new ArrayList<String>();
758       allowedModifiers.add(PsiModifier.FINAL);
759       if (!toplevel) {
760         allowedModifiers.addAll(Arrays.asList(PsiModifier.PROTECTED, PsiModifier.PRIVATE, PsiModifier.STATIC));
761       }
762       if (!isInterface) {
763         allowedModifiers.add(PsiModifier.ABSTRACT);
764       }
765
766       for (String modifierType : allowedModifiers) {
767         if (modifierList.hasModifierProperty(modifierType)) {
768           text.append(modifierType).append(" ");
769         }
770       }
771     }
772   }
773
774   private static String getTypeText(GrTypeElement typeElement) {
775     if (typeElement == null) {
776       return CommonClassNames.JAVA_LANG_OBJECT;
777     }
778
779     return getTypeText(typeElement.getType(), typeElement, false);
780   }
781
782   private static String getTypeText(@Nullable PsiType type, @Nullable PsiElement context, boolean allowVarargs) {
783     if (context != null && type instanceof PsiClassType) {
784       final String accessible = findAccessibleSuperClass(context, ((PsiClassType)type).resolve());
785       if (accessible != null) {
786         return accessible;
787       }
788     }
789
790     if (type instanceof PsiArrayType) {
791       String componentText = getTypeText(((PsiArrayType)type).getComponentType(), context, false);
792       if (allowVarargs && type instanceof PsiEllipsisType) {
793         return componentText + "...";
794       }
795       return componentText + "[]";
796     }
797
798     if (type == null) {
799       return CommonClassNames.JAVA_LANG_OBJECT;
800     }
801
802     String canonicalText = type.getCanonicalText();
803     return canonicalText != null ? canonicalText : type.getPresentableText();
804   }
805
806   @Nullable
807   private static String findAccessibleSuperClass(PsiElement context, @Nullable PsiClass initialClass) {
808     if (initialClass == null) {
809       return null;
810     }
811
812     PsiClass curClass = initialClass;
813     final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(context.getProject()).getResolveHelper();
814     while (curClass != null && !resolveHelper.isAccessible(curClass, context, null)) {
815       curClass = curClass.getSuperClass();
816     }
817     if (curClass != null && !initialClass.isEquivalentTo(curClass)) {
818       final String qname = curClass.getQualifiedName();
819       if (qname != null) {
820         return qname;
821       }
822     }
823     return null;
824   }
825
826   CharTrie myTrie = new CharTrie();
827
828   public class GenerationItem {
829     ValidityState myState;
830     final Module myModule;
831     public int myHashCode;
832     private final VirtualFile myVFile;
833
834     public GenerationItem(String path, Module module, ValidityState state, VirtualFile vFile) {
835       myVFile = vFile;
836       myModule = module;
837       myState = state;
838       myHashCode = myTrie.getHashCode(path);
839     }
840
841     public String getPath() {
842       return myTrie.getString(myHashCode);
843     }
844
845     public Module getModule() {
846       return myModule;
847     }
848
849     public VirtualFile getVFile() {
850       return myVFile;
851     }
852   }
853 }