Files
openide/platform/util/src/com/intellij/openapi/application/PathManager.java
Nikita Iarychenko 75584ba12a OPENIDE #153 Help -> Edit Custom Properties: idea in file name
(cherry picked from commit 2e474f259f)

(cherry picked from commit f646d43bc9)
2025-10-29 13:27:16 +04:00

1043 lines
37 KiB
Java

// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
//
// Modified by Nikita Iarychenko at 2025 as part of the OpenIDE project(https://openide.ru).
// Any modifications are available on the same license terms as the original source code.
package com.intellij.openapi.application;
import com.intellij.openapi.util.SystemInfoRt;
import com.intellij.openapi.util.text.StringUtilRt;
import com.intellij.util.io.URLUtil;
import com.intellij.util.system.CpuArch;
import org.jetbrains.annotations.*;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class PathManager {
public static final String PROPERTIES_FILE = "idea.properties.file";
public static final String PROPERTIES_FILE_NAME = "openide.properties";
public static final String PROPERTY_HOME_PATH = "idea.home.path";
public static final String PROPERTY_CONFIG_PATH = "idea.config.path";
public static final String PROPERTY_SYSTEM_PATH = "idea.system.path";
public static final String PROPERTY_SCRATCH_PATH = "idea.scratch.path";
public static final String PROPERTY_PLUGINS_PATH = "idea.plugins.path";
public static final String PROPERTY_LOG_PATH = "idea.log.path";
public static final String PROPERTY_LOG_CONFIG_FILE = "idea.log.config.properties.file";
public static final String PROPERTY_PATHS_SELECTOR = "idea.paths.selector";
public static final String SYSTEM_PATHS_CUSTOMIZER = "idea.paths.customizer";
public static final String OPTIONS_DIRECTORY = "options";
public static final String DEFAULT_EXT = ".xml";
private static final String PROPERTY_HOME = "idea.home"; // a reduced variant of PROPERTY_HOME_PATH, now deprecated
private static final String PROPERTY_VENDOR_NAME = "idea.vendor.name";
private static final String JRE_DIRECTORY = "jbr";
private static final String LIB_DIRECTORY = "lib";
private static final String PLUGINS_DIRECTORY = "plugins";
private static final String BIN_DIRECTORY = "bin";
private static final String LOG_DIRECTORY = "log";
private static final String CONFIG_DIRECTORY = "config";
private static final String SYSTEM_DIRECTORY = "system";
private static final String COMMUNITY_MARKER = "intellij.idea.community.main.iml";
private static final String ULTIMATE_MARKER = ".ultimate.root.marker";
private static final String PRODUCT_INFO_JSON = "product-info.json";
private static final class Lazy {
private static final Pattern PROPERTY_REF = Pattern.compile("\\$\\{(.+?)}");
}
private static volatile String ourHomePath;
private static volatile List<Path> ourBinDirectories;
private static Path ourCommonDataPath;
private static String ourPathSelector = System.getProperty(PROPERTY_PATHS_SELECTOR);
private static String ourConfigPath;
private static String ourSystemPath;
private static String ourScratchPath;
private static String ourPluginPath;
private static String ourLogPath;
private static Path ourStartupScriptDir;
private static Path ourOriginalConfigDir;
private static Path ourOriginalSystemDir;
private static Path ourOriginalLogDir;
private static Map<String, String> ourArchivedCompiledClassesMapping;
/**
* Returns paths to the directory where the IDE is installed, i.e., the directory containing 'lib', 'plugins' and other subdirectories.
* On macOS, it's {@code <product>.app/Contents} directory.
* <br>
* If the IDE is started from source code rather than installation, the method returns paths to the Git repository root.
* <br>
* The method is supposed to be called from the main IDE process. For other processes started from the IDE process (e.g., build process)
* use {@link #getHomePath(boolean)} with {@code false} argument.
*/
public static @NotNull String getHomePath() {
return getHomePath(true);
}
/**
* A variant of {@link #getHomePath()} which also works inside additional processes started from the main IDE process.
* @param insideIde {@code true} if the calling code works inside IDE; {@code false} otherwise (e.g., in a build process or a script)
*/
@Contract("true -> !null")
public static String getHomePath(boolean insideIde) {
String result = ourHomePath;
if (result != null) return result;
//noinspection SynchronizeOnThis
synchronized (PathManager.class) {
result = ourHomePath;
if (result != null) return result;
String explicit = getExplicitPath(PROPERTY_HOME_PATH);
if (explicit == null) explicit = getExplicitPath(PROPERTY_HOME);
if (explicit != null) {
result = explicit;
if (!Files.isDirectory(Paths.get(result))) {
ourHomePath = result;
throw new RuntimeException("Invalid home path '" + result + "'");
}
}
else if (insideIde) {
//noinspection TestOnlyProblems
result = getHomePathFor(PathManager.class);
if (result == null) {
String advice = SystemInfoRt.isMac ? "reinstall the software." : "make sure product-info.json is present in the installation directory.";
throw new RuntimeException("Could not find installation home path. Please " + advice);
}
}
if (result != null && SystemInfoRt.isWindows) {
try {
result = Paths.get(result).toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
}
catch (IOException ignored) { }
}
// set before ourHomePath because getBinDirectories() rely on the fact that if `getHomePath(true)`
// returns something, then `ourBinDirectories` is already computed
if (result == null) {
ourBinDirectories = Collections.emptyList();
}
else {
Path root = Paths.get(result);
if (Boolean.getBoolean("idea.use.dev.build.server")) {
while (root.getParent() != null) {
if (Files.exists(root.resolve(ULTIMATE_MARKER)) || Files.exists(root.resolve(COMMUNITY_MARKER))) {
break;
}
root = root.getParent();
}
}
ourBinDirectories = getBinDirectories(root);
}
ourHomePath = result;
}
return result;
}
private static List<Path> getBinDirectories() {
List<Path> result = ourBinDirectories;
if (result == null) {
getHomePath(true);
result = ourBinDirectories;
}
return result;
}
public static boolean isUnderHomeDirectory(@NotNull String path) {
try {
return isUnderHomeDirectory(Paths.get(path));
}
catch (InvalidPathException e) {
return false;
}
}
public static boolean isUnderHomeDirectory(@NotNull Path target) {
Path home = Paths.get(getHomePath());
try {
home = home.toRealPath();
target = target.toRealPath();
}
catch (IOException ignored) { }
return target.startsWith(home);
}
/**
* Returns the path to the git repository of 'intellij' project when running tests from sources.
* Consider using {@link #getHomeDirFor(Class)} instead.
*/
@ApiStatus.Internal
@TestOnly
public static @Nullable String getHomePathFor(@NotNull Class<?> aClass) {
Path result = getHomeDirFor(aClass);
return result == null ? null : result.toString();
}
/**
* Returns the path to the git repository of 'intellij' project when running tests from sources.
* In production code, use {@link #getHomePath()} instead.
*/
@ApiStatus.Internal
@TestOnly
public static @Nullable Path getHomeDirFor(@NotNull Class<?> aClass) {
Path result = null;
String rootPath = getResourceRoot(aClass, '/' + aClass.getName().replace('.', '/') + ".class");
if (rootPath != null) {
String relevantJarsRoot = getArchivedCompliedClassesLocation();
if (relevantJarsRoot != null && rootPath.startsWith(relevantJarsRoot)) {
String home = System.getProperty(PROPERTY_HOME_PATH);
if (home != null) {
Path path = Paths.get(home).toAbsolutePath();
if (isIdeaHome(path)) {
return path;
}
}
}
Path root = Paths.get(rootPath).toAbsolutePath();
do root = root.getParent();
while (root != null && !isIdeaHome(root));
result = root;
}
return result;
}
private static boolean isIdeaHome(Path root) {
return Files.isRegularFile(root.resolve(PRODUCT_INFO_JSON)) ||
Files.isRegularFile(root.resolve("Resources").resolve(PRODUCT_INFO_JSON)) ||
Files.isRegularFile(root.resolve(COMMUNITY_MARKER)) ||
Files.isRegularFile(root.resolve(ULTIMATE_MARKER));
}
private static List<Path> getBinDirectories(Path root) {
List<Path> binDirs = new ArrayList<>();
Path[] candidates = {root.resolve(BIN_DIRECTORY), Paths.get(getCommunityHomePath(root.toString()), "bin")};
String osSuffix = SystemInfoRt.isWindows ? "win" : SystemInfoRt.isMac ? "mac" : "linux";
for (Path dir : candidates) {
if (binDirs.contains(dir) || !Files.isDirectory(dir)) {
continue;
}
binDirs.add(dir);
dir = dir.resolve(osSuffix);
if (Files.isDirectory(dir)) {
binDirs.add(dir);
String arch = CpuArch.isIntel64() ? "amd64" : CpuArch.isArm64() ? "aarch64" : null;
if (arch != null) {
dir = dir.resolve(arch);
if (Files.isDirectory(dir)) {
binDirs.add(dir);
}
}
}
}
return binDirs;
}
/**
* Bin path may be not what you want when developing an IDE. Consider using {@link #findBinFile(String)} if applicable.
*/
public static @NotNull String getBinPath() {
return getHomePath() + '/' + BIN_DIRECTORY;
}
/**
* Looks for a file in all possible bin directories.
*
* @return first that exists, or {@code null} if nothing found.
* @see #findBinFileWithException(String)
*/
public static @Nullable Path findBinFile(@NotNull String fileName) {
for (Path binDir : getBinDirectories()) {
Path candidate = binDir.resolve(fileName);
if (Files.isRegularFile(candidate)) {
return candidate;
}
}
return null;
}
/**
* Looks for a file in all possible bin directories.
*
* @return the first file that exists.
* @throws RuntimeException if nothing found.
* @see #findBinFile(String)
*/
public static @NotNull Path findBinFileWithException(@NotNull String fileName) {
Path file = findBinFile(fileName);
if (file != null) {
return file;
}
StringBuilder message = new StringBuilder();
message.append('\'').append(fileName).append("' not found in directories:");
for (Path directory : getBinDirectories()) {
message.append('\n').append(directory);
}
throw new RuntimeException(message.toString());
}
/**
* Returns the path to the directory where IDE's JAR files are stored.
*/
public static @NotNull String getLibPath() {
return getHomePath() + '/' + LIB_DIRECTORY;
}
/**
* Returns the path to the directory where bundled plugins are located.
*/
public static @NotNull String getPreInstalledPluginsPath() {
return getHomePath() + '/' + PLUGINS_DIRECTORY;
}
/** <b>Note</b>: on macOS, the method returns a "functional" home, pointing to a JRE subdirectory inside a bundle. */
public static @NotNull String getBundledRuntimePath() {
return getHomePath() + '/' + JRE_DIRECTORY + (SystemInfoRt.isMac ? "/Contents/Home" : "");
}
/**
* Returns the path to the directory where data common for all IntelliJ-based IDEs is stored.
*/
public static synchronized @NotNull Path getCommonDataPath() {
Path path = ourCommonDataPath;
if (path == null) {
path = Paths.get(platformPath("", "Application Support", "", "APPDATA", "", "XDG_DATA_HOME", ".local/share", ""));
if (!Files.exists(path, LinkOption.NOFOLLOW_LINKS)) {
try {
Files.createDirectories(path);
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
}
ourCommonDataPath = path;
}
return path;
}
/**
* Returns a base name used to compose default locations of directories used by the IDE.
*/
@ApiStatus.Internal
@SuppressWarnings("IdentifierGrammar")
public static @Nullable String getPathsSelector() {
return ourPathSelector;
}
/**
* Provides a way to update the path selected. This is a temporary solution, it'll be removed when RDCT-1474 is fixed.
*/
@ApiStatus.Experimental
@ApiStatus.Internal
public static void setPathSelector(@NotNull String newValue) {
ourPathSelector = newValue;
System.setProperty(PROPERTY_PATHS_SELECTOR, newValue);
}
/**
* Returns the path to the directory where settings are stored.
* Usually, you don't need to access this directory directly, use {@link com.intellij.openapi.components.PersistentStateComponent} instead.
*/
public static @NotNull Path getConfigDir() {
return Paths.get(getConfigPath());
}
/**
* Returns the path to the directory where settings are stored. Consider using {@link #getConfigDir()} instead.
*/
public static @NotNull String getConfigPath() {
String path = ourConfigPath;
if (path == null) {
String explicit = getExplicitPath(PROPERTY_CONFIG_PATH);
ourConfigPath = path = explicit != null ? explicit :
ourPathSelector != null ? getDefaultConfigPathFor(ourPathSelector) :
getHomePath() + '/' + CONFIG_DIRECTORY;
}
return path;
}
@TestOnly
public static void setExplicitConfigPath(@Nullable String path) {
ourConfigPath = path;
}
/**
* Returns the path to the directory where scratch files are stored.
*/
public static @NotNull String getScratchPath() {
String path = ourScratchPath;
if (path == null) {
String explicit = getExplicitPath(PROPERTY_SCRATCH_PATH);
ourScratchPath = path = explicit == null ? getConfigPath() : explicit;
}
return path;
}
/**
* Returns the path to the directory where settings are stored by default for IDE designated by {@code selector}.
*/
public static @NotNull String getDefaultConfigPathFor(@NotNull String selector) {
return platformPath(selector, "Application Support", "", "APPDATA", "", "XDG_CONFIG_HOME", ".config", "");
}
/**
* Returns the path to the directory where regular settings are stored.
* Usually, you don't need to access this directory directly, use {@link com.intellij.openapi.components.PersistentStateComponent} instead.
*/
public static @NotNull String getOptionsPath() {
return getConfigPath() + '/' + OPTIONS_DIRECTORY;
}
/**
* Returns the path to a file with name {@code fileName} where regular settings are stored.
* Usually, you don't need to access this directory directly, use {@link com.intellij.openapi.components.PersistentStateComponent} instead.
*/
public static @NotNull File getOptionsFile(@NotNull String fileName) {
return Paths.get(getOptionsPath(), fileName + DEFAULT_EXT).toFile();
}
/**
* Returns the path to the directory where custom plugins are stored.
*/
public static @NotNull Path getPluginsDir() {
return Paths.get(getPluginsPath());
}
/**
* Returns the path to the directory where custom plugins are stored. Consider using {@link #getPluginsDir()} instead.
*/
public static @NotNull String getPluginsPath() {
String path = ourPluginPath;
if (path == null) {
String explicit = getExplicitPath(PROPERTY_PLUGINS_PATH);
ourPluginPath = path = explicit != null ? explicit :
ourPathSelector != null && System.getProperty(PROPERTY_CONFIG_PATH) == null ? getDefaultPluginPathFor(
ourPathSelector) :
getConfigPath() + '/' + PLUGINS_DIRECTORY;
}
return path;
}
/**
* Return the path to the directory where custom plugins are stored by default for IDE with the given path selector.
*/
@ApiStatus.Internal
public static @NotNull String getDefaultPluginPathFor(@NotNull String selector) {
return platformPath(selector, "Application Support", PLUGINS_DIRECTORY, "APPDATA", PLUGINS_DIRECTORY, "XDG_DATA_HOME", ".local/share", "");
}
/**
* Return the path to the directory where custom openide.properties and *.vmoptions files are stored.
*/
public static @Nullable String getCustomOptionsDirectory() {
// do not use getConfigPath() here - as it may be not yet defined
return ourPathSelector != null ? getDefaultConfigPathFor(ourPathSelector) : null;
}
/**
* Returns the path to the directory where caches are stored.
* To store plugin-related caches, always use a subdirectory named after the plugin.
* To store caches related to a particular project, use
* {@link com.intellij.openapi.project.ProjectUtil#getProjectDataPath} instead.
*/
public static @NotNull Path getSystemDir() {
return Paths.get(getSystemPath());
}
/**
* Returns the path to the directory where caches are stored.
*/
public static @NotNull String getSystemPath() {
String path = ourSystemPath;
if (path == null) {
String explicit = getExplicitPath(PROPERTY_SYSTEM_PATH);
ourSystemPath = path = explicit != null ? explicit :
ourPathSelector != null ? getDefaultSystemPathFor(ourPathSelector) :
getHomePath() + '/' + SYSTEM_DIRECTORY;
}
return path;
}
/**
* Returns the path to the directory where caches are stored by default for IDE with the given path selector.
*/
public static @NotNull String getDefaultSystemPathFor(@NotNull String selector) {
return getDefaultSystemPathFor(getLocalOS(), System.getProperty("user.home"), selector).toString();
}
@ApiStatus.Internal
public enum OS {
LINUX,
WINDOWS,
MACOS,
// placeholder for BSD-like systems
GENERIC_UNIX,
}
@ApiStatus.Internal
public static @NotNull OS getLocalOS() {
if (SystemInfoRt.isMac) {
return OS.MACOS;
}
else if (SystemInfoRt.isWindows) {
return OS.WINDOWS;
}
else if (SystemInfoRt.isLinux) {
return OS.LINUX;
}
else if (SystemInfoRt.isUnix) {
return OS.GENERIC_UNIX;
}
else {
throw new UnsupportedOperationException("Unsupported OS:" + SystemInfoRt.OS_NAME);
}
}
@ApiStatus.Internal
public static @NotNull Path getDefaultSystemPathFor(@NotNull OS os, @NotNull String userHome, @NotNull String selector) {
return Paths.get(platformPath(os, userHome, selector, "Caches", "", "LOCALAPPDATA", "", "XDG_CACHE_HOME", ".cache", ""));
}
@ApiStatus.Internal
public static @NotNull String getDefaultUnixSystemPath(@NotNull String userHome, @NotNull String selector) {
return getUnixPlatformPath(userHome, selector, null, ".cache", "");
}
/**
* Returns the path to the directory to store temporary files.
*/
public static @NotNull String getTempPath() {
return getSystemPath() + "/tmp";
}
/**
* Returns the path to the directory where indices are stored.
*/
@ApiStatus.Internal
public static @NotNull Path getIndexRoot() {
String indexRootPath = getExplicitPath("index_root_path");
if (indexRootPath == null) {
indexRootPath = getSystemPath() + "/index";
}
return Paths.get(indexRootPath);
}
/**
* Returns the path to the directory where log files are stored.
* Usually you don't need to access it directly, use {@link com.intellij.openapi.diagnostic.Logger} instead.
*/
public static @NotNull Path getLogDir() {
return Paths.get(getLogPath());
}
/**
* Returns the path to the directory where log files are stored. Consider using {@link #getLogDir()} instead.
*/
public static @NotNull String getLogPath() {
String path = ourLogPath;
if (path == null) {
String explicit = getExplicitPath(PROPERTY_LOG_PATH);
ourLogPath = path = explicit != null ? explicit :
ourPathSelector != null && System.getProperty(PROPERTY_SYSTEM_PATH) == null ? getDefaultLogPathFor(
ourPathSelector) :
getSystemPath() + '/' + LOG_DIRECTORY;
}
return path;
}
/**
* Returns the path to the directory where log files are stored by default for IDE with the given path selector.
*/
@ApiStatus.Internal
public static @NotNull String getDefaultLogPathFor(@NotNull String selector) {
return platformPath(selector, "Logs", "", "LOCALAPPDATA", LOG_DIRECTORY, "XDG_CACHE_HOME", ".cache", LOG_DIRECTORY);
}
/**
* Returns the path to the directory where the script which is executed at startup and files used by it are located.
* @see com.intellij.ide.startup.StartupActionScriptManager
*/
@ApiStatus.Internal
public static @NotNull Path getStartupScriptDir() {
if (ourStartupScriptDir != null) return ourStartupScriptDir;
return getSystemDir().resolve(PLUGINS_DIRECTORY);
}
/**
* This method isn't supposed to be used in new code. If you need to locate a directory where the startup script and related files are
* located, use {@link #getStartupScriptDir()} instead. If you need to save some custom caches related to plugins, create your own
* directory under {@link #getSystemDir()}.
*/
@ApiStatus.Obsolete
public static @NotNull String getPluginTempPath() {
return getSystemPath() + '/' + PLUGINS_DIRECTORY;
}
// misc stuff
/**
* Attempts to detect classpath entry containing the resource.
*/
public static @Nullable String getResourceRoot(@NotNull Class<?> context, @NotNull String path) {
URL url = context.getResource(path);
if (url == null) {
url = ClassLoader.getSystemResource(path.substring(1));
}
return url != null ? extractRoot(url, path) : null;
}
/**
* Attempts to detect classpath entry containing the resource.
*/
public static @Nullable String getResourceRoot(@NotNull ClassLoader classLoader, @NotNull String resourcePath) {
URL url = classLoader.getResource(resourcePath);
return url == null ? null : extractRoot(url, "/" + resourcePath);
}
/**
* Attempts to extract classpath entry part from passed URL.
*/
private static @Nullable String extractRoot(URL resourceURL, String resourcePath) {
if (resourcePath.isEmpty() || resourcePath.charAt(0) != '/' && resourcePath.charAt(0) != '\\') {
log("precondition failed: " + resourcePath);
return null;
}
String resultPath = null;
String protocol = resourceURL.getProtocol();
if (URLUtil.FILE_PROTOCOL.equals(protocol)) {
File result;
try {
result = new File(resourceURL.toURI().getSchemeSpecificPart());
}
catch (URISyntaxException e) {
throw new IllegalArgumentException("URL='" + resourceURL + "'", e);
}
String path = result.getPath();
String testPath = path.replace('\\', '/');
String testResourcePath = resourcePath.replace('\\', '/');
if (StringUtilRt.endsWithIgnoreCase(testPath, testResourcePath)) {
resultPath = path.substring(0, path.length() - resourcePath.length());
}
}
else if (URLUtil.JAR_PROTOCOL.equals(protocol)) {
// do not use URLUtil.splitJarUrl here - used in bootstrap
String jarPath = splitJarUrl(resourceURL.getFile());
if (jarPath != null) {
resultPath = jarPath;
}
}
else if (URLUtil.JRT_PROTOCOL.equals(protocol)) {
return null;
}
if (resultPath == null) {
log("cannot extract '" + resourcePath + "' from '" + resourceURL + "'");
return null;
}
return Paths.get(resultPath).normalize().toString();
}
// do not use URLUtil.splitJarUrl here - used in bootstrap
private static @Nullable String splitJarUrl(@NotNull String url) {
int pivot = url.indexOf(URLUtil.JAR_SEPARATOR);
if (pivot < 0) {
return null;
}
String jarPath = url.substring(0, pivot);
boolean startsWithConcatenation = true;
int offset = 0;
for (String prefix : new String[]{URLUtil.JAR_PROTOCOL, ":"}) {
int prefixLen = prefix.length();
if (!jarPath.regionMatches(offset, prefix, 0, prefixLen)) {
startsWithConcatenation = false;
break;
}
offset += prefixLen;
}
if (startsWithConcatenation) {
jarPath = jarPath.substring(URLUtil.JAR_PROTOCOL.length() + 1);
}
if (!jarPath.startsWith(URLUtil.FILE_PROTOCOL)) {
return jarPath;
}
try {
File result;
URL parsedUrl = new URL(jarPath);
try {
result = new File(parsedUrl.toURI().getSchemeSpecificPart());
}
catch (URISyntaxException e) {
throw new IllegalArgumentException("URL='" + parsedUrl + "'", e);
}
return result.getPath().replace('\\', '/');
}
catch (Exception e) {
jarPath = jarPath.substring(URLUtil.FILE_PROTOCOL.length());
if (jarPath.startsWith(URLUtil.SCHEME_SEPARATOR)) {
return jarPath.substring(URLUtil.SCHEME_SEPARATOR.length());
}
else if (!jarPath.isEmpty() && jarPath.charAt(0) == ':') {
return jarPath.substring(1);
}
else {
return jarPath;
}
}
}
@ApiStatus.Internal
public static void loadProperties() {
List<Path> files = new ArrayList<>();
String customFile = System.getProperty(PROPERTIES_FILE);
if (customFile != null) {
files.add(Paths.get(customFile));
}
String optionsDir = getCustomOptionsDirectory();
if (optionsDir != null) {
files.add(Paths.get(optionsDir, PROPERTIES_FILE_NAME));
}
files.add(Paths.get(System.getProperty("user.home"), PROPERTIES_FILE_NAME));
for (Path binDir : getBinDirectories()) {
files.add(binDir.resolve(PROPERTIES_FILE_NAME));
}
Properties sysProperties = System.getProperties();
String homePath = getHomePath(true);
for (Path file : files) {
try (Reader reader = Files.newBufferedReader(file)) {
//noinspection NonSynchronizedMethodOverridesSynchronizedMethod
new Properties() {
@Override
public Object put(Object key, Object value) {
if (PROPERTY_HOME_PATH.equals(key) || PROPERTY_HOME.equals(key)) {
log(file + ": '" + key + "' cannot be redefined");
}
else if (!sysProperties.containsKey(key)) {
sysProperties.setProperty((String)key, substituteVars((String)value, homePath));
}
return null;
}
}.load(reader);
}
catch (NoSuchFileException | AccessDeniedException ignore) { }
catch (IOException e) {
log("Can't read property file '" + file + "': " + e.getMessage());
}
}
// check and fix conflicting properties
if (SystemInfoRt.isJBSystemMenu) {
sysProperties.setProperty("apple.laf.useScreenMenuBar", "false");
}
}
@ApiStatus.Internal
public static void customizePaths(List<String> args) {
String property = System.getProperty(SYSTEM_PATHS_CUSTOMIZER);
if (property == null) return;
try {
Class<?> aClass = PathManager.class.getClassLoader().loadClass(property);
Object customizer = aClass.getConstructor().newInstance();
if (customizer instanceof PathCustomizer) {
PathCustomizer.CustomPaths paths = ((PathCustomizer)customizer).customizePaths(args);
if (paths != null) {
ourOriginalConfigDir = getConfigDir();
ourOriginalSystemDir = getSystemDir();
ourOriginalLogDir = getLogDir();
if (paths.configPath != null) System.setProperty(PROPERTY_CONFIG_PATH, paths.configPath);
if (paths.systemPath != null) System.setProperty(PROPERTY_SYSTEM_PATH, paths.systemPath);
if (paths.pluginsPath != null) System.setProperty(PROPERTY_PLUGINS_PATH, paths.pluginsPath);
if (paths.logDirPath != null) System.setProperty(PROPERTY_LOG_PATH, paths.logDirPath);
if (paths.startupScriptDir != null) ourStartupScriptDir = paths.startupScriptDir;
// NB: IDE might use an instance from a different classloader
ourConfigPath = null;
ourSystemPath = null;
ourPluginPath = null;
ourScratchPath = null;
ourLogPath = null;
}
}
}
catch (Throwable e) {
log("Failed to set up '" + property + "' as PathCustomizer: " + e);
}
}
/**
* Return original value of the config path ignoring possible customizations made by {@link PathCustomizer}.
*/
@ApiStatus.Internal
public static @NotNull Path getOriginalConfigDir() {
return ourOriginalConfigDir != null ? ourOriginalConfigDir : getConfigDir();
}
/**
* Return original value of the system path ignoring possible customizations made by {@link PathCustomizer}.
*/
@ApiStatus.Internal
public static @NotNull Path getOriginalSystemDir() {
return ourOriginalSystemDir != null ? ourOriginalSystemDir : getSystemDir();
}
/**
* Return original value of the log path ignoring possible customizations made by {@link PathCustomizer}.
*/
@ApiStatus.Internal
public static @NotNull Path getOriginalLogDir() {
return ourOriginalLogDir != null ? ourOriginalLogDir : getLogDir();
}
@ApiStatus.Internal
@Contract("null, _ -> null")
public static String substituteVars(String s, @NotNull String ideaHomePath) {
if (s == null) return null;
if (s.startsWith("..")) {
s = ideaHomePath + '/' + BIN_DIRECTORY + '/' + s;
}
Matcher m = Lazy.PROPERTY_REF.matcher(s);
while (m.find()) {
String key = m.group(1);
String value = System.getProperty(key);
if (value == null) {
switch (key) {
case PROPERTY_HOME_PATH:
case PROPERTY_HOME:
value = ideaHomePath;
break;
case PROPERTY_CONFIG_PATH:
value = getConfigPath();
break;
case PROPERTY_SYSTEM_PATH:
value = getSystemPath();
break;
}
}
if (value == null) {
log("Unknown property: " + key);
value = "";
}
s = s.replace(m.group(), value);
m = Lazy.PROPERTY_REF.matcher(s);
}
return s;
}
@ApiStatus.Internal
public static @NotNull File findFileInLibDirectory(@NotNull String relativePath) {
Path file = Paths.get(getLibPath(), relativePath);
if (!Files.exists(file)) file = Paths.get(getHomePath(), "community/lib/" + relativePath);
return file.toFile();
}
/**
* @return path to 'community' project home irrespective of the current project
*/
public static @NotNull String getCommunityHomePath() {
return getCommunityHomePath(getHomePath());
}
private static boolean isDevServer() {
return Boolean.getBoolean("idea.use.dev.build.server");
}
private static @NotNull String getCommunityHomePath(@NotNull String homePath) {
boolean isRunningFromSources = Files.isDirectory(Paths.get(homePath, ".idea"));
if (!isRunningFromSources && !isDevServer()) return homePath;
ArrayList<Path> possibleCommunityPathList = new ArrayList<>();
possibleCommunityPathList.add(Paths.get(homePath, "community"));
possibleCommunityPathList.add(Paths.get(homePath, "..", "..", "..", "community"));
possibleCommunityPathList.add(Paths.get(homePath, "..", "..", "..", "..", "community"));
for (Path possibleCommunityPath : possibleCommunityPathList) {
if (Files.isRegularFile(possibleCommunityPath.resolve(COMMUNITY_MARKER))) {
return possibleCommunityPath.normalize().toString();
}
}
return homePath;
}
/**
* Returns the path to the JAR file or root directory from where class-file for {@code aClass} is located.
* Consider using {@link #getJarForClass(Class)} instead.
*/
public static @Nullable String getJarPathForClass(@NotNull Class<?> aClass) {
Path resourceRoot = getJarForClass(aClass);
return resourceRoot == null ? null : resourceRoot.toString();
}
/**
* Returns the path to the JAR file or root directory from where class-file for {@code aClass} is located.
*/
public static @Nullable Path getJarForClass(@NotNull Class<?> aClass) {
String resourceRoot = getResourceRoot(aClass, '/' + aClass.getName().replace('.', '/') + ".class");
return resourceRoot == null ? null : Paths.get(resourceRoot).toAbsolutePath();
}
@SuppressWarnings("UseOfSystemOutOrSystemErr")
private static void log(String x) {
System.err.println(x);
}
/**
* Convert the given path to an absolute path substituting '~' symbol with the user home path.
*/
public static @NotNull String getAbsolutePath(@NotNull String path) {
if (path.startsWith("~/") || path.startsWith("~\\")) {
path = System.getProperty("user.home") + path.substring(1);
}
return Paths.get(path).toAbsolutePath().normalize().toString();
}
private static @Nullable String getExplicitPath(@NotNull String property) {
String path = System.getProperty(property);
if (path == null) {
return null;
}
boolean quoted = path.length() > 1 && '"' == path.charAt(0) && '"' == path.charAt(path.length() - 1);
return getAbsolutePath(quoted ? path.substring(1, path.length() - 1) : path);
}
private static String platformPath(String selector,
String macDir, String macSub,
String winVar, String winSub,
String xdgVar, String xdgDfl, String xdgSub) {
return platformPath(getLocalOS(), System.getProperty("user.home"), selector, macDir, macSub, winVar, winSub, xdgVar, xdgDfl, xdgSub);
}
private static String platformPath(@NotNull OS os,
String userHome,
String selector,
String macDir, String macSub,
String winVar, String winSub,
String xdgVar, String xdgDfl, String xdgSub) {
String vendorName = vendorName();
if (os == OS.MACOS) {
String dir = userHome + "/Library/" + macDir + '/' + vendorName;
if (!selector.isEmpty()) dir = dir + '/' + selector;
if (!macSub.isEmpty()) dir = dir + '/' + macSub;
return dir;
}
if (os == OS.WINDOWS) {
String dir = System.getenv(winVar);
if (dir == null || dir.isEmpty()) dir = userHome + "\\AppData\\" + (winVar.startsWith("LOCAL") ? "Local" : "Roaming");
dir = dir + '\\' + vendorName;
if (!selector.isEmpty()) dir = dir + '\\' + selector;
if (!winSub.isEmpty()) dir = dir + '\\' + winSub;
return dir;
}
if (os == OS.LINUX || os == OS.GENERIC_UNIX) {
return getUnixPlatformPath(userHome, selector, xdgVar, xdgDfl, xdgSub);
}
throw new UnsupportedOperationException("Unsupported OS: " + SystemInfoRt.OS_NAME);
}
private static String getUnixPlatformPath(String userHome, String selector, @Nullable String xdgVar, String xdgDfl, String xdgSub) {
String dir = xdgVar != null ? System.getenv(xdgVar) : null;
if (dir == null || dir.isEmpty()) dir = userHome + '/' + xdgDfl;
dir = dir + '/' + vendorName();
if (!selector.isEmpty()) dir = dir + '/' + selector;
if (!xdgSub.isEmpty()) dir = dir + '/' + xdgSub;
return dir;
}
@NotNull
private static String vendorName() {
String property = System.getProperty(PROPERTY_VENDOR_NAME);
if (property == null) {
try {
Class<?> ex = Class.forName("com.intellij.openapi.application.ex.ApplicationInfoEx");
Class<?> impl = Class.forName("com.intellij.openapi.application.impl.ApplicationInfoImpl");
MethodHandles.Lookup lookup = MethodHandles.lookup();
Object instance = lookup.findStatic(impl, "getShadowInstance", MethodType.methodType(ex)).invoke();
property = (String)lookup.findVirtual(impl, "getShortCompanyName", MethodType.methodType(String.class)).invoke(instance);
}
catch (Throwable ignored) { }
if (property == null) {
property = "JetBrains";
}
System.setProperty(PROPERTY_VENDOR_NAME, property);
}
return property;
}
/**
* NB: actual jars might be in subdirectories
*/
@ApiStatus.Internal
public static @Nullable String getArchivedCompliedClassesLocation() {
return System.getProperty("intellij.test.jars.location");
}
/**
* Returns a map of IntelliJ modules to .jar absolute paths, e.g.:
* "production/intellij.platform.util" => ".../production/intellij.platform.util/$hash.jar"
*/
@ApiStatus.Internal
public static @Nullable Map<String, String> getArchivedCompiledClassesMapping() {
if (ourArchivedCompiledClassesMapping == null) {
ourArchivedCompiledClassesMapping = computeArchivedCompiledClassesMapping();
}
return ourArchivedCompiledClassesMapping;
}
private static @Nullable Map<String, String> computeArchivedCompiledClassesMapping() {
final String filePath = System.getProperty("intellij.test.jars.mapping.file");
if (StringUtilRt.isEmptyOrSpaces(filePath)) {
return null;
}
final List<String> lines;
try {
lines = Files.readAllLines(Paths.get(filePath));
}
catch (Exception e) {
log("Failed to load jars mappings from " + filePath);
return null;
}
final Map<String, String> mapping = new HashMap<>(lines.size());
for (String line : lines) {
String[] split = line.split("=", 2);
if (split.length < 2) {
log("Ignored jars mapping line: " + line);
continue;
}
mapping.put(split[0], split[1]);
}
return Collections.unmodifiableMap(mapping);
}
}