blob: 8725dc003da5067693e83607e2ffef109327139f [file] [log] [blame]
/*
* Copyright 2000-2014 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.diagnostic.PluginException;
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.ApplicationNamesInfo;
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.StorageScheme;
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.IComponentStore;
import com.intellij.openapi.components.impl.stores.IProjectStore;
import com.intellij.openapi.components.impl.stores.StoreUtil;
import com.intellij.openapi.components.impl.stores.UnknownMacroNotification;
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.progress.ProgressIndicatorProvider;
import com.intellij.openapi.project.*;
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.ui.ex.MessagesEx;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.impl.FrameTitleBuilder;
import com.intellij.util.Function;
import com.intellij.util.TimedReference;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.pico.ConstructorInjectionComponentAdapter;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.picocontainer.*;
import org.picocontainer.defaults.CachingComponentAdapter;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import java.io.File;
import java.io.IOException;
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");
private static final String PLUGIN_SETTINGS_ERROR = "Plugin Settings Error";
public static final String NAME_FILE = ".name";
public static Key<Long> CREATION_TIME = Key.create("ProjectImpl.CREATION_TIME");
private ProjectManager myManager;
private volatile IProjectStore myComponentStore;
private MyProjectManagerListener myProjectManagerListener;
private final AtomicBoolean mySavingInProgress = new AtomicBoolean(false);
public boolean myOptimiseTestLoadSpeed;
private String myName;
private String myOldName;
protected ProjectImpl(@NotNull ProjectManager manager, @NotNull String filePath, boolean optimiseTestLoadSpeed, @Nullable String projectName) {
super(ApplicationManager.getApplication(), "Project " + (projectName == null ? filePath : projectName));
putUserData(CREATION_TIME, System.nanoTime());
getPicoContainer().registerComponentInstance(Project.class, this);
if (!isDefault()) {
getStateStore().setProjectFilePath(filePath);
}
myOptimiseTestLoadSpeed = optimiseTestLoadSpeed;
myManager = manager;
myName = projectName == null ? getStateStore().getProjectName() : projectName;
if (!isDefault() && projectName != null && getStateStore().getStorageScheme().equals(StorageScheme.DIRECTORY_BASED)) {
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.IDEA_PROJECT, this, null);
super.bootstrapPicoContainer(name);
final MutablePicoContainer picoContainer = getPicoContainer();
final ProjectStoreClassProvider projectStoreClassProvider =
(ProjectStoreClassProvider)picoContainer.getComponentInstanceOfType(ProjectStoreClassProvider.class);
picoContainer.registerComponentImplementation(ProjectPathMacroManager.class);
picoContainer.registerComponent(new ComponentAdapter() {
ComponentAdapter myDelegate;
public ComponentAdapter getDelegate() {
if (myDelegate == null) {
final Class storeClass = projectStoreClassProvider.getProjectStoreClass(isDefault());
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() {
IProjectStore componentStore = myComponentStore;
if (componentStore != null) return componentStore;
//noinspection SynchronizeOnThis
synchronized (this) {
componentStore = myComponentStore;
if (componentStore == null) {
myComponentStore = componentStore = (IProjectStore)getPicoContainer().getComponentInstance(IComponentStore.class);
}
return componentStore;
}
}
@Override
public void initializeComponent(@NotNull Object component, boolean service) {
if (!service) {
ProgressIndicator indicator = getProgressIndicator();
if (indicator != null) {
// indicator.setText2(getComponentName(component));
indicator.setIndeterminate(false);
indicator.setFraction(getPercentageOfComponentsLoaded());
}
}
getStateStore().initComponent(component, service);
}
@Override
public boolean isOpen() {
return ProjectManagerEx.getInstanceEx().isProjectOpened(this);
}
@Override
public boolean isInitialized() {
return !isDisposed() && isOpen() && 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 = getStateStore().getStorageScheme() == StorageScheme.DIRECTORY_BASED ? "" : 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();
final ProgressIndicator progressIndicator = isDefault() ? null : ProgressIndicatorProvider.getGlobalProgressIndicator();
if (progressIndicator != null) {
progressIndicator.pushState();
}
super.init();
if (progressIndicator != null) {
progressIndicator.popState();
}
long time = System.currentTimeMillis() - start;
LOG.info(getComponentConfigurations().length + " project components initialized in " + time + " ms");
getMessageBus().syncPublisher(ProjectLifecycleListener.TOPIC).projectComponentsInitialized(this);
//noinspection SynchronizeOnThis
synchronized (this) {
myProjectManagerListener = new MyProjectManagerListener();
myManager.addProjectManagerListener(this, myProjectManagerListener);
}
}
public boolean isToSaveProjectName() {
if (!isDefault()) {
final IProjectStore stateStore = getStateStore();
if (stateStore.getStorageScheme().equals(StorageScheme.DIRECTORY_BASED)) {
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()) return; //no need to save
if (!mySavingInProgress.compareAndSet(false, true)) {
return;
}
try {
if (isToSaveProjectName()) {
final IProjectStore stateStore = getStateStore();
final VirtualFile baseDir = stateStore.getProjectBaseDir();
if (baseDir != null && baseDir.isValid()) {
final VirtualFile ideaDir = baseDir.findChild(DIRECTORY_STORE_FOLDER);
if (ideaDir != null && ideaDir.isValid() && ideaDir.isDirectory()) {
final File nameFile = new File(ideaDir.getPath(), NAME_FILE);
try {
FileUtil.writeToFile(nameFile, getName().getBytes("UTF-8"), false);
myOldName = null;
}
catch (IOException e) {
LOG.info("Unable to store project name to: " + nameFile.getPath());
}
RecentProjectsManagerBase.getInstance().clearNameCache();
}
}
}
StoreUtil.doSave(getStateStore());
}
catch (IComponentStore.SaveCancelledException e) {
LOG.info(e);
}
catch (PluginException e) {
PluginManagerCore.disablePlugin(e.getPluginId().getIdString());
Notification notification = new Notification(
PLUGIN_SETTINGS_ERROR,
"Unable to save plugin settings!",
"<p>The plugin <i>" + e.getPluginId() + "</i> failed to save settings and has been disabled. Please restart " +
ApplicationNamesInfo.getInstance().getFullProductName() + "</p>" +
(ApplicationManagerEx.getApplicationEx().isInternal() ? "<p>" + StringUtil.getThrowableText(e) + "</p>" : ""),
NotificationType.ERROR);
Notifications.Bus.notify(notification, this);
LOG.info("Unable to save plugin settings",e);
}
catch (IOException e) {
MessagesEx.error(this, ProjectBundle.message("project.save.error", e.getMessage())).showLater();
LOG.info("Error saving project", e);
} 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;
myComponentStore = 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);
}
}
}
@NotNull
@Override
public <T> T[] getExtensions(@NotNull final ExtensionPointName<T> extensionPointName) {
return Extensions.getArea(this).getExtensionPoint(extensionPointName).getExtensions();
}
public String getDefaultName() {
return isDefault() ? myName : 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();
}
}
@NotNull
@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 THashSet<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(new Runnable() {
@Override
public void run() {
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() {
return "<p>Unable to save project files. Please ensure project files are writable and you have permissions to modify them." +
" <a href=\"\">Try to save project again</a>.</p>";
}
public Project getProject() {
return myProject;
}
@Override
public void expire() {
myProject = null;
super.expire();
}
}
}