idea groovy stub generator: always generate them when there are java files in current...
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / compiler / generator / GroovycStubGenerator.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
17 package org.jetbrains.plugins.groovy.compiler.generator;
18
19 import com.intellij.compiler.impl.CompilerUtil;
20 import com.intellij.compiler.impl.FileSetCompileScope;
21 import com.intellij.compiler.impl.TranslatingCompilerFilesMonitor;
22 import com.intellij.ide.highlighter.JavaFileType;
23 import com.intellij.openapi.application.AccessToken;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.application.ModalityState;
26 import com.intellij.openapi.application.WriteAction;
27 import com.intellij.openapi.compiler.CompileContext;
28 import com.intellij.openapi.compiler.CompileScope;
29 import com.intellij.openapi.compiler.CompilerPaths;
30 import com.intellij.openapi.compiler.ex.CompileContextEx;
31 import com.intellij.openapi.compiler.options.ExcludedEntriesConfiguration;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.module.Module;
34 import com.intellij.openapi.module.ModuleManager;
35 import com.intellij.openapi.progress.ProgressIndicator;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.roots.*;
38 import com.intellij.openapi.util.Pair;
39 import com.intellij.openapi.util.io.FileUtil;
40 import com.intellij.openapi.util.text.StringUtil;
41 import com.intellij.openapi.vfs.LocalFileSystem;
42 import com.intellij.openapi.vfs.VfsUtil;
43 import com.intellij.openapi.vfs.VirtualFile;
44 import com.intellij.psi.JavaPsiFacade;
45 import com.intellij.psi.PsiClass;
46 import com.intellij.psi.PsiManager;
47 import com.intellij.psi.search.GlobalSearchScope;
48 import com.intellij.util.Chunk;
49 import com.intellij.util.Processor;
50 import com.intellij.util.containers.CollectionFactory;
51 import com.intellij.util.containers.ContainerUtil;
52 import com.intellij.util.containers.FactoryMap;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
55 import org.jetbrains.plugins.groovy.compiler.GroovyCompilerBase;
56 import org.jetbrains.plugins.groovy.compiler.GroovyCompilerConfiguration;
57 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
58 import org.jetbrains.plugins.groovy.refactoring.GroovyNamesUtil;
59 import org.jetbrains.plugins.groovy.refactoring.convertToJava.GroovyToJavaGenerator;
60
61 import java.io.File;
62 import java.io.IOException;
63 import java.util.*;
64
65 /**
66  * @author peter
67  */
68 public class GroovycStubGenerator extends GroovyCompilerBase {
69   private static Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.compiler.generator.GroovycStubGenerator");
70
71   public static final String GROOVY_STUBS = "groovyStubs";
72
73   public GroovycStubGenerator(Project project) {
74     super(project);
75   }
76
77   @Override
78   public void compile(CompileContext compileContext, Chunk<Module> moduleChunk, VirtualFile[] virtualFiles, OutputSink sink) {
79     final ExcludedEntriesConfiguration excluded = GroovyCompilerConfiguration.getExcludeConfiguration(myProject);
80     
81     @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") FactoryMap<Pair<Module, Boolean>, Boolean> hasJava = new FactoryMap<Pair<Module, Boolean>, Boolean>() {
82       @Override
83       protected Boolean create(Pair<Module, Boolean> key) {
84         return containsJavaSources(key.first, key.second);
85       }
86     };
87
88     ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex();
89
90     List<VirtualFile> total = new ArrayList<VirtualFile>();
91     for (final VirtualFile virtualFile : virtualFiles) {
92       if (!excluded.isExcluded(virtualFile) &&
93           GroovyNamesUtil.isIdentifier(virtualFile.getNameWithoutExtension())) {
94         Module module = index.getModuleForFile(virtualFile);
95         if (module == null || hasJava.get(Pair.create(module, index.isInTestSourceContent(virtualFile)))) {
96           total.add(virtualFile);
97         }
98       }
99     }
100
101     if (total.isEmpty()) {
102       return;
103     }
104
105     //long l = System.currentTimeMillis();
106     super.compile(compileContext, moduleChunk, VfsUtil.toVirtualFileArray(total), sink);
107     //System.out.println("Stub generation took " + (System.currentTimeMillis() - l));
108   }
109
110   private static boolean containsJavaSources(Module module, boolean inTests) {
111     ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
112     for (ContentEntry entry : rootManager.getContentEntries()) {
113       for (SourceFolder folder : entry.getSourceFolders()) {
114         VirtualFile dir = folder.getFile();
115         if (inTests == folder.isTestSource() && dir != null) {
116           if (!rootManager.getFileIndex().iterateContentUnderDirectory(dir, new ContentIterator() {
117             @Override
118             public boolean processFile(VirtualFile fileOrDir) {
119               if (!fileOrDir.isDirectory() && JavaFileType.INSTANCE == fileOrDir.getFileType()) {
120                 return false;
121               }
122               return true;
123             }
124           })) {
125             return true;
126           }
127         }
128       }
129     }
130     return false;
131   }
132
133   @Override
134   protected void compileFiles(CompileContext compileContext,
135                               Module module,
136                               final List<VirtualFile> toCompile,
137                               OutputSink sink,
138                               boolean tests) {
139     final File outDir = getStubOutput(module, tests);
140     outDir.mkdirs();
141
142     final VirtualFile tempOutput = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(outDir);
143     assert tempOutput != null;
144     cleanDirectory(tempOutput);
145
146     ((CompileContextEx)compileContext).assignModule(tempOutput, module, tests, this);
147
148     ProgressIndicator indicator = compileContext.getProgressIndicator();
149     indicator.pushState();
150
151     try {
152       final GroovyToJavaGenerator generator = new GroovyToJavaGenerator(myProject, new HashSet<VirtualFile>(toCompile));
153       for (int i = 0; i < toCompile.size(); i++) {
154         indicator.setFraction((double)i / toCompile.size());
155
156         final Collection<VirtualFile> stubFiles = generateItems(generator, toCompile.get(i), tempOutput, compileContext, myProject);
157         ((CompileContextEx)compileContext).addScope(new FileSetCompileScope(stubFiles, new Module[]{module}));
158       }
159     }
160     finally {
161       indicator.popState();
162     }
163   }
164
165   private static File getStubOutput(Module module, boolean tests) {
166     final Project project = module.getProject();
167     final String rootPath = CompilerPaths.getGeneratedDataDirectory(project).getPath() + "/" + GROOVY_STUBS + "/";
168     return new File(rootPath + project.getLocationHash() + "/" + module.getName() + "/" + (tests ? "tests" : "production") + "/");
169   }
170
171   @Nullable
172   public static PsiClass findClassByStub(Project project, VirtualFile stubFile) {
173     final String[] components = StringUtil.trimEnd(stubFile.getPath(), ".java").split("[\\\\/]");
174     final int stubs = Arrays.asList(components).indexOf(GROOVY_STUBS);
175     if (stubs < 0 || stubs >= components.length - 4) return null;
176
177     final String moduleName = components[stubs + 2];
178     final Module module = ModuleManager.getInstance(project).findModuleByName(moduleName);
179     if (module == null) return null;
180
181     final String fqn = StringUtil.join(Arrays.asList(components).subList(stubs + 4, components.length), ".");
182     return JavaPsiFacade.getInstance(project).findClass(fqn, GlobalSearchScope.moduleScope(module));
183   }
184
185   private void cleanDirectory(final VirtualFile dir) {
186     Runnable runnable = new Runnable() {
187       @Override
188       public void run() {
189         AccessToken token = WriteAction.start();
190         try {
191           VfsUtil.processFilesRecursively(dir, new Processor<VirtualFile>() {
192             @Override
193             public boolean process(VirtualFile virtualFile) {
194               if (!virtualFile.isDirectory()) {
195                 TranslatingCompilerFilesMonitor.removeSourceInfo(virtualFile);
196                 try {
197                   virtualFile.delete(this);
198                 }
199                 catch (IOException e) {
200                   LOG.info(e);
201                 }
202               }
203               return true;
204             }
205           });
206         }
207         finally {
208           token.finish();
209         }
210       }
211     };
212     if (ApplicationManager.getApplication().isDispatchThread()) {
213       assert ApplicationManager.getApplication().isUnitTestMode();
214       runnable.run();
215     } else {
216       ApplicationManager.getApplication().invokeAndWait(runnable, ModalityState.NON_MODAL);
217     }
218   }
219
220   @NotNull
221   public String getDescription() {
222     return "Groovy to java source code generator";
223   }
224
225   public boolean validateConfiguration(CompileScope scope) {
226     return true;
227   }
228
229   public static Collection<VirtualFile> generateItems(final GroovyToJavaGenerator generator,
230                                                       final VirtualFile item,
231                                                       final VirtualFile outputRootDirectory,
232                                                       CompileContext context,
233                                                       final Project project) {
234     ProgressIndicator indicator = context.getProgressIndicator();
235     indicator.setText("Generating stubs for " + item.getName() + "...");
236
237     if (LOG.isDebugEnabled()) {
238       LOG.debug("Generating stubs for " + item.getName() + "...");
239     }
240
241     final Map<String, CharSequence> output;
242
243     AccessToken accessToken = ApplicationManager.getApplication().acquireReadActionLock();
244
245     try {
246       output = generator.generateStubs((GroovyFile)PsiManager.getInstance(project).findFile(item));
247     }
248     finally {
249       accessToken.finish();
250     }
251
252     return writeStubs(outputRootDirectory, output, item);
253   }
254
255   private static List<VirtualFile> writeStubs(VirtualFile outputRootDirectory, Map<String, CharSequence> output, VirtualFile src) {
256     final ArrayList<VirtualFile> stubs = CollectionFactory.arrayList();
257     for (String relativePath : output.keySet()) {
258       final File stubFile = new File(outputRootDirectory.getPath(), relativePath);
259       FileUtil.createIfDoesntExist(stubFile);
260       try {
261         FileUtil.writeToFile(stubFile, output.get(relativePath).toString().getBytes(src.getCharset()));
262       }
263       catch (IOException e) {
264         LOG.error(e);
265       }
266       CompilerUtil.refreshIOFile(stubFile);
267       ContainerUtil.addIfNotNull(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(stubFile), stubs);
268     }
269     return stubs;
270   }
271 }