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