SystemReader.java
- /*
- * Copyright (C) 2009, Google Inc.
- * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2009, Yann Simon <yann.simon.fr@gmail.com>
- * Copyright (C) 2012, Daniel Megert <daniel_megert@ch.ibm.com> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- package org.eclipse.jgit.util;
- import java.io.File;
- import java.io.IOException;
- import java.net.InetAddress;
- import java.net.UnknownHostException;
- import java.nio.charset.Charset;
- import java.nio.charset.IllegalCharsetNameException;
- import java.nio.charset.UnsupportedCharsetException;
- import java.nio.file.InvalidPathException;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.security.AccessController;
- import java.security.PrivilegedAction;
- import java.text.DateFormat;
- import java.text.SimpleDateFormat;
- import java.util.Locale;
- import java.util.TimeZone;
- import java.util.concurrent.atomic.AtomicReference;
- import org.eclipse.jgit.errors.ConfigInvalidException;
- import org.eclipse.jgit.errors.CorruptObjectException;
- import org.eclipse.jgit.internal.JGitText;
- import org.eclipse.jgit.lib.Config;
- import org.eclipse.jgit.lib.Constants;
- import org.eclipse.jgit.lib.ObjectChecker;
- import org.eclipse.jgit.lib.StoredConfig;
- import org.eclipse.jgit.storage.file.FileBasedConfig;
- import org.eclipse.jgit.util.time.MonotonicClock;
- import org.eclipse.jgit.util.time.MonotonicSystemClock;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- /**
- * Interface to read values from the system.
- * <p>
- * When writing unit tests, extending this interface with a custom class
- * permits to simulate an access to a system variable or property and
- * permits to control the user's global configuration.
- * </p>
- */
- public abstract class SystemReader {
- private static final Logger LOG = LoggerFactory
- .getLogger(SystemReader.class);
- private static final SystemReader DEFAULT;
- private static volatile Boolean isMacOS;
- private static volatile Boolean isWindows;
- private static volatile Boolean isLinux;
- static {
- SystemReader r = new Default();
- r.init();
- DEFAULT = r;
- }
- private static class Default extends SystemReader {
- private volatile String hostname;
- @Override
- public String getenv(String variable) {
- return System.getenv(variable);
- }
- @Override
- public String getProperty(String key) {
- return System.getProperty(key);
- }
- @Override
- public FileBasedConfig openSystemConfig(Config parent, FS fs) {
- if (StringUtils
- .isEmptyOrNull(getenv(Constants.GIT_CONFIG_NOSYSTEM_KEY))) {
- File configFile = fs.getGitSystemConfig();
- if (configFile != null) {
- return new FileBasedConfig(parent, configFile, fs);
- }
- }
- return new FileBasedConfig(parent, null, fs) {
- @Override
- public void load() {
- // empty, do not load
- }
- @Override
- public boolean isOutdated() {
- // regular class would bomb here
- return false;
- }
- };
- }
- @Override
- public FileBasedConfig openUserConfig(Config parent, FS fs) {
- return new FileBasedConfig(parent, new File(fs.userHome(), ".gitconfig"), //$NON-NLS-1$
- fs);
- }
- private Path getXDGConfigHome(FS fs) {
- String configHomePath = getenv(Constants.XDG_CONFIG_HOME);
- if (StringUtils.isEmptyOrNull(configHomePath)) {
- configHomePath = new File(fs.userHome(), ".config") //$NON-NLS-1$
- .getAbsolutePath();
- }
- try {
- return Paths.get(configHomePath);
- } catch (InvalidPathException e) {
- LOG.error(JGitText.get().logXDGConfigHomeInvalid,
- configHomePath, e);
- }
- return null;
- }
- @Override
- public FileBasedConfig openJGitConfig(Config parent, FS fs) {
- Path xdgPath = getXDGConfigHome(fs);
- if (xdgPath != null) {
- Path configPath = xdgPath.resolve("jgit") //$NON-NLS-1$
- .resolve(Constants.CONFIG);
- return new FileBasedConfig(parent, configPath.toFile(), fs);
- }
- return new FileBasedConfig(parent,
- new File(fs.userHome(), ".jgitconfig"), fs); //$NON-NLS-1$
- }
- @Override
- public String getHostname() {
- if (hostname == null) {
- try {
- InetAddress localMachine = InetAddress.getLocalHost();
- hostname = localMachine.getCanonicalHostName();
- } catch (UnknownHostException e) {
- // we do nothing
- hostname = "localhost"; //$NON-NLS-1$
- }
- assert hostname != null;
- }
- return hostname;
- }
- @Override
- public long getCurrentTime() {
- return System.currentTimeMillis();
- }
- @Override
- public int getTimezone(long when) {
- return getTimeZone().getOffset(when) / (60 * 1000);
- }
- }
- private static volatile SystemReader INSTANCE = DEFAULT;
- /**
- * Get the current SystemReader instance
- *
- * @return the current SystemReader instance.
- */
- public static SystemReader getInstance() {
- return INSTANCE;
- }
- /**
- * Set a new SystemReader instance to use when accessing properties.
- *
- * @param newReader
- * the new instance to use when accessing properties, or null for
- * the default instance.
- */
- public static void setInstance(SystemReader newReader) {
- isMacOS = null;
- isWindows = null;
- isLinux = null;
- if (newReader == null)
- INSTANCE = DEFAULT;
- else {
- newReader.init();
- INSTANCE = newReader;
- }
- }
- private ObjectChecker platformChecker;
- private AtomicReference<FileBasedConfig> systemConfig = new AtomicReference<>();
- private AtomicReference<FileBasedConfig> userConfig = new AtomicReference<>();
- private AtomicReference<FileBasedConfig> jgitConfig = new AtomicReference<>();
- private volatile Charset defaultCharset;
- private void init() {
- // Creating ObjectChecker must be deferred. Unit tests change
- // behavior of is{Windows,MacOS} in constructor of subclass.
- if (platformChecker == null)
- setPlatformChecker();
- }
- /**
- * Should be used in tests when the platform is explicitly changed.
- *
- * @since 3.6
- */
- protected final void setPlatformChecker() {
- platformChecker = new ObjectChecker()
- .setSafeForWindows(isWindows())
- .setSafeForMacOS(isMacOS());
- }
- /**
- * Gets the hostname of the local host. If no hostname can be found, the
- * hostname is set to the default value "localhost".
- *
- * @return the canonical hostname
- */
- public abstract String getHostname();
- /**
- * Get value of the system variable
- *
- * @param variable
- * system variable to read
- * @return value of the system variable
- */
- public abstract String getenv(String variable);
- /**
- * Get value of the system property
- *
- * @param key
- * of the system property to read
- * @return value of the system property
- */
- public abstract String getProperty(String key);
- /**
- * Open the git configuration found in the user home. Use
- * {@link #getUserConfig()} to get the current git configuration in the user
- * home since it manages automatic reloading when the gitconfig file was
- * modified and avoids unnecessary reloads.
- *
- * @param parent
- * a config with values not found directly in the returned config
- * @param fs
- * the file system abstraction which will be necessary to perform
- * certain file system operations.
- * @return the git configuration found in the user home
- */
- public abstract FileBasedConfig openUserConfig(Config parent, FS fs);
- /**
- * Open the gitconfig configuration found in the system-wide "etc"
- * directory. Use {@link #getSystemConfig()} to get the current system-wide
- * git configuration since it manages automatic reloading when the gitconfig
- * file was modified and avoids unnecessary reloads.
- *
- * @param parent
- * a config with values not found directly in the returned
- * config. Null is a reasonable value here.
- * @param fs
- * the file system abstraction which will be necessary to perform
- * certain file system operations.
- * @return the gitconfig configuration found in the system-wide "etc"
- * directory
- */
- public abstract FileBasedConfig openSystemConfig(Config parent, FS fs);
- /**
- * Open the jgit configuration located at $XDG_CONFIG_HOME/jgit/config. Use
- * {@link #getJGitConfig()} to get the current jgit configuration in the
- * user home since it manages automatic reloading when the jgit config file
- * was modified and avoids unnecessary reloads.
- *
- * @param parent
- * a config with values not found directly in the returned config
- * @param fs
- * the file system abstraction which will be necessary to perform
- * certain file system operations.
- * @return the jgit configuration located at $XDG_CONFIG_HOME/jgit/config
- * @since 5.5.2
- */
- public abstract FileBasedConfig openJGitConfig(Config parent, FS fs);
- /**
- * Get the git configuration found in the user home. The configuration will
- * be reloaded automatically if the configuration file was modified. Also
- * reloads the system config if the system config file was modified. If the
- * configuration file wasn't modified returns the cached configuration.
- *
- * @return the git configuration found in the user home
- * @throws ConfigInvalidException
- * if configuration is invalid
- * @throws IOException
- * if something went wrong when reading files
- * @since 5.1.9
- */
- public StoredConfig getUserConfig()
- throws ConfigInvalidException, IOException {
- FileBasedConfig c = userConfig.get();
- if (c == null) {
- userConfig.compareAndSet(null,
- openUserConfig(getSystemConfig(), FS.DETECTED));
- c = userConfig.get();
- }
- // on the very first call this will check a second time if the system
- // config is outdated
- updateAll(c);
- return c;
- }
- /**
- * Get the jgit configuration located at $XDG_CONFIG_HOME/jgit/config. The
- * configuration will be reloaded automatically if the configuration file
- * was modified. If the configuration file wasn't modified returns the
- * cached configuration.
- *
- * @return the jgit configuration located at $XDG_CONFIG_HOME/jgit/config
- * @throws ConfigInvalidException
- * if configuration is invalid
- * @throws IOException
- * if something went wrong when reading files
- * @since 5.5.2
- */
- public StoredConfig getJGitConfig()
- throws ConfigInvalidException, IOException {
- FileBasedConfig c = jgitConfig.get();
- if (c == null) {
- jgitConfig.compareAndSet(null,
- openJGitConfig(null, FS.DETECTED));
- c = jgitConfig.get();
- }
- updateAll(c);
- return c;
- }
- /**
- * Get the gitconfig configuration found in the system-wide "etc" directory.
- * The configuration will be reloaded automatically if the configuration
- * file was modified otherwise returns the cached system level config.
- *
- * @return the gitconfig configuration found in the system-wide "etc"
- * directory
- * @throws ConfigInvalidException
- * if configuration is invalid
- * @throws IOException
- * if something went wrong when reading files
- * @since 5.1.9
- */
- public StoredConfig getSystemConfig()
- throws ConfigInvalidException, IOException {
- FileBasedConfig c = systemConfig.get();
- if (c == null) {
- systemConfig.compareAndSet(null,
- openSystemConfig(getJGitConfig(), FS.DETECTED));
- c = systemConfig.get();
- }
- updateAll(c);
- return c;
- }
- /**
- * Update config and its parents if they seem modified
- *
- * @param config
- * configuration to reload if outdated
- * @throws ConfigInvalidException
- * if configuration is invalid
- * @throws IOException
- * if something went wrong when reading files
- */
- private void updateAll(Config config)
- throws ConfigInvalidException, IOException {
- if (config == null) {
- return;
- }
- updateAll(config.getBaseConfig());
- if (config instanceof FileBasedConfig) {
- FileBasedConfig cfg = (FileBasedConfig) config;
- if (cfg.isOutdated()) {
- LOG.debug("loading config {}", cfg); //$NON-NLS-1$
- cfg.load();
- }
- }
- }
- /**
- * Get the current system time
- *
- * @return the current system time
- */
- public abstract long getCurrentTime();
- /**
- * Get clock instance preferred by this system.
- *
- * @return clock instance preferred by this system.
- * @since 4.6
- */
- public MonotonicClock getClock() {
- return new MonotonicSystemClock();
- }
- /**
- * Get the local time zone
- *
- * @param when
- * a system timestamp
- * @return the local time zone
- */
- public abstract int getTimezone(long when);
- /**
- * Get system time zone, possibly mocked for testing
- *
- * @return system time zone, possibly mocked for testing
- * @since 1.2
- */
- public TimeZone getTimeZone() {
- return TimeZone.getDefault();
- }
- /**
- * Get the locale to use
- *
- * @return the locale to use
- * @since 1.2
- */
- public Locale getLocale() {
- return Locale.getDefault();
- }
- /**
- * Retrieves the default {@link Charset} depending on the system locale.
- *
- * @return the {@link Charset}
- * @since 6.0
- * @see <a href="https://openjdk.java.net/jeps/400">JEP 400</a>
- */
- public Charset getDefaultCharset() {
- Charset result = defaultCharset;
- if (result == null) {
- // JEP 400: Java 18 populates this system property.
- String encoding = getProperty("native.encoding"); //$NON-NLS-1$
- try {
- if (!StringUtils.isEmptyOrNull(encoding)) {
- result = Charset.forName(encoding);
- }
- } catch (IllegalCharsetNameException
- | UnsupportedCharsetException e) {
- LOG.error(JGitText.get().logInvalidDefaultCharset, encoding);
- }
- if (result == null) {
- // This is always UTF-8 on Java >= 18.
- result = Charset.defaultCharset();
- }
- defaultCharset = result;
- }
- return result;
- }
- /**
- * Returns a simple date format instance as specified by the given pattern.
- *
- * @param pattern
- * the pattern as defined in
- * {@link java.text.SimpleDateFormat#SimpleDateFormat(String)}
- * @return the simple date format
- * @since 2.0
- */
- public SimpleDateFormat getSimpleDateFormat(String pattern) {
- return new SimpleDateFormat(pattern);
- }
- /**
- * Returns a simple date format instance as specified by the given pattern.
- *
- * @param pattern
- * the pattern as defined in
- * {@link java.text.SimpleDateFormat#SimpleDateFormat(String)}
- * @param locale
- * locale to be used for the {@code SimpleDateFormat}
- * @return the simple date format
- * @since 3.2
- */
- public SimpleDateFormat getSimpleDateFormat(String pattern, Locale locale) {
- return new SimpleDateFormat(pattern, locale);
- }
- /**
- * Returns a date/time format instance for the given styles.
- *
- * @param dateStyle
- * the date style as specified in
- * {@link java.text.DateFormat#getDateTimeInstance(int, int)}
- * @param timeStyle
- * the time style as specified in
- * {@link java.text.DateFormat#getDateTimeInstance(int, int)}
- * @return the date format
- * @since 2.0
- */
- public DateFormat getDateTimeInstance(int dateStyle, int timeStyle) {
- return DateFormat.getDateTimeInstance(dateStyle, timeStyle);
- }
- /**
- * Whether we are running on Windows.
- *
- * @return true if we are running on Windows.
- */
- public boolean isWindows() {
- if (isWindows == null) {
- String osDotName = getOsName();
- isWindows = Boolean.valueOf(osDotName.startsWith("Windows")); //$NON-NLS-1$
- }
- return isWindows.booleanValue();
- }
- /**
- * Whether we are running on Mac OS X
- *
- * @return true if we are running on Mac OS X
- */
- public boolean isMacOS() {
- if (isMacOS == null) {
- String osDotName = getOsName();
- isMacOS = Boolean.valueOf(
- "Mac OS X".equals(osDotName) || "Darwin".equals(osDotName)); //$NON-NLS-1$ //$NON-NLS-2$
- }
- return isMacOS.booleanValue();
- }
- /**
- * Whether we are running on Linux.
- *
- * @return true if we are running on Linux.
- * @since 6.3
- */
- public boolean isLinux() {
- if (isLinux == null) {
- String osname = getOsName();
- isLinux = Boolean.valueOf(osname.toLowerCase().startsWith("linux")); //$NON-NLS-1$
- }
- return isLinux.booleanValue();
- }
- private String getOsName() {
- return AccessController.doPrivileged(
- (PrivilegedAction<String>) () -> getProperty("os.name") //$NON-NLS-1$
- );
- }
- /**
- * Check tree path entry for validity.
- * <p>
- * Scans a multi-directory path string such as {@code "src/main.c"}.
- *
- * @param path path string to scan.
- * @throws org.eclipse.jgit.errors.CorruptObjectException path is invalid.
- * @since 3.6
- */
- public void checkPath(String path) throws CorruptObjectException {
- platformChecker.checkPath(path);
- }
- /**
- * Check tree path entry for validity.
- * <p>
- * Scans a multi-directory path string such as {@code "src/main.c"}.
- *
- * @param path
- * path string to scan.
- * @throws org.eclipse.jgit.errors.CorruptObjectException
- * path is invalid.
- * @since 4.2
- */
- public void checkPath(byte[] path) throws CorruptObjectException {
- platformChecker.checkPath(path, 0, path.length);
- }
- }