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
;
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
;
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
) {
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
) {
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());
113 HelpManager
.getInstance().invokeHelp("project.migrationProblems");
117 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
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
) {
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
) {
146 ProjectBundle
.message("project.load.new.version.warning", myProject
.getName(), appNamesInfo
.getProductName());
148 if (Messages
.showYesNoDialog(message
, CommonBundle
.getWarningTitle(), Messages
.getWarningIcon()) != 0) return false;
154 public TrackingPathMacroSubstitutor
[] getSubstitutors() {
155 return new TrackingPathMacroSubstitutor
[] {getStateStorageManager().getMacroSubstitutor()};
159 protected boolean optimizeTestLoading() {
160 return myProject
.isOptimiseTestLoadSpeed();
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) {
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());
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()) {
237 final InputStream is
= oldWs
.openInputStream();
241 bytes
= FileUtil
.loadBytes(is
, (int)oldWs
.length());
247 final OutputStream os
= ws
.openOutputStream();
256 catch (IOException e
) {
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
) {
283 public String
getLocation() {
284 if (myScheme
== StorageScheme
.DEFAULT
) {
285 return getProjectFilePath();
288 final VirtualFile baseDir
= getProjectBaseDir();
289 return baseDir
== null ?
null : baseDir
.getPath();
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
);
307 temp
= temp
.substring(i
+ 1, temp
.length() - i
+ 1);
313 public StorageScheme
getStorageScheme() {
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;
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
{
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();
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
);
370 public String
getProjectFileName() {
371 final FileBasedStorage storage
= (FileBasedStorage
)getStateStorageManager().getFileStateStorage(PROJECT_FILE_STORAGE
);
372 assert storage
!= null;
373 return storage
.getFileName();
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;
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
);
404 protected ProjectStorageData(ProjectStorageData 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
) {
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
) {
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
);
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));
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();
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
;
505 filesToSave
= getAllStorageFilesToSave(true);
507 catch (IOException e
) {
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
) {
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
) {
557 if (!result
.isEmpty()) return result
.toArray(new Storage
[result
.size()]);
559 for (Storage storage
: storages
) {
560 if (storage
.scheme() == StorageScheme
.DEFAULT
) {
565 return result
.toArray(new Storage
[result
.size()]);
568 return new Storage
[]{};
573 protected StateStorageChooser
getDefaultStateStorageChooser() {
574 return myStateStorageChooser
;
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
;
591 private static class MyStorage
implements Storage
{
593 return "___Default___";
596 public boolean isDefault() {
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
;
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) {
636 if (!componentNames
.isEmpty()) {
637 StorageUtil
.logStateDiffInfo(changedFiles
, componentNames
);
640 if (!isReloadPossible(componentNames
)) {
645 finishSave(saveSession
);
648 if (!componentNames
.isEmpty()) {
649 myProject
.getMessageBus().syncPublisher(BatchUpdateListener
.TOPIC
).onBatchUpdateStarted();
652 doReload(changedFiles
, componentNames
);
653 reinitComponents(componentNames
, false);
656 myProject
.getMessageBus().syncPublisher(BatchUpdateListener
.TOPIC
).onBatchUpdateFinished();