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