IDEA-136810 ($HOME/idea.properties lookup returned)
[idea/community.git] / platform / util / src / com / intellij / openapi / application / PathManager.java
1 /*
2  * Copyright 2000-2015 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.application;
17
18 import com.intellij.openapi.util.*;
19 import com.intellij.openapi.util.io.FileUtil;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.util.io.URLUtil;
22 import com.sun.jna.TypeMapper;
23 import com.sun.jna.platform.FileUtils;
24 import gnu.trove.THashSet;
25 import org.apache.log4j.Appender;
26 import org.apache.oro.text.regex.PatternMatcher;
27 import org.jdom.Document;
28 import org.jetbrains.annotations.Contract;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31 import org.picocontainer.PicoContainer;
32
33 import java.io.*;
34 import java.net.URL;
35 import java.util.*;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38
39 import static com.intellij.util.SystemProperties.getUserHome;
40
41 public class PathManager {
42   public static final String PROPERTIES_FILE = "idea.properties.file";
43   public static final String PROPERTY_HOME_PATH = "idea.home.path";
44   public static final String PROPERTY_CONFIG_PATH = "idea.config.path";
45   public static final String PROPERTY_SYSTEM_PATH = "idea.system.path";
46   public static final String PROPERTY_PLUGINS_PATH = "idea.plugins.path";
47   public static final String PROPERTY_LOG_PATH = "idea.log.path";
48   public static final String PROPERTY_PATHS_SELECTOR = "idea.paths.selector";
49   public static final String DEFAULT_OPTIONS_FILE_NAME = "other";
50
51   private static final String PROPERTY_HOME = "idea.home";  // reduced variant of PROPERTY_HOME_PATH, now deprecated
52
53   private static final String LIB_FOLDER = "lib";
54   private static final String PLUGINS_FOLDER = "plugins";
55   private static final String BIN_FOLDER = "bin";
56   private static final String LOG_DIRECTORY = "log";
57   private static final String CONFIG_FOLDER = "config";
58   private static final String OPTIONS_FOLDER = "options";
59   private static final String SYSTEM_FOLDER = "system";
60   private static final String PATHS_SELECTOR = System.getProperty(PROPERTY_PATHS_SELECTOR);
61
62   private static final Pattern PROPERTY_REF = Pattern.compile("\\$\\{(.+?)}");
63
64   private static String ourHomePath;
65   private static String ourConfigPath;
66   private static String ourSystemPath;
67   private static String ourPluginsPath;
68   private static String ourLogPath;
69
70   // IDE installation paths
71
72   @NotNull
73   public static String getHomePath() {
74     if (ourHomePath != null) return ourHomePath;
75
76     String fromProperty = System.getProperty(PROPERTY_HOME_PATH, System.getProperty(PROPERTY_HOME));
77     if (fromProperty != null) {
78       ourHomePath = getAbsolutePath(fromProperty);
79       if (!new File(ourHomePath).isDirectory()) {
80         throw new RuntimeException("Invalid home path '" + ourHomePath + "'");
81       }
82     }
83     else {
84       ourHomePath = getHomePathFor(PathManager.class);
85       if (ourHomePath == null) {
86         String advice = SystemInfo.isMac ? "reinstall the software."
87                                          : "make sure bin/idea.properties is present in the installation directory.";
88         throw new RuntimeException("Could not find installation home path. Please " + advice);
89       }
90     }
91
92     if (SystemInfo.isWindows) {
93       try {
94         ourHomePath = new File(ourHomePath).getCanonicalPath();
95       }
96       catch (IOException ignored) { }
97     }
98
99     return ourHomePath;
100   }
101
102   @Nullable
103   public static String getHomePathFor(@NotNull Class aClass) {
104     String rootPath = getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class");
105     if (rootPath == null) return null;
106
107     File root = new File(rootPath).getAbsoluteFile();
108     do {
109       String parent = root.getParent();
110       if (parent == null) return null;
111       root = new File(parent).getAbsoluteFile();  // one step back to get folder
112     }
113     while (!isIdeaHome(root));
114     return root.getAbsolutePath();
115   }
116
117   private static boolean isIdeaHome(final File root) {
118     return new File(root, FileUtil.toSystemDependentName("bin/idea.properties")).exists() ||
119            new File(root, FileUtil.toSystemDependentName("community/bin/idea.properties")).exists();
120   }
121
122   @NotNull
123   public static String getBinPath() {
124     return getHomePath() + File.separator + BIN_FOLDER;
125   }
126
127   @NotNull
128   public static String getLibPath() {
129     return getHomePath() + File.separator + LIB_FOLDER;
130   }
131
132   @SuppressWarnings("MethodNamesDifferingOnlyByCase")
133   @NotNull
134   public static String getPreInstalledPluginsPath() {
135     return getHomePath() + File.separatorChar + PLUGINS_FOLDER;
136   }
137
138   // config paths
139
140   @Nullable
141   public static String getPathsSelector() {
142     return PATHS_SELECTOR;
143   }
144
145   @NotNull
146   public static String getConfigPath() {
147     if (ourConfigPath != null) return ourConfigPath;
148
149     if (System.getProperty(PROPERTY_CONFIG_PATH) != null) {
150       ourConfigPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_CONFIG_PATH)));
151     }
152     else if (PATHS_SELECTOR != null) {
153       ourConfigPath = getDefaultConfigPathFor(PATHS_SELECTOR);
154     }
155     else {
156       ourConfigPath = getHomePath() + File.separator + CONFIG_FOLDER;
157     }
158
159     return ourConfigPath;
160   }
161
162   @NotNull
163   public static String getDefaultConfigPathFor(@NotNull String selector) {
164     return platformPath(selector, "Library/Preferences", CONFIG_FOLDER);
165   }
166
167   public static void ensureConfigFolderExists() {
168     checkAndCreate(getConfigPath(), true);
169   }
170
171   @NotNull
172   public static String getOptionsPath() {
173     return getConfigPath() + File.separator + OPTIONS_FOLDER;
174   }
175
176   @NotNull
177   public static File getOptionsFile(@NotNull String fileName) {
178     return new File(getOptionsPath(), fileName + ".xml");
179   }
180
181   @NotNull
182   public static File getOptionsFile(@NotNull NamedJDOMExternalizable externalizable) {
183     return getOptionsFile(externalizable.getExternalFileName());
184   }
185
186   @NotNull
187   public static String getPluginsPath() {
188     if (ourPluginsPath != null) return ourPluginsPath;
189
190     if (System.getProperty(PROPERTY_PLUGINS_PATH) != null) {
191       ourPluginsPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_PLUGINS_PATH)));
192     }
193     else if (SystemInfo.isMac && PATHS_SELECTOR != null) {
194       ourPluginsPath = getUserHome() + File.separator + "Library/Application Support" + File.separator + PATHS_SELECTOR;
195     }
196     else {
197       ourPluginsPath = getConfigPath() + File.separatorChar + PLUGINS_FOLDER;
198     }
199
200     return ourPluginsPath;
201   }
202
203   @NotNull
204   public static String getDefaultPluginPathFor(@NotNull String selector) {
205     return platformPath(selector, "Library/Application Support", PLUGINS_FOLDER);
206   }
207
208   // runtime paths
209
210   @NotNull
211   public static String getSystemPath() {
212     if (ourSystemPath != null) return ourSystemPath;
213
214     if (System.getProperty(PROPERTY_SYSTEM_PATH) != null) {
215       ourSystemPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_SYSTEM_PATH)));
216     }
217     else if (PATHS_SELECTOR != null) {
218       ourSystemPath = platformPath(PATHS_SELECTOR, "Library/Caches", SYSTEM_FOLDER);
219     }
220     else {
221       ourSystemPath = getHomePath() + File.separator + SYSTEM_FOLDER;
222     }
223
224     checkAndCreate(ourSystemPath, true);
225     return ourSystemPath;
226   }
227
228   @NotNull
229   public static String getTempPath() {
230     return getSystemPath() + File.separator + "tmp";
231   }
232
233   @NotNull
234   public static File getIndexRoot() {
235     String indexRoot = System.getProperty("index_root_path", getSystemPath() + "/index");
236     checkAndCreate(indexRoot, true);
237     return new File(indexRoot);
238   }
239
240   @NotNull
241   public static String getLogPath() {
242     if (ourLogPath != null) return ourLogPath;
243
244     if (System.getProperty(PROPERTY_LOG_PATH) != null) {
245       ourLogPath = getAbsolutePath(trimPathQuotes(System.getProperty(PROPERTY_LOG_PATH)));
246     }
247     else if (SystemInfo.isMac && PATHS_SELECTOR != null) {
248       ourLogPath = getUserHome() + File.separator + "Library/Logs" + File.separator + PATHS_SELECTOR;
249     }
250     else {
251       ourLogPath = getSystemPath() + File.separatorChar + LOG_DIRECTORY;
252     }
253
254     return ourLogPath;
255   }
256
257   @NotNull
258   public static String getPluginTempPath () {
259     return getSystemPath() + File.separator + PLUGINS_FOLDER;
260   }
261
262   // misc stuff
263
264   /**
265    * Attempts to detect classpath entry which contains given resource.
266    */
267   @Nullable
268   public static String getResourceRoot(@NotNull Class context, String path) {
269     URL url = context.getResource(path);
270     if (url == null) {
271       url = ClassLoader.getSystemResource(path.substring(1));
272     }
273     return url != null ? extractRoot(url, path) : null;
274   }
275
276   /**
277    * Attempts to extract classpath entry part from passed URL.
278    */
279   @Nullable
280   private static String extractRoot(URL resourceURL, String resourcePath) {
281     if (!(StringUtil.startsWithChar(resourcePath, '/') || StringUtil.startsWithChar(resourcePath, '\\'))) {
282       log("precondition failed: " + resourcePath);
283       return null;
284     }
285
286     String resultPath = null;
287     String protocol = resourceURL.getProtocol();
288     if (URLUtil.FILE_PROTOCOL.equals(protocol)) {
289       String path = resourceURL.getFile();
290       String testPath = path.replace('\\', '/');
291       String testResourcePath = resourcePath.replace('\\', '/');
292       if (StringUtil.endsWithIgnoreCase(testPath, testResourcePath)) {
293         resultPath = path.substring(0, path.length() - resourcePath.length());
294       }
295     }
296     else if (URLUtil.JAR_PROTOCOL.equals(protocol)) {
297       Pair<String, String> paths = URLUtil.splitJarUrl(resourceURL.getFile());
298       if (paths != null) {
299         resultPath = paths.first;
300       }
301     }
302
303     if (resultPath == null) {
304       log("cannot extract: " + resourcePath + " from " + resourceURL);
305       return null;
306     }
307
308     resultPath = StringUtil.trimEnd(resultPath, File.separator);
309     resultPath = URLUtil.unescapePercentSequences(resultPath);
310
311     return resultPath;
312   }
313
314   public static void loadProperties() {
315     String[] propFiles = {
316       System.getProperty(PROPERTIES_FILE),
317       getUserPropertiesPath(),
318       getUserHome() + "/idea.properties",
319       getHomePath() + "/bin/idea.properties",
320       getHomePath() + "/community/bin/idea.properties"};
321
322     for (String path : propFiles) {
323       if (path != null) {
324         File propFile = new File(path);
325         if (propFile.exists()) {
326           try {
327             Reader fis = new BufferedReader(new FileReader(propFile));
328             try {
329               Map<String, String> properties = FileUtil.loadProperties(fis);
330
331               Properties sysProperties = System.getProperties();
332               for (String key : properties.keySet()) {
333                 if (PROPERTY_HOME_PATH.equals(key) || PROPERTY_HOME.equals(key)) {
334                   log(propFile.getPath() + ": '" + PROPERTY_HOME_PATH + "' and '" + PROPERTY_HOME + "' properties cannot be redefined");
335                 }
336                 else if (sysProperties.getProperty(key, null) != null) {
337                   log(propFile.getPath() + ": '" + key + "' already defined");
338                 }
339                 else {
340                   String value = substituteVars(properties.get(key));
341                   sysProperties.setProperty(key, value);
342                 }
343               }
344             }
345             finally {
346               fis.close();
347             }
348           }
349           catch (IOException e) {
350             log("Problem reading from property file: " + propFile.getPath());
351           }
352         }
353       }
354     }
355   }
356
357   private static String getUserPropertiesPath() {
358     if (PATHS_SELECTOR != null) {
359       // do not use getConfigPath() here - as it may be not yet defined
360       return platformPath(PATHS_SELECTOR, "Library/Preferences", /*"APPDATA", "XDG_CONFIG_HOME", ".config",*/ "") + "/idea.properties";
361     }
362     else {
363       return null;
364     }
365   }
366
367   @Contract("null -> null")
368   public static String substituteVars(String s) {
369     return substituteVars(s, getHomePath());
370   }
371
372   @Contract("null, _ -> null")
373   public static String substituteVars(String s, String ideaHomePath) {
374     if (s == null) return null;
375
376     if (s.startsWith("..")) {
377       s = ideaHomePath + File.separatorChar + BIN_FOLDER + File.separatorChar + s;
378     }
379
380     Matcher m = PROPERTY_REF.matcher(s);
381     while (m.find()) {
382       String key = m.group(1);
383       String value = System.getProperty(key);
384
385       if (value == null) {
386         if (PROPERTY_HOME_PATH.equals(key) || PROPERTY_HOME.equals(key)) {
387           value = ideaHomePath;
388         }
389         else if (PROPERTY_CONFIG_PATH.equals(key)) {
390           value = getConfigPath();
391         }
392         else if (PROPERTY_SYSTEM_PATH.equals(key)) {
393           value = getSystemPath();
394         }
395       }
396
397       if (value == null) {
398         log("Unknown property: " + key);
399         value = "";
400       }
401
402       s = StringUtil.replace(s, m.group(), value);
403       m = PROPERTY_REF.matcher(s);
404     }
405
406     return s;
407   }
408
409   @NotNull
410   public static File findFileInLibDirectory(@NotNull String relativePath) {
411     File file = new File(getLibPath() + File.separator + relativePath);
412     return file.exists() ? file : new File(getHomePath(), "community" + File.separator + "lib" + File.separator + relativePath);
413   }
414
415   @Nullable
416   public static String getJarPathForClass(@NotNull Class aClass) {
417     String resourceRoot = getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class");
418     return resourceRoot != null ? new File(resourceRoot).getAbsolutePath() : null;
419   }
420
421   @NotNull
422   public static Collection<String> getUtilClassPath() {
423     final Class<?>[] classes = {
424       PathManager.class,            // module 'util'
425       NotNull.class,                // module 'annotations'
426       SystemInfoRt.class,           // module 'util-rt'
427       Document.class,               // jDOM
428       Appender.class,               // log4j
429       THashSet.class,               // trove4j
430       PicoContainer.class,          // PicoContainer
431       TypeMapper.class,             // JNA
432       FileUtils.class,              // JNA (jna-utils)
433       PatternMatcher.class          // OROMatcher
434     };
435
436     final Set<String> classPath = new HashSet<String>();
437     for (Class<?> aClass : classes) {
438       final String path = getJarPathForClass(aClass);
439       if (path != null) {
440         classPath.add(path);
441       }
442     }
443
444     final String resourceRoot = getResourceRoot(PathManager.class, "/messages/CommonBundle.properties");  // platform-resources-en
445     if (resourceRoot != null) {
446       classPath.add(new File(resourceRoot).getAbsolutePath());
447     }
448
449     return Collections.unmodifiableCollection(classPath);
450   }
451
452   // helpers
453
454   @SuppressWarnings("UseOfSystemOutOrSystemErr")
455   private static void log(String x) {
456     System.err.println(x);
457   }
458
459   private static String getAbsolutePath(String path) {
460     path = FileUtil.expandUserHome(path);
461     return FileUtil.toCanonicalPath(new File(path).getAbsolutePath());
462   }
463
464   private static String trimPathQuotes(String path){
465     if (!(path != null && !(path.length() < 3))){
466       return path;
467     }
468     if (StringUtil.startsWithChar(path, '\"') && StringUtil.endsWithChar(path, '\"')){
469       return path.substring(1, path.length() - 1);
470     }
471     return path;
472   }
473
474   // todo[r.sh] XDG directories, Windows folders
475   // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
476   // http://www.microsoft.com/security/portal/mmpc/shared/variables.aspx
477   private static String platformPath(@NotNull String selector, @Nullable String macPart, @NotNull String fallback) {
478     return platformPath(selector, macPart, null, null, null, fallback);
479   }
480
481   private static String platformPath(@NotNull String selector,
482                                      @Nullable String macPart,
483                                      @Nullable String winVar,
484                                      @Nullable String xdgVar,
485                                      @Nullable String xdgDir,
486                                      @NotNull String fallback) {
487     if (macPart != null && SystemInfo.isMac) {
488       return getUserHome() + File.separator + macPart + File.separator + selector;
489     }
490
491     if (winVar != null && SystemInfo.isWindows) {
492       String dir = System.getenv(winVar);
493       if (dir != null) {
494         return dir + File.separator + selector;
495       }
496     }
497
498     if (xdgVar != null && xdgDir != null && SystemInfo.hasXdgOpen()) {
499       String dir = System.getenv(xdgVar);
500       if (dir == null) dir = getUserHome() + File.separator + xdgDir;
501       return dir + File.separator + selector;
502     }
503
504     return getUserHome() + File.separator + "." + selector + (!fallback.isEmpty() ? File.separator + fallback : "");
505   }
506
507   private static boolean checkAndCreate(String path, boolean createIfNotExists) {
508     if (createIfNotExists) {
509       File file = new File(path);
510       if (!file.exists()) {
511         return file.mkdirs();
512       }
513     }
514     return false;
515   }
516
517   // outdated stuff
518
519   /** @deprecated use {@link #getPluginsPath()} (to remove in IDEA 14) */
520   @SuppressWarnings("UnusedDeclaration") public static final String PLUGINS_DIRECTORY = PLUGINS_FOLDER;
521
522   /** @deprecated use {@link #getPreInstalledPluginsPath()} (to remove in IDEA 14) */
523   @SuppressWarnings({"UnusedDeclaration", "MethodNamesDifferingOnlyByCase", "SpellCheckingInspection"})
524   public static String getPreinstalledPluginsPath() {
525     return getPreInstalledPluginsPath();
526   }
527
528   /** @deprecated use {@link #getConfigPath()} (to remove in IDEA 14) */
529   @SuppressWarnings("UnusedDeclaration")
530   public static String getConfigPath(boolean createIfNotExists) {
531     ensureConfigFolderExists();
532     return ourConfigPath;
533   }
534
535   /** @deprecated use {@link #ensureConfigFolderExists()} (to remove in IDEA 14) */
536   @SuppressWarnings("UnusedDeclaration")
537   public static boolean ensureConfigFolderExists(boolean createIfNotExists) {
538     return checkAndCreate(getConfigPath(), createIfNotExists);
539   }
540
541   /** @deprecated use {@link #getOptionsPath()} (to remove in IDEA 14) */
542   @SuppressWarnings("UnusedDeclaration")
543   public static String getOptionsPathWithoutDialog() {
544     return getOptionsPath();
545   }
546
547   /** @deprecated use {@link #getOptionsFile(String)} (to remove in IDEA 14) */
548   @SuppressWarnings("UnusedDeclaration")
549   public static File getDefaultOptionsFile() {
550     return new File(getOptionsPath(), DEFAULT_OPTIONS_FILE_NAME + ".xml");
551   }
552 }