a13aeb826140bd03b8a2ada3b0b1c7cfb598bbb4
[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);
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) {
585     final GroovyResolveResult resolveResult = resolveChainingConstructor(constructor);
586     if (resolveResult == null) {
587       return Collections.emptySet();
588     }
589
590     final Set<String> result = CollectionFactory.newTroveSet();
591
592     final PsiSubstitutor substitutor = resolveResult.getSubstitutor();
593     final PsiMethod chainedConstructor = (PsiMethod)resolveResult.getElement();
594     assert chainedConstructor != null;
595
596     for (PsiClassType type : chainedConstructor.getThrowsList().getReferencedTypes()) {
597       result.add(getTypeText(substitutor.substitute(type), null, false));
598     }
599
600     if (chainedConstructor instanceof GrConstructor) {
601       result.addAll(collectThrowsTypes((GrConstructor)chainedConstructor));
602     }
603     return result;
604   }
605
606   @Nullable
607   private static GroovyResolveResult resolveChainingConstructor(GrConstructor constructor) {
608     final GrConstructorInvocation constructorInvocation = constructor.getChainingConstructorInvocation();
609     if (constructorInvocation == null) {
610       return null;
611     }
612
613     GroovyResolveResult resolveResult = constructorInvocation.resolveConstructorGenerics();
614     if (resolveResult.getElement() != null) {
615       return resolveResult;
616     }
617
618     final GroovyResolveResult[] results = constructorInvocation.multiResolveConstructor();
619     if (results.length > 0) {
620       int i = 0;
621       while (results.length > i+1) {
622         final PsiMethod candidate = (PsiMethod)results[i].getElement();
623         final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(constructor.getProject()).getResolveHelper();
624         if (candidate != null && candidate != constructor && resolveHelper.isAccessible(candidate, constructorInvocation, null)) {
625           break;
626         }
627         i++;
628       }
629       return results[i];
630     }
631     return null;
632   }
633
634   private static String getDefaultValueText(String typeCanonicalText) {
635     final String result = typesToInitialValues.get(typeCanonicalText);
636     if (result == null) return "null";
637     return result;
638   }
639
640   private static void writeVariableDeclarations(StringBuffer text, GrVariableDeclaration variableDeclaration) {
641     final String type = getTypeText(variableDeclaration.getTypeElementGroovy());
642     final String initializer = getDefaultValueText(type);
643
644     final GrModifierList modifierList = variableDeclaration.getModifierList();
645     final PsiNameHelper nameHelper = JavaPsiFacade.getInstance(variableDeclaration.getProject()).getNameHelper();
646     for (final GrVariable variable : variableDeclaration.getVariables()) {
647       String name = variable.getName();
648       if (!nameHelper.isIdentifier(name)) {
649         continue; //does not have a java image
650       }
651
652       text.append("\n  ");
653       writeFieldModifiers(text, modifierList, JAVA_MODIFIERS);
654
655       //type
656       text.append(type).append(" ").append(name).append(" = ").append(initializer).append(";\n");
657     }
658   }
659
660   private static void writeMethod(StringBuffer text, PsiMethod method, final PsiParameter[] parameters) {
661     if (method == null) return;
662     String name = method.getName();
663     if (!JavaPsiFacade.getInstance(method.getProject()).getNameHelper().isIdentifier(name))
664       return; //does not have a java image
665
666     boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT);
667
668     PsiModifierList modifierList = method.getModifierList();
669
670     text.append("\n");
671     text.append("  ");
672     writeMethodModifiers(text, modifierList, JAVA_MODIFIERS);
673     if (method.hasTypeParameters()) {
674       appendTypeParameters(text, method);
675       text.append(" ");
676     }
677
678     //append return type
679     PsiType retType = method.getReturnType();
680     if (retType == null) retType = TypesUtil.getJavaLangObject(method);
681
682     text.append(getTypeText(retType, method, false));
683     text.append(" ");
684
685     //append method name
686     text.append(name);
687
688     /************* parameters **********/
689
690     text.append("(");
691
692     //writes myParameters
693     int i = 0;
694     while (i < parameters.length) {
695       PsiParameter parameter = parameters[i];
696       if (parameter == null) continue;
697
698       if (i > 0) text.append(", ");  //append ','
699
700       text.append(getTypeText(parameter.getType(), parameter, i == parameters.length - 1));
701       text.append(" ");
702       text.append(parameter.getName());
703
704       i++;
705     }
706     text.append(")");
707     text.append(" ");
708
709     if (!isAbstract) {
710       /************* body **********/
711       text.append("{\n");
712       text.append("    return ");
713
714       text.append(getDefaultValueText(getTypeText(retType, method, false)));
715
716       text.append(";");
717
718       text.append("\n  }");
719     } else {
720       text.append(";");
721     }
722     text.append("\n");
723   }
724
725   private static boolean writeMethodModifiers(StringBuffer text, PsiModifierList modifierList, String[] modifiers) {
726     boolean wasAddedModifiers = false;
727     for (String modifierType : modifiers) {
728       if (modifierList.hasModifierProperty(modifierType)) {
729         text.append(modifierType);
730         text.append(" ");
731         wasAddedModifiers = true;
732       }
733     }
734     return wasAddedModifiers;
735   }
736
737   private static void writeFieldModifiers(StringBuffer text, GrModifierList modifierList, String[] modifiers) {
738     for (String modifierType : modifiers) {
739       if (modifierList.hasModifierProperty(modifierType)) {
740         text.append(modifierType);
741         text.append(" ");
742       }
743     }
744   }
745
746   private static void writeClassModifiers(StringBuffer text,
747                                              @Nullable PsiModifierList modifierList, boolean isInterface, boolean toplevel) {
748     if (modifierList == null || modifierList.hasModifierProperty(PsiModifier.PUBLIC)) {
749       text.append("public ");
750     }
751
752     if (modifierList != null) {
753       List<String> allowedModifiers = new ArrayList<String>();
754       allowedModifiers.add(PsiModifier.FINAL);
755       if (!toplevel) {
756         allowedModifiers.addAll(Arrays.asList(PsiModifier.PROTECTED, PsiModifier.PRIVATE, PsiModifier.STATIC));
757       }
758       if (!isInterface) {
759         allowedModifiers.add(PsiModifier.ABSTRACT);
760       }
761
762       for (String modifierType : allowedModifiers) {
763         if (modifierList.hasModifierProperty(modifierType)) {
764           text.append(modifierType).append(" ");
765         }
766       }
767     }
768   }
769
770   private static String getTypeText(GrTypeElement typeElement) {
771     if (typeElement == null) {
772       return CommonClassNames.JAVA_LANG_OBJECT;
773     }
774
775     return getTypeText(typeElement.getType(), typeElement, false);
776   }
777
778   private static String getTypeText(@Nullable PsiType type, @Nullable PsiElement context, boolean allowVarargs) {
779     if (context != null && type instanceof PsiClassType) {
780       final String accessible = findAccessibleSuperClass(context, ((PsiClassType)type).resolve());
781       if (accessible != null) {
782         return accessible;
783       }
784     }
785
786     if (type instanceof PsiArrayType) {
787       String componentText = getTypeText(((PsiArrayType)type).getComponentType(), context, false);
788       if (allowVarargs && type instanceof PsiEllipsisType) {
789         return componentText + "...";
790       }
791       return componentText + "[]";
792     }
793
794     if (type == null) {
795       return CommonClassNames.JAVA_LANG_OBJECT;
796     }
797
798     String canonicalText = type.getCanonicalText();
799     return canonicalText != null ? canonicalText : type.getPresentableText();
800   }
801
802   @Nullable
803   private static String findAccessibleSuperClass(PsiElement context, @Nullable PsiClass initialClass) {
804     if (initialClass == null) {
805       return null;
806     }
807
808     PsiClass curClass = initialClass;
809     final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(context.getProject()).getResolveHelper();
810     while (curClass != null && !resolveHelper.isAccessible(curClass, context, null)) {
811       curClass = curClass.getSuperClass();
812     }
813     if (curClass != null && !initialClass.isEquivalentTo(curClass)) {
814       final String qname = curClass.getQualifiedName();
815       if (qname != null) {
816         return qname;
817       }
818     }
819     return null;
820   }
821
822   CharTrie myTrie = new CharTrie();
823
824   public class GenerationItem {
825     ValidityState myState;
826     final Module myModule;
827     public int myHashCode;
828     private final VirtualFile myVFile;
829
830     public GenerationItem(String path, Module module, ValidityState state, VirtualFile vFile) {
831       myVFile = vFile;
832       myModule = module;
833       myState = state;
834       myHashCode = myTrie.getHashCode(path);
835     }
836
837     public String getPath() {
838       return myTrie.getString(myHashCode);
839     }
840
841     public Module getModule() {
842       return myModule;
843     }
844
845     public VirtualFile getVFile() {
846       return myVFile;
847     }
848   }
849 }