Don't call refresh with PsiLock in hands.
[fedora-idea.git] / platform-impl / src / com / intellij / openapi / components / impl / stores / FileBasedStorage.java
blob3d427f1640295aecbdace127fd07e3b75e86536d
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;
30 import javax.swing.*;
31 import java.io.File;
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())) {
63 try {
64 String optionsPath = PathManager.getOptionsPath();
65 File optionsFile = new File(optionsPath);
66 if (!optionsFile.exists()) {
67 optionsFile.mkdirs();
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);
78 finally {
79 myConfigDirectoryRefreshed = true;
84 else {
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);
112 }, false, this);
116 private static boolean isOptionsFile(final String filePath) {
117 try {
118 return FileUtil.isAncestor(new File(PathManager.getOptionsPath()), new File(filePath), false);
120 catch (IOException e) {
121 return false;
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);
139 @Override
140 protected boolean phisicalContentNeedsSave() {
141 if (!myFile.exists()) return true;
143 final byte[] text = StorageUtil.printDocument(getDocumentToSave());
145 try {
146 return !Arrays.equals(myFile.loadBytes(), text);
148 catch (IOException e) {
149 LOG.debug(e);
150 return true;
156 @Override
157 protected Integer calcHash() {
158 int hash = myStorageData.getHash();
160 if (myPathMacroSubstitutor != null) {
161 hash = 31*hash + myPathMacroSubstitutor.hashCode();
163 return hash;
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) {
173 try {
174 OutputStream out = virtualFile.getOutputStream(this);
175 try {
176 out.write(text);
178 finally {
179 out.close();
182 catch (IOException e) {
183 LOG.error(e);
189 public Collection<IFile> getStorageFilesToSave() throws StateStorageException {
190 boolean needsSave = needsSave();
191 if (needsSave) {
192 if (LOG.isDebugEnabled()) {
193 LOG.info("File " + myFileSpec + " needs save; hash=" + myUpToDateHash + "; currentHash=" + calcHash() + "; content needs save=" + phisicalContentNeedsSave());
195 return getAllStorageFiles();
197 else {
198 return Collections.emptyList();
202 public List<IFile> getAllStorageFiles() {
203 return Collections.singletonList(myFile);
208 private VirtualFile ensureVirtualFile() {
209 if (!myFile.exists()) {
210 try {
211 File ioFile = new File(myFile.getPath());
212 File parentFile = ioFile.getParentFile();
213 if (parentFile != null) {
214 parentFile.mkdirs();
215 ioFile.createNewFile();
218 catch (IOException e) {
219 LOG.error(e);
222 return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(myFile);
225 VirtualFile result = LocalFileSystem.getInstance().findFileByIoFile(myFile);
226 if (result != null) {
227 return result;
229 if (myFile.exists()) {
230 FileUtil.delete(new File(myFile.getAbsolutePath()));
232 VirtualFile parentVirtualFile = LocalFileSystem.getInstance().findFileByIoFile(myFile.getParentFile());
233 LOG.assertTrue(parentVirtualFile != null);
234 try {
235 return parentVirtualFile.createChildData(this, myFile.getName());
237 catch (IOException e) {
238 LOG.error(e);
239 return null;
242 * */
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);
252 @NotNull
253 protected StorageData createStorageData() {
254 return new FileStorageData(myRootElementName);
257 public static class FileStorageData extends StorageData {
258 String myFilePath;
259 String myFileName;
261 public FileStorageData(final String rootElementName) {
262 super(rootElementName);
265 protected FileStorageData(FileStorageData storageData) {
266 super(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 + "]";
280 @Nullable
281 public VirtualFile getVirtualFile() {
282 return StorageUtil.getVirtualFile(myFile);
286 public IFile getFile() {
287 return myFile;
290 @Nullable
291 protected Document loadDocument() throws StateStorage.StateStorageException {
292 myBlockSavingTheContent = false;
293 try {
294 VirtualFile file = getVirtualFile();
295 if (file == null || file.isDirectory()) {
296 return null;
298 else {
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(){
314 public void run() {
315 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(),
316 "Cannot load settings from file '" + myFile.getPath() + "': " + e.getLocalizedMessage() + "\n" +
317 getInvalidContentMessage(),
318 "Load Settings",
319 JOptionPane.ERROR_MESSAGE);
324 return null;
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();
340 try {
341 if (bytesToSkip > 0) {
342 synchronized (BUFFER) {
343 int read = 0;
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());
347 read += r;
352 return JDOMUtil.loadDocument(stream);
354 finally {
355 stream.close();
359 private int skipBom(final VirtualFile virtualFile) {
360 synchronized (BUFFER) {
361 final int read;
362 try {
363 InputStream input = virtualFile.getInputStream();
364 try {
365 read = input.read(BUFFER);
367 finally {
368 input.close();
371 if (Patches.SUN_BUG_ID_4508058) {
372 if (startsWith(BUFFER, read, CharsetToolkit.UTF8_BOM)) {
373 return CharsetToolkit.UTF8_BOM.length;
376 return 0;
378 catch (IOException e) {
379 return 0;
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() {
393 return myFilePath;
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);
406 try {
407 return !Arrays.equals(myFile.loadBytes(), text);
409 catch (IOException e) {
410 LOG.debug(e);
411 return true;
415 @Nullable
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");
422 return file;
426 return null;