1 package com
.intellij
.openapi
.components
.impl
.stores
;
4 import com
.intellij
.Patches
;
5 import com
.intellij
.openapi
.Disposable
;
6 import com
.intellij
.openapi
.application
.Application
;
7 import com
.intellij
.openapi
.application
.ApplicationManager
;
8 import com
.intellij
.openapi
.application
.PathManager
;
9 import com
.intellij
.openapi
.components
.StateStorage
;
10 import com
.intellij
.openapi
.components
.TrackingPathMacroSubstitutor
;
11 import com
.intellij
.openapi
.diagnostic
.Logger
;
12 import com
.intellij
.openapi
.options
.StreamProvider
;
13 import com
.intellij
.openapi
.util
.JDOMUtil
;
14 import com
.intellij
.openapi
.util
.io
.FileUtil
;
15 import com
.intellij
.openapi
.vfs
.*;
16 import com
.intellij
.openapi
.vfs
.newvfs
.NewVirtualFile
;
17 import com
.intellij
.openapi
.vfs
.tracker
.VirtualFileTracker
;
18 import com
.intellij
.psi
.PsiLock
;
19 import com
.intellij
.util
.ArrayUtil
;
20 import static com
.intellij
.util
.io
.fs
.FileSystem
.FILE_SYSTEM
;
21 import com
.intellij
.util
.io
.fs
.IFile
;
22 import com
.intellij
.util
.messages
.MessageBus
;
23 import org
.jdom
.Document
;
24 import org
.jdom
.Element
;
25 import org
.jdom
.JDOMException
;
26 import org
.jetbrains
.annotations
.NotNull
;
27 import org
.jetbrains
.annotations
.Nullable
;
28 import org
.picocontainer
.PicoContainer
;
32 import java
.io
.IOException
;
33 import java
.io
.InputStream
;
34 import java
.io
.OutputStream
;
35 import java
.util
.Arrays
;
36 import java
.util
.Collection
;
37 import java
.util
.Collections
;
38 import java
.util
.List
;
40 public class FileBasedStorage
extends XmlElementStorage
{
41 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.components.impl.stores.FileBasedStorage");
43 private final String myFilePath
;
44 private final IFile myFile
;
45 protected final String myRootElementName
;
46 private static final byte[] BUFFER
= new byte[10];
48 private static boolean myConfigDirectoryRefreshed
= false;
50 public FileBasedStorage(@Nullable TrackingPathMacroSubstitutor pathMacroManager
,
51 StreamProvider streamProvider
,
52 final String filePath
,
53 final String fileSpec
,
54 String rootElementName
,
55 @NotNull Disposable parentDisposable
,
56 PicoContainer picoContainer
,
57 ComponentRoamingManager componentRoamingManager
) {
58 super(pathMacroManager
, parentDisposable
, rootElementName
, streamProvider
, fileSpec
, componentRoamingManager
);
59 Application app
= ApplicationManager
.getApplication();
61 if (isOptionsFile(filePath
)) {
62 if (!myConfigDirectoryRefreshed
&& (app
.isUnitTestMode() || app
.isDispatchThread())) {
64 String optionsPath
= PathManager
.getOptionsPath();
65 File optionsFile
= new File(optionsPath
);
66 if (!optionsFile
.exists()) {
69 VirtualFile voptionsFile
= LocalFileSystem
.getInstance().refreshAndFindFileByIoFile(optionsFile
);
70 if (voptionsFile
!= null) {
71 voptionsFile
.getChildren();
72 if (voptionsFile
instanceof NewVirtualFile
) {
73 ((NewVirtualFile
)voptionsFile
).markDirtyRecursively();
75 voptionsFile
.refresh(false, true);
79 myConfigDirectoryRefreshed
= true;
85 if (!Thread
.holdsLock(PsiLock
.LOCK
) && (app
.isUnitTestMode() || app
.isDispatchThread())) {
86 VirtualFile virtualFile
= LocalFileSystem
.getInstance().refreshAndFindFileByPath(filePath
);
87 if (virtualFile
!= null) {
88 virtualFile
.refresh(false, false);
94 myRootElementName
= rootElementName
;
95 myFilePath
= filePath
;
96 myFile
= FILE_SYSTEM
.createFile(myFilePath
);
98 VirtualFileTracker virtualFileTracker
= (VirtualFileTracker
)picoContainer
.getComponentInstanceOfType(VirtualFileTracker
.class);
99 MessageBus messageBus
= (MessageBus
)picoContainer
.getComponentInstanceOfType(MessageBus
.class);
102 if (virtualFileTracker
!= null && messageBus
!= null) {
103 final String path
= myFile
.getAbsolutePath();
104 final String fileUrl
= LocalFileSystem
.PROTOCOL
+ "://" + path
.replace(File
.separatorChar
, '/');
107 final Listener listener
= messageBus
.syncPublisher(StateStorage
.STORAGE_TOPIC
);
108 virtualFileTracker
.addTracker(fileUrl
, new VirtualFileAdapter() {
109 public void contentsChanged(final VirtualFileEvent event
) {
110 listener
.storageFileChanged(event
, FileBasedStorage
.this);
116 private static boolean isOptionsFile(final String filePath
) {
118 return FileUtil
.isAncestor(new File(PathManager
.getOptionsPath()), new File(filePath
), false);
120 catch (IOException e
) {
125 protected MySaveSession
createSaveSession(final XmlElementStorage
.MyExternalizationSession externalizationSession
) {
126 return new FileSaveSession(externalizationSession
);
129 public void resetCache() {
130 myUpToDateHash
= null;
134 protected class FileSaveSession
extends MySaveSession
{
135 public FileSaveSession(MyExternalizationSession externalizationSession
) {
136 super(externalizationSession
);
140 protected boolean phisicalContentNeedsSave() {
141 if (!myFile
.exists()) return true;
143 final byte[] text
= StorageUtil
.printDocument(getDocumentToSave());
146 return !Arrays
.equals(myFile
.loadBytes(), text
);
148 catch (IOException e
) {
157 protected Integer
calcHash() {
158 int hash
= myStorageData
.getHash();
160 if (myPathMacroSubstitutor
!= null) {
161 hash
= 31*hash
+ myPathMacroSubstitutor
.hashCode();
166 protected void doSave() throws StateStorageException
{
167 if (!myBlockSavingTheContent
) {
168 final byte[] text
= StorageUtil
.printDocument(getDocumentToSave());
170 //StorageUtil.save(myFile, text);
171 VirtualFile virtualFile
= ensureVirtualFile();
172 if (virtualFile
!= null) {
174 OutputStream out
= virtualFile
.getOutputStream(this);
182 catch (IOException e
) {
189 public Collection
<IFile
> getStorageFilesToSave() throws StateStorageException
{
190 boolean needsSave
= needsSave();
192 if (LOG
.isDebugEnabled()) {
193 LOG
.info("File " + myFileSpec
+ " needs save; hash=" + myUpToDateHash
+ "; currentHash=" + calcHash() + "; content needs save=" + phisicalContentNeedsSave());
195 return getAllStorageFiles();
198 return Collections
.emptyList();
202 public List
<IFile
> getAllStorageFiles() {
203 return Collections
.singletonList(myFile
);
208 private VirtualFile
ensureVirtualFile() {
209 if (!myFile
.exists()) {
211 File ioFile
= new File(myFile
.getPath());
212 File parentFile
= ioFile
.getParentFile();
213 if (parentFile
!= null) {
215 ioFile
.createNewFile();
218 catch (IOException e
) {
222 return LocalFileSystem
.getInstance().refreshAndFindFileByIoFile(myFile
);
225 VirtualFile result = LocalFileSystem.getInstance().findFileByIoFile(myFile);
226 if (result != null) {
229 if (myFile.exists()) {
230 FileUtil.delete(new File(myFile.getAbsolutePath()));
232 VirtualFile parentVirtualFile = LocalFileSystem.getInstance().findFileByIoFile(myFile.getParentFile());
233 LOG.assertTrue(parentVirtualFile != null);
235 return parentVirtualFile.createChildData(this, myFile.getName());
237 catch (IOException e) {
246 protected void loadState(final StorageData result
, final Element element
) throws StateStorageException
{
247 ((FileStorageData
)result
).myFileName
= myFile
.getAbsolutePath();
248 ((FileStorageData
)result
).myFilePath
= myFile
.getAbsolutePath();
249 super.loadState(result
, element
);
253 protected StorageData
createStorageData() {
254 return new FileStorageData(myRootElementName
);
257 public static class FileStorageData
extends StorageData
{
261 public FileStorageData(final String rootElementName
) {
262 super(rootElementName
);
265 protected FileStorageData(FileStorageData storageData
) {
267 myFileName
= storageData
.myFileName
;
268 myFilePath
= storageData
.myFilePath
;
271 public StorageData
clone() {
272 return new FileStorageData(this);
275 public String
toString() {
276 return "FileStorageData[" + myFileName
+ "]";
281 public VirtualFile
getVirtualFile() {
282 return StorageUtil
.getVirtualFile(myFile
);
286 public IFile
getFile() {
291 protected Document
loadDocument() throws StateStorage
.StateStorageException
{
292 myBlockSavingTheContent
= false;
294 VirtualFile file
= getVirtualFile();
295 if (file
== null || file
.isDirectory()) {
299 return loadDocumentImpl(file
);
302 catch (final JDOMException e
) {
303 return processReadException(e
);
305 catch (final IOException e
) {
306 return processReadException(e
);
310 private Document
processReadException(final Exception e
) {
311 myBlockSavingTheContent
= isProjectOrModuleFile();
312 if (!ApplicationManager
.getApplication().isUnitTestMode()) {
313 SwingUtilities
.invokeLater(new Runnable(){
315 JOptionPane
.showMessageDialog(JOptionPane
.getRootFrame(),
316 "Cannot load settings from file '" + myFile
.getPath() + "': " + e
.getLocalizedMessage() + "\n" +
317 getInvalidContentMessage(),
319 JOptionPane
.ERROR_MESSAGE
);
327 private boolean isProjectOrModuleFile() {
328 return myIsProjectSettings
|| myFileSpec
.equals("$MODULE_FILE$");
331 private String
getInvalidContentMessage() {
332 return isProjectOrModuleFile() ?
"Please correct the file content" : "File content will be recreated";
335 private Document
loadDocumentImpl(final VirtualFile file
) throws IOException
, JDOMException
{
337 int bytesToSkip
= skipBom(file
);
339 InputStream stream
= file
.getInputStream();
341 if (bytesToSkip
> 0) {
342 synchronized (BUFFER
) {
344 while (read
< bytesToSkip
) {
345 int r
= stream
.read(BUFFER
, 0, bytesToSkip
- read
);
346 if (r
< 0) throw new IOException("Can't skip BOM for file: " + myFile
.getPath());
352 return JDOMUtil
.loadDocument(stream
);
359 private int skipBom(final VirtualFile virtualFile
) {
360 synchronized (BUFFER
) {
363 InputStream input
= virtualFile
.getInputStream();
365 read
= input
.read(BUFFER
);
371 if (Patches
.SUN_BUG_ID_4508058
) {
372 if (startsWith(BUFFER
, read
, CharsetToolkit
.UTF8_BOM
)) {
373 return CharsetToolkit
.UTF8_BOM
.length
;
378 catch (IOException e
) {
384 private static boolean startsWith(final byte[] buffer
, final int read
, final byte[] bom
) {
385 return read
>= bom
.length
&& ArrayUtil
.startsWith(buffer
, bom
);
388 public String
getFileName() {
389 return myFile
.getName();
392 public String
getFilePath() {
396 public void setDefaultState(final Element element
) {
397 element
.setName(myRootElementName
);
398 super.setDefaultState(element
);
401 protected boolean phisicalContentNeedsSave(final Document doc
) {
402 if (!myFile
.exists()) return true;
404 final byte[] text
= StorageUtil
.printDocument(doc
);
407 return !Arrays
.equals(myFile
.loadBytes(), text
);
409 catch (IOException e
) {
416 public File
updateFileExternallyFromStreamProviders() throws IOException
{
417 StorageData loadedData
= loadData(true);
418 Document document
= getDocument(loadedData
);
419 if (phisicalContentNeedsSave(document
)) {
420 File file
= new File(myFile
.getAbsolutePath());
421 JDOMUtil
.writeDocument(document
, file
, "\n");