cleanup (inspection "Java | Class structure | Utility class is not 'final'")
[idea/community.git] / jps / jps-builders / src / org / jetbrains / jps / builders / java / JavaBuilderUtil.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package org.jetbrains.jps.builders.java;
3
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.openapi.util.Key;
6 import com.intellij.openapi.util.Pair;
7 import com.intellij.openapi.util.io.FileUtil;
8 import com.intellij.util.ObjectUtils;
9 import com.intellij.util.containers.ContainerUtil;
10 import gnu.trove.THashSet;
11 import org.jetbrains.annotations.NotNull;
12 import org.jetbrains.annotations.Nullable;
13 import org.jetbrains.jps.ModuleChunk;
14 import org.jetbrains.jps.ProjectPaths;
15 import org.jetbrains.jps.builders.BuildRootIndex;
16 import org.jetbrains.jps.builders.BuildTarget;
17 import org.jetbrains.jps.builders.BuildTargetIndex;
18 import org.jetbrains.jps.builders.DirtyFilesHolder;
19 import org.jetbrains.jps.builders.java.dependencyView.Callbacks;
20 import org.jetbrains.jps.builders.java.dependencyView.Mappings;
21 import org.jetbrains.jps.builders.storage.BuildDataCorruptedException;
22 import org.jetbrains.jps.incremental.*;
23 import org.jetbrains.jps.incremental.fs.CompilationRound;
24 import org.jetbrains.jps.incremental.messages.BuildMessage;
25 import org.jetbrains.jps.incremental.messages.CompilerMessage;
26 import org.jetbrains.jps.incremental.messages.ProgressMessage;
27 import org.jetbrains.jps.model.JpsDummyElement;
28 import org.jetbrains.jps.model.JpsProject;
29 import org.jetbrains.jps.model.java.JavaModuleIndex;
30 import org.jetbrains.jps.model.java.JpsJavaExtensionService;
31 import org.jetbrains.jps.model.java.JpsJavaSdkType;
32 import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration;
33 import org.jetbrains.jps.model.java.compiler.ProcessorConfigProfile;
34 import org.jetbrains.jps.model.library.JpsLibrary;
35 import org.jetbrains.jps.model.library.JpsTypedLibrary;
36 import org.jetbrains.jps.model.library.sdk.JpsSdk;
37 import org.jetbrains.jps.model.library.sdk.JpsSdkReference;
38 import org.jetbrains.jps.model.library.sdk.JpsSdkType;
39 import org.jetbrains.jps.model.module.JpsModule;
40 import org.jetbrains.jps.service.JpsServiceManager;
41
42 import java.io.File;
43 import java.io.FileFilter;
44 import java.io.IOException;
45 import java.util.*;
46
47 public final class JavaBuilderUtil {
48   /**
49    * @deprecated This functionality is obsolete and is not used by dependency analysis anymore. To be removed in future releases
50    */
51   @Deprecated
52   public static final Key<Callbacks.ConstantAffectionResolver> CONSTANT_SEARCH_SERVICE = Key.create("_constant_search_service_");
53
54   private static final Logger LOG = Logger.getInstance(Builder.class);
55   private static final Key<Set<File>> ALL_AFFECTED_FILES_KEY = Key.create("_all_affected_files_");
56   private static final Key<Set<File>> ALL_COMPILED_FILES_KEY = Key.create("_all_compiled_files_");
57   private static final Key<Set<File>> FILES_TO_COMPILE_KEY = Key.create("_files_to_compile_");
58   private static final Key<Set<File>> COMPILED_WITH_ERRORS_KEY = Key.create("_compiled_with_errors_");
59   private static final Key<Set<File>> SUCCESSFULLY_COMPILED_FILES_KEY = Key.create("_successfully_compiled_files_");
60   private static final Key<List<FileFilter>> SKIP_MARKING_DIRTY_FILTERS_KEY = Key.create("_skip_marking_dirty_filters_");
61   private static final Key<Pair<Mappings, Callbacks.Backend>> MAPPINGS_DELTA_KEY = Key.create("_mappings_delta_");
62   private static final String MODULE_INFO_FILE = "module-info.java";
63
64   public static void registerFileToCompile(CompileContext context, File file) {
65     registerFilesToCompile(context, Collections.singleton(file));
66   }
67
68   public static void registerFilesToCompile(CompileContext context, Collection<? extends File> files) {
69     getFilesContainer(context, FILES_TO_COMPILE_KEY).addAll(files);
70   }
71
72   public static void registerFilesWithErrors(CompileContext context, Collection<? extends File> files) {
73     getFilesContainer(context, COMPILED_WITH_ERRORS_KEY).addAll(files);
74   }
75
76   public static void registerSuccessfullyCompiled(CompileContext context, File file) {
77     registerSuccessfullyCompiled(context, Collections.singleton(file));
78   }
79
80   public static void registerSuccessfullyCompiled(CompileContext context, Collection<? extends File> files) {
81     getFilesContainer(context, SUCCESSFULLY_COMPILED_FILES_KEY).addAll(files);
82   }
83
84   /**
85    * The files accepted by {@code filter} won't be marked dirty by {@link #updateMappings} method when this compilation round finishes.
86    * Call this method from {@link ModuleLevelBuilder#build} to register a filter accepting files of your language if you compute and mark
87    * as dirty affected files yourself.
88    */
89   public static void registerFilterToSkipMarkingAffectedFileDirty(@NotNull CompileContext context, @NotNull FileFilter filter) {
90     List<FileFilter> filters = SKIP_MARKING_DIRTY_FILTERS_KEY.get(context);
91     if (filters == null) {
92       SKIP_MARKING_DIRTY_FILTERS_KEY.set(context, filters = new ArrayList<>());
93     }
94     filters.add(filter);
95   }
96
97   @NotNull
98   public static Callbacks.Backend getDependenciesRegistrar(CompileContext context) {
99     Pair<Mappings, Callbacks.Backend> pair = MAPPINGS_DELTA_KEY.get(context);
100     if (pair == null) {
101       final Mappings delta = context.getProjectDescriptor().dataManager.getMappings().createDelta();
102       pair = Pair.create(delta, delta.getCallback());
103       MAPPINGS_DELTA_KEY.set(context, pair);
104     }
105     return pair.second;
106   }
107
108   public static boolean updateMappingsOnRoundCompletion(
109     CompileContext context, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, ModuleChunk chunk) throws IOException {
110
111     Mappings delta = null;
112
113     final Pair<Mappings, Callbacks.Backend> pair = MAPPINGS_DELTA_KEY.get(context);
114     if (pair != null) {
115       MAPPINGS_DELTA_KEY.set(context, null);
116       delta = pair.getFirst();
117     }
118
119     if (delta == null) {
120       return false;
121     }
122     final Set<File> compiledFiles = getFilesContainer(context, FILES_TO_COMPILE_KEY);
123     FILES_TO_COMPILE_KEY.set(context, null);
124     final Set<File> successfullyCompiled = getFilesContainer(context, SUCCESSFULLY_COMPILED_FILES_KEY);
125     SUCCESSFULLY_COMPILED_FILES_KEY.set(context, null);
126     FileFilter filter = createOrFilter(SKIP_MARKING_DIRTY_FILTERS_KEY.get(context));
127     return updateMappings(context, delta, dirtyFilesHolder, chunk, compiledFiles, successfullyCompiled, CompilationRound.NEXT, filter);
128   }
129
130   public static void clearDataOnRoundCompletion(CompileContext context) {
131     //during next compilation round ModuleLevelBuilders may register filters again so we need to remove old ones to avoid duplicating instances
132     SKIP_MARKING_DIRTY_FILTERS_KEY.set(context, null);
133   }
134
135   /**
136    * @deprecated this method isn't supposed to be called by plugins anymore, the mappings are updated
137    * by the build process infrastructure automatically. Use {@link #getDependenciesRegistrar(CompileContext)},
138    * {@link #registerFilesToCompile(CompileContext, Collection)}, or
139    * {@link #registerSuccessfullyCompiled(CompileContext, Collection)} instead.
140    */
141   @Deprecated
142   public static boolean updateMappings(CompileContext context,
143                                        final Mappings delta,
144                                        DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
145                                        ModuleChunk chunk,
146                                        Collection<? extends File> filesToCompile,
147                                        Collection<? extends File> successfullyCompiled) throws IOException {
148     return updateMappings(context, delta, dirtyFilesHolder, chunk, filesToCompile, successfullyCompiled, CompilationRound.NEXT, null);
149   }
150
151   public static void markDirtyDependenciesForInitialRound(CompileContext context, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dfh, ModuleChunk chunk) throws IOException {
152     if (hasRemovedPaths(chunk, dfh)) {
153       final Mappings delta = context.getProjectDescriptor().dataManager.getMappings().createDelta();
154       final Set<File> empty = Collections.emptySet();
155       updateMappings(context, delta, dfh, chunk, empty, empty, CompilationRound.CURRENT, null);
156     }
157   }
158
159   /**
160    * @param filesToCompile   files compiled in this round
161    * @param markDirtyRound   compilation round at which dirty files should be visible to builders
162    * @return true if additional compilation pass is required, false otherwise
163    * @throws Exception
164    */
165   private static boolean updateMappings(CompileContext context,
166                                         final Mappings delta,
167                                         DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
168                                         ModuleChunk chunk,
169                                         Collection<? extends File> filesToCompile,
170                                         Collection<? extends File> successfullyCompiled,
171                                         final CompilationRound markDirtyRound,
172                                         @Nullable FileFilter skipMarkingDirtyFilter) throws IOException {
173     try {
174       boolean performIntegrate = true;
175       boolean additionalPassRequired = false;
176
177       final Set<String> removedPaths = getRemovedPaths(chunk, dirtyFilesHolder);
178
179       final Mappings globalMappings = context.getProjectDescriptor().dataManager.getMappings();
180
181       final boolean errorsDetected = Utils.errorsDetected(context);
182       if (!isForcedRecompilationAllJavaModules(context)) {
183         if (context.shouldDifferentiate(chunk)) {
184           context.processMessage(new ProgressMessage("Checking dependencies... [" + chunk.getPresentableShortName() + "]"));
185           final Set<File> allCompiledFiles = getFilesContainer(context, ALL_COMPILED_FILES_KEY);
186           final Set<File> allAffectedFiles = getFilesContainer(context, ALL_AFFECTED_FILES_KEY);
187
188           // mark as affected all files that were dirty before compilation
189           allAffectedFiles.addAll(filesToCompile);
190           // accumulate all successfully compiled in this round
191           allCompiledFiles.addAll(successfullyCompiled);
192           // unmark as affected all successfully compiled
193           allAffectedFiles.removeAll(successfullyCompiled);
194
195           final Set<File> affectedBeforeDif = new THashSet<>(FileUtil.FILE_HASHING_STRATEGY);
196           affectedBeforeDif.addAll(allAffectedFiles);
197
198           final Set<File> compiledWithErrors = getFilesContainer(context, COMPILED_WITH_ERRORS_KEY);
199           COMPILED_WITH_ERRORS_KEY.set(context, null);
200
201           final ModulesBasedFileFilter moduleBasedFilter = new ModulesBasedFileFilter(context, chunk);
202           final boolean incremental = globalMappings.differentiateOnIncrementalMake(
203             delta, removedPaths, filesToCompile, compiledWithErrors, allCompiledFiles, allAffectedFiles, moduleBasedFilter
204           );
205
206           if (LOG.isDebugEnabled()) {
207             LOG.debug("Differentiate Results:");
208             LOG.debug("   Compiled Files:");
209             for (final File c : allCompiledFiles) {
210               LOG.debug("      " + c.getAbsolutePath());
211             }
212             LOG.debug("   Affected Files:");
213             for (final File c : allAffectedFiles) {
214               LOG.debug("      " + c.getAbsolutePath());
215             }
216             LOG.debug("End Of Differentiate Results.");
217           }
218
219           final boolean compilingIncrementally = isCompileJavaIncrementally(context);
220           if (incremental) {
221             final Set<File> newlyAffectedFiles = new HashSet<>(allAffectedFiles);
222             newlyAffectedFiles.removeAll(affectedBeforeDif);
223
224             final String infoMessage = "Dependency analysis found " + newlyAffectedFiles.size() + " affected files";
225             LOG.info(infoMessage);
226             context.processMessage(new ProgressMessage(infoMessage));
227
228             removeFilesAcceptedByFilter(newlyAffectedFiles, skipMarkingDirtyFilter);
229
230             if (!newlyAffectedFiles.isEmpty()) {
231
232               if (LOG.isDebugEnabled()) {
233                 for (File file : newlyAffectedFiles) {
234                   LOG.debug("affected file: " + file.getPath());
235                 }
236                 final List<Pair<File, JpsModule>> wrongFiles =
237                   checkAffectedFilesInCorrectModules(context, newlyAffectedFiles, moduleBasedFilter);
238                 if (!wrongFiles.isEmpty()) {
239                   LOG.debug("Wrong affected files for module chunk " + chunk.getName() + ": ");
240                   for (Pair<File, JpsModule> pair : wrongFiles) {
241                     final String name = pair.second != null ? pair.second.getName() : "null";
242                     LOG.debug("\t[" + name + "] " + pair.first.getPath());
243                   }
244                 }
245               }
246
247               Set<ModuleBuildTarget> targetsToMark = null;
248               final JavaModuleIndex moduleIndex = getJavaModuleIndex(context);
249               for (File file : newlyAffectedFiles) {
250                 if (MODULE_INFO_FILE.equals(file.getName())) {
251                   final JavaSourceRootDescriptor rootDescr = context.getProjectDescriptor().getBuildRootIndex().findJavaRootDescriptor(context, file);
252                   if (rootDescr != null) {
253                     final ModuleBuildTarget target = rootDescr.getTarget();
254                     final File targetModuleInfo = moduleIndex.getModuleInfoFile(target.getModule(), target.isTests());
255                     if (FileUtil.filesEqual(targetModuleInfo, file)) {
256                       if (targetsToMark == null) {
257                         targetsToMark = new THashSet<>(); // lazy init
258                       }
259                       targetsToMark.add(target);
260                     }
261                   }
262                 }
263                 else {
264                   FSOperations.markDirtyIfNotDeleted(context, markDirtyRound, file);
265                 }
266               }
267
268               if (targetsToMark == null || !targetsToMark.contains(chunk.representativeTarget())) {
269                 // additionally check whether annotation-processor generated files from this chunk are affected
270                 if (containsProcessorGeneratedFiles(chunk, newlyAffectedFiles)) {
271                   // If among affected files are those processor-generated, then we need to re-generate them before compiling.
272                   // To achieve this, we need to recompile the whole chunk which will cause processors to re-generated these affected files
273                   if (targetsToMark == null) {
274                     targetsToMark = new THashSet<>(); // lazy init
275                   }
276                   targetsToMark.addAll(chunk.getTargets());
277                 }
278               }
279
280               boolean currentChunkAfected = false;
281               if (targetsToMark != null) {
282                 for (ModuleBuildTarget target : targetsToMark) {
283                   if (chunk.getTargets().contains(target)) {
284                     currentChunkAfected = true;
285                   }
286                   else {
287                     FSOperations.markDirty(context, markDirtyRound, target, null);
288                   }
289                 }
290                 if (currentChunkAfected) {
291                   if (compilingIncrementally) {
292                     // turn on non-incremental mode for targets from the current chunk, if at least one of them was affected.
293                     for (ModuleBuildTarget target : chunk.getTargets()) {
294                       context.markNonIncremental(target);
295                     }
296                   }
297                   FSOperations.markDirty(context, markDirtyRound, chunk, null);
298                 }
299               }
300               additionalPassRequired = compilingIncrementally && (currentChunkAfected || moduleBasedFilter.containsFilesFromCurrentTargetChunk(newlyAffectedFiles));
301             }
302           }
303           else {
304             // non-incremental mode
305             final String messageText = "Marking " + chunk.getPresentableShortName() + " and direct dependants for recompilation";
306             LOG.info("Non-incremental mode: " + messageText);
307             context.processMessage(new ProgressMessage(messageText));
308
309             final boolean alreadyMarkedDirty = FSOperations.isMarkedDirty(context, chunk);
310             additionalPassRequired = compilingIncrementally && !alreadyMarkedDirty;
311
312             if (alreadyMarkedDirty) {
313               // need this to make sure changes data stored in Delta is complete
314               globalMappings.differentiateOnNonIncrementalMake(delta, removedPaths, filesToCompile);
315             }
316             else {
317               performIntegrate = false;
318             }
319
320             FileFilter toBeMarkedFilter = skipMarkingDirtyFilter == null ? null : new NegationFileFilter(skipMarkingDirtyFilter);
321             FSOperations.markDirtyRecursively(context, markDirtyRound, chunk, toBeMarkedFilter);
322           }
323         }
324         else {
325           if (!errorsDetected) { // makes sense only if we are going to integrate changes
326             globalMappings.differentiateOnNonIncrementalMake(delta, removedPaths, filesToCompile);
327           }
328         }
329       }
330       else {
331         if (!errorsDetected) { // makes sense only if we are going to integrate changes
332           globalMappings.differentiateOnRebuild(delta);
333         }
334       }
335
336       if (errorsDetected) {
337         // important: perform dependency analysis and mark found dependencies even if there were errors during the first phase of make.
338         // Integration of changes should happen only if the corresponding phase of make succeeds
339         // In case of errors this wil ensure that all dependencies marked after the first phase
340         // will be compiled during the first phase of the next make
341         return false;
342       }
343
344       if (performIntegrate) {
345         context.processMessage(new ProgressMessage("Updating dependency information... [" + chunk.getPresentableShortName() + "]"));
346         globalMappings.integrate(delta);
347       }
348
349       return additionalPassRequired;
350     }
351     catch (BuildDataCorruptedException e) {
352       throw e.getCause();
353     }
354     finally {
355       context.processMessage(new ProgressMessage("")); // clean progress messages
356     }
357   }
358
359   private static boolean containsProcessorGeneratedFiles(ModuleChunk chunk, Collection<? extends File> files) {
360     final JpsModule module = chunk.representativeTarget().getModule();
361     final JpsJavaCompilerConfiguration compilerConfig = JpsJavaExtensionService.getInstance().getCompilerConfiguration(module.getProject());
362     assert compilerConfig != null;
363     final ProcessorConfigProfile profile = compilerConfig.getAnnotationProcessingProfile(module);
364     if (!profile.isEnabled()) {
365       return false;
366     }
367     final File outputDir = ProjectPaths.getAnnotationProcessorGeneratedSourcesOutputDir(module, chunk.containsTests(), profile);
368     if (outputDir == null) {
369       return false;
370     }
371     for (File file : files) {
372       if (FileUtil.isAncestor(outputDir, file, true)) {
373         return true;
374       }
375     }
376     return false;
377   }
378
379   @Nullable
380   public static File findModuleInfoFile(CompileContext context, ModuleBuildTarget target) {
381     return getJavaModuleIndex(context).getModuleInfoFile(target.getModule(), target.isTests());
382   }
383
384   private static JavaModuleIndex getJavaModuleIndex(CompileContext context) {
385     JpsProject project = context.getProjectDescriptor().getProject();
386     return JpsJavaExtensionService.getInstance().getJavaModuleIndex(project);
387   }
388
389   private static FileFilter createOrFilter(final List<? extends FileFilter> filters) {
390     if (filters == null || filters.isEmpty()) return null;
391     return pathname -> {
392       for (FileFilter filter : filters) {
393         if (filter.accept(pathname)) {
394           return true;
395         }
396       }
397       return false;
398     };
399   }
400
401   private static void removeFilesAcceptedByFilter(@NotNull Set<? extends File> files, @Nullable FileFilter filter) {
402     if (filter != null) {
403       for (final Iterator<? extends File> it = files.iterator(); it.hasNext();) {
404         if (filter.accept(it.next())) {
405           it.remove();
406         }
407       }
408     }
409   }
410
411   public static boolean isForcedRecompilationAllJavaModules(CompileContext context) {
412     CompileScope scope = context.getScope();
413     return scope.isBuildForcedForAllTargets(JavaModuleBuildTargetType.PRODUCTION) &&
414            scope.isBuildForcedForAllTargets(JavaModuleBuildTargetType.TEST);
415   }
416
417   public static boolean isCompileJavaIncrementally(CompileContext context) {
418     CompileScope scope = context.getScope();
419     return scope.isBuildIncrementally(JavaModuleBuildTargetType.PRODUCTION) || scope.isBuildIncrementally(JavaModuleBuildTargetType.TEST);
420   }
421
422   private static List<Pair<File, JpsModule>> checkAffectedFilesInCorrectModules(CompileContext context, Collection<? extends File> affected, ModulesBasedFileFilter moduleBasedFilter) {
423     if (affected.isEmpty()) {
424       return Collections.emptyList();
425     }
426     final List<Pair<File, JpsModule>> result = new ArrayList<>();
427     final BuildRootIndex rootIndex = context.getProjectDescriptor().getBuildRootIndex();
428     for (File file : affected) {
429       if (!moduleBasedFilter.accept(file)) {
430         final JavaSourceRootDescriptor moduleAndRoot = rootIndex.findJavaRootDescriptor(context, file);
431         result.add(Pair.create(file, moduleAndRoot != null ? moduleAndRoot.target.getModule() : null));
432       }
433     }
434     return result;
435   }
436
437   @NotNull
438   private static Set<File> getFilesContainer(CompileContext context, final Key<Set<File>> dataKey) {
439     Set<File> files = dataKey.get(context);
440     if (files == null) {
441       files = new THashSet<>(FileUtil.FILE_HASHING_STRATEGY);
442       dataKey.set(context, files);
443     }
444     return files;
445   }
446
447   private static Set<String> getRemovedPaths(ModuleChunk chunk, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder) {
448     if (!dirtyFilesHolder.hasRemovedFiles()) {
449       return Collections.emptySet();
450     }
451     final Set<String> removed = new THashSet<>(FileUtil.PATH_HASHING_STRATEGY);
452     for (ModuleBuildTarget target : chunk.getTargets()) {
453       removed.addAll(dirtyFilesHolder.getRemovedFiles(target));
454     }
455     return removed;
456   }
457
458   private static boolean hasRemovedPaths(ModuleChunk chunk, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder) {
459     if (dirtyFilesHolder.hasRemovedFiles()) {
460       for (ModuleBuildTarget target : chunk.getTargets()) {
461         if (!dirtyFilesHolder.getRemovedFiles(target).isEmpty()) {
462           return true;
463         }
464       }
465     }
466     return false;
467   }
468
469   public static void cleanupChunkResources(CompileContext context) {
470     ALL_AFFECTED_FILES_KEY.set(context, null);
471     ALL_COMPILED_FILES_KEY.set(context, null);
472   }
473
474   @NotNull
475   public static JpsSdk<JpsDummyElement> ensureModuleHasJdk(JpsModule module, CompileContext context, final String compilerName) throws
476                                                                                                                                 ProjectBuildException {
477     JpsSdkReference<JpsDummyElement> reference = module.getSdkReference(JpsJavaSdkType.INSTANCE);
478     if (reference == null) {
479       context.processMessage(new CompilerMessage(compilerName, BuildMessage.Kind.ERROR, "JDK isn't specified for module '" + module.getName() + "'"));
480       throw new StopBuildException();
481     }
482
483     JpsTypedLibrary<JpsSdk<JpsDummyElement>> sdkLibrary = reference.resolve();
484     if (sdkLibrary == null) {
485       JpsLibrary library = context.getProjectDescriptor().getModel().getGlobal().getLibraryCollection().findLibrary(reference.getSdkName());
486       JpsSdkType sdkType = library != null ? ObjectUtils.tryCast(library.getType(), JpsSdkType.class) : null;
487       String errorMessage;
488       if (sdkType == null) {
489         errorMessage = "Cannot find JDK '" + reference.getSdkName() + "' for module '" + module.getName() + "'";
490       }
491       else {
492         errorMessage = "Cannot find JDK for module '" + module.getName() + "': '" + reference.getSdkName() + "' points to " + sdkType.getPresentableName();
493       }
494       context.processMessage(new CompilerMessage(compilerName, BuildMessage.Kind.ERROR, errorMessage));
495       throw new StopBuildException();
496     }
497     return sdkLibrary.getProperties();
498   }
499
500   @Nullable
501   public static JavaCompilingTool findCompilingTool(@NotNull String compilerId) {
502     for (JavaCompilingTool tool : JpsServiceManager.getInstance().getExtensions(JavaCompilingTool.class)) {
503       if (compilerId.equals(tool.getId()) || compilerId.equals(tool.getAlternativeId())) {
504         return tool;
505       }
506     }
507     return null;
508   }
509
510   private static class ModulesBasedFileFilter implements Mappings.DependentFilesFilter {
511     private final CompileContext myContext;
512     private final Set<? extends BuildTarget<?>> myChunkTargets;
513     private final Map<BuildTarget<?>, Set<BuildTarget<?>>> myCache = new HashMap<>();
514     private final BuildRootIndex myBuildRootIndex;
515     private final BuildTargetIndex myBuildTargetIndex;
516
517     private ModulesBasedFileFilter(CompileContext context, ModuleChunk chunk) {
518       myContext = context;
519       myChunkTargets = chunk.getTargets();
520       myBuildRootIndex = context.getProjectDescriptor().getBuildRootIndex();
521       myBuildTargetIndex = context.getProjectDescriptor().getBuildTargetIndex();
522     }
523
524     @Override
525     public boolean accept(File file) {
526       final JavaSourceRootDescriptor rd = myBuildRootIndex.findJavaRootDescriptor(myContext, file);
527       if (rd == null) {
528         return true;
529       }
530       final ModuleBuildTarget targetOfFile = rd.target;
531       if (myChunkTargets.contains(targetOfFile)) {
532         return true;
533       }
534       Set<BuildTarget<?>> targetOfFileWithDependencies = myCache.get(targetOfFile);
535       if (targetOfFileWithDependencies == null) {
536         targetOfFileWithDependencies = myBuildTargetIndex.getDependenciesRecursively(targetOfFile, myContext);
537         myCache.put(targetOfFile, targetOfFileWithDependencies);
538       }
539       return ContainerUtil.intersects(targetOfFileWithDependencies, myChunkTargets);
540     }
541
542     @Override
543     public boolean belongsToCurrentTargetChunk(File file) {
544       final JavaSourceRootDescriptor rd = myBuildRootIndex.findJavaRootDescriptor(myContext, file);
545       return rd != null && myChunkTargets.contains(rd.target);
546     }
547
548     public boolean containsFilesFromCurrentTargetChunk(Collection<? extends File> files) {
549       for (File file : files) {
550         if (belongsToCurrentTargetChunk(file)) {
551           return true;
552         }
553       }
554       return false;
555     }
556   }
557
558   private static class NegationFileFilter implements FileFilter {
559     private final FileFilter myFilter;
560
561     NegationFileFilter(FileFilter filter) {
562       myFilter = filter;
563     }
564
565     @Override
566     public boolean accept(File pathname) {
567       return !myFilter.accept(pathname);
568     }
569   }
570 }