[maven] IDEA-91662 doesn't take into account mirror repositories as Indexed Maven...
[idea/community.git] / plugins / maven / src / main / java / org / jetbrains / idea / maven / project / MavenProjectsTree.java
1 // Copyright 2000-2021 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.idea.maven.project;
3
4 import com.intellij.openapi.Disposable;
5 import com.intellij.openapi.application.ApplicationManager;
6 import com.intellij.openapi.application.ReadAction;
7 import com.intellij.openapi.diagnostic.Logger;
8 import com.intellij.openapi.module.Module;
9 import com.intellij.openapi.project.Project;
10 import com.intellij.openapi.roots.ProjectFileIndex;
11 import com.intellij.openapi.util.Comparing;
12 import com.intellij.openapi.util.Pair;
13 import com.intellij.openapi.util.io.FileUtil;
14 import com.intellij.openapi.util.text.StringUtil;
15 import com.intellij.openapi.vfs.LocalFileSystem;
16 import com.intellij.openapi.vfs.VfsUtilCore;
17 import com.intellij.openapi.vfs.VirtualFile;
18 import com.intellij.util.containers.ArrayListSet;
19 import com.intellij.util.containers.DisposableWrapperList;
20 import com.intellij.util.containers.FileCollectionFactory;
21 import com.intellij.util.containers.Stack;
22 import com.intellij.util.io.PathKt;
23 import it.unimi.dsi.fastutil.Hash;
24 import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
25 import org.jdom.Element;
26 import org.jdom.output.Format;
27 import org.jdom.output.XMLOutputter;
28 import org.jetbrains.annotations.ApiStatus;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31 import org.jetbrains.annotations.TestOnly;
32 import org.jetbrains.idea.maven.dom.references.MavenFilteredPropertyPsiReferenceProvider;
33 import org.jetbrains.idea.maven.model.*;
34 import org.jetbrains.idea.maven.server.NativeMavenProjectHolder;
35 import org.jetbrains.idea.maven.utils.MavenJDOMUtil;
36 import org.jetbrains.idea.maven.utils.*;
37
38 import java.io.*;
39 import java.nio.file.Path;
40 import java.util.*;
41 import java.util.concurrent.locks.Lock;
42 import java.util.concurrent.locks.ReentrantReadWriteLock;
43 import java.util.regex.Pattern;
44 import java.util.stream.Collectors;
45 import java.util.zip.CRC32;
46
47 public final class MavenProjectsTree {
48   private static final Logger LOG = Logger.getInstance(MavenProjectsTree.class);
49
50   private static final String STORAGE_VERSION = MavenProjectsTree.class.getSimpleName() + ".7";
51
52   private final Object myStateLock = new Object();
53   private final ReentrantReadWriteLock myStructureLock = new ReentrantReadWriteLock();
54   private final Lock myStructureReadLock = myStructureLock.readLock();
55   private final Lock myStructureWriteLock = myStructureLock.writeLock();
56
57   // TODO replace with sets
58   private volatile Set<String> myManagedFilesPaths = new LinkedHashSet<>();
59   private volatile List<String> myIgnoredFilesPaths = new ArrayList<>();
60   private volatile List<String> myIgnoredFilesPatterns = new ArrayList<>();
61   private volatile Pattern myIgnoredFilesPatternsCache;
62
63   private MavenExplicitProfiles myExplicitProfiles = MavenExplicitProfiles.NONE;
64   private final MavenExplicitProfiles myTemporarilyRemovedExplicitProfiles =
65     new MavenExplicitProfiles(new HashSet<>(), new HashSet<>());
66
67   private final List<MavenProject> myRootProjects = new ArrayList<>();
68
69   private final Map<MavenProject, MavenProjectTimestamp> myTimestamps = new HashMap<>();
70   private final MavenWorkspaceMap myWorkspaceMap = new MavenWorkspaceMap();
71   private final Map<MavenId, MavenProject> myMavenIdToProjectMapping = new HashMap<>();
72   private final Map<VirtualFile, MavenProject> myVirtualFileToProjectMapping = new HashMap<>();
73   private final Map<MavenProject, List<MavenProject>> myAggregatorToModuleMapping = new HashMap<>();
74   private final Map<MavenProject, MavenProject> myModuleToAggregatorMapping = new HashMap<>();
75
76   private final DisposableWrapperList<Listener> myListeners = new DisposableWrapperList<>();
77   private final Project myProject;
78
79   private final MavenProjectReaderProjectLocator myProjectLocator = new MavenProjectReaderProjectLocator() {
80     @Override
81     public VirtualFile findProjectFile(MavenId coordinates) {
82       MavenProject project = findProject(coordinates);
83       return project == null ? null : project.getFile();
84     }
85   };
86
87   public MavenProjectsTree(@NotNull Project project) {
88     myProject = project;
89   }
90
91   Project getProject() {
92     return myProject;
93   }
94
95
96   public MavenProjectReaderProjectLocator getProjectLocator() {
97     return myProjectLocator;
98   }
99
100   @Nullable
101   public static MavenProjectsTree read(Project project, Path file) throws IOException {
102     MavenProjectsTree result = new MavenProjectsTree(project);
103
104     try (DataInputStream in = new DataInputStream(new BufferedInputStream(PathKt.inputStream(file)))) {
105       try {
106         if (!STORAGE_VERSION.equals(in.readUTF())) return null;
107         result.myManagedFilesPaths = readCollection(in, new LinkedHashSet<>());
108         result.myIgnoredFilesPaths = readCollection(in, new ArrayList<>());
109         result.myIgnoredFilesPatterns = readCollection(in, new ArrayList<>());
110         result.myExplicitProfiles = new MavenExplicitProfiles(readCollection(in, new HashSet<>()),
111                                                               readCollection(in, new HashSet<>()));
112         result.myRootProjects.addAll(readProjectsRecursively(in, result));
113       }
114       catch (IOException e) {
115         in.close();
116         PathKt.delete(file);
117         throw e;
118       }
119       catch (Throwable e) {
120         throw new IOException(e);
121       }
122     }
123     return result;
124   }
125
126   private static <T extends Collection<String>> T readCollection(DataInputStream in, T result) throws IOException {
127     int count = in.readInt();
128     while (count-- > 0) {
129       result.add(in.readUTF());
130     }
131     return result;
132   }
133
134   private static void writeCollection(DataOutputStream out, Collection<String> list) throws IOException {
135     out.writeInt(list.size());
136     for (String each : list) {
137       out.writeUTF(each);
138     }
139   }
140
141   private static List<MavenProject> readProjectsRecursively(DataInputStream in,
142                                                             MavenProjectsTree tree) throws IOException {
143     int count = in.readInt();
144     List<MavenProject> result = new ArrayList<>(count);
145     while (count-- > 0) {
146       MavenProject project = MavenProject.read(in);
147       MavenProjectTimestamp timestamp = MavenProjectTimestamp.read(in);
148       List<MavenProject> modules = readProjectsRecursively(in, tree);
149       if (project != null) {
150         result.add(project);
151         tree.myTimestamps.put(project, timestamp);
152         tree.myVirtualFileToProjectMapping.put(project.getFile(), project);
153         tree.fillIDMaps(project);
154         tree.myAggregatorToModuleMapping.put(project, modules);
155         for (MavenProject eachModule : modules) {
156           tree.myModuleToAggregatorMapping.put(eachModule, project);
157         }
158       }
159     }
160     return result;
161   }
162
163   public void save(@NotNull Path file) throws IOException {
164     synchronized (myStateLock) {
165       readLock();
166       try {
167         try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(PathKt.outputStream(file)))) {
168           out.writeUTF(STORAGE_VERSION);
169           writeCollection(out, myManagedFilesPaths);
170           writeCollection(out, myIgnoredFilesPaths);
171           writeCollection(out, myIgnoredFilesPatterns);
172           writeCollection(out, myExplicitProfiles.getEnabledProfiles());
173           writeCollection(out, myExplicitProfiles.getDisabledProfiles());
174           writeProjectsRecursively(out, myRootProjects);
175         }
176       }
177       finally {
178         readUnlock();
179       }
180     }
181   }
182
183   private void writeProjectsRecursively(DataOutputStream out, List<MavenProject> list) throws IOException {
184     out.writeInt(list.size());
185     for (MavenProject each : list) {
186       each.write(out);
187       myTimestamps.get(each).write(out);
188       writeProjectsRecursively(out, getModules(each));
189     }
190   }
191
192   public List<String> getManagedFilesPaths() {
193     synchronized (myStateLock) {
194       return new ArrayList<>(myManagedFilesPaths);
195     }
196   }
197
198   public void resetManagedFilesPathsAndProfiles(List<String> paths, MavenExplicitProfiles profiles) {
199     synchronized (myStateLock) {
200       myManagedFilesPaths = new LinkedHashSet<>(paths);
201     }
202     setExplicitProfiles(profiles);
203   }
204
205   @TestOnly
206   public void resetManagedFilesAndProfiles(List<VirtualFile> files, MavenExplicitProfiles profiles) {
207     resetManagedFilesPathsAndProfiles(MavenUtil.collectPaths(files), profiles);
208   }
209
210   public void addManagedFilesWithProfiles(List<VirtualFile> files, MavenExplicitProfiles profiles) {
211     List<String> newFiles;
212     MavenExplicitProfiles newProfiles;
213     synchronized (myStateLock) {
214       newFiles = new ArrayList<>(myManagedFilesPaths);
215       newFiles.addAll(MavenUtil.collectPaths(files));
216
217       newProfiles = myExplicitProfiles.clone();
218       newProfiles.getEnabledProfiles().addAll(profiles.getEnabledProfiles());
219       newProfiles.getDisabledProfiles().addAll(profiles.getDisabledProfiles());
220     }
221
222     resetManagedFilesPathsAndProfiles(newFiles, newProfiles);
223   }
224
225   public void removeManagedFiles(List<VirtualFile> files) {
226     synchronized (myStateLock) {
227       myManagedFilesPaths.removeAll(MavenUtil.collectPaths(files));
228     }
229   }
230
231   public List<VirtualFile> getExistingManagedFiles() {
232     List<VirtualFile> result = new ArrayList<>();
233     for (String path : getManagedFilesPaths()) {
234       VirtualFile f = LocalFileSystem.getInstance().findFileByPath(path);
235       if (f != null && f.exists()) result.add(f);
236     }
237     return result;
238   }
239
240   public List<String> getIgnoredFilesPaths() {
241     synchronized (myStateLock) {
242       return new ArrayList<>(myIgnoredFilesPaths);
243     }
244   }
245
246   public void setIgnoredFilesPaths(final List<String> paths) {
247     doChangeIgnoreStatus(() -> myIgnoredFilesPaths = new ArrayList<>(paths));
248   }
249
250   public void removeIgnoredFilesPaths(final Collection<String> paths) {
251     doChangeIgnoreStatus(() -> myIgnoredFilesPaths.removeAll(paths));
252   }
253
254   public boolean getIgnoredState(MavenProject project) {
255     synchronized (myStateLock) {
256       return myIgnoredFilesPaths.contains(project.getPath());
257     }
258   }
259
260   public void setIgnoredState(List<MavenProject> projects, boolean ignored) {
261     setIgnoredState(projects, ignored, false);
262   }
263
264   public void setIgnoredState(List<MavenProject> projects, boolean ignored, boolean fromImport) {
265     doSetIgnoredState(projects, ignored, fromImport);
266   }
267
268   private void doSetIgnoredState(List<MavenProject> projects, final boolean ignored, boolean fromImport) {
269     final List<String> paths = MavenUtil.collectPaths(MavenUtil.collectFiles(projects));
270     doChangeIgnoreStatus(() -> {
271       if (ignored) {
272         myIgnoredFilesPaths.addAll(paths);
273       }
274       else {
275         myIgnoredFilesPaths.removeAll(paths);
276       }
277     }, fromImport);
278   }
279
280   public List<String> getIgnoredFilesPatterns() {
281     synchronized (myStateLock) {
282       return new ArrayList<>(myIgnoredFilesPatterns);
283     }
284   }
285
286   public void setIgnoredFilesPatterns(final List<String> patterns) {
287     doChangeIgnoreStatus(() -> {
288       myIgnoredFilesPatternsCache = null;
289       myIgnoredFilesPatterns = new ArrayList<>(patterns);
290     });
291   }
292
293   private void doChangeIgnoreStatus(Runnable runnable) {
294     doChangeIgnoreStatus(runnable, false);
295   }
296
297   private void doChangeIgnoreStatus(Runnable runnable, boolean fromImport) {
298     List<MavenProject> ignoredBefore;
299     List<MavenProject> ignoredAfter;
300
301     synchronized (myStateLock) {
302       ignoredBefore = getIgnoredProjects();
303       runnable.run();
304       ignoredAfter = getIgnoredProjects();
305     }
306
307     List<MavenProject> ignored = new ArrayList<>(ignoredAfter);
308     ignored.removeAll(ignoredBefore);
309
310     List<MavenProject> unignored = new ArrayList<>(ignoredBefore);
311     unignored.removeAll(ignoredAfter);
312
313     if (ignored.isEmpty() && unignored.isEmpty()) return;
314
315     fireProjectsIgnoredStateChanged(ignored, unignored, fromImport);
316   }
317
318   private List<MavenProject> getIgnoredProjects() {
319     List<MavenProject> result = new ArrayList<>();
320     for (MavenProject each : getProjects()) {
321       if (isIgnored(each)) result.add(each);
322     }
323     return result;
324   }
325
326   public boolean isIgnored(MavenProject project) {
327     String path = project.getPath();
328     synchronized (myStateLock) {
329       return myIgnoredFilesPaths.contains(path) || matchesIgnoredFilesPatterns(path);
330     }
331   }
332
333   private boolean matchesIgnoredFilesPatterns(String path) {
334     synchronized (myStateLock) {
335       if (myIgnoredFilesPatternsCache == null) {
336         myIgnoredFilesPatternsCache = Pattern.compile(Strings.translateMasks(myIgnoredFilesPatterns));
337       }
338       return myIgnoredFilesPatternsCache.matcher(path).matches();
339     }
340   }
341
342   public MavenExplicitProfiles getExplicitProfiles() {
343     synchronized (myStateLock) {
344       return myExplicitProfiles.clone();
345     }
346   }
347
348   public void setExplicitProfiles(MavenExplicitProfiles explicitProfiles) {
349     synchronized (myStateLock) {
350       myExplicitProfiles = explicitProfiles.clone();
351     }
352     fireProfilesChanged();
353   }
354
355   private void updateExplicitProfiles() {
356     Collection<String> available = getAvailableProfiles();
357
358     synchronized (myStateLock) {
359       updateExplicitProfiles(myExplicitProfiles.getEnabledProfiles(), myTemporarilyRemovedExplicitProfiles.getEnabledProfiles(),
360                              available);
361       updateExplicitProfiles(myExplicitProfiles.getDisabledProfiles(), myTemporarilyRemovedExplicitProfiles.getDisabledProfiles(),
362                              available);
363     }
364   }
365
366   private static void updateExplicitProfiles(Collection<String> explicitProfiles, Collection<String> temporarilyRemovedExplicitProfiles,
367                                              Collection<String> available) {
368     Collection<String> removedProfiles = new HashSet<>(explicitProfiles);
369     removedProfiles.removeAll(available);
370     temporarilyRemovedExplicitProfiles.addAll(removedProfiles);
371
372     Collection<String> restoredProfiles = new HashSet<>(temporarilyRemovedExplicitProfiles);
373     restoredProfiles.retainAll(available);
374     temporarilyRemovedExplicitProfiles.removeAll(restoredProfiles);
375
376     explicitProfiles.removeAll(removedProfiles);
377     explicitProfiles.addAll(restoredProfiles);
378   }
379
380   public Collection<String> getAvailableProfiles() {
381     Collection<String> res = new HashSet<>();
382
383     for (MavenProject each : getProjects()) {
384       res.addAll(each.getProfilesIds());
385     }
386
387     return res;
388   }
389
390   public Collection<Pair<String, MavenProfileKind>> getProfilesWithStates() {
391     Collection<Pair<String, MavenProfileKind>> result = new ArrayListSet<>();
392
393     Collection<String> available = new HashSet<>();
394     Collection<String> active = new HashSet<>();
395     for (MavenProject each : getProjects()) {
396       available.addAll(each.getProfilesIds());
397       active.addAll(each.getActivatedProfilesIds().getEnabledProfiles());
398     }
399
400     Collection<String> enabledProfiles = getExplicitProfiles().getEnabledProfiles();
401     Collection<String> disabledProfiles = getExplicitProfiles().getDisabledProfiles();
402
403     for (String each : available) {
404       MavenProfileKind state;
405       if (disabledProfiles.contains(each)) {
406         state = MavenProfileKind.NONE;
407       }
408       else if (enabledProfiles.contains(each)) {
409         state = MavenProfileKind.EXPLICIT;
410       }
411       else if (active.contains(each)) {
412         state = MavenProfileKind.IMPLICIT;
413       }
414       else {
415         state = MavenProfileKind.NONE;
416       }
417       result.add(Pair.create(each, state));
418     }
419     return result;
420   }
421
422   public void updateAll(boolean force, MavenGeneralSettings generalSettings, MavenProgressIndicator process) {
423     List<VirtualFile> managedFiles = getExistingManagedFiles();
424     MavenExplicitProfiles explicitProfiles = getExplicitProfiles();
425
426     MavenProjectReader projectReader = new MavenProjectReader(myProject);
427     update(managedFiles, true, force, explicitProfiles, projectReader, generalSettings, process);
428
429     List<VirtualFile> obsoleteFiles = getRootProjectsFiles();
430     obsoleteFiles.removeAll(managedFiles);
431     delete(projectReader, obsoleteFiles, explicitProfiles, generalSettings, process);
432   }
433
434   public void update(Collection<VirtualFile> files,
435                      boolean force,
436                      MavenGeneralSettings generalSettings,
437                      MavenProgressIndicator process) {
438     update(files, false, force, getExplicitProfiles(), new MavenProjectReader(myProject), generalSettings, process);
439   }
440
441   private void update(Collection<VirtualFile> files,
442                       boolean recursive,
443                       boolean force,
444                       MavenExplicitProfiles explicitProfiles,
445                       MavenProjectReader projectReader,
446                       MavenGeneralSettings generalSettings,
447                       MavenProgressIndicator process) {
448     if (files.isEmpty()) return;
449
450     UpdateContext updateContext = new UpdateContext();
451     Stack<MavenProject> updateStack = new Stack<>();
452
453     for (VirtualFile each : files) {
454       MavenProject mavenProject = findProject(each);
455       if (mavenProject == null) {
456         doAdd(each, recursive, explicitProfiles, updateContext, updateStack, projectReader, generalSettings, process);
457       }
458       else {
459         doUpdate(mavenProject,
460                  findAggregator(mavenProject),
461                  false,
462                  recursive,
463                  force,
464                  explicitProfiles,
465                  updateContext,
466                  updateStack,
467                  projectReader,
468                  generalSettings,
469                  process);
470       }
471     }
472
473     updateExplicitProfiles();
474     updateContext.fireUpdatedIfNecessary();
475   }
476
477   private void doAdd(final VirtualFile f,
478                      boolean recursuve,
479                      MavenExplicitProfiles explicitProfiles,
480                      UpdateContext updateContext,
481                      Stack<MavenProject> updateStack,
482                      MavenProjectReader reader,
483                      MavenGeneralSettings generalSettings,
484                      MavenProgressIndicator process) {
485     MavenProject newMavenProject = new MavenProject(f);
486
487     MavenProject intendedAggregator = null;
488     for (MavenProject each : getProjects()) {
489       if (each.getExistingModuleFiles().contains(f)) {
490         intendedAggregator = each;
491         break;
492       }
493     }
494
495     doUpdate(newMavenProject,
496              intendedAggregator,
497              true,
498              recursuve,
499              false,
500              explicitProfiles,
501              updateContext,
502              updateStack,
503              reader,
504              generalSettings,
505              process);
506   }
507
508   private void doUpdate(MavenProject mavenProject,
509                         MavenProject aggregator,
510                         boolean isNew,
511                         boolean recursive,
512                         boolean force,
513                         MavenExplicitProfiles explicitProfiles,
514                         UpdateContext updateContext,
515                         Stack<MavenProject> updateStack,
516                         MavenProjectReader reader,
517                         MavenGeneralSettings generalSettings,
518                         MavenProgressIndicator process) {
519     if (updateStack.contains(mavenProject)) {
520       MavenLog.LOG.info("Recursion detected in " + mavenProject.getFile());
521       return;
522     }
523     updateStack.push(mavenProject);
524     process.setText(MavenProjectBundle.message("maven.reading.pom", mavenProject.getPath()));
525     process.setText2("");
526
527     List<MavenProject> prevModules = getModules(mavenProject);
528
529     Set<MavenProject> prevInheritors = new HashSet<>();
530     if (!isNew) {
531       prevInheritors.addAll(findInheritors(mavenProject));
532     }
533
534     MavenProjectTimestamp timestamp = calculateTimestamp(mavenProject, explicitProfiles, generalSettings);
535     boolean isChanged = force || !timestamp.equals(myTimestamps.get(mavenProject));
536
537     MavenProjectChanges changes = force ? MavenProjectChanges.ALL : MavenProjectChanges.NONE;
538     if (isChanged) {
539       writeLock();
540       try {
541         if (!isNew) {
542           clearIDMaps(mavenProject);
543         }
544       }
545       finally {
546         writeUnlock();
547       }
548       MavenId oldParentId = mavenProject.getParentId();
549       changes = changes.mergedWith(mavenProject.read(generalSettings, explicitProfiles, reader, myProjectLocator));
550
551       writeLock();
552       try {
553         myVirtualFileToProjectMapping.put(mavenProject.getFile(), mavenProject);
554         fillIDMaps(mavenProject);
555       }
556       finally {
557         writeUnlock();
558       }
559
560       if (!Comparing.equal(oldParentId, mavenProject.getParentId())) {
561         // ensure timestamp reflects actual parent's timestamp
562         timestamp = calculateTimestamp(mavenProject, explicitProfiles, generalSettings);
563       }
564       myTimestamps.put(mavenProject, timestamp);
565     }
566
567     boolean reconnected = isNew;
568     if (isNew) {
569       connect(aggregator, mavenProject);
570     }
571     else {
572       reconnected = reconnect(aggregator, mavenProject);
573     }
574
575     if (isChanged || reconnected) {
576       updateContext.update(mavenProject, changes);
577     }
578
579     List<VirtualFile> existingModuleFiles = mavenProject.getExistingModuleFiles();
580     List<MavenProject> modulesToRemove = new ArrayList<>();
581     List<MavenProject> modulesToBecomeRoots = new ArrayList<>();
582
583     for (MavenProject each : prevModules) {
584       VirtualFile moduleFile = each.getFile();
585       if (!existingModuleFiles.contains(moduleFile)) {
586         if (isManagedFile(moduleFile)) {
587           modulesToBecomeRoots.add(each);
588         }
589         else {
590           modulesToRemove.add(each);
591         }
592       }
593     }
594     for (MavenProject each : modulesToRemove) {
595       removeModule(mavenProject, each);
596       doDelete(mavenProject, each, updateContext);
597       prevInheritors.removeAll(updateContext.deletedProjects);
598     }
599
600     for (MavenProject each : modulesToBecomeRoots) {
601       if (reconnect(null, each)) updateContext.update(each, MavenProjectChanges.NONE);
602     }
603
604     for (VirtualFile each : existingModuleFiles) {
605       MavenProject module = findProject(each);
606       boolean isNewModule = module == null;
607       if (isNewModule) {
608         module = new MavenProject(each);
609       }
610       else {
611         MavenProject currentAggregator = findAggregator(module);
612         if (currentAggregator != null && currentAggregator != mavenProject) {
613           MavenLog.LOG.info("Module " + each + " is already included into " + mavenProject.getFile());
614           continue;
615         }
616       }
617
618       if (isChanged || isNewModule || recursive) {
619         doUpdate(module,
620                  mavenProject,
621                  isNewModule,
622                  recursive,
623                  recursive && force, // do not force update modules if only this project was requested to be updated
624                  explicitProfiles,
625                  updateContext,
626                  updateStack,
627                  reader,
628                  generalSettings,
629                  process);
630       }
631       else {
632         if (reconnect(mavenProject, module)) {
633           updateContext.update(module, MavenProjectChanges.NONE);
634         }
635       }
636     }
637
638     prevInheritors.addAll(findInheritors(mavenProject));
639
640     for (MavenProject each : prevInheritors) {
641       doUpdate(each,
642                findAggregator(each),
643                false,
644                false, // no need to go recursively in case of inheritance, only when updating modules
645                false,
646                explicitProfiles,
647                updateContext,
648                updateStack,
649                reader,
650                generalSettings,
651                process);
652     }
653
654     updateStack.pop();
655   }
656
657   private MavenProjectTimestamp calculateTimestamp(final MavenProject mavenProject,
658                                                    final MavenExplicitProfiles explicitProfiles,
659                                                    final MavenGeneralSettings generalSettings) {
660     return ReadAction.compute(() -> {
661       long pomTimestamp = getFileTimestamp(mavenProject.getFile());
662       MavenProject parent = findParent(mavenProject);
663       long parentLastReadStamp = parent == null ? -1 : parent.getLastReadStamp();
664       VirtualFile profilesXmlFile = mavenProject.getProfilesXmlFile();
665       long profilesTimestamp = getFileTimestamp(profilesXmlFile);
666       VirtualFile jvmConfigFile = MavenUtil.getConfigFile(mavenProject, MavenConstants.JVM_CONFIG_RELATIVE_PATH);
667       long jvmConfigTimestamp = getFileTimestamp(jvmConfigFile);
668       VirtualFile mavenConfigFile = MavenUtil.getConfigFile(mavenProject, MavenConstants.MAVEN_CONFIG_RELATIVE_PATH);
669       long mavenConfigTimestamp = getFileTimestamp(mavenConfigFile);
670
671       long userSettingsTimestamp = getFileTimestamp(generalSettings.getEffectiveUserSettingsFile());
672       long globalSettingsTimestamp = getFileTimestamp(generalSettings.getEffectiveGlobalSettingsFile());
673
674       int profilesHashCode = explicitProfiles.hashCode();
675
676       return new MavenProjectTimestamp(pomTimestamp,
677                                        parentLastReadStamp,
678                                        profilesTimestamp,
679                                        userSettingsTimestamp,
680                                        globalSettingsTimestamp,
681                                        profilesHashCode,
682                                        jvmConfigTimestamp,
683                                        mavenConfigTimestamp);
684     });
685   }
686
687   @Override
688   public String toString() {
689     return "MavenProjectsTree{" +
690            "myRootProjects=" + myRootProjects +
691            ", myProject=" + myProject +
692            '}';
693   }
694
695   private static long getFileTimestamp(VirtualFile file) {
696     if (file == null || !file.isValid()) return -1;
697     return file.getTimeStamp();
698   }
699
700   public boolean isManagedFile(VirtualFile moduleFile) {
701     return isManagedFile(moduleFile.getPath());
702   }
703
704   public boolean isManagedFile(String path) {
705     synchronized (myStateLock) {
706       for (String each : myManagedFilesPaths) {
707         if (FileUtil.pathsEqual(each, path)) return true;
708       }
709       return false;
710     }
711   }
712
713   public boolean isPotentialProject(String path) {
714     if (isManagedFile(path)) return true;
715
716     for (MavenProject each : getProjects()) {
717       if (VfsUtilCore.pathEqualsTo(each.getFile(), path)) return true;
718       if (each.getModulePaths().contains(path)) return true;
719     }
720     return false;
721   }
722
723   public void delete(List<VirtualFile> files,
724                      MavenGeneralSettings generalSettings,
725                      MavenProgressIndicator process) {
726     delete(new MavenProjectReader(myProject), files, getExplicitProfiles(), generalSettings, process);
727   }
728
729   private void delete(MavenProjectReader projectReader,
730                       List<VirtualFile> files,
731                       MavenExplicitProfiles explicitProfiles,
732                       MavenGeneralSettings generalSettings,
733                       MavenProgressIndicator process) {
734     if (files.isEmpty()) return;
735
736     UpdateContext updateContext = new UpdateContext();
737     Stack<MavenProject> updateStack = new Stack<>();
738
739     Set<MavenProject> inheritorsToUpdate = new HashSet<>();
740     for (VirtualFile each : files) {
741       MavenProject mavenProject = findProject(each);
742       if (mavenProject == null) return;
743
744       inheritorsToUpdate.addAll(findInheritors(mavenProject));
745       doDelete(findAggregator(mavenProject), mavenProject, updateContext);
746     }
747     inheritorsToUpdate.removeAll(updateContext.deletedProjects);
748
749     for (MavenProject each : inheritorsToUpdate) {
750       doUpdate(each, null, false, false, false, explicitProfiles, updateContext, updateStack, projectReader, generalSettings, process);
751     }
752
753     updateExplicitProfiles();
754     updateContext.fireUpdatedIfNecessary();
755   }
756
757   private void doDelete(MavenProject aggregator, MavenProject project, UpdateContext updateContext) {
758     for (MavenProject each : getModules(project)) {
759       if (isManagedFile(each.getPath())) {
760         if (reconnect(null, each)) {
761           updateContext.update(each, MavenProjectChanges.NONE);
762         }
763       }
764       else {
765         doDelete(project, each, updateContext);
766       }
767     }
768
769     writeLock();
770     try {
771       if (aggregator != null) {
772         removeModule(aggregator, project);
773       }
774       else {
775         myRootProjects.remove(project);
776       }
777       myTimestamps.remove(project);
778       myVirtualFileToProjectMapping.remove(project.getFile());
779       clearIDMaps(project);
780       myAggregatorToModuleMapping.remove(project);
781       myModuleToAggregatorMapping.remove(project);
782     }
783     finally {
784       writeUnlock();
785     }
786
787     updateContext.deleted(project);
788   }
789
790   private void fillIDMaps(MavenProject mavenProject) {
791     MavenId id = mavenProject.getMavenId();
792     myWorkspaceMap.register(id, new File(mavenProject.getFile().getPath()));
793     myMavenIdToProjectMapping.put(id, mavenProject);
794   }
795
796   private void clearIDMaps(MavenProject mavenProject) {
797     MavenId id = mavenProject.getMavenId();
798     myWorkspaceMap.unregister(id);
799     myMavenIdToProjectMapping.remove(id);
800   }
801
802   private void connect(MavenProject newAggregator, MavenProject project) {
803     writeLock();
804     try {
805       if (newAggregator != null) {
806         addModule(newAggregator, project);
807       }
808       else {
809         myRootProjects.add(project);
810       }
811     }
812     finally {
813       writeUnlock();
814     }
815   }
816
817   private boolean reconnect(MavenProject newAggregator, MavenProject project) {
818     MavenProject prevAggregator = findAggregator(project);
819
820     if (prevAggregator == newAggregator) return false;
821
822     writeLock();
823     try {
824       if (prevAggregator != null) {
825         removeModule(prevAggregator, project);
826       }
827       else {
828         myRootProjects.remove(project);
829       }
830
831       if (newAggregator != null) {
832         addModule(newAggregator, project);
833       }
834       else {
835         myRootProjects.add(project);
836       }
837     }
838     finally {
839       writeUnlock();
840     }
841
842     return true;
843   }
844
845   public boolean hasProjects() {
846     readLock();
847     try {
848       return !myRootProjects.isEmpty();
849     }
850     finally {
851       readUnlock();
852     }
853   }
854
855   public List<MavenProject> getRootProjects() {
856     readLock();
857     try {
858       return new ArrayList<>(myRootProjects);
859     }
860     finally {
861       readUnlock();
862     }
863   }
864
865   private static void updateCrc(CRC32 crc, int x) {
866     crc.update(x & 0xFF);
867     x >>>= 8;
868     crc.update(x & 0xFF);
869     x >>>= 8;
870     crc.update(x & 0xFF);
871     x >>>= 8;
872     crc.update(x);
873   }
874
875   private static void updateCrc(CRC32 crc, long l) {
876     updateCrc(crc, (int)l);
877     updateCrc(crc, (int)(l >>> 32));
878   }
879
880   private static void updateCrc(CRC32 crc, @Nullable String s) {
881     if (s == null) {
882       crc.update(111);
883     }
884     else {
885       updateCrc(crc, s.hashCode());
886       crc.update(s.length() & 0xFF);
887     }
888   }
889
890   @NotNull
891   public static Collection<String> getFilterExclusions(MavenProject mavenProject) {
892     Element config = mavenProject.getPluginConfiguration("org.apache.maven.plugins", "maven-resources-plugin");
893     if (config == null) {
894       return Collections.emptySet();
895     }
896     final List<String> customNonFilteredExtensions =
897       MavenJDOMUtil.findChildrenValuesByPath(config, "nonFilteredFileExtensions", "nonFilteredFileExtension");
898     if (customNonFilteredExtensions.isEmpty()) {
899       return Collections.emptySet();
900     }
901     return Collections.unmodifiableList(customNonFilteredExtensions);
902   }
903
904   public int getFilterConfigCrc(@NotNull ProjectFileIndex fileIndex) {
905     ApplicationManager.getApplication().assertReadAccessAllowed();
906
907     readLock();
908     try {
909       final CRC32 crc = new CRC32();
910
911       MavenExplicitProfiles profiles = myExplicitProfiles;
912       if (profiles != null) {
913         updateCrc(crc, profiles.hashCode());
914       }
915
916       Collection<MavenProject> allProjects = myVirtualFileToProjectMapping.values();
917
918       crc.update(allProjects.size() & 0xFF);
919       for (MavenProject mavenProject : allProjects) {
920         VirtualFile pomFile = mavenProject.getFile();
921         Module module = fileIndex.getModuleForFile(pomFile);
922         if (module == null) continue;
923
924         if (!Comparing.equal(fileIndex.getContentRootForFile(pomFile), pomFile.getParent())) continue;
925
926         updateCrc(crc, module.getName());
927
928         MavenId mavenId = mavenProject.getMavenId();
929         updateCrc(crc, mavenId.getGroupId());
930         updateCrc(crc, mavenId.getArtifactId());
931         updateCrc(crc, mavenId.getVersion());
932
933         MavenId parentId = mavenProject.getParentId();
934         if (parentId != null) {
935           updateCrc(crc, parentId.getGroupId());
936           updateCrc(crc, parentId.getArtifactId());
937           updateCrc(crc, parentId.getVersion());
938         }
939
940
941         updateCrc(crc, mavenProject.getDirectory());
942         updateCrc(crc, MavenFilteredPropertyPsiReferenceProvider.getDelimitersPattern(mavenProject).pattern());
943         updateCrc(crc, mavenProject.getModelMap().hashCode());
944         updateCrc(crc, mavenProject.getResources().hashCode());
945         updateCrc(crc, mavenProject.getTestResources().hashCode());
946         updateCrc(crc, getFilterExclusions(mavenProject).hashCode());
947         updateCrc(crc, mavenProject.getProperties().hashCode());
948
949         for (String each : mavenProject.getFilterPropertiesFiles()) {
950           File file = new File(each);
951           updateCrc(crc, file.lastModified());
952         }
953
954         XMLOutputter outputter = new XMLOutputter(Format.getCompactFormat());
955
956         Writer crcWriter = new Writer() {
957           @Override
958           public void write(char[] cbuf, int off, int len) throws IOException {
959             for (int i = off, end = off + len; i < end; i++) {
960               crc.update(cbuf[i]);
961             }
962           }
963
964           @Override
965           public void flush() throws IOException {
966
967           }
968
969           @Override
970           public void close() throws IOException {
971
972           }
973         };
974
975         try {
976           Element resourcePluginCfg = mavenProject.getPluginConfiguration("org.apache.maven.plugins", "maven-resources-plugin");
977           if (resourcePluginCfg != null) {
978             outputter.output(resourcePluginCfg, crcWriter);
979           }
980
981           Element warPluginCfg = mavenProject.getPluginConfiguration("org.apache.maven.plugins", "maven-war-plugin");
982           if (warPluginCfg != null) {
983             outputter.output(warPluginCfg, crcWriter);
984           }
985         }
986         catch (IOException e) {
987           LOG.error(e);
988         }
989       }
990
991       return (int)crc.getValue();
992     }
993     finally {
994       readUnlock();
995     }
996   }
997
998   public List<VirtualFile> getRootProjectsFiles() {
999     return MavenUtil.collectFiles(getRootProjects());
1000   }
1001
1002   public List<MavenProject> getProjects() {
1003     readLock();
1004     try {
1005       return new ArrayList<>(myVirtualFileToProjectMapping.values());
1006     }
1007     finally {
1008       readUnlock();
1009     }
1010   }
1011
1012   public List<MavenProject> getNonIgnoredProjects() {
1013     readLock();
1014     try {
1015       List<MavenProject> result = new ArrayList<>();
1016       for (MavenProject each : myVirtualFileToProjectMapping.values()) {
1017         if (!isIgnored(each)) result.add(each);
1018       }
1019       return result;
1020     }
1021     finally {
1022       readUnlock();
1023     }
1024   }
1025
1026   public List<VirtualFile> getProjectsFiles() {
1027     readLock();
1028     try {
1029       return new ArrayList<>(myVirtualFileToProjectMapping.keySet());
1030     }
1031     finally {
1032       readUnlock();
1033     }
1034   }
1035
1036   @Nullable
1037   public MavenProject findProject(VirtualFile f) {
1038     readLock();
1039     try {
1040       return myVirtualFileToProjectMapping.get(f);
1041     }
1042     finally {
1043       readUnlock();
1044     }
1045   }
1046
1047   @Nullable
1048   public MavenProject findProject(MavenId id) {
1049     readLock();
1050     try {
1051       return myMavenIdToProjectMapping.get(id);
1052     }
1053     finally {
1054       readUnlock();
1055     }
1056   }
1057
1058   @Nullable
1059   public MavenProject findProject(MavenArtifact artifact) {
1060     return findProject(artifact.getMavenId());
1061   }
1062
1063   public MavenProject findSingleProjectInReactor(MavenId id) {
1064     readLock();
1065     try {
1066       List<MavenProject> list = myMavenIdToProjectMapping.values().stream().filter(
1067         it -> StringUtil.equals(it.getMavenId().getArtifactId(), id.getArtifactId()) &&
1068               StringUtil.equals(it.getMavenId().getGroupId(), id.getGroupId())
1069       ).collect(Collectors.toList());
1070       return list.size() == 1 ? list.get(0) : null;
1071     }
1072     finally {
1073       readUnlock();
1074     }
1075   }
1076
1077   MavenWorkspaceMap getWorkspaceMap() {
1078     readLock();
1079     try {
1080       return myWorkspaceMap.copy();
1081     }
1082     finally {
1083       readUnlock();
1084     }
1085   }
1086
1087   public MavenProject findAggregator(MavenProject project) {
1088     readLock();
1089     try {
1090       return myModuleToAggregatorMapping.get(project);
1091     }
1092     finally {
1093       readUnlock();
1094     }
1095   }
1096
1097   @NotNull
1098   public MavenProject findRootProject(@NotNull MavenProject project) {
1099     readLock();
1100     try {
1101       MavenProject rootProject = project;
1102       while (true) {
1103         MavenProject aggregator = myModuleToAggregatorMapping.get(rootProject);
1104         if (aggregator == null) {
1105           return rootProject;
1106         }
1107         rootProject = aggregator;
1108       }
1109     }
1110     finally {
1111       readUnlock();
1112     }
1113   }
1114
1115   public boolean isRootProject(@NotNull MavenProject project) {
1116     readLock();
1117     try {
1118       return myModuleToAggregatorMapping.get(project) == null;
1119     }
1120     finally {
1121       readUnlock();
1122     }
1123   }
1124
1125   public List<MavenProject> getModules(MavenProject aggregator) {
1126     readLock();
1127     try {
1128       List<MavenProject> modules = myAggregatorToModuleMapping.get(aggregator);
1129       return modules == null
1130              ? Collections.emptyList()
1131              : new ArrayList<>(modules);
1132     }
1133     finally {
1134       readUnlock();
1135     }
1136   }
1137
1138   private void addModule(MavenProject aggregator, MavenProject module) {
1139     writeLock();
1140     try {
1141       List<MavenProject> modules = myAggregatorToModuleMapping.get(aggregator);
1142       if (modules == null) {
1143         modules = new ArrayList<>();
1144         myAggregatorToModuleMapping.put(aggregator, modules);
1145       }
1146       modules.add(module);
1147
1148       myModuleToAggregatorMapping.put(module, aggregator);
1149     }
1150     finally {
1151       writeUnlock();
1152     }
1153   }
1154
1155   private void removeModule(MavenProject aggregator, MavenProject module) {
1156     writeLock();
1157     try {
1158       List<MavenProject> modules = myAggregatorToModuleMapping.get(aggregator);
1159       if (modules == null) return;
1160       modules.remove(module);
1161       myModuleToAggregatorMapping.remove(module);
1162     }
1163     finally {
1164       writeUnlock();
1165     }
1166   }
1167
1168   private MavenProject findParent(MavenProject project) {
1169     return findProject(project.getParentId());
1170   }
1171
1172   public Collection<MavenProject> findInheritors(MavenProject project) {
1173     readLock();
1174     try {
1175       List<MavenProject> result = null;
1176       MavenId id = project.getMavenId();
1177
1178       for (MavenProject each : myVirtualFileToProjectMapping.values()) {
1179         if (each == project) continue;
1180         if (id.equals(each.getParentId())) {
1181           if (result == null) result = new ArrayList<>();
1182           result.add(each);
1183         }
1184       }
1185
1186       return result == null ? Collections.emptyList() : result;
1187     }
1188     finally {
1189       readUnlock();
1190     }
1191   }
1192
1193   public List<MavenProject> getDependentProjects(Collection<MavenProject> projects) {
1194     readLock();
1195     try {
1196       List<MavenProject> result = null;
1197
1198       Set<MavenCoordinate> projectIds = new ObjectOpenCustomHashSet<>(projects.size(), new MavenCoordinateHashCodeStrategy());
1199       for (MavenProject project : projects) {
1200         projectIds.add(project.getMavenId());
1201       }
1202
1203       Set<File> projectPaths = FileCollectionFactory.createCanonicalFileSet();
1204       for (MavenProject project : projects) {
1205         projectPaths.add(new File(project.getFile().getPath()));
1206       }
1207
1208       for (MavenProject project : myVirtualFileToProjectMapping.values()) {
1209         boolean isDependent = false;
1210
1211         Set<String> pathsInStack = project.getModulePaths();
1212         for (final String path : pathsInStack) {
1213           if (projectPaths.contains(new File(path))) {
1214             isDependent = true;
1215             break;
1216           }
1217         }
1218
1219         if (!isDependent) {
1220           for (MavenArtifact dep : project.getDependencies()) {
1221             if (projectIds.contains(dep)) {
1222               isDependent = true;
1223               break;
1224             }
1225           }
1226         }
1227
1228         if (isDependent) {
1229           if (result == null) result = new ArrayList<>();
1230           result.add(project);
1231         }
1232       }
1233
1234       return result == null ? Collections.emptyList() : result;
1235     }
1236     finally {
1237       readUnlock();
1238     }
1239   }
1240
1241   public <Result> Result visit(Visitor<Result> visitor) {
1242     for (MavenProject each : getRootProjects()) {
1243       if (visitor.isDone()) break;
1244       doVisit(each, visitor);
1245     }
1246     return visitor.getResult();
1247   }
1248
1249   private <Result> void doVisit(MavenProject project, Visitor<Result> visitor) {
1250     if (!visitor.isDone() && visitor.shouldVisit(project)) {
1251       visitor.visit(project);
1252       for (MavenProject each : getModules(project)) {
1253         if (visitor.isDone()) break;
1254         doVisit(each, visitor);
1255       }
1256       visitor.leave(project);
1257     }
1258   }
1259
1260   private void writeLock() {
1261     myStructureWriteLock.lock();
1262   }
1263
1264   private void writeUnlock() {
1265     myStructureWriteLock.unlock();
1266   }
1267
1268   private void readLock() {
1269     myStructureReadLock.lock();
1270   }
1271
1272   private void readUnlock() {
1273     myStructureReadLock.unlock();
1274   }
1275
1276   public void addListener(@NotNull Listener l, @NotNull Disposable disposable) {
1277     myListeners.add(l, disposable);
1278   }
1279
1280   void fireProfilesChanged() {
1281     for (Listener each : myListeners) {
1282       each.profilesChanged();
1283     }
1284   }
1285
1286   void fireProjectsIgnoredStateChanged(@NotNull List<MavenProject> ignored, @NotNull List<MavenProject> unignored, boolean fromImport) {
1287     for (Listener each : myListeners) {
1288       each.projectsIgnoredStateChanged(ignored, unignored, fromImport);
1289     }
1290   }
1291
1292   void fireProjectsUpdated(@NotNull List<Pair<MavenProject, MavenProjectChanges>> updated, @NotNull List<MavenProject> deleted) {
1293     for (Listener each : myListeners) {
1294       each.projectsUpdated(updated, deleted);
1295     }
1296   }
1297
1298   void fireProjectResolved(@NotNull Pair<MavenProject, MavenProjectChanges> projectWithChanges,
1299                            @Nullable NativeMavenProjectHolder nativeMavenProject) {
1300     for (Listener each : myListeners) {
1301       each.projectResolved(projectWithChanges, nativeMavenProject);
1302     }
1303   }
1304
1305   void fireAllProjectsResolved() {
1306     for (Listener each : myListeners) {
1307       each.allProjectsResolved();
1308     }
1309   }
1310
1311   void firePluginsResolved(@NotNull MavenProject project) {
1312     for (Listener each : myListeners) {
1313       each.pluginsResolved(project);
1314     }
1315   }
1316
1317   void fireFoldersResolved(@NotNull Pair<MavenProject, MavenProjectChanges> projectWithChanges) {
1318     for (Listener each : myListeners) {
1319       each.foldersResolved(projectWithChanges);
1320     }
1321   }
1322
1323   void fireArtifactsDownloaded(@NotNull MavenProject project) {
1324     for (Listener each : myListeners) {
1325       each.artifactsDownloaded(project);
1326     }
1327   }
1328
1329   private class UpdateContext {
1330     public final Map<MavenProject, MavenProjectChanges> updatedProjectsWithChanges = new LinkedHashMap<>();
1331     public final Set<MavenProject> deletedProjects = new LinkedHashSet<>();
1332
1333     public void update(MavenProject project, MavenProjectChanges changes) {
1334       deletedProjects.remove(project);
1335       updatedProjectsWithChanges.put(project, changes.mergedWith(updatedProjectsWithChanges.get(project)));
1336     }
1337
1338     public void deleted(MavenProject project) {
1339       updatedProjectsWithChanges.remove(project);
1340       deletedProjects.add(project);
1341     }
1342
1343     public void deleted(Collection<MavenProject> projects) {
1344       for (MavenProject each : projects) {
1345         deleted(each);
1346       }
1347     }
1348
1349     public void fireUpdatedIfNecessary() {
1350       if (updatedProjectsWithChanges.isEmpty() && deletedProjects.isEmpty()) {
1351         //MavenProjectsManager.getInstance(myProject).getSyncConsole().finishImport();
1352         return;
1353       }
1354       List<MavenProject> mavenProjects = deletedProjects.isEmpty()
1355                                          ? Collections.emptyList()
1356                                          : new ArrayList<>(deletedProjects);
1357       List<Pair<MavenProject, MavenProjectChanges>> updated = updatedProjectsWithChanges.isEmpty()
1358                                                               ? Collections.emptyList()
1359                                                               : MavenUtil.mapToList(updatedProjectsWithChanges);
1360       fireProjectsUpdated(updated, mavenProjects);
1361     }
1362   }
1363
1364   public abstract static class Visitor<Result> {
1365     private Result result;
1366
1367     public boolean shouldVisit(MavenProject project) {
1368       return true;
1369     }
1370
1371     public abstract void visit(MavenProject project);
1372
1373     public void leave(MavenProject node) {
1374     }
1375
1376     public void setResult(Result result) {
1377       this.result = result;
1378     }
1379
1380     public Result getResult() {
1381       return result;
1382     }
1383
1384     public boolean isDone() {
1385       return result != null;
1386     }
1387   }
1388
1389   public abstract static class SimpleVisitor extends Visitor<Object> {
1390   }
1391
1392   private static final class MavenProjectTimestamp {
1393     private final long myPomTimestamp;
1394     private final long myParentLastReadStamp;
1395     private final long myProfilesTimestamp;
1396     private final long myUserSettingsTimestamp;
1397     private final long myGlobalSettingsTimestamp;
1398     private final long myExplicitProfilesHashCode;
1399     private final long myJvmConfigTimestamp;
1400     private final long myMavenConfigTimestamp;
1401
1402     private MavenProjectTimestamp(long pomTimestamp,
1403                                   long parentLastReadStamp,
1404                                   long profilesTimestamp,
1405                                   long userSettingsTimestamp,
1406                                   long globalSettingsTimestamp,
1407                                   long explicitProfilesHashCode,
1408                                   long jvmConfigTimestamp,
1409                                   long mavenConfigTimestamp) {
1410       myPomTimestamp = pomTimestamp;
1411       myParentLastReadStamp = parentLastReadStamp;
1412       myProfilesTimestamp = profilesTimestamp;
1413       myUserSettingsTimestamp = userSettingsTimestamp;
1414       myGlobalSettingsTimestamp = globalSettingsTimestamp;
1415       myExplicitProfilesHashCode = explicitProfilesHashCode;
1416       myJvmConfigTimestamp = jvmConfigTimestamp;
1417       myMavenConfigTimestamp = mavenConfigTimestamp;
1418     }
1419
1420     public static MavenProjectTimestamp read(DataInputStream in) throws IOException {
1421       return new MavenProjectTimestamp(in.readLong(),
1422                                        in.readLong(),
1423                                        in.readLong(),
1424                                        in.readLong(),
1425                                        in.readLong(),
1426                                        in.readLong(),
1427                                        in.readLong(),
1428                                        in.readLong());
1429     }
1430
1431     public void write(DataOutputStream out) throws IOException {
1432       out.writeLong(myPomTimestamp);
1433       out.writeLong(myParentLastReadStamp);
1434       out.writeLong(myProfilesTimestamp);
1435       out.writeLong(myUserSettingsTimestamp);
1436       out.writeLong(myGlobalSettingsTimestamp);
1437       out.writeLong(myExplicitProfilesHashCode);
1438       out.writeLong(myJvmConfigTimestamp);
1439       out.writeLong(myMavenConfigTimestamp);
1440     }
1441
1442     @Override
1443     public String toString() {
1444       return "(" + myPomTimestamp
1445              + ":" + myParentLastReadStamp
1446              + ":" + myProfilesTimestamp
1447              + ":" + myUserSettingsTimestamp
1448              + ":" + myGlobalSettingsTimestamp
1449              + ":" + myExplicitProfilesHashCode
1450              + ":" + myJvmConfigTimestamp
1451              + ":" + myMavenConfigTimestamp + ")";
1452     }
1453
1454     @Override
1455     public boolean equals(Object o) {
1456       if (this == o) return true;
1457       if (o == null || getClass() != o.getClass()) return false;
1458
1459       MavenProjectTimestamp timestamp = (MavenProjectTimestamp)o;
1460
1461       if (myPomTimestamp != timestamp.myPomTimestamp) return false;
1462       if (myParentLastReadStamp != timestamp.myParentLastReadStamp) return false;
1463       if (myProfilesTimestamp != timestamp.myProfilesTimestamp) return false;
1464       if (myUserSettingsTimestamp != timestamp.myUserSettingsTimestamp) return false;
1465       if (myGlobalSettingsTimestamp != timestamp.myGlobalSettingsTimestamp) return false;
1466       if (myExplicitProfilesHashCode != timestamp.myExplicitProfilesHashCode) return false;
1467       if (myJvmConfigTimestamp != timestamp.myJvmConfigTimestamp) return false;
1468       if (myMavenConfigTimestamp != timestamp.myMavenConfigTimestamp) return false;
1469
1470       return true;
1471     }
1472
1473     @Override
1474     public int hashCode() {
1475       int result = 0;
1476       result = 31 * result + (int)(myPomTimestamp ^ (myPomTimestamp >>> 32));
1477       result = 31 * result + (int)(myParentLastReadStamp ^ (myParentLastReadStamp >>> 32));
1478       result = 31 * result + (int)(myProfilesTimestamp ^ (myProfilesTimestamp >>> 32));
1479       result = 31 * result + (int)(myUserSettingsTimestamp ^ (myUserSettingsTimestamp >>> 32));
1480       result = 31 * result + (int)(myGlobalSettingsTimestamp ^ (myGlobalSettingsTimestamp >>> 32));
1481       result = 31 * result + (int)(myExplicitProfilesHashCode ^ (myExplicitProfilesHashCode >>> 32));
1482       result = 31 * result + (int)(myJvmConfigTimestamp ^ (myJvmConfigTimestamp >>> 32));
1483       result = 31 * result + (int)(myMavenConfigTimestamp ^ (myMavenConfigTimestamp >>> 32));
1484       return result;
1485     }
1486   }
1487
1488   public interface Listener extends EventListener {
1489     default void profilesChanged() {
1490     }
1491
1492     default void projectsIgnoredStateChanged(@NotNull List<MavenProject> ignored, @NotNull List<MavenProject> unignored, boolean fromImport) {
1493     }
1494
1495     default void projectsUpdated(@NotNull List<Pair<MavenProject, MavenProjectChanges>> updated, @NotNull List<MavenProject> deleted) {
1496     }
1497
1498     default void projectResolved(@NotNull Pair<MavenProject, MavenProjectChanges> projectWithChanges,
1499                                  @Nullable NativeMavenProjectHolder nativeMavenProject) {
1500     }
1501
1502     default void pluginsResolved(@NotNull MavenProject project) {
1503     }
1504
1505     default void foldersResolved(@NotNull Pair<MavenProject, MavenProjectChanges> projectWithChanges) {
1506     }
1507
1508     default void artifactsDownloaded(@NotNull MavenProject project) {
1509     }
1510
1511     default void allProjectsResolved() {}
1512   }
1513
1514   @ApiStatus.Internal
1515   public static final class MavenCoordinateHashCodeStrategy implements Hash.Strategy<MavenCoordinate> {
1516     @Override
1517     public int hashCode(MavenCoordinate object) {
1518       String artifactId = object == null ? null : object.getArtifactId();
1519       return artifactId == null ? 0 : artifactId.hashCode();
1520     }
1521
1522     @Override
1523     public boolean equals(MavenCoordinate o1, MavenCoordinate o2) {
1524       if (o1 == o2) {
1525         return true;
1526       }
1527       if (o1 == null || o2 == null) {
1528         return false;
1529       }
1530
1531       return Objects.equals(o1.getArtifactId(), o2.getArtifactId())
1532              && Objects.equals(o1.getVersion(), o2.getVersion())
1533              && Objects.equals(o1.getGroupId(), o2.getGroupId());
1534     }
1535   }
1536 }