fix project name for projects in root of disk (RUBY-5181)
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / components / impl / stores / ProjectStoreImpl.java
blobfc7b1adf71f6445ac05478d9b8f36a79ca4d8350
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.components.impl.stores;
18 import com.intellij.CommonBundle;
19 import com.intellij.ide.highlighter.ProjectFileType;
20 import com.intellij.ide.highlighter.WorkspaceFileType;
21 import com.intellij.notification.*;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.application.ApplicationNamesInfo;
24 import com.intellij.openapi.components.*;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.help.HelpManager;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.project.ProjectBundle;
29 import com.intellij.openapi.project.ex.ProjectEx;
30 import com.intellij.openapi.project.impl.ProjectImpl;
31 import com.intellij.openapi.project.impl.ProjectManagerImpl;
32 import com.intellij.openapi.ui.Messages;
33 import com.intellij.openapi.ui.ex.MessagesEx;
34 import com.intellij.openapi.util.Computable;
35 import com.intellij.openapi.util.InvalidDataException;
36 import com.intellij.openapi.util.Pair;
37 import com.intellij.openapi.util.io.FileUtil;
38 import com.intellij.openapi.util.text.StringUtil;
39 import com.intellij.openapi.vfs.LocalFileSystem;
40 import com.intellij.openapi.vfs.ReadonlyStatusHandler;
41 import com.intellij.openapi.vfs.VfsUtil;
42 import com.intellij.openapi.vfs.VirtualFile;
43 import com.intellij.util.containers.OrderedSet;
44 import com.intellij.util.io.fs.FileSystem;
45 import com.intellij.util.io.fs.IFile;
46 import org.jdom.Element;
47 import org.jdom.JDOMException;
48 import org.jetbrains.annotations.NonNls;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
52 import javax.swing.event.HyperlinkEvent;
53 import java.io.File;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.io.OutputStream;
57 import java.lang.annotation.Annotation;
58 import java.util.ArrayList;
59 import java.util.Collection;
60 import java.util.List;
61 import java.util.Set;
63 class ProjectStoreImpl extends BaseFileConfigurableStoreImpl implements IProjectStore {
64 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.components.impl.stores.ProjectStoreImpl");
65 @NonNls private static final String OLD_PROJECT_SUFFIX = "_old.";
66 @NonNls static final String OPTION_WORKSPACE = "workspace";
68 protected ProjectEx myProject;
70 @NonNls static final String PROJECT_FILE_MACRO = "PROJECT_FILE";
71 @NonNls static final String WS_FILE_MACRO = "WORKSPACE_FILE";
72 @NonNls private static final String PROJECT_CONFIG_DIR = "PROJECT_CONFIG_DIR";
74 static final String PROJECT_FILE_STORAGE = "$" + PROJECT_FILE_MACRO + "$";
75 static final String WS_FILE_STORAGE = "$" + WS_FILE_MACRO + "$";
76 static final String DEFAULT_STATE_STORAGE = PROJECT_FILE_STORAGE;
78 static final Storage DEFAULT_STORAGE_ANNOTATION = new MyStorage();
79 private static int originalVersion = -1;
81 private StorageScheme myScheme = StorageScheme.DEFAULT;
83 ProjectStoreImpl(final ProjectEx project) {
84 super(project);
85 myProject = project;
88 public boolean checkVersion() {
89 final ApplicationNamesInfo appNamesInfo = ApplicationNamesInfo.getInstance();
90 if (originalVersion >= 0 && originalVersion < ProjectManagerImpl.CURRENT_FORMAT_VERSION) {
91 final VirtualFile projectFile = getProjectFile();
92 LOG.assertTrue(projectFile != null);
93 String name = projectFile.getNameWithoutExtension();
95 String message = ProjectBundle.message("project.convert.old.prompt", projectFile.getName(),
96 appNamesInfo.getProductName(),
97 name + OLD_PROJECT_SUFFIX + projectFile.getExtension());
98 if (Messages.showYesNoDialog(message, CommonBundle.getWarningTitle(), Messages.getWarningIcon()) != 0) return false;
100 final ArrayList<String> conversionProblems = getConversionProblemsStorage();
101 if (conversionProblems != null && !conversionProblems.isEmpty()) {
102 StringBuilder buffer = new StringBuilder();
103 buffer.append(ProjectBundle.message("project.convert.problems.detected"));
104 for (String s : conversionProblems) {
105 buffer.append('\n');
106 buffer.append(s);
108 buffer.append(ProjectBundle.message("project.convert.problems.help"));
109 final int result = Messages.showDialog(myProject, buffer.toString(), ProjectBundle.message("project.convert.problems.title"),
110 new String[]{ProjectBundle.message("project.convert.problems.help.button"),
111 CommonBundle.getCloseButtonText()}, 0, Messages.getWarningIcon());
112 if (result == 0) {
113 HelpManager.getInstance().invokeHelp("project.migrationProblems");
117 ApplicationManager.getApplication().runWriteAction(new Runnable() {
118 public void run() {
119 try {
120 VirtualFile projectDir = projectFile.getParent();
121 assert projectDir != null;
123 backup(projectDir, projectFile);
125 VirtualFile workspaceFile = getWorkspaceFile();
126 if (workspaceFile != null) {
127 backup(projectDir, workspaceFile);
130 catch (IOException e) {
131 LOG.error(e);
135 private void backup(final VirtualFile projectDir, final VirtualFile vile) throws IOException {
136 final String oldName = vile.getNameWithoutExtension() + OLD_PROJECT_SUFFIX + vile.getExtension();
137 VirtualFile oldFile = projectDir.findOrCreateChildData(this, oldName);
138 VfsUtil.saveText(oldFile, VfsUtil.loadText(vile));
144 if (originalVersion > ProjectManagerImpl.CURRENT_FORMAT_VERSION) {
145 String message =
146 ProjectBundle.message("project.load.new.version.warning", myProject.getName(), appNamesInfo.getProductName());
148 if (Messages.showYesNoDialog(message, CommonBundle.getWarningTitle(), Messages.getWarningIcon()) != 0) return false;
151 return true;
154 public TrackingPathMacroSubstitutor[] getSubstitutors() {
155 return new TrackingPathMacroSubstitutor[] {getStateStorageManager().getMacroSubstitutor()};
158 @Override
159 protected boolean optimizeTestLoading() {
160 return myProject.isOptimiseTestLoadSpeed();
163 @Override
164 public String initComponent(@NotNull Object component, boolean service) {
165 final String componentName = super.initComponent(component, service);
167 if (!ApplicationManager.getApplication().isHeadlessEnvironment() && !ApplicationManager.getApplication().isUnitTestMode()) {
168 if (service && componentName != null && myProject.isInitialized()) {
169 final TrackingPathMacroSubstitutor substitutor = getStateStorageManager().getMacroSubstitutor();
170 if (substitutor != null) {
171 final Collection<String> macros = substitutor.getUnknownMacros(componentName);
172 if (!macros.isEmpty()) {
173 Notifications.Bus.notify(new UnknownMacroNotification("Load Error", "Component load error: undefined path variables!",
174 String.format("<p><i>%s</i> %s undefined. <a href=\"\">Fix it!</a></p>",
175 StringUtil.join(macros, ", "), macros.size() == 1 ? "is" : "are"),
176 NotificationType.ERROR,
177 new NotificationListener() {
178 public void hyperlinkUpdate(@NotNull Notification notification,
179 @NotNull HyperlinkEvent event) {
180 myProject.checkUnknownMacros();
182 }, macros), NotificationDisplayType.STICKY_BALLOON, myProject);
188 return componentName;
191 public void setProjectFilePath(final String filePath) {
192 if (filePath == null) {
193 return;
195 final IFile iFile = FileSystem.FILE_SYSTEM.createFile(filePath);
196 final IFile dir_store =
197 iFile.isDirectory() ? iFile.getChild(Project.DIRECTORY_STORE_FOLDER) : iFile.getParentFile().getChild(Project.DIRECTORY_STORE_FOLDER);
199 final StateStorageManager stateStorageManager = getStateStorageManager();
200 if (dir_store.exists() && iFile.isDirectory()) {
201 FileBasedStorage.syncRefreshPathRecursively(dir_store.getPath(), null);
203 myScheme = StorageScheme.DIRECTORY_BASED;
205 stateStorageManager.addMacro(PROJECT_FILE_MACRO, dir_store.getChild("misc.xml").getPath());
206 final IFile ws = dir_store.getChild("workspace.xml");
207 stateStorageManager.addMacro(WS_FILE_MACRO, ws.getPath());
209 if (!ws.exists() && !iFile.isDirectory()) {
210 useOldWsContent(filePath, ws);
213 stateStorageManager.addMacro(PROJECT_CONFIG_DIR, dir_store.getPath());
215 else {
216 myScheme = StorageScheme.DEFAULT;
217 stateStorageManager.addMacro(PROJECT_FILE_MACRO, filePath);
219 LocalFileSystem.getInstance().refreshAndFindFileByPath(filePath);
221 int lastDot = filePath.lastIndexOf(".");
222 final String filePathWithoutExt = lastDot > 0 ? filePath.substring(0, lastDot) : filePath;
223 String workspacePath = filePathWithoutExt + WorkspaceFileType.DOT_DEFAULT_EXTENSION;
225 LocalFileSystem.getInstance().refreshAndFindFileByPath(workspacePath);
226 stateStorageManager.addMacro(WS_FILE_MACRO, workspacePath);
230 private static void useOldWsContent(final String filePath, final IFile ws) {
231 int lastDot = filePath.lastIndexOf(".");
232 final String filePathWithoutExt = lastDot > 0 ? filePath.substring(0, lastDot) : filePath;
233 String workspacePath = filePathWithoutExt + WorkspaceFileType.DOT_DEFAULT_EXTENSION;
234 IFile oldWs = FileSystem.FILE_SYSTEM.createFile(workspacePath);
235 if (oldWs.exists()) {
236 try {
237 final InputStream is = oldWs.openInputStream();
238 final byte[] bytes;
240 try {
241 bytes = FileUtil.loadBytes(is, (int)oldWs.length());
243 finally {
244 is.close();
247 final OutputStream os = ws.openOutputStream();
248 try {
249 os.write(bytes);
251 finally {
252 os.close();
256 catch (IOException e) {
257 LOG.error(e);
262 @Nullable
263 public VirtualFile getProjectBaseDir() {
264 final VirtualFile projectFile = getProjectFile();
265 if (projectFile != null) return myScheme == StorageScheme.DEFAULT ? projectFile.getParent() : projectFile.getParent().getParent();
267 //we are not yet initialized completely
268 final StateStorage s = getStateStorageManager().getFileStateStorage(PROJECT_FILE_STORAGE);
269 if (!(s instanceof FileBasedStorage)) return null;
270 final FileBasedStorage storage = (FileBasedStorage)s;
272 final IFile file = storage.getFile();
273 if (file == null) return null;
275 return LocalFileSystem.getInstance()
276 .findFileByIoFile(myScheme == StorageScheme.DEFAULT ? file.getParentFile() : file.getParentFile().getParentFile());
279 public void setStorageFormat(final StorageFormat storageFormat) {
282 @Nullable
283 public String getLocation() {
284 if (myScheme == StorageScheme.DEFAULT) {
285 return getProjectFilePath();
287 else {
288 final VirtualFile baseDir = getProjectBaseDir();
289 return baseDir == null ? null : baseDir.getPath();
293 @NotNull
294 public String getProjectName() {
295 if (myScheme == StorageScheme.DIRECTORY_BASED) {
296 final VirtualFile baseDir = getProjectBaseDir();
297 assert baseDir != null;
298 return baseDir.getName().replace(":", "");
301 String temp = getProjectFileName();
302 if (temp.endsWith(ProjectFileType.DOT_DEFAULT_EXTENSION)) {
303 temp = temp.substring(0, temp.length() - ProjectFileType.DOT_DEFAULT_EXTENSION.length());
305 final int i = temp.lastIndexOf(File.separatorChar);
306 if (i >= 0) {
307 temp = temp.substring(i + 1, temp.length() - i + 1);
309 return temp;
312 @NotNull
313 public StorageScheme getStorageScheme() {
314 return myScheme;
317 @Nullable
318 public String getPresentableUrl() {
319 if (myProject.isDefault()) return null;
320 if (myScheme == StorageScheme.DIRECTORY_BASED) {
321 final VirtualFile baseDir = getProjectBaseDir();
322 return baseDir != null ? baseDir.getPresentableUrl() : null;
324 else {
325 if (myProject.isDefault()) return null;
326 final FileBasedStorage storage = (FileBasedStorage)getStateStorageManager().getFileStateStorage(PROJECT_FILE_STORAGE);
327 assert storage != null;
328 return storage.getFilePath().replace('/', File.separatorChar);
332 public void loadProject() throws IOException, JDOMException, InvalidDataException, StateStorage.StateStorageException {
333 //load();
334 myProject.init();
337 @Nullable
338 public VirtualFile getProjectFile() {
339 if (myProject.isDefault()) return null;
340 final FileBasedStorage storage = (FileBasedStorage)getStateStorageManager().getFileStateStorage(PROJECT_FILE_STORAGE);
341 assert storage != null;
342 return storage.getVirtualFile();
345 @Nullable
346 public VirtualFile getWorkspaceFile() {
347 if (myProject.isDefault()) return null;
348 final FileBasedStorage storage = (FileBasedStorage)getStateStorageManager().getFileStateStorage(WS_FILE_STORAGE);
349 assert storage != null;
350 return storage.getVirtualFile();
353 public void loadProjectFromTemplate(final ProjectImpl defaultProject) {
354 final StateStorage stateStorage = getStateStorageManager().getFileStateStorage(DEFAULT_STATE_STORAGE);
356 assert stateStorage instanceof XmlElementStorage;
357 XmlElementStorage xmlElementStorage = (XmlElementStorage)stateStorage;
359 defaultProject.save();
360 final IProjectStore projectStore = defaultProject.getStateStore();
361 assert projectStore instanceof DefaultProjectStoreImpl;
362 DefaultProjectStoreImpl defaultProjectStore = (DefaultProjectStoreImpl)projectStore;
363 final Element element = defaultProjectStore.getStateCopy();
364 if (element != null) {
365 xmlElementStorage.setDefaultState(element);
369 @NotNull
370 public String getProjectFileName() {
371 final FileBasedStorage storage = (FileBasedStorage)getStateStorageManager().getFileStateStorage(PROJECT_FILE_STORAGE);
372 assert storage != null;
373 return storage.getFileName();
376 @NotNull
377 public String getProjectFilePath() {
378 if (myProject.isDefault()) return "";
380 final FileBasedStorage storage = (FileBasedStorage)getStateStorageManager().getFileStateStorage(PROJECT_FILE_STORAGE);
381 assert storage != null;
382 return storage.getFilePath();
385 protected XmlElementStorage getMainStorage() {
386 final XmlElementStorage storage = (XmlElementStorage)getStateStorageManager().getFileStateStorage(DEFAULT_STATE_STORAGE);
387 assert storage != null;
388 return storage;
391 protected StateStorageManager createStateStorageManager() {
392 return new ProjectStateStorageManager(PathMacroManager.getInstance(getComponentManager()).createTrackingSubstitutor(), myProject);
396 static class ProjectStorageData extends BaseStorageData {
397 protected final Project myProject;
399 ProjectStorageData(final String rootElementName, Project project) {
400 super(rootElementName);
401 myProject = project;
404 protected ProjectStorageData(ProjectStorageData storageData) {
405 super(storageData);
406 myProject = storageData.myProject;
409 public XmlElementStorage.StorageData clone() {
410 return new ProjectStorageData(this);
414 static class WsStorageData extends ProjectStorageData {
416 WsStorageData(final String rootElementName, final Project project) {
417 super(rootElementName, project);
420 WsStorageData(final WsStorageData storageData) {
421 super(storageData);
424 public XmlElementStorage.StorageData clone() {
425 return new WsStorageData(this);
429 static class IprStorageData extends ProjectStorageData {
430 IprStorageData(final String rootElementName, Project project) {
431 super(rootElementName, project);
434 IprStorageData(final IprStorageData storageData) {
435 super(storageData);
438 protected void load(@NotNull final Element root) throws IOException {
439 final String v = root.getAttributeValue(VERSION_OPTION);
440 originalVersion = v != null ? Integer.parseInt(v) : 0;
442 if (originalVersion != ProjectManagerImpl.CURRENT_FORMAT_VERSION) {
443 convert(root, originalVersion);
446 super.load(root);
449 protected void convert(final Element root, final int originalVersion) {
452 public XmlElementStorage.StorageData clone() {
453 return new IprStorageData(this);
457 protected SaveSessionImpl createSaveSession() throws StateStorage.StateStorageException {
458 return new ProjectSaveSession();
461 protected class ProjectSaveSession extends SaveSessionImpl {
463 ProjectSaveSession() throws StateStorage.StateStorageException {
466 public List<IFile> getAllStorageFilesToSave(final boolean includingSubStructures) throws IOException {
467 List<IFile> result = new ArrayList<IFile>();
469 if (includingSubStructures) {
470 collectSubfilesToSave(result);
473 result.addAll(super.getAllStorageFilesToSave(false));
475 return result;
478 protected void collectSubfilesToSave(final List<IFile> result) throws IOException { }
480 public SaveSession save() throws IOException {
481 final ReadonlyStatusHandler.OperationStatus operationStatus = ensureConfigFilesWritable();
482 if (operationStatus == null) {
483 throw new IOException();
485 else if (operationStatus.hasReadonlyFiles()) {
486 MessagesEx.error(myProject, ProjectBundle.message("project.save.error", operationStatus.getReadonlyFilesMessage())).showLater();
487 throw new SaveCancelledException();
490 beforeSave();
492 super.save();
494 return this;
497 protected void beforeSave() throws IOException {
500 private ReadonlyStatusHandler.OperationStatus ensureConfigFilesWritable() {
501 return ApplicationManager.getApplication().runReadAction(new Computable<ReadonlyStatusHandler.OperationStatus>() {
502 public ReadonlyStatusHandler.OperationStatus compute() {
503 final List<IFile> filesToSave;
504 try {
505 filesToSave = getAllStorageFilesToSave(true);
507 catch (IOException e) {
508 LOG.error(e);
509 return null;
512 List<VirtualFile> readonlyFiles = new ArrayList<VirtualFile>();
514 for (IFile file : filesToSave) {
515 final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByIoFile(file);
517 if (virtualFile != null) {
518 virtualFile.refresh(false, false);
519 if (virtualFile.isValid() && !virtualFile.isWritable()) readonlyFiles.add(virtualFile);
523 return ReadonlyStatusHandler.getInstance(myProject).ensureFilesWritable(readonlyFiles);
530 private final StateStorageChooser myStateStorageChooser = new StateStorageChooser() {
531 public Storage[] selectStorages(final Storage[] storages, final Object component, final StateStorageOperation operation) {
532 if (operation == StateStorageOperation.READ) {
533 OrderedSet<Storage> result = new OrderedSet<Storage>();
535 for (Storage storage : storages) {
536 if (storage.scheme() == myScheme) {
537 result.add(0, storage);
541 for (Storage storage : storages) {
542 if (storage.scheme() == StorageScheme.DEFAULT) {
543 result.add(storage);
547 return result.toArray(new Storage[result.size()]);
549 else if (operation == StateStorageOperation.WRITE) {
550 List<Storage> result = new ArrayList<Storage>();
551 for (Storage storage : storages) {
552 if (storage.scheme() == myScheme) {
553 result.add(storage);
557 if (!result.isEmpty()) return result.toArray(new Storage[result.size()]);
559 for (Storage storage : storages) {
560 if (storage.scheme() == StorageScheme.DEFAULT) {
561 result.add(storage);
565 return result.toArray(new Storage[result.size()]);
568 return new Storage[]{};
572 @Nullable
573 protected StateStorageChooser getDefaultStateStorageChooser() {
574 return myStateStorageChooser;
577 @NotNull
578 protected <T> Storage[] getComponentStorageSpecs(@NotNull final PersistentStateComponent<T> persistentStateComponent, final StateStorageOperation operation) throws StateStorage.StateStorageException {
579 Storage[] result = super.getComponentStorageSpecs(persistentStateComponent, operation);
581 if (operation == StateStorageOperation.READ) {
582 Storage[] upd = new Storage[result.length + 1];
583 System.arraycopy(result, 0, upd, 0, result.length);
584 upd[result.length] = DEFAULT_STORAGE_ANNOTATION;
585 result = upd;
588 return result;
591 private static class MyStorage implements Storage {
592 public String id() {
593 return "___Default___";
596 public boolean isDefault() {
597 return true;
600 public String file() {
601 return DEFAULT_STATE_STORAGE;
604 public StorageScheme scheme() {
605 return StorageScheme.DEFAULT;
608 public Class<? extends StateStorage> storageClass() {
609 return StorageAnnotationsDefaultValues.NullStateStorage.class;
612 public Class<? extends StateSplitter> stateSplitter() {
613 return StorageAnnotationsDefaultValues.NullStateSplitter.class;
616 public Class<? extends Annotation> annotationType() {
617 throw new UnsupportedOperationException("Method annotationType not implemented in " + getClass());
621 public boolean reload(final Set<Pair<VirtualFile, StateStorage>> changedFiles) throws IOException, StateStorage.StateStorageException {
622 final SaveSession saveSession = startSave();
624 final Set<String> componentNames;
625 try {
626 componentNames = saveSession.analyzeExternalChanges(changedFiles);
627 if (componentNames == null) return false;
629 // TODO[mike]: This is a hack to prevent NPE (assert != null) in StateStorageManagerImpl.reload, storage is null for...
630 for (Pair<VirtualFile, StateStorage> pair : changedFiles) {
631 if (pair.second == null) {
632 return false;
636 if (!componentNames.isEmpty()) {
637 StorageUtil.logStateDiffInfo(changedFiles, componentNames);
640 if (!isReloadPossible(componentNames)) {
641 return false;
644 finally {
645 finishSave(saveSession);
648 if (!componentNames.isEmpty()) {
649 myProject.getMessageBus().syncPublisher(BatchUpdateListener.TOPIC).onBatchUpdateStarted();
651 try {
652 doReload(changedFiles, componentNames);
653 reinitComponents(componentNames, false);
655 finally {
656 myProject.getMessageBus().syncPublisher(BatchUpdateListener.TOPIC).onBatchUpdateFinished();
661 return true;