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 		@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 }