1 /* 2 * Copyright (C) 2009, Google Inc. 3 * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com> 4 * Copyright (C) 2009, Yann Simon <yann.simon.fr@gmail.com> 5 * Copyright (C) 2012, Daniel Megert <daniel_megert@ch.ibm.com> 6 * and other copyright owners as documented in the project's IP log. 7 * 8 * This program and the accompanying materials are made available 9 * under the terms of the Eclipse Distribution License v1.0 which 10 * accompanies this distribution, is reproduced below, and is 11 * available at http://www.eclipse.org/org/documents/edl-v10.php 12 * 13 * All rights reserved. 14 * 15 * Redistribution and use in source and binary forms, with or 16 * without modification, are permitted provided that the following 17 * conditions are met: 18 * 19 * - Redistributions of source code must retain the above copyright 20 * notice, this list of conditions and the following disclaimer. 21 * 22 * - Redistributions in binary form must reproduce the above 23 * copyright notice, this list of conditions and the following 24 * disclaimer in the documentation and/or other materials provided 25 * with the distribution. 26 * 27 * - Neither the name of the Eclipse Foundation, Inc. nor the 28 * names of its contributors may be used to endorse or promote 29 * products derived from this software without specific prior 30 * written permission. 31 * 32 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 33 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 34 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 35 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 36 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 37 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 38 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 39 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 40 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 41 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 42 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 43 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 44 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 45 */ 46 47 package org.eclipse.jgit.util; 48 49 import java.io.File; 50 import java.io.IOException; 51 import java.net.InetAddress; 52 import java.net.UnknownHostException; 53 import java.security.AccessController; 54 import java.security.PrivilegedAction; 55 import java.text.DateFormat; 56 import java.text.SimpleDateFormat; 57 import java.util.Locale; 58 import java.util.TimeZone; 59 import java.util.concurrent.atomic.AtomicReference; 60 61 import org.eclipse.jgit.errors.ConfigInvalidException; 62 import org.eclipse.jgit.errors.CorruptObjectException; 63 import org.eclipse.jgit.lib.Config; 64 import org.eclipse.jgit.lib.Constants; 65 import org.eclipse.jgit.lib.ObjectChecker; 66 import org.eclipse.jgit.lib.StoredConfig; 67 import org.eclipse.jgit.storage.file.FileBasedConfig; 68 import org.eclipse.jgit.util.time.MonotonicClock; 69 import org.eclipse.jgit.util.time.MonotonicSystemClock; 70 import org.slf4j.Logger; 71 import org.slf4j.LoggerFactory; 72 73 /** 74 * Interface to read values from the system. 75 * <p> 76 * When writing unit tests, extending this interface with a custom class 77 * permits to simulate an access to a system variable or property and 78 * permits to control the user's global configuration. 79 * </p> 80 */ 81 public abstract class SystemReader { 82 83 private final static Logger LOG = LoggerFactory 84 .getLogger(SystemReader.class); 85 86 private static final SystemReader DEFAULT; 87 88 private static Boolean isMacOS; 89 90 private static Boolean isWindows; 91 92 static { 93 SystemReader r = new Default(); 94 r.init(); 95 DEFAULT = r; 96 } 97 98 private static class Default extends SystemReader { 99 private volatile String hostname; 100 101 @Override 102 public String getenv(String variable) { 103 return System.getenv(variable); 104 } 105 106 @Override 107 public String getProperty(String key) { 108 return System.getProperty(key); 109 } 110 111 @Override 112 public FileBasedConfig openSystemConfig(Config parent, FS fs) { 113 if (StringUtils 114 .isEmptyOrNull(getenv(Constants.GIT_CONFIG_NOSYSTEM_KEY))) { 115 File configFile = fs.getGitSystemConfig(); 116 if (configFile != null) { 117 return new FileBasedConfig(parent, configFile, fs); 118 } 119 } 120 return new FileBasedConfig(parent, null, fs) { 121 @Override 122 public void load() { 123 // empty, do not load 124 } 125 126 @Override 127 public boolean isOutdated() { 128 // regular class would bomb here 129 return false; 130 } 131 }; 132 } 133 134 @Override 135 public FileBasedConfig openUserConfig(Config parent, FS fs) { 136 return new FileBasedConfig(parent, new File(fs.userHome(), ".gitconfig"), //$NON-NLS-1$ 137 fs); 138 } 139 140 @Override 141 public String getHostname() { 142 if (hostname == null) { 143 try { 144 InetAddress localMachine = InetAddress.getLocalHost(); 145 hostname = localMachine.getCanonicalHostName(); 146 } catch (UnknownHostException e) { 147 // we do nothing 148 hostname = "localhost"; //$NON-NLS-1$ 149 } 150 assert hostname != null; 151 } 152 return hostname; 153 } 154 155 @Override 156 public long getCurrentTime() { 157 return System.currentTimeMillis(); 158 } 159 160 @Override 161 public int getTimezone(long when) { 162 return getTimeZone().getOffset(when) / (60 * 1000); 163 } 164 } 165 166 private static volatile SystemReader INSTANCE = DEFAULT; 167 168 /** 169 * Get the current SystemReader instance 170 * 171 * @return the current SystemReader instance. 172 */ 173 public static SystemReader getInstance() { 174 return INSTANCE; 175 } 176 177 /** 178 * Set a new SystemReader instance to use when accessing properties. 179 * 180 * @param newReader 181 * the new instance to use when accessing properties, or null for 182 * the default instance. 183 */ 184 public static void setInstance(SystemReader newReader) { 185 isMacOS = null; 186 isWindows = null; 187 if (newReader == null) 188 INSTANCE = DEFAULT; 189 else { 190 newReader.init(); 191 INSTANCE = newReader; 192 } 193 } 194 195 private ObjectChecker platformChecker; 196 197 private AtomicReference<FileBasedConfig> systemConfig = new AtomicReference<>(); 198 199 private AtomicReference<FileBasedConfig> userConfig = new AtomicReference<>(); 200 201 private void init() { 202 // Creating ObjectChecker must be deferred. Unit tests change 203 // behavior of is{Windows,MacOS} in constructor of subclass. 204 if (platformChecker == null) 205 setPlatformChecker(); 206 } 207 208 /** 209 * Should be used in tests when the platform is explicitly changed. 210 * 211 * @since 3.6 212 */ 213 protected final void setPlatformChecker() { 214 platformChecker = new ObjectChecker() 215 .setSafeForWindows(isWindows()) 216 .setSafeForMacOS(isMacOS()); 217 } 218 219 /** 220 * Gets the hostname of the local host. If no hostname can be found, the 221 * hostname is set to the default value "localhost". 222 * 223 * @return the canonical hostname 224 */ 225 public abstract String getHostname(); 226 227 /** 228 * Get value of the system variable 229 * 230 * @param variable 231 * system variable to read 232 * @return value of the system variable 233 */ 234 public abstract String getenv(String variable); 235 236 /** 237 * Get value of the system property 238 * 239 * @param key 240 * of the system property to read 241 * @return value of the system property 242 */ 243 public abstract String getProperty(String key); 244 245 /** 246 * Open the git configuration found in the user home. Use 247 * {@link #getUserConfig()} to get the current git configuration in the user 248 * home since it manages automatic reloading when the gitconfig file was 249 * modified and avoids unnecessary reloads. 250 * 251 * @param parent 252 * a config with values not found directly in the returned config 253 * @param fs 254 * the file system abstraction which will be necessary to perform 255 * certain file system operations. 256 * @return the git configuration found in the user home 257 */ 258 public abstract FileBasedConfig openUserConfig(Config parent, FS fs); 259 260 /** 261 * Open the gitconfig configuration found in the system-wide "etc" 262 * directory. Use {@link #getSystemConfig()} to get the current system-wide 263 * git configuration since it manages automatic reloading when the gitconfig 264 * file was modified and avoids unnecessary reloads. 265 * 266 * @param parent 267 * a config with values not found directly in the returned 268 * config. Null is a reasonable value here. 269 * @param fs 270 * the file system abstraction which will be necessary to perform 271 * certain file system operations. 272 * @return the gitconfig configuration found in the system-wide "etc" 273 * directory 274 */ 275 public abstract FileBasedConfig openSystemConfig(Config parent, FS fs); 276 277 /** 278 * Get the git configuration found in the user home. The configuration will 279 * be reloaded automatically if the configuration file was modified. Also 280 * reloads the system config if the system config file was modified. If the 281 * configuration file wasn't modified returns the cached configuration. 282 * 283 * @return the git configuration found in the user home 284 * @throws ConfigInvalidException 285 * if configuration is invalid 286 * @throws IOException 287 * if something went wrong when reading files 288 * @since 5.1.9 289 */ 290 public StoredConfig getUserConfig() 291 throws IOException, ConfigInvalidException { 292 FileBasedConfig c = userConfig.get(); 293 if (c == null) { 294 userConfig.compareAndSet(null, 295 openUserConfig(getSystemConfig(), FS.DETECTED)); 296 c = userConfig.get(); 297 } else { 298 // Ensure the parent is up to date 299 getSystemConfig(); 300 } 301 if (c.isOutdated()) { 302 LOG.debug("loading user config {}", userConfig); //$NON-NLS-1$ 303 c.load(); 304 } 305 return c; 306 } 307 308 /** 309 * Get the gitconfig configuration found in the system-wide "etc" directory. 310 * The configuration will be reloaded automatically if the configuration 311 * file was modified otherwise returns the cached system level config. 312 * 313 * @return the gitconfig configuration found in the system-wide "etc" 314 * directory 315 * @throws ConfigInvalidException 316 * if configuration is invalid 317 * @throws IOException 318 * if something went wrong when reading files 319 * @since 5.1.9 320 */ 321 public StoredConfig getSystemConfig() 322 throws IOException, ConfigInvalidException { 323 FileBasedConfig c = systemConfig.get(); 324 if (c == null) { 325 systemConfig.compareAndSet(null, 326 openSystemConfig(null, FS.DETECTED)); 327 c = systemConfig.get(); 328 } 329 if (c.isOutdated()) { 330 LOG.debug("loading system config {}", systemConfig); //$NON-NLS-1$ 331 c.load(); 332 } 333 return c; 334 } 335 336 /** 337 * Get the current system time 338 * 339 * @return the current system time 340 */ 341 public abstract long getCurrentTime(); 342 343 /** 344 * Get clock instance preferred by this system. 345 * 346 * @return clock instance preferred by this system. 347 * @since 4.6 348 */ 349 public MonotonicClock getClock() { 350 return new MonotonicSystemClock(); 351 } 352 353 /** 354 * Get the local time zone 355 * 356 * @param when 357 * a system timestamp 358 * @return the local time zone 359 */ 360 public abstract int getTimezone(long when); 361 362 /** 363 * Get system time zone, possibly mocked for testing 364 * 365 * @return system time zone, possibly mocked for testing 366 * @since 1.2 367 */ 368 public TimeZone getTimeZone() { 369 return TimeZone.getDefault(); 370 } 371 372 /** 373 * Get the locale to use 374 * 375 * @return the locale to use 376 * @since 1.2 377 */ 378 public Locale getLocale() { 379 return Locale.getDefault(); 380 } 381 382 /** 383 * Returns a simple date format instance as specified by the given pattern. 384 * 385 * @param pattern 386 * the pattern as defined in 387 * {@link java.text.SimpleDateFormat#SimpleDateFormat(String)} 388 * @return the simple date format 389 * @since 2.0 390 */ 391 public SimpleDateFormat getSimpleDateFormat(String pattern) { 392 return new SimpleDateFormat(pattern); 393 } 394 395 /** 396 * Returns a simple date format instance as specified by the given pattern. 397 * 398 * @param pattern 399 * the pattern as defined in 400 * {@link java.text.SimpleDateFormat#SimpleDateFormat(String)} 401 * @param locale 402 * locale to be used for the {@code SimpleDateFormat} 403 * @return the simple date format 404 * @since 3.2 405 */ 406 public SimpleDateFormat getSimpleDateFormat(String pattern, Locale locale) { 407 return new SimpleDateFormat(pattern, locale); 408 } 409 410 /** 411 * Returns a date/time format instance for the given styles. 412 * 413 * @param dateStyle 414 * the date style as specified in 415 * {@link java.text.DateFormat#getDateTimeInstance(int, int)} 416 * @param timeStyle 417 * the time style as specified in 418 * {@link java.text.DateFormat#getDateTimeInstance(int, int)} 419 * @return the date format 420 * @since 2.0 421 */ 422 public DateFormat getDateTimeInstance(int dateStyle, int timeStyle) { 423 return DateFormat.getDateTimeInstance(dateStyle, timeStyle); 424 } 425 426 /** 427 * Whether we are running on Windows. 428 * 429 * @return true if we are running on Windows. 430 */ 431 public boolean isWindows() { 432 if (isWindows == null) { 433 String osDotName = getOsName(); 434 isWindows = Boolean.valueOf(osDotName.startsWith("Windows")); //$NON-NLS-1$ 435 } 436 return isWindows.booleanValue(); 437 } 438 439 /** 440 * Whether we are running on Mac OS X 441 * 442 * @return true if we are running on Mac OS X 443 */ 444 public boolean isMacOS() { 445 if (isMacOS == null) { 446 String osDotName = getOsName(); 447 isMacOS = Boolean.valueOf( 448 "Mac OS X".equals(osDotName) || "Darwin".equals(osDotName)); //$NON-NLS-1$ //$NON-NLS-2$ 449 } 450 return isMacOS.booleanValue(); 451 } 452 453 private String getOsName() { 454 return AccessController.doPrivileged( 455 (PrivilegedAction<String>) () -> getProperty("os.name") //$NON-NLS-1$ 456 ); 457 } 458 459 /** 460 * Check tree path entry for validity. 461 * <p> 462 * Scans a multi-directory path string such as {@code "src/main.c"}. 463 * 464 * @param path path string to scan. 465 * @throws org.eclipse.jgit.errors.CorruptObjectException path is invalid. 466 * @since 3.6 467 */ 468 public void checkPath(String path) throws CorruptObjectException { 469 platformChecker.checkPath(path); 470 } 471 472 /** 473 * Check tree path entry for validity. 474 * <p> 475 * Scans a multi-directory path string such as {@code "src/main.c"}. 476 * 477 * @param path 478 * path string to scan. 479 * @throws org.eclipse.jgit.errors.CorruptObjectException 480 * path is invalid. 481 * @since 4.2 482 */ 483 public void checkPath(byte[] path) throws CorruptObjectException { 484 platformChecker.checkPath(path, 0, path.length); 485 } 486 }