View Javadoc
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 		private AtomicReference<FileBasedConfig> systemConfig = new AtomicReference<>();
102 
103 		private volatile AtomicReference<FileBasedConfig> userConfig = new AtomicReference<>();
104 
105 		@Override
106 		public String getenv(String variable) {
107 			return System.getenv(variable);
108 		}
109 
110 		@Override
111 		public String getProperty(String key) {
112 			return System.getProperty(key);
113 		}
114 
115 		@Override
116 		public FileBasedConfig openSystemConfig(Config parent, FS fs) {
117 			FileBasedConfig c = systemConfig.get();
118 			if (c == null) {
119 				systemConfig.compareAndSet(null,
120 						createSystemConfig(parent, fs));
121 				c = systemConfig.get();
122 			}
123 			return c;
124 		}
125 
126 		protected FileBasedConfig createSystemConfig(Config parent, FS fs) {
127 			if (StringUtils.isEmptyOrNull(getenv(Constants.GIT_CONFIG_NOSYSTEM_KEY))) {
128 				File configFile = fs.getGitSystemConfig();
129 				if (configFile != null) {
130 					return new FileBasedConfig(parent, configFile, fs);
131 				}
132 			}
133 			return new FileBasedConfig(parent, null, fs) {
134 				@Override
135 				public void load() {
136 					// empty, do not load
137 				}
138 
139 				@Override
140 				public boolean isOutdated() {
141 					// regular class would bomb here
142 					return false;
143 				}
144 			};
145 		}
146 
147 		@Override
148 		public FileBasedConfig openUserConfig(Config parent, FS fs) {
149 			FileBasedConfig c = userConfig.get();
150 			if (c == null) {
151 				userConfig.compareAndSet(null, new FileBasedConfig(parent,
152 						new File(fs.userHome(), ".gitconfig"), fs)); //$NON-NLS-1$
153 				c = userConfig.get();
154 			}
155 			return c;
156 		}
157 
158 		@Override
159 		public StoredConfig getSystemConfig()
160 				throws IOException, ConfigInvalidException {
161 			FileBasedConfig c = openSystemConfig(null, FS.DETECTED);
162 			if (c.isOutdated()) {
163 				LOG.debug("loading system config {}", systemConfig); //$NON-NLS-1$
164 				c.load();
165 			}
166 			return c;
167 		}
168 
169 		@Override
170 		public StoredConfig getUserConfig()
171 				throws IOException, ConfigInvalidException {
172 			FileBasedConfig c = openUserConfig(getSystemConfig(), FS.DETECTED);
173 			if (c.isOutdated()) {
174 				LOG.debug("loading user config {}", userConfig); //$NON-NLS-1$
175 				c.load();
176 			}
177 			return c;
178 		}
179 
180 		@Override
181 		public String getHostname() {
182 			if (hostname == null) {
183 				try {
184 					InetAddress localMachine = InetAddress.getLocalHost();
185 					hostname = localMachine.getCanonicalHostName();
186 				} catch (UnknownHostException e) {
187 					// we do nothing
188 					hostname = "localhost"; //$NON-NLS-1$
189 				}
190 				assert hostname != null;
191 			}
192 			return hostname;
193 		}
194 
195 		@Override
196 		public long getCurrentTime() {
197 			return System.currentTimeMillis();
198 		}
199 
200 		@Override
201 		public int getTimezone(long when) {
202 			return getTimeZone().getOffset(when) / (60 * 1000);
203 		}
204 	}
205 
206 	private static volatile SystemReader INSTANCE = DEFAULT;
207 
208 	/**
209 	 * Get the current SystemReader instance
210 	 *
211 	 * @return the current SystemReader instance.
212 	 */
213 	public static SystemReader getInstance() {
214 		return INSTANCE;
215 	}
216 
217 	/**
218 	 * Set a new SystemReader instance to use when accessing properties.
219 	 *
220 	 * @param newReader
221 	 *            the new instance to use when accessing properties, or null for
222 	 *            the default instance.
223 	 */
224 	public static void setInstance(SystemReader newReader) {
225 		isMacOS = null;
226 		isWindows = null;
227 		if (newReader == null)
228 			INSTANCE = DEFAULT;
229 		else {
230 			newReader.init();
231 			INSTANCE = newReader;
232 		}
233 	}
234 
235 	private ObjectChecker platformChecker;
236 
237 	private void init() {
238 		// Creating ObjectChecker must be deferred. Unit tests change
239 		// behavior of is{Windows,MacOS} in constructor of subclass.
240 		if (platformChecker == null)
241 			setPlatformChecker();
242 	}
243 
244 	/**
245 	 * Should be used in tests when the platform is explicitly changed.
246 	 *
247 	 * @since 3.6
248 	 */
249 	protected final void setPlatformChecker() {
250 		platformChecker = new ObjectChecker()
251 			.setSafeForWindows(isWindows())
252 			.setSafeForMacOS(isMacOS());
253 	}
254 
255 	/**
256 	 * Gets the hostname of the local host. If no hostname can be found, the
257 	 * hostname is set to the default value "localhost".
258 	 *
259 	 * @return the canonical hostname
260 	 */
261 	public abstract String getHostname();
262 
263 	/**
264 	 * Get value of the system variable
265 	 *
266 	 * @param variable
267 	 *            system variable to read
268 	 * @return value of the system variable
269 	 */
270 	public abstract String getenv(String variable);
271 
272 	/**
273 	 * Get value of the system property
274 	 *
275 	 * @param key
276 	 *            of the system property to read
277 	 * @return value of the system property
278 	 */
279 	public abstract String getProperty(String key);
280 
281 	/**
282 	 * Open the git configuration found in the user home. Use
283 	 * {@link #getUserConfig()} to get the current git configuration in the user
284 	 * home since it manages automatic reloading when the gitconfig file was
285 	 * modified and avoids unnecessary reloads.
286 	 *
287 	 * @param parent
288 	 *            a config with values not found directly in the returned config
289 	 * @param fs
290 	 *            the file system abstraction which will be necessary to perform
291 	 *            certain file system operations.
292 	 * @return the git configuration found in the user home
293 	 */
294 	public abstract FileBasedConfig openUserConfig(Config parent, FS fs);
295 
296 	/**
297 	 * Open the gitconfig configuration found in the system-wide "etc"
298 	 * directory. Use {@link #getSystemConfig()} to get the current system-wide
299 	 * git configuration since it manages automatic reloading when the gitconfig
300 	 * file was modified and avoids unnecessary reloads.
301 	 *
302 	 * @param parent
303 	 *            a config with values not found directly in the returned
304 	 *            config. Null is a reasonable value here.
305 	 * @param fs
306 	 *            the file system abstraction which will be necessary to perform
307 	 *            certain file system operations.
308 	 * @return the gitconfig configuration found in the system-wide "etc"
309 	 *         directory
310 	 */
311 	public abstract FileBasedConfig openSystemConfig(Config parent, FS fs);
312 
313 	/**
314 	 * Get the git configuration found in the user home. The configuration will
315 	 * be reloaded automatically if the configuration file was modified. Also
316 	 * reloads the system config if the system config file was modified. If the
317 	 * configuration file wasn't modified returns the cached configuration.
318 	 *
319 	 * @return the git configuration found in the user home
320 	 * @throws ConfigInvalidException
321 	 *             if configuration is invalid
322 	 * @throws IOException
323 	 *             if something went wrong when reading files
324 	 * @since 5.1.9
325 	 */
326 	public abstract StoredConfig getUserConfig()
327 			throws IOException, ConfigInvalidException;
328 
329 	/**
330 	 * Get the gitconfig configuration found in the system-wide "etc" directory.
331 	 * The configuration will be reloaded automatically if the configuration
332 	 * file was modified otherwise returns the cached system level config.
333 	 *
334 	 * @return the gitconfig configuration found in the system-wide "etc"
335 	 *         directory
336 	 * @throws ConfigInvalidException
337 	 *             if configuration is invalid
338 	 * @throws IOException
339 	 *             if something went wrong when reading files
340 	 * @since 5.1.9
341 	 */
342 	public abstract StoredConfig getSystemConfig()
343 			throws IOException, ConfigInvalidException;
344 
345 	/**
346 	 * Get the current system time
347 	 *
348 	 * @return the current system time
349 	 */
350 	public abstract long getCurrentTime();
351 
352 	/**
353 	 * Get clock instance preferred by this system.
354 	 *
355 	 * @return clock instance preferred by this system.
356 	 * @since 4.6
357 	 */
358 	public MonotonicClock getClock() {
359 		return new MonotonicSystemClock();
360 	}
361 
362 	/**
363 	 * Get the local time zone
364 	 *
365 	 * @param when
366 	 *            a system timestamp
367 	 * @return the local time zone
368 	 */
369 	public abstract int getTimezone(long when);
370 
371 	/**
372 	 * Get system time zone, possibly mocked for testing
373 	 *
374 	 * @return system time zone, possibly mocked for testing
375 	 * @since 1.2
376 	 */
377 	public TimeZone getTimeZone() {
378 		return TimeZone.getDefault();
379 	}
380 
381 	/**
382 	 * Get the locale to use
383 	 *
384 	 * @return the locale to use
385 	 * @since 1.2
386 	 */
387 	public Locale getLocale() {
388 		return Locale.getDefault();
389 	}
390 
391 	/**
392 	 * Returns a simple date format instance as specified by the given pattern.
393 	 *
394 	 * @param pattern
395 	 *            the pattern as defined in
396 	 *            {@link java.text.SimpleDateFormat#SimpleDateFormat(String)}
397 	 * @return the simple date format
398 	 * @since 2.0
399 	 */
400 	public SimpleDateFormat getSimpleDateFormat(String pattern) {
401 		return new SimpleDateFormat(pattern);
402 	}
403 
404 	/**
405 	 * Returns a simple date format instance as specified by the given pattern.
406 	 *
407 	 * @param pattern
408 	 *            the pattern as defined in
409 	 *            {@link java.text.SimpleDateFormat#SimpleDateFormat(String)}
410 	 * @param locale
411 	 *            locale to be used for the {@code SimpleDateFormat}
412 	 * @return the simple date format
413 	 * @since 3.2
414 	 */
415 	public SimpleDateFormat getSimpleDateFormat(String pattern, Locale locale) {
416 		return new SimpleDateFormat(pattern, locale);
417 	}
418 
419 	/**
420 	 * Returns a date/time format instance for the given styles.
421 	 *
422 	 * @param dateStyle
423 	 *            the date style as specified in
424 	 *            {@link java.text.DateFormat#getDateTimeInstance(int, int)}
425 	 * @param timeStyle
426 	 *            the time style as specified in
427 	 *            {@link java.text.DateFormat#getDateTimeInstance(int, int)}
428 	 * @return the date format
429 	 * @since 2.0
430 	 */
431 	public DateFormat getDateTimeInstance(int dateStyle, int timeStyle) {
432 		return DateFormat.getDateTimeInstance(dateStyle, timeStyle);
433 	}
434 
435 	/**
436 	 * Whether we are running on Windows.
437 	 *
438 	 * @return true if we are running on Windows.
439 	 */
440 	public boolean isWindows() {
441 		if (isWindows == null) {
442 			String osDotName = getOsName();
443 			isWindows = Boolean.valueOf(osDotName.startsWith("Windows")); //$NON-NLS-1$
444 		}
445 		return isWindows.booleanValue();
446 	}
447 
448 	/**
449 	 * Whether we are running on Mac OS X
450 	 *
451 	 * @return true if we are running on Mac OS X
452 	 */
453 	public boolean isMacOS() {
454 		if (isMacOS == null) {
455 			String osDotName = getOsName();
456 			isMacOS = Boolean.valueOf(
457 					"Mac OS X".equals(osDotName) || "Darwin".equals(osDotName)); //$NON-NLS-1$ //$NON-NLS-2$
458 		}
459 		return isMacOS.booleanValue();
460 	}
461 
462 	private String getOsName() {
463 		return AccessController.doPrivileged(
464 				(PrivilegedAction<String>) () -> getProperty("os.name") //$NON-NLS-1$
465 		);
466 	}
467 
468 	/**
469 	 * Check tree path entry for validity.
470 	 * <p>
471 	 * Scans a multi-directory path string such as {@code "src/main.c"}.
472 	 *
473 	 * @param path path string to scan.
474 	 * @throws org.eclipse.jgit.errors.CorruptObjectException path is invalid.
475 	 * @since 3.6
476 	 */
477 	public void checkPath(String path) throws CorruptObjectException {
478 		platformChecker.checkPath(path);
479 	}
480 
481 	/**
482 	 * Check tree path entry for validity.
483 	 * <p>
484 	 * Scans a multi-directory path string such as {@code "src/main.c"}.
485 	 *
486 	 * @param path
487 	 *            path string to scan.
488 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
489 	 *             path is invalid.
490 	 * @since 4.2
491 	 */
492 	public void checkPath(byte[] path) throws CorruptObjectException {
493 		platformChecker.checkPath(path, 0, path.length);
494 	}
495 }