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.net.InetAddress;
51 import java.net.UnknownHostException;
52 import java.security.AccessController;
53 import java.security.PrivilegedAction;
54 import java.text.DateFormat;
55 import java.text.SimpleDateFormat;
56 import java.util.Locale;
57 import java.util.TimeZone;
58
59 import org.eclipse.jgit.errors.CorruptObjectException;
60 import org.eclipse.jgit.lib.Config;
61 import org.eclipse.jgit.lib.ObjectChecker;
62 import org.eclipse.jgit.storage.file.FileBasedConfig;
63 import org.eclipse.jgit.util.time.MonotonicClock;
64 import org.eclipse.jgit.util.time.MonotonicSystemClock;
65
66 /**
67 * Interface to read values from the system.
68 * <p>
69 * When writing unit tests, extending this interface with a custom class
70 * permits to simulate an access to a system variable or property and
71 * permits to control the user's global configuration.
72 * </p>
73 */
74 public abstract class SystemReader {
75 private static final SystemReader DEFAULT;
76
77 private static Boolean isMacOS;
78
79 private static Boolean isWindows;
80
81 static {
82 SystemReader r = new Default();
83 r.init();
84 DEFAULT = r;
85 }
86
87 private static class Default extends SystemReader {
88 private volatile String hostname;
89
90 @Override
91 public String getenv(String variable) {
92 return System.getenv(variable);
93 }
94
95 @Override
96 public String getProperty(String key) {
97 return System.getProperty(key);
98 }
99
100 @Override
101 public FileBasedConfig openSystemConfig(Config parent, FS fs) {
102 File configFile = fs.getGitSystemConfig();
103 if (configFile == null) {
104 return new FileBasedConfig(null, fs) {
105 @Override
106 public void load() {
107 // empty, do not load
108 }
109
110 @Override
111 public boolean isOutdated() {
112 // regular class would bomb here
113 return false;
114 }
115 };
116 }
117 return new FileBasedConfig(parent, configFile, fs);
118 }
119
120 @Override
121 public FileBasedConfig openUserConfig(Config parent, FS fs) {
122 final File home = fs.userHome();
123 return new FileBasedConfig(parent, new File(home, ".gitconfig"), fs); //$NON-NLS-1$
124 }
125
126 @Override
127 public String getHostname() {
128 if (hostname == null) {
129 try {
130 InetAddress localMachine = InetAddress.getLocalHost();
131 hostname = localMachine.getCanonicalHostName();
132 } catch (UnknownHostException e) {
133 // we do nothing
134 hostname = "localhost"; //$NON-NLS-1$
135 }
136 assert hostname != null;
137 }
138 return hostname;
139 }
140
141 @Override
142 public long getCurrentTime() {
143 return System.currentTimeMillis();
144 }
145
146 @Override
147 public int getTimezone(long when) {
148 return getTimeZone().getOffset(when) / (60 * 1000);
149 }
150 }
151
152 private static SystemReader INSTANCE = DEFAULT;
153
154 /**
155 * Get time since epoch, with up to millisecond resolution.
156 *
157 * @return time since epoch, with up to millisecond resolution.
158 */
159 public static SystemReader getInstance() {
160 return INSTANCE;
161 }
162
163 /**
164 * Set the new instance to use when accessing properties.
165 *
166 * @param newReader
167 * the new instance to use when accessing properties, or null for
168 * the default instance.
169 */
170 public static void setInstance(SystemReader newReader) {
171 isMacOS = null;
172 isWindows = null;
173 if (newReader == null)
174 INSTANCE = DEFAULT;
175 else {
176 newReader.init();
177 INSTANCE = newReader;
178 }
179 }
180
181 private ObjectChecker platformChecker;
182
183 private void init() {
184 // Creating ObjectChecker must be deferred. Unit tests change
185 // behavior of is{Windows,MacOS} in constructor of subclass.
186 if (platformChecker == null)
187 setPlatformChecker();
188 }
189
190 /**
191 * Should be used in tests when the platform is explicitly changed.
192 *
193 * @since 3.6
194 */
195 protected final void setPlatformChecker() {
196 platformChecker = new ObjectChecker()
197 .setSafeForWindows(isWindows())
198 .setSafeForMacOS(isMacOS());
199 }
200
201 /**
202 * Gets the hostname of the local host. If no hostname can be found, the
203 * hostname is set to the default value "localhost".
204 *
205 * @return the canonical hostname
206 */
207 public abstract String getHostname();
208
209 /**
210 * Get value of the system variable
211 *
212 * @param variable
213 * system variable to read
214 * @return value of the system variable
215 */
216 public abstract String getenv(String variable);
217
218 /**
219 * Get value of the system property
220 *
221 * @param key
222 * of the system property to read
223 * @return value of the system property
224 */
225 public abstract String getProperty(String key);
226
227 /**
228 * Open the git configuration found in the user home
229 *
230 * @param parent
231 * a config with values not found directly in the returned config
232 * @param fs
233 * the file system abstraction which will be necessary to perform
234 * certain file system operations.
235 * @return the git configuration found in the user home
236 */
237 public abstract FileBasedConfig openUserConfig(Config parent, FS fs);
238
239 /**
240 * Open the gitonfig configuration found in the system-wide "etc" directory
241 *
242 * @param parent
243 * a config with values not found directly in the returned
244 * config. Null is a reasonable value here.
245 * @param fs
246 * the file system abstraction which will be necessary to perform
247 * certain file system operations.
248 * @return the gitonfig configuration found in the system-wide "etc"
249 * directory
250 */
251 public abstract FileBasedConfig openSystemConfig(Config parent, FS fs);
252
253 /**
254 * Get the current system time
255 *
256 * @return the current system time
257 */
258 public abstract long getCurrentTime();
259
260 /**
261 * Get clock instance preferred by this system.
262 *
263 * @return clock instance preferred by this system.
264 * @since 4.6
265 */
266 public MonotonicClock getClock() {
267 return new MonotonicSystemClock();
268 }
269
270 /**
271 * Get the local time zone
272 *
273 * @param when
274 * a system timestamp
275 * @return the local time zone
276 */
277 public abstract int getTimezone(long when);
278
279 /**
280 * Get system time zone, possibly mocked for testing
281 *
282 * @return system time zone, possibly mocked for testing
283 * @since 1.2
284 */
285 public TimeZone getTimeZone() {
286 return TimeZone.getDefault();
287 }
288
289 /**
290 * Get the locale to use
291 *
292 * @return the locale to use
293 * @since 1.2
294 */
295 public Locale getLocale() {
296 return Locale.getDefault();
297 }
298
299 /**
300 * Returns a simple date format instance as specified by the given pattern.
301 *
302 * @param pattern
303 * the pattern as defined in
304 * {@link java.text.SimpleDateFormat#SimpleDateFormat(String)}
305 * @return the simple date format
306 * @since 2.0
307 */
308 public SimpleDateFormat getSimpleDateFormat(String pattern) {
309 return new SimpleDateFormat(pattern);
310 }
311
312 /**
313 * Returns a simple date format instance as specified by the given pattern.
314 *
315 * @param pattern
316 * the pattern as defined in
317 * {@link java.text.SimpleDateFormat#SimpleDateFormat(String)}
318 * @param locale
319 * locale to be used for the {@code SimpleDateFormat}
320 * @return the simple date format
321 * @since 3.2
322 */
323 public SimpleDateFormat getSimpleDateFormat(String pattern, Locale locale) {
324 return new SimpleDateFormat(pattern, locale);
325 }
326
327 /**
328 * Returns a date/time format instance for the given styles.
329 *
330 * @param dateStyle
331 * the date style as specified in
332 * {@link java.text.DateFormat#getDateTimeInstance(int, int)}
333 * @param timeStyle
334 * the time style as specified in
335 * {@link java.text.DateFormat#getDateTimeInstance(int, int)}
336 * @return the date format
337 * @since 2.0
338 */
339 public DateFormat getDateTimeInstance(int dateStyle, int timeStyle) {
340 return DateFormat.getDateTimeInstance(dateStyle, timeStyle);
341 }
342
343 /**
344 * Whether we are running on Windows.
345 *
346 * @return true if we are running on Windows.
347 */
348 public boolean isWindows() {
349 if (isWindows == null) {
350 String osDotName = getOsName();
351 isWindows = Boolean.valueOf(osDotName.startsWith("Windows")); //$NON-NLS-1$
352 }
353 return isWindows.booleanValue();
354 }
355
356 /**
357 * Whether we are running on Mac OS X
358 *
359 * @return true if we are running on Mac OS X
360 */
361 public boolean isMacOS() {
362 if (isMacOS == null) {
363 String osDotName = getOsName();
364 isMacOS = Boolean.valueOf(
365 "Mac OS X".equals(osDotName) || "Darwin".equals(osDotName)); //$NON-NLS-1$ //$NON-NLS-2$
366 }
367 return isMacOS.booleanValue();
368 }
369
370 private String getOsName() {
371 return AccessController.doPrivileged(new PrivilegedAction<String>() {
372 @Override
373 public String run() {
374 return getProperty("os.name"); //$NON-NLS-1$
375 }
376 });
377 }
378
379 /**
380 * Check tree path entry for validity.
381 * <p>
382 * Scans a multi-directory path string such as {@code "src/main.c"}.
383 *
384 * @param path path string to scan.
385 * @throws org.eclipse.jgit.errors.CorruptObjectException path is invalid.
386 * @since 3.6
387 */
388 public void checkPath(String path) throws CorruptObjectException {
389 platformChecker.checkPath(path);
390 }
391
392 /**
393 * Check tree path entry for validity.
394 * <p>
395 * Scans a multi-directory path string such as {@code "src/main.c"}.
396 *
397 * @param path
398 * path string to scan.
399 * @throws org.eclipse.jgit.errors.CorruptObjectException
400 * path is invalid.
401 * @since 4.2
402 */
403 public void checkPath(byte[] path) throws CorruptObjectException {
404 platformChecker.checkPath(path, 0, path.length);
405 }
406 }