fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / project / impl / ProjectImpl.java
1 // Copyright 2000-2019 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 com.intellij.openapi.project.impl;
3
4 import com.intellij.configurationStore.StoreUtil;
5 import com.intellij.diagnostic.Activity;
6 import com.intellij.diagnostic.StartUpMeasurer;
7 import com.intellij.diagnostic.StartUpMeasurer.Phases;
8 import com.intellij.ide.plugins.ContainerDescriptor;
9 import com.intellij.ide.plugins.IdeaPluginDescriptorImpl;
10 import com.intellij.ide.plugins.PluginManagerCore;
11 import com.intellij.ide.startup.StartupManagerEx;
12 import com.intellij.openapi.application.Application;
13 import com.intellij.openapi.application.ApplicationManager;
14 import com.intellij.openapi.application.ex.ApplicationManagerEx;
15 import com.intellij.openapi.application.impl.LaterInvocator;
16 import com.intellij.openapi.components.ServiceManager;
17 import com.intellij.openapi.components.StorageScheme;
18 import com.intellij.openapi.components.impl.stores.IComponentStore;
19 import com.intellij.openapi.components.impl.stores.IProjectStore;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.fileEditor.FileEditorManager;
22 import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
23 import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
24 import com.intellij.openapi.module.ModuleManager;
25 import com.intellij.openapi.module.impl.ModuleManagerImpl;
26 import com.intellij.openapi.progress.ProgressIndicator;
27 import com.intellij.openapi.project.DumbAwareRunnable;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.project.ProjectManager;
30 import com.intellij.openapi.project.ex.ProjectEx;
31 import com.intellij.openapi.project.ex.ProjectManagerEx;
32 import com.intellij.openapi.startup.StartupManager;
33 import com.intellij.openapi.util.AtomicNotNullLazyValue;
34 import com.intellij.openapi.util.Key;
35 import com.intellij.openapi.vfs.LocalFileSystem;
36 import com.intellij.openapi.vfs.VirtualFile;
37 import com.intellij.openapi.wm.WindowManager;
38 import com.intellij.openapi.wm.impl.FrameTitleBuilder;
39 import com.intellij.project.ProjectStoreOwner;
40 import com.intellij.psi.impl.DebugUtil;
41 import com.intellij.serviceContainer.PlatformComponentManagerImpl;
42 import com.intellij.util.ExceptionUtil;
43 import com.intellij.util.PathUtil;
44 import com.intellij.util.TimedReference;
45 import org.jetbrains.annotations.*;
46
47 import javax.swing.*;
48 import java.nio.file.Path;
49 import java.util.concurrent.ExecutionException;
50
51 public class ProjectImpl extends PlatformComponentManagerImpl implements ProjectEx, ProjectStoreOwner {
52   private static final Logger LOG = Logger.getInstance("#com.intellij.project.impl.ProjectImpl");
53
54   public static final Key<Long> CREATION_TIME = Key.create("ProjectImpl.CREATION_TIME");
55
56   /**
57    * @deprecated use {@link #getCreationTrace()}
58    */
59   @Deprecated
60   public static final Key<String> CREATION_TRACE = Key.create("ProjectImpl.CREATION_TRACE");
61   @TestOnly
62   public static final String LIGHT_PROJECT_NAME = "light_temp";
63
64   private String myName;
65   private final boolean myLight;
66   static boolean ourClassesAreLoaded;
67   private final String creationTrace;
68
69   private final AtomicNotNullLazyValue<IComponentStore> myComponentStore = AtomicNotNullLazyValue.createValue(() -> {
70     //noinspection CodeBlock2Expr
71     return ServiceManager.getService(ProjectStoreFactory.class).createStore(this);
72   });
73
74   protected ProjectImpl(@NotNull Path filePath, @Nullable String projectName) {
75     super(ApplicationManager.getApplication());
76
77     putUserData(CREATION_TIME, System.nanoTime());
78     creationTrace = ApplicationManager.getApplication().isUnitTestMode() ? DebugUtil.currentStackTrace() : null;
79
80     getPicoContainer().registerComponentInstance(Project.class, this);
81
82     myName = projectName;
83     // light project may be changed later during test, so we need to remember its initial state
84     //noinspection TestOnlyProblems
85     myLight = ApplicationManager.getApplication().isUnitTestMode() && filePath.toString().contains(LIGHT_PROJECT_NAME);
86   }
87
88   static final String TEMPLATE_PROJECT_NAME = "Default (Template) Project";
89   // default project constructor
90   ProjectImpl() {
91     super(ApplicationManager.getApplication());
92
93     putUserData(CREATION_TIME, System.nanoTime());
94     if (ApplicationManager.getApplication().isUnitTestMode()) {
95       putUserData(CREATION_TRACE, DebugUtil.currentStackTrace());
96     }
97
98     creationTrace = ApplicationManager.getApplication().isUnitTestMode() ? DebugUtil.currentStackTrace() : null;
99
100     myName = TEMPLATE_PROJECT_NAME;
101     myLight = false;
102   }
103
104
105
106   @Override
107   public boolean isDisposed() {
108     return super.isDisposed() || temporarilyDisposed;
109   }
110
111   @Override
112   @TestOnly
113   public boolean isLight() {
114     return myLight;
115   }
116
117   private volatile boolean temporarilyDisposed;
118   @TestOnly
119   void setTemporarilyDisposed(boolean disposed) {
120     temporarilyDisposed = disposed;
121   }
122
123   @TestOnly
124   boolean isTemporarilyDisposed() {
125     return temporarilyDisposed;
126   }
127
128   @Override
129   public void setProjectName(@NotNull String projectName) {
130     if (!projectName.equals(myName)) {
131       myName = projectName;
132
133       StartupManager.getInstance(this).runWhenProjectIsInitialized((DumbAwareRunnable)() -> {
134         if (isDisposed()) return;
135
136         JFrame frame = WindowManager.getInstance().getFrame(this);
137         String title = FrameTitleBuilder.getInstance().getProjectTitle(this);
138         if (frame != null && title != null) {
139           frame.setTitle(title);
140         }
141       });
142     }
143   }
144
145   // do not call for default project
146   @NotNull
147   public final IProjectStore getStateStore() {
148     return (IProjectStore)getComponentStore();
149   }
150
151   @Override
152   @NotNull
153   public IComponentStore getComponentStore() {
154     return myComponentStore.getValue();
155   }
156
157   @Override
158   public boolean isOpen() {
159     return ProjectManagerEx.getInstanceEx().isProjectOpened(this);
160   }
161
162   @Override
163   public boolean isInitialized() {
164     return !isDisposed() && isOpen() && StartupManagerEx.getInstanceEx(this).startupActivityPassed();
165   }
166
167   @NotNull
168   @Override
169   protected ContainerDescriptor getContainerDescriptor(@NotNull IdeaPluginDescriptorImpl pluginDescriptor) {
170     return pluginDescriptor.getProject();
171   }
172
173   @Nullable
174   @Override
175   public @SystemIndependent String getProjectFilePath() {
176     return getStateStore().getProjectFilePath();
177   }
178
179   @Override
180   public VirtualFile getProjectFile() {
181     return LocalFileSystem.getInstance().findFileByPath(getStateStore().getProjectFilePath());
182   }
183
184   @Override
185   public VirtualFile getBaseDir() {
186     return LocalFileSystem.getInstance().findFileByPath(getStateStore().getProjectBasePath());
187   }
188
189   @Override
190   @Nullable
191   public @SystemIndependent String getBasePath() {
192     return getStateStore().getProjectBasePath();
193   }
194
195   @NotNull
196   @Override
197   public String getName() {
198     if (myName == null) {
199       return getStateStore().getProjectName();
200     }
201     return myName;
202   }
203
204   @Override
205   public @SystemDependent String getPresentableUrl() {
206     IProjectStore store = getStateStore();
207     return PathUtil.toSystemDependentName(store.getStorageScheme() == StorageScheme.DIRECTORY_BASED ? store.getProjectBasePath() : store.getProjectFilePath());
208   }
209
210   @NotNull
211   @NonNls
212   @Override
213   public String getLocationHash() {
214     String str = getPresentableUrl();
215     if (str == null) {
216       str = getName();
217     }
218
219     final String prefix = getStateStore().getStorageScheme() == StorageScheme.DIRECTORY_BASED ? "" : getName();
220     return prefix + Integer.toHexString(str.hashCode());
221   }
222
223   @Override
224   @Nullable
225   public VirtualFile getWorkspaceFile() {
226     String workspaceFilePath = getStateStore().getWorkspaceFilePath();
227     return workspaceFilePath == null ? null : LocalFileSystem.getInstance().findFileByPath(workspaceFilePath);
228   }
229
230   public void registerComponents() {
231     String activityNamePrefix = activityNamePrefix();
232     Activity activity = activityNamePrefix == null ? null : StartUpMeasurer.start(activityNamePrefix + Phases.REGISTER_COMPONENTS_SUFFIX);
233     //  at this point of time plugins are already loaded by application - no need to pass indicator to getLoadedPlugins call
234     registerComponents(PluginManagerCore.getLoadedPlugins());
235     if (activity != null) {
236       activity.end();
237     }
238
239     Application app = ApplicationManager.getApplication();
240     app.getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).projectComponentsRegistered(this);
241   }
242
243   public void init(@Nullable ProgressIndicator indicator) {
244     Application application = ApplicationManager.getApplication();
245
246     // before components
247     if (!isDefault()) {
248       Activity activity = StartUpMeasurer.start("preload project services");
249       try {
250         preloadServices(PluginManagerCore.getLoadedPlugins()).get();
251       }
252       catch (InterruptedException | ExecutionException e) {
253         ExceptionUtil.rethrow(e);
254       }
255       activity.end();
256     }
257
258     createComponents(indicator);
259
260     if (indicator != null && !application.isHeadlessEnvironment()) {
261       distributeProgress(indicator);
262     }
263
264     if (myName == null) {
265       myName = getStateStore().getProjectName();
266     }
267     application.getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).projectComponentsInitialized(this);
268   }
269
270   @Override
271   protected void setProgressDuringInit(@NotNull ProgressIndicator indicator) {
272     indicator.setFraction(getPercentageOfComponentsLoaded() / (ourClassesAreLoaded ? 10 : 2));
273   }
274
275   private void distributeProgress(@NotNull ProgressIndicator indicator) {
276     ModuleManager moduleManager = ModuleManager.getInstance(this);
277     if (!(moduleManager instanceof ModuleManagerImpl)) {
278       return;
279     }
280
281     double toDistribute = 1 - indicator.getFraction();
282     int modulesCount = ((ModuleManagerImpl)moduleManager).getModulePathsCount();
283     EditorsSplitters splitters = ((FileEditorManagerImpl)FileEditorManager.getInstance(this)).getMainSplitters();
284     int editors = splitters.getEditorsCount();
285
286     double modulesPart = ourClassesAreLoaded || editors == 0 ? toDistribute : toDistribute * 0.5;
287     if (modulesCount != 0) {
288
289       double step = modulesPart / modulesCount;
290       ((ModuleManagerImpl)moduleManager).setProgressStep(step);
291     }
292
293     if (editors != 0) {
294       splitters.setProgressStep(toDistribute - modulesPart / editors);
295     }
296   }
297
298   @Override
299   public void save() {
300     if (!ApplicationManagerEx.getApplicationEx().isSaveAllowed()) {
301       // no need to save
302       return;
303     }
304
305     // ensure that expensive save operation is not performed before startupActivityPassed
306     // first save may be quite cost operation, because cache is not warmed up yet
307     if (!isInitialized()) {
308       LOG.debug("Skip save for " + getName() + ": not initialized");
309       return;
310     }
311
312     StoreUtil.saveSettings(this, false);
313   }
314
315   @Override
316   public synchronized void dispose() {
317     Application application = ApplicationManager.getApplication();
318     application.assertWriteAccessAllowed();  // dispose must be under write action
319
320     // can call dispose only via com.intellij.ide.impl.ProjectUtil.closeAndDispose()
321     if (ProjectManagerEx.getInstanceEx().isProjectOpened(this)) {
322       throw new IllegalStateException("Must call .dispose() for a closed project only. See ProjectManager.closeProject() or ProjectUtil.closeAndDispose().");
323     }
324
325     // we use super here, because temporarilyDisposed will be true if project closed
326     LOG.assertTrue(!super.isDisposed(), this + " is disposed already");
327     disposeComponents();
328
329     super.dispose();
330
331     if (!application.isDisposed()) {
332       application.getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).afterProjectClosed(this);
333     }
334     ((ProjectManagerImpl)ProjectManager.getInstance()).updateTheOnlyProjectField();
335
336     TimedReference.disposeTimed();
337     LaterInvocator.purgeExpiredItems();
338   }
339
340   @Override
341   public boolean isDefault() {
342     return false;
343   }
344
345   @NonNls
346   @Override
347   public String toString() {
348     return "Project" +
349            (isDisposed() ? " (Disposed" + (temporarilyDisposed ? " temporarily" : "") + ")"
350                          : " '" + (myComponentStore.isComputed() ? getPresentableUrl() : "<no component store>") + "'") +
351            " " + myName;
352   }
353
354   @TestOnly
355   public String getCreationTrace() {
356     return creationTrace;
357   }
358
359   @Nullable
360   @ApiStatus.Internal
361   @Override
362   public String activityNamePrefix() {
363     return "project ";
364   }
365 }