5e4431fc4099222c32045e980fae1b1e11bcced5
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / components / impl / stores / StorageUtil.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.components.impl.stores;
17
18 import com.intellij.application.options.PathMacrosCollector;
19 import com.intellij.notification.*;
20 import com.intellij.openapi.application.ApplicationInfo;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.application.PathManager;
23 import com.intellij.openapi.application.ex.ApplicationManagerEx;
24 import com.intellij.openapi.components.RoamingType;
25 import com.intellij.openapi.components.StateStorage;
26 import com.intellij.openapi.components.StateStorageException;
27 import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.editor.DocumentRunnable;
30 import com.intellij.openapi.options.StreamProvider;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.project.ProjectBundle;
33 import com.intellij.openapi.project.ex.ProjectEx;
34 import com.intellij.openapi.util.JDOMUtil;
35 import com.intellij.openapi.util.Pair;
36 import com.intellij.openapi.util.Ref;
37 import com.intellij.openapi.util.io.FileUtil;
38 import com.intellij.openapi.util.text.StringUtil;
39 import com.intellij.openapi.vfs.CharsetToolkit;
40 import com.intellij.openapi.vfs.LocalFileSystem;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import com.intellij.util.NotNullFunction;
43 import com.intellij.util.SystemProperties;
44 import com.intellij.util.UniqueFileNamesProvider;
45 import com.intellij.util.containers.ContainerUtil;
46 import com.intellij.util.io.fs.FileSystem;
47 import com.intellij.util.io.fs.IFile;
48 import org.jdom.*;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
51
52 import javax.swing.event.HyperlinkEvent;
53 import java.io.*;
54 import java.text.SimpleDateFormat;
55 import java.util.Collection;
56 import java.util.Date;
57 import java.util.Set;
58
59 /**
60  * @author mike
61  */
62 public class StorageUtil {
63   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.components.impl.stores.StorageUtil");
64   private static final boolean ourDumpChangedComponentStates = "true".equals(System.getProperty("log.externally.changed.component.states"));
65
66   private StorageUtil() {
67   }
68
69   public static void notifyUnknownMacros(@NotNull final TrackingPathMacroSubstitutor substitutor, @NotNull final Project project, @Nullable final String componentName) {
70     Collection<String> macros = substitutor.getUnknownMacros(componentName);
71     if (!macros.isEmpty()) {
72       final UnknownMacroNotification[] notifications =
73         NotificationsManager.getNotificationsManager().getNotificationsOfType(UnknownMacroNotification.class, project);
74       for (final UnknownMacroNotification notification : notifications) {
75         macros = ContainerUtil.subtract(macros, notification.getMacros());
76       }
77
78       if (!macros.isEmpty()) {
79         Notifications.Bus.notify(new UnknownMacroNotification("Load Error", "Load error: undefined path variables!",
80                                                               String.format("<p><i>%s</i> %s undefined. <a href=\"define\">Fix it</a>.</p>",
81                                                                             StringUtil.join(macros, ", "),
82                                                                             macros.size() == 1 ? "is" : "are"), NotificationType.ERROR,
83                                                               new NotificationListener() {
84                                                                 public void hyperlinkUpdate(@NotNull Notification notification,
85                                                                                             @NotNull HyperlinkEvent event) {
86                                                                   ((ProjectEx)project).checkUnknownMacros(true);
87                                                                 }
88                                                               }, macros), project);
89       }
90     }
91   }
92
93   static void save(final IFile file, final Parent element, final Object requestor) throws StateStorageException {
94     final String filePath = file.getCanonicalPath();
95     try {
96       final Ref<IOException> refIOException = Ref.create(null);
97
98       final Pair<String, String> pair = loadFile(file);
99       final byte[] text = JDOMUtil.writeParent(element, pair.second).getBytes(CharsetToolkit.UTF8);
100       if (file.exists()) {
101         if (new String(text).equals(pair.first)) return;
102         IFile backupFile = deleteBackup(filePath);
103         file.renameTo(backupFile);
104       }
105
106       // mark this action as modifying the file which daemon analyzer should ignore
107       ApplicationManager.getApplication().runWriteAction(new DocumentRunnable.IgnoreDocumentRunnable() {
108         public void run() {
109           if (!file.exists()) {
110             file.createParentDirs();
111           }
112
113           try {
114             final VirtualFile virtualFile = getOrCreateVirtualFile(requestor, file);
115
116             virtualFile.setBinaryContent(text, -1, -1, requestor);
117           }
118           catch (IOException e) {
119             refIOException.set(e);
120           }
121
122           deleteBackup(filePath);
123         }
124       });
125       if (refIOException.get() != null) {
126         throw new StateStorageException(refIOException.get());
127       }
128     }
129     catch (IOException e) {
130       throw new StateStorageException(e);
131     }
132   }
133
134   static IFile deleteBackup(final String path) {
135     IFile backupFile = FileSystem.FILE_SYSTEM.createFile(path + "~");
136     if (backupFile.exists()) {
137       backupFile.delete();
138     }
139     return backupFile;
140   }
141
142   static VirtualFile getOrCreateVirtualFile(final Object requestor, final IFile ioFile) throws IOException {
143     VirtualFile vFile = getVirtualFile(ioFile);
144
145     if (vFile == null) {
146       vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(ioFile);
147     }
148
149     if (vFile == null) {
150       final IFile parentFile = ioFile.getParentFile();
151       final VirtualFile parentVFile =
152         LocalFileSystem.getInstance().refreshAndFindFileByIoFile(parentFile); // need refresh if the directory has just been created
153       if (parentVFile == null) {
154         throw new IOException(ProjectBundle.message("project.configuration.save.file.not.found", parentFile.getPath()));
155       }
156       vFile = parentVFile.createChildData(requestor, ioFile.getName());
157     }
158
159     return vFile;
160   }
161
162   @Nullable
163   static VirtualFile getVirtualFile(final IFile ioFile) {
164     return LocalFileSystem.getInstance().findFileByIoFile(ioFile);
165   }
166
167   @Deprecated
168   public static byte[] printDocument(final Document document) throws StateStorageException {
169     try {
170       return printDocumentToString(document).getBytes(CharsetToolkit.UTF8);
171     }
172     catch (IOException e) {
173       throw new StateStorageException(e);
174     }
175   }
176
177   /**
178    * @return pair.first - file contents (null if file does not exist), pair.second - file line separators
179    */
180   public static Pair<String, String> loadFile(@NotNull final IFile file) throws IOException {
181     if (!file.exists()) return Pair.create(null, SystemProperties.getLineSeparator());
182
183     String fileText = new String(file.loadBytes(), CharsetToolkit.UTF8);
184     final int ndx = fileText.indexOf('\n');
185     return Pair.create(fileText, ndx == -1
186                                  ? SystemProperties.getLineSeparator()
187                                  : ndx - 1 >=0 ? fileText.charAt(ndx - 1) == '\r' ? "\r\n" : "\n" : "\n");
188   }
189
190   public static boolean contentEquals(@NotNull final Document document, @NotNull final IFile file) {
191     try {
192       final Pair<String, String> pair = loadFile(file);
193       return pair.first == null ? false : pair.first.equals(printDocumentToString(document, pair.second));
194     }
195     catch (IOException e) {
196       LOG.debug(e);
197       return false;
198     }
199   }
200
201   public static boolean contentEquals(@NotNull final Element element, @NotNull final IFile file) {
202     try {
203       final Pair<String, String> pair = loadFile(file);
204       return pair.first == null ? false : pair.first.equals(printElement(element, pair.second));
205     }
206     catch (IOException e) {
207       LOG.debug(e);
208       return false;
209     }
210   }
211
212   public static String printDocumentToString(final Document document, final String lineSeparator) {
213     return JDOMUtil.writeDocument(document, lineSeparator);
214   }
215
216   @Deprecated
217   public static String printDocumentToString(final Document document) {
218     return printDocumentToString(document, SystemProperties.getLineSeparator());
219   }
220
221   static String printElement(final Element element, final String lineSeparator) throws StateStorageException {
222     return JDOMUtil.writeElement(element, lineSeparator);
223   }
224
225   @NotNull
226   public static Set<String> getMacroNames(@NotNull final Element e) {
227     return PathMacrosCollector.getMacroNames(e, new NotNullFunction<Object, Boolean>() {
228       @NotNull
229       public Boolean fun(Object o) {
230         if (o instanceof Attribute) {
231           final Attribute attribute = (Attribute)o;
232           final Element parent = attribute.getParent();
233           final String parentName = parent.getName();
234           if (("value".equals(attribute.getName()) || "name".equals(attribute.getName())) && "env".equals(parentName)) {
235             return false; // do not proceed environment variables from run configurations
236           }
237
238           if ("MESSAGE".equals(parentName) && "value".equals(attribute.getName())) return false;
239           if ("option".equals(parentName) && "LAST_COMMIT_MESSAGE".equals(parent.getAttributeValue("name"))) return false;
240
241           // do not proceed macros in searchConfigurations (structural search)
242           if ("replaceConfiguration".equals(parentName) || "searchConfiguration".equals(parentName)) return false;
243         }
244
245         return true;
246       }
247     }, new NotNullFunction<Object, Boolean>() {
248       @NotNull
249       public Boolean fun(Object o) {
250         if (o instanceof Attribute) {
251           // process run configuration's options recursively
252           final Element parent = ((Attribute)o).getParent();
253           if (parent != null && "option".equals(parent.getName())) {
254             final Element grandParent = parent.getParentElement();
255             return grandParent != null && "configuration".equals(grandParent.getName());
256           }
257         }
258
259         return false;
260       }
261     });
262   }
263
264   @Nullable
265   public static Document loadDocument(final byte[] bytes) {
266     try {
267       return bytes == null || bytes.length == 0 ? null : JDOMUtil.loadDocument(new ByteArrayInputStream(bytes));
268     }
269     catch (JDOMException e) {
270       return null;
271     }
272     catch (IOException e) {
273       return null;
274     }
275   }
276
277   @Nullable
278   public static Document loadDocument(final InputStream stream) {
279     if (stream == null) return null;
280
281     try {
282       return JDOMUtil.loadDocument(stream);
283     }
284     catch (JDOMException e) {
285       return null;
286     }
287     catch (IOException e) {
288       return null;
289     }
290     finally {
291       try {
292         stream.close();
293       }
294       catch (IOException e) {
295         //ignore
296       }
297     }
298   }
299
300   public static void sendContent(final StreamProvider streamProvider, final String fileSpec, final Document copy, final RoamingType roamingType, boolean async)
301       throws IOException {
302     byte[] content = printDocument(copy);
303     ByteArrayInputStream in = new ByteArrayInputStream(content);
304     try {
305       if (streamProvider.isEnabled()) {
306         streamProvider.saveContent(fileSpec, in, content.length, roamingType, async);
307       }
308     }
309     finally {
310       in.close();
311     }
312
313   }
314
315   public static void logStateDiffInfo(Set<Pair<VirtualFile, StateStorage>> changedFiles, Set<String> componentNames) throws IOException {
316     if (!ApplicationManagerEx.getApplicationEx().isInternal() && !ourDumpChangedComponentStates) return;
317
318     try {
319       File logDirectory = createLogDirectory();
320
321       logDirectory.mkdirs();
322
323       for (String componentName : componentNames) {
324         for (Pair<VirtualFile, StateStorage> pair : changedFiles) {
325           StateStorage storage = pair.second;
326           if (storage instanceof XmlElementStorage) {
327             Element state = ((XmlElementStorage)storage).getState(componentName);
328             if (state != null) {
329               File logFile = new File(logDirectory, "prev_" + componentName + ".xml");
330               FileUtil.writeToFile(logFile, JDOMUtil.writeElement(state, "\n").getBytes());
331             }
332           }
333         }
334       }
335
336       for (Pair<VirtualFile, StateStorage> changedFile : changedFiles) {
337         File in = new File(changedFile.first.getPath());
338         if (in.exists()) {
339           File logFile = new File(logDirectory, "new_" + changedFile.first.getName());
340           FileUtil.copy(in, logFile);
341         }
342       }
343     }
344     catch (Throwable e) {
345       LOG.info(e);
346     }
347   }
348
349   static File createLogDirectory() {
350     UniqueFileNamesProvider namesProvider = new UniqueFileNamesProvider();
351
352     File statesDir = new File(PathManager.getSystemPath(), "log/componentStates");
353     File[] children = statesDir.listFiles();
354     if (children != null) {
355       if (children.length > 10) {
356         File childToDelete = null;
357
358         for (File child : children) {
359           if (childToDelete == null || childToDelete.lastModified() > child.lastModified()) {
360             childToDelete = child;
361           }
362         }
363
364         if (childToDelete != null) {
365           FileUtil.delete(childToDelete);
366         }
367       }
368
369
370       for (File child : children) {
371         namesProvider.reserveFileName(child.getName());
372       }
373     }
374
375     return new File(statesDir, namesProvider.suggestName("state-" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date())
376                         + "-" + ApplicationInfo.getInstance().getBuild().asString()));
377   }
378 }