View Javadoc
1   /*
2    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3    * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
4    * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> and others
5    *
6    * This program and the accompanying materials are made available under the
7    * terms of the Eclipse Distribution License v. 1.0 which is available at
8    * https://www.eclipse.org/org/documents/edl-v10.php.
9    *
10   * SPDX-License-Identifier: BSD-3-Clause
11   */
12  
13  package org.eclipse.jgit.lib;
14  
15  import java.io.Serializable;
16  import java.text.SimpleDateFormat;
17  import java.time.Instant;
18  import java.time.ZoneId;
19  import java.util.Date;
20  import java.util.Locale;
21  import java.util.TimeZone;
22  
23  import org.eclipse.jgit.internal.JGitText;
24  import org.eclipse.jgit.util.SystemReader;
25  import org.eclipse.jgit.util.time.ProposedTimestamp;
26  
27  /**
28   * A combination of a person identity and time in Git.
29   *
30   * Git combines Name + email + time + time zone to specify who wrote or
31   * committed something.
32   */
33  public class PersonIdent implements Serializable {
34  	private static final long serialVersionUID = 1L;
35  
36  	/**
37  	 * Get timezone object for the given offset.
38  	 *
39  	 * @param tzOffset
40  	 *            timezone offset as in {@link #getTimeZoneOffset()}.
41  	 * @return time zone object for the given offset.
42  	 * @since 4.1
43  	 */
44  	public static TimeZone getTimeZone(int tzOffset) {
45  		StringBuilder tzId = new StringBuilder(8);
46  		tzId.append("GMT"); //$NON-NLS-1$
47  		appendTimezone(tzId, tzOffset);
48  		return TimeZone.getTimeZone(tzId.toString());
49  	}
50  
51  	/**
52  	 * Format a timezone offset.
53  	 *
54  	 * @param r
55  	 *            string builder to append to.
56  	 * @param offset
57  	 *            timezone offset as in {@link #getTimeZoneOffset()}.
58  	 * @since 4.1
59  	 */
60  	public static void appendTimezone(StringBuilder r, int offset) {
61  		final char sign;
62  		final int offsetHours;
63  		final int offsetMins;
64  
65  		if (offset < 0) {
66  			sign = '-';
67  			offset = -offset;
68  		} else {
69  			sign = '+';
70  		}
71  
72  		offsetHours = offset / 60;
73  		offsetMins = offset % 60;
74  
75  		r.append(sign);
76  		if (offsetHours < 10) {
77  			r.append('0');
78  		}
79  		r.append(offsetHours);
80  		if (offsetMins < 10) {
81  			r.append('0');
82  		}
83  		r.append(offsetMins);
84  	}
85  
86  	/**
87  	 * Sanitize the given string for use in an identity and append to output.
88  	 * <p>
89  	 * Trims whitespace from both ends and special characters {@code \n < >} that
90  	 * interfere with parsing; appends all other characters to the output.
91  	 * Analogous to the C git function {@code strbuf_addstr_without_crud}.
92  	 *
93  	 * @param r
94  	 *            string builder to append to.
95  	 * @param str
96  	 *            input string.
97  	 * @since 4.4
98  	 */
99  	public static void appendSanitized(StringBuilder r, String str) {
100 		// Trim any whitespace less than \u0020 as in String#trim().
101 		int i = 0;
102 		while (i < str.length() && str.charAt(i) <= ' ') {
103 			i++;
104 		}
105 		int end = str.length();
106 		while (end > i && str.charAt(end - 1) <= ' ') {
107 			end--;
108 		}
109 
110 		for (; i < end; i++) {
111 			char c = str.charAt(i);
112 			switch (c) {
113 				case '\n':
114 				case '<':
115 				case '>':
116 					continue;
117 				default:
118 					r.append(c);
119 					break;
120 			}
121 		}
122 	}
123 
124 	private final String name;
125 
126 	private final String emailAddress;
127 
128 	private final long when;
129 
130 	private final int tzOffset;
131 
132 	/**
133 	 * Creates new PersonIdent from config info in repository, with current time.
134 	 * This new PersonIdent gets the info from the default committer as available
135 	 * from the configuration.
136 	 *
137 	 * @param repo a {@link org.eclipse.jgit.lib.Repository} object.
138 	 */
139 	public PersonIdent(Repository repo) {
140 		this(repo.getConfig().get(UserConfig.KEY));
141 	}
142 
143 	/**
144 	 * Copy a {@link org.eclipse.jgit.lib.PersonIdent}.
145 	 *
146 	 * @param pi
147 	 *            Original {@link org.eclipse.jgit.lib.PersonIdent}
148 	 */
149 	public PersonIdent(PersonIdent pi) {
150 		this(pi.getName(), pi.getEmailAddress());
151 	}
152 
153 	/**
154 	 * Construct a new {@link org.eclipse.jgit.lib.PersonIdent} with current
155 	 * time.
156 	 *
157 	 * @param aName
158 	 *            a {@link java.lang.String} object.
159 	 * @param aEmailAddress
160 	 *            a {@link java.lang.String} object.
161 	 */
162 	public PersonIdent(String aName, String aEmailAddress) {
163 		this(aName, aEmailAddress, SystemReader.getInstance().getCurrentTime());
164 	}
165 
166 	/**
167 	 * Construct a new {@link org.eclipse.jgit.lib.PersonIdent} with current
168 	 * time.
169 	 *
170 	 * @param aName
171 	 *            a {@link java.lang.String} object.
172 	 * @param aEmailAddress
173 	 *            a {@link java.lang.String} object.
174 	 * @param when
175 	 *            a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object.
176 	 * @since 4.6
177 	 */
178 	public PersonIdent(String aName, String aEmailAddress,
179 			ProposedTimestamp when) {
180 		this(aName, aEmailAddress, when.millis());
181 	}
182 
183 	/**
184 	 * Copy a PersonIdent, but alter the clone's time stamp
185 	 *
186 	 * @param pi
187 	 *            original {@link org.eclipse.jgit.lib.PersonIdent}
188 	 * @param when
189 	 *            local time
190 	 * @param tz
191 	 *            time zone
192 	 */
193 	public PersonIdent(PersonIdent pi, Date when, TimeZone tz) {
194 		this(pi.getName(), pi.getEmailAddress(), when, tz);
195 	}
196 
197 	/**
198 	 * Copy a {@link org.eclipse.jgit.lib.PersonIdent}, but alter the clone's
199 	 * time stamp
200 	 *
201 	 * @param pi
202 	 *            original {@link org.eclipse.jgit.lib.PersonIdent}
203 	 * @param aWhen
204 	 *            local time
205 	 */
206 	public PersonIdent(PersonIdent pi, Date aWhen) {
207 		this(pi.getName(), pi.getEmailAddress(), aWhen.getTime(), pi.tzOffset);
208 	}
209 
210 	/**
211 	 * Copy a {@link org.eclipse.jgit.lib.PersonIdent}, but alter the clone's
212 	 * time stamp
213 	 *
214 	 * @param pi
215 	 *            original {@link org.eclipse.jgit.lib.PersonIdent}
216 	 * @param aWhen
217 	 *            local time as Instant
218 	 * @since 6.1
219 	 */
220 	public PersonIdent(PersonIdent pi, Instant aWhen) {
221 		this(pi.getName(), pi.getEmailAddress(), aWhen.toEpochMilli(), pi.tzOffset);
222 	}
223 
224 	/**
225 	 * Construct a PersonIdent from simple data
226 	 *
227 	 * @param aName a {@link java.lang.String} object.
228 	 * @param aEmailAddress a {@link java.lang.String} object.
229 	 * @param aWhen
230 	 *            local time stamp
231 	 * @param aTZ
232 	 *            time zone
233 	 */
234 	public PersonIdent(final String aName, final String aEmailAddress,
235 			final Date aWhen, final TimeZone aTZ) {
236 		this(aName, aEmailAddress, aWhen.getTime(), aTZ.getOffset(aWhen
237 				.getTime()) / (60 * 1000));
238 	}
239 
240 	/**
241 	 * Construct a PersonIdent from simple data
242 	 *
243 	 * @param aName
244 	 *            a {@link java.lang.String} object.
245 	 * @param aEmailAddress
246 	 *            a {@link java.lang.String} object.
247 	 * @param aWhen
248 	 *            local time stamp
249 	 * @param zoneId
250 	 *            time zone id
251 	 * @since 6.1
252 	 */
253 	public PersonIdent(final String aName, String aEmailAddress, Instant aWhen,
254 			ZoneId zoneId) {
255 		this(aName, aEmailAddress, aWhen.toEpochMilli(),
256 				TimeZone.getTimeZone(zoneId)
257 						.getOffset(aWhen
258 				.toEpochMilli()) / (60 * 1000));
259 	}
260 
261 	/**
262 	 * Copy a PersonIdent, but alter the clone's time stamp
263 	 *
264 	 * @param pi
265 	 *            original {@link org.eclipse.jgit.lib.PersonIdent}
266 	 * @param aWhen
267 	 *            local time stamp
268 	 * @param aTZ
269 	 *            time zone
270 	 */
271 	public PersonIdent(PersonIdent pi, long aWhen, int aTZ) {
272 		this(pi.getName(), pi.getEmailAddress(), aWhen, aTZ);
273 	}
274 
275 	private PersonIdent(final String aName, final String aEmailAddress,
276 			long when) {
277 		this(aName, aEmailAddress, when, SystemReader.getInstance()
278 				.getTimezone(when));
279 	}
280 
281 	private PersonIdent(UserConfig config) {
282 		this(config.getCommitterName(), config.getCommitterEmail());
283 	}
284 
285 	/**
286 	 * Construct a {@link org.eclipse.jgit.lib.PersonIdent}.
287 	 * <p>
288 	 * Whitespace in the name and email is preserved for the lifetime of this
289 	 * object, but are trimmed by {@link #toExternalString()}. This means that
290 	 * parsing the result of {@link #toExternalString()} may not return an
291 	 * equivalent instance.
292 	 *
293 	 * @param aName
294 	 *            a {@link java.lang.String} object.
295 	 * @param aEmailAddress
296 	 *            a {@link java.lang.String} object.
297 	 * @param aWhen
298 	 *            local time stamp
299 	 * @param aTZ
300 	 *            time zone
301 	 */
302 	public PersonIdent(final String aName, final String aEmailAddress,
303 			final long aWhen, final int aTZ) {
304 		if (aName == null)
305 			throw new IllegalArgumentException(
306 					JGitText.get().personIdentNameNonNull);
307 		if (aEmailAddress == null)
308 			throw new IllegalArgumentException(
309 					JGitText.get().personIdentEmailNonNull);
310 		name = aName;
311 		emailAddress = aEmailAddress;
312 		when = aWhen;
313 		tzOffset = aTZ;
314 	}
315 
316 	/**
317 	 * Get name of person
318 	 *
319 	 * @return Name of person
320 	 */
321 	public String getName() {
322 		return name;
323 	}
324 
325 	/**
326 	 * Get email address of person
327 	 *
328 	 * @return email address of person
329 	 */
330 	public String getEmailAddress() {
331 		return emailAddress;
332 	}
333 
334 	/**
335 	 * Get timestamp
336 	 *
337 	 * @return timestamp
338 	 */
339 	public Date getWhen() {
340 		return new Date(when);
341 	}
342 
343 	/**
344 	 * Get when attribute as instant
345 	 *
346 	 * @return timestamp
347 	 * @since 6.1
348 	 */
349 	public Instant getWhenAsInstant() {
350 		return Instant.ofEpochMilli(when);
351 	}
352 
353 	/**
354 	 * Get this person's declared time zone
355 	 *
356 	 * @return this person's declared time zone; null if time zone is unknown.
357 	 */
358 	public TimeZone getTimeZone() {
359 		return getTimeZone(tzOffset);
360 	}
361 
362 	/**
363 	 * Get the time zone id
364 	 *
365 	 * @return the time zone id
366 	 * @since 6.1
367 	 */
368 	public ZoneId getZoneId() {
369 		return getTimeZone().toZoneId();
370 	}
371 
372 	/**
373 	 * Get this person's declared time zone as minutes east of UTC.
374 	 *
375 	 * @return this person's declared time zone as minutes east of UTC. If the
376 	 *         timezone is to the west of UTC it is negative.
377 	 */
378 	public int getTimeZoneOffset() {
379 		return tzOffset;
380 	}
381 
382 	/**
383 	 * {@inheritDoc}
384 	 * <p>
385 	 * Hashcode is based only on the email address and timestamp.
386 	 */
387 	@Override
388 	public int hashCode() {
389 		int hc = getEmailAddress().hashCode();
390 		hc *= 31;
391 		hc += (int) (when / 1000L);
392 		return hc;
393 	}
394 
395 	/** {@inheritDoc} */
396 	@Override
397 	public boolean equals(Object o) {
398 		if (o instanceof PersonIdent) {
399 			final PersonIdent p = (PersonIdent) o;
400 			return getName().equals(p.getName())
401 					&& getEmailAddress().equals(p.getEmailAddress())
402 					&& when / 1000L == p.when / 1000L;
403 		}
404 		return false;
405 	}
406 
407 	/**
408 	 * Format for Git storage.
409 	 *
410 	 * @return a string in the git author format
411 	 */
412 	public String toExternalString() {
413 		final StringBuilder r = new StringBuilder();
414 		appendSanitized(r, getName());
415 		r.append(" <"); //$NON-NLS-1$
416 		appendSanitized(r, getEmailAddress());
417 		r.append("> "); //$NON-NLS-1$
418 		r.append(when / 1000);
419 		r.append(' ');
420 		appendTimezone(r, tzOffset);
421 		return r.toString();
422 	}
423 
424 	/** {@inheritDoc} */
425 	@Override
426 	@SuppressWarnings("nls")
427 	public String toString() {
428 		final StringBuilder r = new StringBuilder();
429 		final SimpleDateFormat dtfmt;
430 		dtfmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US);
431 		dtfmt.setTimeZone(getTimeZone());
432 
433 		r.append("PersonIdent[");
434 		r.append(getName());
435 		r.append(", ");
436 		r.append(getEmailAddress());
437 		r.append(", ");
438 		r.append(dtfmt.format(Long.valueOf(when)));
439 		r.append("]");
440 
441 		return r.toString();
442 	}
443 }
444