/* * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.project.impl; import com.intellij.ide.RecentProjectsManagerBase; import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.ide.startup.StartupManagerEx; import com.intellij.notification.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathMacros; import com.intellij.openapi.application.ex.ApplicationEx; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.application.impl.ApplicationInfoImpl; import com.intellij.openapi.components.ExtensionAreas; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.components.TrackingPathMacroSubstitutor; import com.intellij.openapi.components.impl.PlatformComponentManagerImpl; import com.intellij.openapi.components.impl.ProjectPathMacroManager; import com.intellij.openapi.components.impl.stores.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ProjectManagerAdapter; import com.intellij.openapi.project.ex.ProjectEx; import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.WindowManager; import com.intellij.openapi.wm.impl.FrameTitleBuilder; import com.intellij.psi.impl.DebugUtil; import com.intellij.util.Function; import com.intellij.util.TimedReference; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.picocontainer.*; import org.picocontainer.defaults.CachingComponentAdapter; import org.picocontainer.defaults.ConstructorInjectionComponentAdapter; import javax.swing.*; import javax.swing.event.HyperlinkEvent; import java.io.File; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; public class ProjectImpl extends PlatformComponentManagerImpl implements ProjectEx { private static final Logger LOG = Logger.getInstance("#com.intellij.project.impl.ProjectImpl"); public static final String NAME_FILE = ".name"; private ProjectManager myManager; private MyProjectManagerListener myProjectManagerListener; private final AtomicBoolean mySavingInProgress = new AtomicBoolean(false); public boolean myOptimiseTestLoadSpeed; @NonNls public static final String TEMPLATE_PROJECT_NAME = "Default (Template) Project"; private String myName; private String myOldName; public static Key<Long> CREATION_TIME = Key.create("ProjectImpl.CREATION_TIME"); public static final Key<String> CREATION_TRACE = Key.create("ProjectImpl.CREATION_TRACE"); protected ProjectImpl(@NotNull ProjectManager manager, @NotNull String dirPath, boolean isOptimiseTestLoadSpeed, String projectName) { super(ApplicationManager.getApplication(), "Project " + (projectName == null ? dirPath : projectName)); putUserData(CREATION_TIME, System.nanoTime()); if (ApplicationManager.getApplication().isUnitTestMode()) { putUserData(CREATION_TRACE, DebugUtil.currentStackTrace()); } getPicoContainer().registerComponentInstance(Project.class, this); if (!isDefault()) { getStateStore().setProjectFilePath(dirPath); } myOptimiseTestLoadSpeed = isOptimiseTestLoadSpeed; myManager = manager; myName = isDefault() ? TEMPLATE_PROJECT_NAME : projectName == null ? getStateStore().getProjectName() : projectName; if (!isDefault() && projectName != null) { myOldName = ""; // new project } } @Override public void setProjectName(@NotNull String projectName) { if (!projectName.equals(myName)) { myOldName = myName; myName = projectName; StartupManager.getInstance(this).runWhenProjectIsInitialized(new DumbAwareRunnable() { @Override public void run() { if (isDisposed()) return; JFrame frame = WindowManager.getInstance().getFrame(ProjectImpl.this); String title = FrameTitleBuilder.getInstance().getProjectTitle(ProjectImpl.this); if (frame != null && title != null) { frame.setTitle(title); } } }); } } @Override protected void bootstrapPicoContainer(@NotNull String name) { Extensions.instantiateArea(ExtensionAreas.PROJECT, this, null); super.bootstrapPicoContainer(name); final MutablePicoContainer picoContainer = getPicoContainer(); picoContainer.registerComponentImplementation(ProjectPathMacroManager.class); picoContainer.registerComponent(new ComponentAdapter() { ComponentAdapter myDelegate; public ComponentAdapter getDelegate() { if (myDelegate == null) { final Class storeClass = isDefault() ? DefaultProjectStoreImpl.class : ProjectStoreImpl.class; myDelegate = new CachingComponentAdapter(new ConstructorInjectionComponentAdapter(storeClass, storeClass, null, true)); } return myDelegate; } @Override public Object getComponentKey() { return IComponentStore.class; } @Override public Class getComponentImplementation() { return getDelegate().getComponentImplementation(); } @Override public Object getComponentInstance(final PicoContainer container) throws PicoInitializationException, PicoIntrospectionException { return getDelegate().getComponentInstance(container); } @Override public void verify(final PicoContainer container) throws PicoIntrospectionException { getDelegate().verify(container); } @Override public void accept(final PicoVisitor visitor) { visitor.visitComponentAdapter(this); getDelegate().accept(visitor); } }); } @NotNull @Override public IProjectStore getStateStore() { return (IProjectStore)getPicoContainer().getComponentInstance(IComponentStore.class); } @Override public void initializeComponent(Object component, boolean service) { if (!service) { ProgressIndicator indicator = getProgressIndicator(); if (indicator != null) { indicator.setText2(getComponentName(component)); // indicator.setIndeterminate(false); // indicator.setFraction(myComponentsRegistry.getPercentageOfComponentsLoaded()); } } getStateStore().initComponent(component); } @Override public boolean isOpen() { return ProjectManagerEx.getInstanceEx().isProjectOpened(this); } @Override public boolean isInitialized() { return isOpen() && !isDisposed() && StartupManagerEx.getInstanceEx(this).startupActivityPassed(); } public void loadProjectComponents() { final IdeaPluginDescriptor[] plugins = PluginManagerCore.getPlugins(); for (IdeaPluginDescriptor plugin : plugins) { if (PluginManagerCore.shouldSkipPlugin(plugin)) continue; loadComponentsConfiguration(plugin.getProjectComponents(), plugin, isDefault()); } } @Override @NotNull public String getProjectFilePath() { return getStateStore().getProjectFilePath(); } @Override public VirtualFile getProjectFile() { return getStateStore().getProjectFile(); } @Override public VirtualFile getBaseDir() { return getStateStore().getProjectBaseDir(); } @Override public String getBasePath() { return getStateStore().getProjectBasePath(); } @NotNull @Override public String getName() { return myName; } @NonNls @Override public String getPresentableUrl() { if (myName == null) return null; // not yet initialized return getStateStore().getPresentableUrl(); } @NotNull @NonNls @Override public String getLocationHash() { String str = getPresentableUrl(); if (str == null) str = getName(); final String prefix = !isDefault() ? "" : getName(); return prefix + Integer.toHexString(str.hashCode()); } @Override @Nullable public VirtualFile getWorkspaceFile() { return getStateStore().getWorkspaceFile(); } @Override public boolean isOptimiseTestLoadSpeed() { return myOptimiseTestLoadSpeed; } @Override public void setOptimiseTestLoadSpeed(final boolean optimiseTestLoadSpeed) { myOptimiseTestLoadSpeed = optimiseTestLoadSpeed; } @Override public void init() { long start = System.currentTimeMillis(); // ProfilingUtil.startCPUProfiling(); super.init(); // ProfilingUtil.captureCPUSnapshot(); long time = System.currentTimeMillis() - start; LOG.info(getComponentConfigurations().length + " project components initialized in " + time + " ms"); getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).projectComponentsInitialized(this); myProjectManagerListener = new MyProjectManagerListener(); myManager.addProjectManagerListener(this, myProjectManagerListener); } public boolean isToSaveProjectName() { if (!isDefault()) { final IProjectStore stateStore = getStateStore(); if (!isDefault()) { final VirtualFile baseDir = stateStore.getProjectBaseDir(); if (baseDir != null && baseDir.isValid()) { return myOldName != null && !myOldName.equals(getName()); } } } return false; } @Override public void save() { if (ApplicationManagerEx.getApplicationEx().isDoNotSave()) { // no need to save return; } if (!mySavingInProgress.compareAndSet(false, true)) { return; } try { if (isToSaveProjectName()) { try { VirtualFile baseDir = getStateStore().getProjectBaseDir(); if (baseDir != null && baseDir.isValid()) { VirtualFile ideaDir = baseDir.findChild(DIRECTORY_STORE_FOLDER); if (ideaDir != null && ideaDir.isValid() && ideaDir.isDirectory()) { File nameFile = new File(ideaDir.getPath(), NAME_FILE); FileUtil.writeToFile(nameFile, getName()); myOldName = null; RecentProjectsManagerBase.getInstance().clearNameCache(); } } } catch (Throwable e) { LOG.error("Unable to store project name"); } } StoreUtil.save(getStateStore(), this); } finally { mySavingInProgress.set(false); ApplicationManager.getApplication().getMessageBus().syncPublisher(ProjectSaved.TOPIC).saved(this); } } @Override public synchronized void dispose() { ApplicationEx application = ApplicationManagerEx.getApplicationEx(); assert application.isWriteAccessAllowed(); // dispose must be under write action // can call dispose only via com.intellij.ide.impl.ProjectUtil.closeAndDispose() LOG.assertTrue(application.isUnitTestMode() || !ProjectManagerEx.getInstanceEx().isProjectOpened(this)); LOG.assertTrue(!isDisposed()); if (myProjectManagerListener != null) { myManager.removeProjectManagerListener(this, myProjectManagerListener); } disposeComponents(); Extensions.disposeArea(this); myManager = null; myProjectManagerListener = null; super.dispose(); if (!application.isDisposed()) { application.getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).afterProjectClosed(this); } TimedReference.disposeTimed(); } private void projectOpened() { final ProjectComponent[] components = getComponents(ProjectComponent.class); for (ProjectComponent component : components) { try { component.projectOpened(); } catch (Throwable e) { LOG.error(component.toString(), e); } } } private void projectClosed() { List<ProjectComponent> components = new ArrayList<ProjectComponent>(Arrays.asList(getComponents(ProjectComponent.class))); Collections.reverse(components); for (ProjectComponent component : components) { try { component.projectClosed(); } catch (Throwable e) { LOG.error(e); } } } @Override public <T> T[] getExtensions(final ExtensionPointName<T> extensionPointName) { return Extensions.getArea(this).getExtensionPoint(extensionPointName).getExtensions(); } public String getDefaultName() { if (isDefault()) return TEMPLATE_PROJECT_NAME; return getStateStore().getProjectName(); } private class MyProjectManagerListener extends ProjectManagerAdapter { @Override public void projectOpened(Project project) { LOG.assertTrue(project == ProjectImpl.this); ProjectImpl.this.projectOpened(); } @Override public void projectClosed(Project project) { LOG.assertTrue(project == ProjectImpl.this); ProjectImpl.this.projectClosed(); } } @Override protected MutablePicoContainer createPicoContainer() { return Extensions.getArea(this).getPicoContainer(); } @Override public boolean isDefault() { return false; } @Override public void checkUnknownMacros(final boolean showDialog) { final IProjectStore stateStore = getStateStore(); final TrackingPathMacroSubstitutor[] substitutors = stateStore.getSubstitutors(); final Set<String> unknownMacros = new HashSet<String>(); for (final TrackingPathMacroSubstitutor substitutor : substitutors) { unknownMacros.addAll(substitutor.getUnknownMacros(null)); } if (!unknownMacros.isEmpty()) { if (!showDialog || ProjectMacrosUtil.checkMacros(this, new HashSet<String>(unknownMacros))) { final PathMacros pathMacros = PathMacros.getInstance(); final Set<String> macros2invalidate = new HashSet<String>(unknownMacros); for (Iterator it = macros2invalidate.iterator(); it.hasNext(); ) { final String macro = (String)it.next(); final String value = pathMacros.getValue(macro); if ((value == null || value.trim().isEmpty()) && !pathMacros.isIgnoredMacroName(macro)) { it.remove(); } } if (!macros2invalidate.isEmpty()) { final Set<String> components = new HashSet<String>(); for (TrackingPathMacroSubstitutor substitutor : substitutors) { components.addAll(substitutor.getComponents(macros2invalidate)); } if (stateStore.isReloadPossible(components)) { for (final TrackingPathMacroSubstitutor substitutor : substitutors) { substitutor.invalidateUnknownMacros(macros2invalidate); } final UnknownMacroNotification[] notifications = NotificationsManager.getNotificationsManager().getNotificationsOfType(UnknownMacroNotification.class, this); for (final UnknownMacroNotification notification : notifications) { if (macros2invalidate.containsAll(notification.getMacros())) notification.expire(); } ApplicationManager.getApplication().runWriteAction(() -> stateStore.reinitComponents(components, true)); } else { if (Messages.showYesNoDialog(this, "Component could not be reloaded. Reload project?", "Configuration Changed", Messages.getQuestionIcon()) == Messages.YES) { ProjectManagerEx.getInstanceEx().reloadProject(this); } } } } } } @NonNls @Override public String toString() { return "Project" + (isDisposed() ? " (Disposed" + (temporarilyDisposed ? " temporarily" : "") + ")" : isDefault() ? "" : " '" + getPresentableUrl() + "'") + (isDefault() ? " (Default)" : "") + " " + myName; } @Override protected boolean logSlowComponents() { return super.logSlowComponents() || ApplicationInfoImpl.getShadowInstance().isEAP(); } public static void dropUnableToSaveProjectNotification(@NotNull final Project project, final VirtualFile[] readOnlyFiles) { final UnableToSaveProjectNotification[] notifications = NotificationsManager.getNotificationsManager().getNotificationsOfType(UnableToSaveProjectNotification.class, project); if (notifications.length == 0) { Notifications.Bus.notify(new UnableToSaveProjectNotification(project, readOnlyFiles), project); } } public static class UnableToSaveProjectNotification extends Notification { private Project myProject; private final String[] myFileNames; private UnableToSaveProjectNotification(@NotNull final Project project, final VirtualFile[] readOnlyFiles) { super("Project Settings", "Could not save project!", buildMessage(), NotificationType.ERROR, new NotificationListener() { @Override public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { final UnableToSaveProjectNotification unableToSaveProjectNotification = (UnableToSaveProjectNotification)notification; final Project _project = unableToSaveProjectNotification.getProject(); notification.expire(); if (_project != null && !_project.isDisposed()) { _project.save(); } } }); myProject = project; myFileNames = ContainerUtil.map(readOnlyFiles, new Function<VirtualFile, String>() { @Override public String fun(VirtualFile file) { return file.getPresentableUrl(); } }, new String[readOnlyFiles.length]); } public String[] getFileNames() { return myFileNames; } private static String buildMessage() { final StringBuilder sb = new StringBuilder("<p>Unable to save project files. Please ensure project files are writable and you have permissions to modify them."); return sb.append(" <a href=\"\">Try to save project again</a>.</p>").toString(); } public Project getProject() { return myProject; } @Override public void expire() { myProject = null; super.expire(); } } }