created FileFilter implementation which filters files by extension
[idea/community.git] / plugins / ui-designer / jps-plugin / src / org / jetbrains / jps / uiDesigner / compiler / FormsBindingManager.java
1 /*
2  * Copyright 2000-2012 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.jps.uiDesigner.compiler;
17
18 import com.intellij.openapi.util.Key;
19 import com.intellij.openapi.util.SystemInfo;
20 import com.intellij.openapi.util.io.FileUtil;
21 import com.intellij.openapi.util.text.StringUtil;
22 import gnu.trove.THashMap;
23 import gnu.trove.THashSet;
24 import org.jetbrains.annotations.NotNull;
25 import org.jetbrains.annotations.Nullable;
26 import org.jetbrains.jps.ModuleChunk;
27 import org.jetbrains.jps.builders.DirtyFilesHolder;
28 import org.jetbrains.jps.builders.FileProcessor;
29 import org.jetbrains.jps.builders.java.JavaBuilderUtil;
30 import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
31 import org.jetbrains.jps.incremental.*;
32 import org.jetbrains.jps.incremental.fs.CompilationRound;
33 import org.jetbrains.jps.incremental.java.CopyResourcesUtil;
34 import org.jetbrains.jps.incremental.java.FormsParsing;
35 import org.jetbrains.jps.incremental.storage.OneToManyPathsMapping;
36 import org.jetbrains.jps.model.JpsProject;
37 import org.jetbrains.jps.model.java.JpsJavaExtensionService;
38 import org.jetbrains.jps.model.java.compiler.JpsCompilerExcludes;
39 import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration;
40 import org.jetbrains.jps.uiDesigner.model.JpsUiDesignerConfiguration;
41 import org.jetbrains.jps.uiDesigner.model.JpsUiDesignerExtensionService;
42
43 import java.io.File;
44 import java.io.FilenameFilter;
45 import java.io.IOException;
46 import java.util.*;
47
48 /**
49  * @author Eugene Zhuravlev
50  *         Date: 11/20/12
51  */
52 public class FormsBindingManager extends FormsBuilder {
53   private static final Key<Boolean> FORCE_FORMS_REBUILD_FLAG = Key.create("_forms_rebuild_flag_");
54   private static final Key<Boolean> FORMS_REBUILD_FORCED = Key.create("_forms_rebuild_forced_flag_");
55   public FormsBindingManager() {
56     super(BuilderCategory.SOURCE_PROCESSOR, "form-bindings");
57   }
58
59   @Override
60   public void buildStarted(CompileContext context) {
61     FORCE_FORMS_REBUILD_FLAG.set(context, getMarkerFile(context).exists());
62   }
63
64   @Override
65   public void chunkBuildFinished(CompileContext context, ModuleChunk chunk) {
66     FORMS_REBUILD_FORCED.set(context, null); // clear the flag on per-chunk basis
67     super.chunkBuildFinished(context, chunk);
68   }
69
70   @Override
71   public void buildFinished(CompileContext context) {
72     final boolean previousValue = FORCE_FORMS_REBUILD_FLAG.get(context, Boolean.FALSE);
73     final JpsUiDesignerConfiguration config = JpsUiDesignerExtensionService.getInstance().getUiDesignerConfiguration(context.getProjectDescriptor().getProject());
74     final boolean currentRebuildValue = config != null && !config.isInstrumentClasses();
75     if (previousValue != currentRebuildValue) {
76       final File marker = getMarkerFile(context);
77       if (currentRebuildValue) {
78         FileUtil.createIfDoesntExist(marker);
79       }
80       else {
81         FileUtil.delete(marker);
82       }
83     }
84   }
85
86   @NotNull
87   private static File getMarkerFile(CompileContext context) {
88     return new File(context.getProjectDescriptor().dataManager.getDataPaths().getDataStorageRoot(), "forms_rebuild_required");
89   }
90
91   @Override
92   public List<String> getCompilableFileExtensions() {
93     return Collections.singletonList(FORM_EXTENSION);
94   }
95
96   @Override
97   public ExitCode build(CompileContext context, ModuleChunk chunk, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, OutputConsumer outputConsumer) throws ProjectBuildException, IOException {
98     ExitCode exitCode = ExitCode.NOTHING_DONE;
99     final JpsProject project = context.getProjectDescriptor().getProject();
100     final JpsUiDesignerConfiguration config = JpsUiDesignerExtensionService.getInstance().getOrCreateUiDesignerConfiguration(project);
101
102     if (!config.isInstrumentClasses() && !config.isCopyFormsRuntimeToOutput()) {
103       return exitCode;
104     }
105
106     final Map<File, ModuleBuildTarget> filesToCompile = new THashMap<File, ModuleBuildTarget>(FileUtil.FILE_HASHING_STRATEGY);
107     final Map<File, ModuleBuildTarget> formsToCompile = new THashMap<File, ModuleBuildTarget>(FileUtil.FILE_HASHING_STRATEGY);
108     final Map<File, Collection<File>> srcToForms = new THashMap<File, Collection<File>>(FileUtil.FILE_HASHING_STRATEGY);
109
110     if (!JavaBuilderUtil.isForcedRecompilationAllJavaModules(context) && config.isInstrumentClasses() && FORCE_FORMS_REBUILD_FLAG.get(context, Boolean.FALSE)) {
111       // force compilation of all forms, but only once per chunk
112       if (!FORMS_REBUILD_FORCED.get(context, Boolean.FALSE)) {
113         FORMS_REBUILD_FORCED.set(context, Boolean.TRUE);
114         FSOperations.markDirty(context, CompilationRound.CURRENT, chunk, FORM_SOURCES_FILTER);
115       }
116     }
117
118     dirtyFilesHolder.processDirtyFiles(new FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>() {
119       public boolean apply(ModuleBuildTarget target, File file, JavaSourceRootDescriptor descriptor) throws IOException {
120         if (JAVA_SOURCES_FILTER.accept(file)) {
121           filesToCompile.put(file, target);
122         }
123         else if (FORM_SOURCES_FILTER.accept(file)) {
124           formsToCompile.put(file, target);
125         }
126         return true;
127       }
128     });
129
130     if (config.isInstrumentClasses()) {
131       final JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project);
132       final JpsCompilerExcludes excludes = configuration.getCompilerExcludes();
133
134       // force compilation of bound source file if the form is dirty
135       for (final Map.Entry<File, ModuleBuildTarget> entry : formsToCompile.entrySet()) {
136         final File form = entry.getKey();
137         final ModuleBuildTarget target = entry.getValue();
138         final Collection<File> sources = findBoundSourceCandidates(context, target, form);
139         for (File boundSource : sources) {
140           if (!excludes.isExcluded(boundSource)) {
141             addBinding(boundSource, form, srcToForms);
142             FSOperations.markDirty(context, CompilationRound.CURRENT, boundSource);
143             filesToCompile.put(boundSource, target);
144             exitCode = ExitCode.OK;
145           }
146         }
147       }
148
149       // form should be considered dirty if the class it is bound to is dirty
150       final OneToManyPathsMapping sourceToFormMap = context.getProjectDescriptor().dataManager.getSourceToFormMap();
151       for (Map.Entry<File, ModuleBuildTarget> entry : filesToCompile.entrySet()) {
152         final File srcFile = entry.getKey();
153         final ModuleBuildTarget target = entry.getValue();
154         final Collection<String> boundForms = sourceToFormMap.getState(srcFile.getPath());
155         if (boundForms != null) {
156           for (String formPath : boundForms) {
157             final File formFile = new File(formPath);
158             if (!excludes.isExcluded(formFile) && formFile.exists()) {
159               addBinding(srcFile, formFile, srcToForms);
160               FSOperations.markDirty(context, CompilationRound.CURRENT, formFile);
161               formsToCompile.put(formFile, target);
162               exitCode = ExitCode.OK;
163             }
164           }
165         }
166       }
167     }
168
169     FORMS_TO_COMPILE.set(context, srcToForms.isEmpty()? null : srcToForms);
170
171     if (config.isCopyFormsRuntimeToOutput() && containsValidForm(formsToCompile.keySet())) {
172       for (ModuleBuildTarget target : chunk.getTargets()) {
173         if (!target.isTests()) {
174           final File outputDir = target.getOutputDir();
175           if (outputDir != null) {
176             final String outputRoot = FileUtil.toSystemIndependentName(outputDir.getPath());
177             final List<File> generatedFiles = CopyResourcesUtil.copyFormsRuntime(outputRoot, false);
178             if (!generatedFiles.isEmpty()) {
179               exitCode = ExitCode.OK;
180               // now inform others about files just copied
181               for (File file : generatedFiles) {
182                 outputConsumer.registerOutputFile(target, file, Collections.<String>emptyList());
183               }
184             }
185           }
186         }
187       }
188     }
189
190     return exitCode;
191   }
192
193   private static boolean containsValidForm(Set<File> files) {
194     for (File file : files) {
195       try {
196         if (FormsParsing.readBoundClassName(file) != null) {
197           return true;
198         }
199       }
200       catch (IOException ignore) {
201       }
202     }
203     return false;
204   }
205
206   @NotNull
207   private static Collection<File> findBoundSourceCandidates(CompileContext context, final ModuleBuildTarget target, File form) throws IOException {
208     final List<JavaSourceRootDescriptor> targetRoots = context.getProjectDescriptor().getBuildRootIndex().getTargetRoots(target, context);
209     if (targetRoots.isEmpty()) {
210       return Collections.emptyList();
211     }
212     final String className = FormsParsing.readBoundClassName(form);
213     if (className == null) {
214       return Collections.emptyList();
215     }
216     for (JavaSourceRootDescriptor rd : targetRoots) {
217       final File boundSource = findSourceForClass(rd, className);
218       if (boundSource != null) {
219         return Collections.singleton(boundSource);
220       }
221     }
222
223     final Set<File> candidates = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
224     for (JavaSourceRootDescriptor rd : targetRoots) {
225       candidates.addAll(findPossibleSourcesForClass(rd, className));
226     }
227     return candidates;
228   }
229
230   @Nullable
231   private static File findSourceForClass(JavaSourceRootDescriptor rd, final @Nullable String boundClassName) throws IOException {
232     if (boundClassName == null) {
233       return null;
234     }
235     String relPath = suggestRelativePath(rd, boundClassName);
236     while (true) {
237       final File candidate = new File(rd.getRootFile(), relPath);
238       if (candidate.exists()) {
239         return candidate.isFile() ? candidate : null;
240       }
241       final int index = relPath.lastIndexOf('/');
242       if (index <= 0) {
243         return null;
244       }
245       relPath = relPath.substring(0, index) + JAVA_EXTENSION;
246     }
247   }
248
249   @NotNull
250   private static Collection<File> findPossibleSourcesForClass(JavaSourceRootDescriptor rd, final @Nullable String boundClassName) throws IOException {
251     if (boundClassName == null) {
252       return Collections.emptyList();
253     }
254     String relPath = suggestRelativePath(rd, boundClassName);
255     final File containingDirectory = new File(rd.getRootFile(), relPath).getParentFile();
256     if (containingDirectory == null) {
257       return Collections.emptyList();
258     }
259     final File[] files = containingDirectory.listFiles(new FilenameFilter() {
260       @Override
261       public boolean accept(File dir, String name) {
262         return name.endsWith(JAVA_EXTENSION);
263       }
264     });
265     if (files == null || files.length == 0) {
266       return Collections.emptyList();
267     }
268     return Arrays.asList(files); 
269   }
270
271   @NotNull
272   private static String suggestRelativePath(@NotNull JavaSourceRootDescriptor rd, @NotNull String className) {
273     String clsName = className;
274     String prefix = rd.getPackagePrefix();
275     if (!StringUtil.isEmpty(prefix)) {
276       if (!StringUtil.endsWith(prefix, ".")) {
277         prefix += ".";
278       }
279       if (SystemInfo.isFileSystemCaseSensitive? StringUtil.startsWith(clsName, prefix) : StringUtil.startsWithIgnoreCase(clsName, prefix)) {
280         clsName = clsName.substring(prefix.length());
281       }
282     }
283
284     return clsName.replace('.', '/') + JAVA_EXTENSION;
285   }
286 }