2 * Copyright 2000-2012 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.jetbrains.jps.uiDesigner.compiler;
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.io.FileUtilRt;
22 import com.intellij.openapi.util.text.StringUtil;
23 import gnu.trove.THashMap;
24 import gnu.trove.THashSet;
25 import org.jetbrains.annotations.NotNull;
26 import org.jetbrains.annotations.Nullable;
27 import org.jetbrains.jps.ModuleChunk;
28 import org.jetbrains.jps.builders.DirtyFilesHolder;
29 import org.jetbrains.jps.builders.FileProcessor;
30 import org.jetbrains.jps.builders.java.JavaBuilderUtil;
31 import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
32 import org.jetbrains.jps.incremental.*;
33 import org.jetbrains.jps.incremental.fs.CompilationRound;
34 import org.jetbrains.jps.incremental.java.CopyResourcesUtil;
35 import org.jetbrains.jps.incremental.java.FormsParsing;
36 import org.jetbrains.jps.incremental.storage.OneToManyPathsMapping;
37 import org.jetbrains.jps.model.JpsProject;
38 import org.jetbrains.jps.model.java.JpsJavaExtensionService;
39 import org.jetbrains.jps.model.java.compiler.JpsCompilerExcludes;
40 import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration;
41 import org.jetbrains.jps.uiDesigner.model.JpsUiDesignerConfiguration;
42 import org.jetbrains.jps.uiDesigner.model.JpsUiDesignerExtensionService;
45 import java.io.IOException;
49 * @author Eugene Zhuravlev
52 public class FormsBindingManager extends FormsBuilder {
53 private static final String JAVA_EXTENSION = ".java";
54 private static final Key<Boolean> FORCE_FORMS_REBUILD_FLAG = Key.create("_forms_rebuild_flag_");
55 private static final Key<Boolean> FORMS_REBUILD_FORCED = Key.create("_forms_rebuild_forced_flag_");
56 public FormsBindingManager() {
57 super(BuilderCategory.SOURCE_PROCESSOR, "form-bindings");
61 public void buildStarted(CompileContext context) {
62 FORCE_FORMS_REBUILD_FLAG.set(context, getMarkerFile(context).exists());
66 public void chunkBuildFinished(CompileContext context, ModuleChunk chunk) {
67 FORMS_REBUILD_FORCED.set(context, null); // clear the flag on per-chunk basis
68 super.chunkBuildFinished(context, chunk);
72 public void buildFinished(CompileContext context) {
73 final boolean previousValue = FORCE_FORMS_REBUILD_FLAG.get(context, Boolean.FALSE);
74 final JpsUiDesignerConfiguration config = JpsUiDesignerExtensionService.getInstance().getUiDesignerConfiguration(context.getProjectDescriptor().getProject());
75 final boolean currentRebuildValue = config != null && !config.isInstrumentClasses();
76 if (previousValue != currentRebuildValue) {
77 final File marker = getMarkerFile(context);
78 if (currentRebuildValue) {
79 FileUtil.createIfDoesntExist(marker);
82 FileUtil.delete(marker);
88 private static File getMarkerFile(CompileContext context) {
89 return new File(context.getProjectDescriptor().dataManager.getDataPaths().getDataStorageRoot(), "forms_rebuild_required");
93 public List<String> getCompilableFileExtensions() {
94 return Collections.singletonList(FORM_EXTENSION);
98 public ExitCode build(CompileContext context, ModuleChunk chunk, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, OutputConsumer outputConsumer) throws ProjectBuildException, IOException {
99 ExitCode exitCode = ExitCode.NOTHING_DONE;
100 final JpsProject project = context.getProjectDescriptor().getProject();
101 final JpsUiDesignerConfiguration config = JpsUiDesignerExtensionService.getInstance().getOrCreateUiDesignerConfiguration(project);
103 if (!config.isInstrumentClasses() && !config.isCopyFormsRuntimeToOutput()) {
107 final Map<File, ModuleBuildTarget> filesToCompile = new THashMap<File, ModuleBuildTarget>(FileUtil.FILE_HASHING_STRATEGY);
108 final Map<File, ModuleBuildTarget> formsToCompile = new THashMap<File, ModuleBuildTarget>(FileUtil.FILE_HASHING_STRATEGY);
109 final Map<File, Collection<File>> srcToForms = new THashMap<File, Collection<File>>(FileUtil.FILE_HASHING_STRATEGY);
111 if (!JavaBuilderUtil.isForcedRecompilationAllJavaModules(context) && config.isInstrumentClasses() && FORCE_FORMS_REBUILD_FLAG.get(context, Boolean.FALSE)) {
112 // force compilation of all forms, but only once per chunk
113 if (!FORMS_REBUILD_FORCED.get(context, Boolean.FALSE)) {
114 FORMS_REBUILD_FORCED.set(context, Boolean.TRUE);
115 FSOperations.markDirty(context, CompilationRound.CURRENT, chunk, FORM_SOURCES_FILTER);
119 dirtyFilesHolder.processDirtyFiles(new FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>() {
120 public boolean apply(ModuleBuildTarget target, File file, JavaSourceRootDescriptor descriptor) throws IOException {
121 if (JAVA_SOURCES_FILTER.accept(file)) {
122 filesToCompile.put(file, target);
124 else if (FORM_SOURCES_FILTER.accept(file)) {
125 formsToCompile.put(file, target);
131 if (config.isInstrumentClasses()) {
132 final JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project);
133 final JpsCompilerExcludes excludes = configuration.getCompilerExcludes();
135 // force compilation of bound source file if the form is dirty
136 for (final Map.Entry<File, ModuleBuildTarget> entry : formsToCompile.entrySet()) {
137 final File form = entry.getKey();
138 final ModuleBuildTarget target = entry.getValue();
139 final Collection<File> sources = findBoundSourceCandidates(context, target, form);
140 for (File boundSource : sources) {
141 if (!excludes.isExcluded(boundSource)) {
142 addBinding(boundSource, form, srcToForms);
143 FSOperations.markDirty(context, CompilationRound.CURRENT, boundSource);
144 filesToCompile.put(boundSource, target);
145 exitCode = ExitCode.OK;
150 // form should be considered dirty if the class it is bound to is dirty
151 final OneToManyPathsMapping sourceToFormMap = context.getProjectDescriptor().dataManager.getSourceToFormMap();
152 for (Map.Entry<File, ModuleBuildTarget> entry : filesToCompile.entrySet()) {
153 final File srcFile = entry.getKey();
154 final ModuleBuildTarget target = entry.getValue();
155 final Collection<String> boundForms = sourceToFormMap.getState(srcFile.getPath());
156 if (boundForms != null) {
157 for (String formPath : boundForms) {
158 final File formFile = new File(formPath);
159 if (!excludes.isExcluded(formFile) && formFile.exists()) {
160 addBinding(srcFile, formFile, srcToForms);
161 FSOperations.markDirty(context, CompilationRound.CURRENT, formFile);
162 formsToCompile.put(formFile, target);
163 exitCode = ExitCode.OK;
170 FORMS_TO_COMPILE.set(context, srcToForms.isEmpty()? null : srcToForms);
172 if (config.isCopyFormsRuntimeToOutput() && containsValidForm(formsToCompile.keySet())) {
173 for (ModuleBuildTarget target : chunk.getTargets()) {
174 if (!target.isTests()) {
175 final File outputDir = target.getOutputDir();
176 if (outputDir != null) {
177 final String outputRoot = FileUtil.toSystemIndependentName(outputDir.getPath());
178 final List<File> generatedFiles = CopyResourcesUtil.copyFormsRuntime(outputRoot, false);
179 if (!generatedFiles.isEmpty()) {
180 exitCode = ExitCode.OK;
181 // now inform others about files just copied
182 for (File file : generatedFiles) {
183 outputConsumer.registerOutputFile(target, file, Collections.<String>emptyList());
194 private static boolean containsValidForm(Set<File> files) {
195 for (File file : files) {
197 if (FormsParsing.readBoundClassName(file) != null) {
201 catch (IOException ignore) {
208 private static Collection<File> findBoundSourceCandidates(CompileContext context, final ModuleBuildTarget target, File form) throws IOException {
209 final List<JavaSourceRootDescriptor> targetRoots = context.getProjectDescriptor().getBuildRootIndex().getTargetRoots(target, context);
210 if (targetRoots.isEmpty()) {
211 return Collections.emptyList();
213 final String className = FormsParsing.readBoundClassName(form);
214 if (className == null) {
215 return Collections.emptyList();
217 for (JavaSourceRootDescriptor rd : targetRoots) {
218 final File boundSource = findSourceForClass(rd, className);
219 if (boundSource != null) {
220 return Collections.singleton(boundSource);
224 final Set<File> candidates = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
225 for (JavaSourceRootDescriptor rd : targetRoots) {
226 candidates.addAll(findPossibleSourcesForClass(rd, className));
232 private static File findSourceForClass(JavaSourceRootDescriptor rd, final @Nullable String boundClassName) throws IOException {
233 if (boundClassName == null) {
236 String relPath = suggestRelativePath(rd, boundClassName);
238 final File candidate = new File(rd.getRootFile(), relPath);
239 if (candidate.exists()) {
240 return candidate.isFile() ? candidate : null;
242 final int index = relPath.lastIndexOf('/');
246 relPath = relPath.substring(0, index) + JAVA_EXTENSION;
251 private static Collection<File> findPossibleSourcesForClass(JavaSourceRootDescriptor rd, final @Nullable String boundClassName) throws IOException {
252 if (boundClassName == null) {
253 return Collections.emptyList();
255 String relPath = suggestRelativePath(rd, boundClassName);
256 final File containingDirectory = new File(rd.getRootFile(), relPath).getParentFile();
257 if (containingDirectory == null) {
258 return Collections.emptyList();
260 final File[] files = containingDirectory.listFiles(FileUtilRt.createFilterByExtension("java"));
261 if (files == null || files.length == 0) {
262 return Collections.emptyList();
264 return Arrays.asList(files);
268 private static String suggestRelativePath(@NotNull JavaSourceRootDescriptor rd, @NotNull String className) {
269 String clsName = className;
270 String prefix = rd.getPackagePrefix();
271 if (!StringUtil.isEmpty(prefix)) {
272 if (!StringUtil.endsWith(prefix, ".")) {
275 if (SystemInfo.isFileSystemCaseSensitive? StringUtil.startsWith(clsName, prefix) : StringUtil.startsWithIgnoreCase(clsName, prefix)) {
276 clsName = clsName.substring(prefix.length());
280 return clsName.replace('.', '/') + JAVA_EXTENSION;