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>
5 * and other copyright owners as documented in the project's IP log.
6 *
7 * This program and the accompanying materials are made available
8 * under the terms of the Eclipse Distribution License v1.0 which
9 * accompanies this distribution, is reproduced below, and is
10 * available at http://www.eclipse.org/org/documents/edl-v10.php
11 *
12 * All rights reserved.
13 *
14 * Redistribution and use in source and binary forms, with or
15 * without modification, are permitted provided that the following
16 * conditions are met:
17 *
18 * - Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 *
21 * - Redistributions in binary form must reproduce the above
22 * copyright notice, this list of conditions and the following
23 * disclaimer in the documentation and/or other materials provided
24 * with the distribution.
25 *
26 * - Neither the name of the Eclipse Foundation, Inc. nor the
27 * names of its contributors may be used to endorse or promote
28 * products derived from this software without specific prior
29 * written permission.
30 *
31 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
32 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
33 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
36 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
38 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
40 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
43 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44 */
45
46 package org.eclipse.jgit.lib;
47
48 import java.io.Serializable;
49 import java.text.SimpleDateFormat;
50 import java.util.Date;
51 import java.util.Locale;
52 import java.util.TimeZone;
53
54 import org.eclipse.jgit.internal.JGitText;
55 import org.eclipse.jgit.util.SystemReader;
56 import org.eclipse.jgit.util.time.ProposedTimestamp;
57
58 /**
59 * A combination of a person identity and time in Git.
60 *
61 * Git combines Name + email + time + time zone to specify who wrote or
62 * committed something.
63 */
64 public class PersonIdent implements Serializable {
65 private static final long serialVersionUID = 1L;
66
67 /**
68 * Get timezone object for the given offset.
69 *
70 * @param tzOffset
71 * timezone offset as in {@link #getTimeZoneOffset()}.
72 * @return time zone object for the given offset.
73 * @since 4.1
74 */
75 public static TimeZone getTimeZone(int tzOffset) {
76 StringBuilder tzId = new StringBuilder(8);
77 tzId.append("GMT"); //$NON-NLS-1$
78 appendTimezone(tzId, tzOffset);
79 return TimeZone.getTimeZone(tzId.toString());
80 }
81
82 /**
83 * Format a timezone offset.
84 *
85 * @param r
86 * string builder to append to.
87 * @param offset
88 * timezone offset as in {@link #getTimeZoneOffset()}.
89 * @since 4.1
90 */
91 public static void appendTimezone(StringBuilder r, int offset) {
92 final char sign;
93 final int offsetHours;
94 final int offsetMins;
95
96 if (offset < 0) {
97 sign = '-';
98 offset = -offset;
99 } else {
100 sign = '+';
101 }
102
103 offsetHours = offset / 60;
104 offsetMins = offset % 60;
105
106 r.append(sign);
107 if (offsetHours < 10) {
108 r.append('0');
109 }
110 r.append(offsetHours);
111 if (offsetMins < 10) {
112 r.append('0');
113 }
114 r.append(offsetMins);
115 }
116
117 /**
118 * Sanitize the given string for use in an identity and append to output.
119 * <p>
120 * Trims whitespace from both ends and special characters {@code \n < >} that
121 * interfere with parsing; appends all other characters to the output.
122 * Analogous to the C git function {@code strbuf_addstr_without_crud}.
123 *
124 * @param r
125 * string builder to append to.
126 * @param str
127 * input string.
128 * @since 4.4
129 */
130 public static void appendSanitized(StringBuilder r, String str) {
131 // Trim any whitespace less than \u0020 as in String#trim().
132 int i = 0;
133 while (i < str.length() && str.charAt(i) <= ' ') {
134 i++;
135 }
136 int end = str.length();
137 while (end > i && str.charAt(end - 1) <= ' ') {
138 end--;
139 }
140
141 for (; i < end; i++) {
142 char c = str.charAt(i);
143 switch (c) {
144 case '\n':
145 case '<':
146 case '>':
147 continue;
148 default:
149 r.append(c);
150 break;
151 }
152 }
153 }
154
155 private final String name;
156
157 private final String emailAddress;
158
159 private final long when;
160
161 private final int tzOffset;
162
163 /**
164 * Creates new PersonIdent from config info in repository, with current time.
165 * This new PersonIdent gets the info from the default committer as available
166 * from the configuration.
167 *
168 * @param repo a {@link org.eclipse.jgit.lib.Repository} object.
169 */
170 public PersonIdent(Repository repo) {
171 this(repo.getConfig().get(UserConfig.KEY));
172 }
173
174 /**
175 * Copy a {@link org.eclipse.jgit.lib.PersonIdent}.
176 *
177 * @param pi
178 * Original {@link org.eclipse.jgit.lib.PersonIdent}
179 */
180 public PersonIdentref="../../../../org/eclipse/jgit/lib/PersonIdent.html#PersonIdent">PersonIdent(PersonIdent pi) {
181 this(pi.getName(), pi.getEmailAddress());
182 }
183
184 /**
185 * Construct a new {@link org.eclipse.jgit.lib.PersonIdent} with current
186 * time.
187 *
188 * @param aName
189 * a {@link java.lang.String} object.
190 * @param aEmailAddress
191 * a {@link java.lang.String} object.
192 */
193 public PersonIdent(String aName, String aEmailAddress) {
194 this(aName, aEmailAddress, SystemReader.getInstance().getCurrentTime());
195 }
196
197 /**
198 * Construct a new {@link org.eclipse.jgit.lib.PersonIdent} with current
199 * time.
200 *
201 * @param aName
202 * a {@link java.lang.String} object.
203 * @param aEmailAddress
204 * a {@link java.lang.String} object.
205 * @param when
206 * a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object.
207 * @since 4.6
208 */
209 public PersonIdent(String aName, String aEmailAddress,
210 ProposedTimestamp when) {
211 this(aName, aEmailAddress, when.millis());
212 }
213
214 /**
215 * Copy a PersonIdent, but alter the clone's time stamp
216 *
217 * @param pi
218 * original {@link org.eclipse.jgit.lib.PersonIdent}
219 * @param when
220 * local time
221 * @param tz
222 * time zone
223 */
224 public PersonIdentref="../../../../org/eclipse/jgit/lib/PersonIdent.html#PersonIdent">PersonIdent(PersonIdent pi, Date when, TimeZone tz) {
225 this(pi.getName(), pi.getEmailAddress(), when, tz);
226 }
227
228 /**
229 * Copy a {@link org.eclipse.jgit.lib.PersonIdent}, but alter the clone's
230 * time stamp
231 *
232 * @param pi
233 * original {@link org.eclipse.jgit.lib.PersonIdent}
234 * @param aWhen
235 * local time
236 */
237 public PersonIdentref="../../../../org/eclipse/jgit/lib/PersonIdent.html#PersonIdent">PersonIdent(PersonIdent pi, Date aWhen) {
238 this(pi.getName(), pi.getEmailAddress(), aWhen.getTime(), pi.tzOffset);
239 }
240
241 /**
242 * Construct a PersonIdent from simple data
243 *
244 * @param aName a {@link java.lang.String} object.
245 * @param aEmailAddress a {@link java.lang.String} object.
246 * @param aWhen
247 * local time stamp
248 * @param aTZ
249 * time zone
250 */
251 public PersonIdent(final String aName, final String aEmailAddress,
252 final Date aWhen, final TimeZone aTZ) {
253 this(aName, aEmailAddress, aWhen.getTime(), aTZ.getOffset(aWhen
254 .getTime()) / (60 * 1000));
255 }
256
257 /**
258 * Copy a PersonIdent, but alter the clone's time stamp
259 *
260 * @param pi
261 * original {@link org.eclipse.jgit.lib.PersonIdent}
262 * @param aWhen
263 * local time stamp
264 * @param aTZ
265 * time zone
266 */
267 public PersonIdentref="../../../../org/eclipse/jgit/lib/PersonIdent.html#PersonIdent">PersonIdent(PersonIdent pi, long aWhen, int aTZ) {
268 this(pi.getName(), pi.getEmailAddress(), aWhen, aTZ);
269 }
270
271 private PersonIdent(final String aName, final String aEmailAddress,
272 long when) {
273 this(aName, aEmailAddress, when, SystemReader.getInstance()
274 .getTimezone(when));
275 }
276
277 private PersonIdent(UserConfig config) {
278 this(config.getCommitterName(), config.getCommitterEmail());
279 }
280
281 /**
282 * Construct a {@link org.eclipse.jgit.lib.PersonIdent}.
283 * <p>
284 * Whitespace in the name and email is preserved for the lifetime of this
285 * object, but are trimmed by {@link #toExternalString()}. This means that
286 * parsing the result of {@link #toExternalString()} may not return an
287 * equivalent instance.
288 *
289 * @param aName
290 * a {@link java.lang.String} object.
291 * @param aEmailAddress
292 * a {@link java.lang.String} object.
293 * @param aWhen
294 * local time stamp
295 * @param aTZ
296 * time zone
297 */
298 public PersonIdent(final String aName, final String aEmailAddress,
299 final long aWhen, final int aTZ) {
300 if (aName == null)
301 throw new IllegalArgumentException(
302 JGitText.get().personIdentNameNonNull);
303 if (aEmailAddress == null)
304 throw new IllegalArgumentException(
305 JGitText.get().personIdentEmailNonNull);
306 name = aName;
307 emailAddress = aEmailAddress;
308 when = aWhen;
309 tzOffset = aTZ;
310 }
311
312 /**
313 * Get name of person
314 *
315 * @return Name of person
316 */
317 public String getName() {
318 return name;
319 }
320
321 /**
322 * Get email address of person
323 *
324 * @return email address of person
325 */
326 public String getEmailAddress() {
327 return emailAddress;
328 }
329
330 /**
331 * Get timestamp
332 *
333 * @return timestamp
334 */
335 public Date getWhen() {
336 return new Date(when);
337 }
338
339 /**
340 * Get this person's declared time zone
341 *
342 * @return this person's declared time zone; null if time zone is unknown.
343 */
344 public TimeZone getTimeZone() {
345 return getTimeZone(tzOffset);
346 }
347
348 /**
349 * Get this person's declared time zone as minutes east of UTC.
350 *
351 * @return this person's declared time zone as minutes east of UTC. If the
352 * timezone is to the west of UTC it is negative.
353 */
354 public int getTimeZoneOffset() {
355 return tzOffset;
356 }
357
358 /**
359 * {@inheritDoc}
360 * <p>
361 * Hashcode is based only on the email address and timestamp.
362 */
363 @Override
364 public int hashCode() {
365 int hc = getEmailAddress().hashCode();
366 hc *= 31;
367 hc += (int) (when / 1000L);
368 return hc;
369 }
370
371 /** {@inheritDoc} */
372 @Override
373 public boolean equals(Object o) {
374 if (o instanceof PersonIdent) {
375 final PersonIdent../../../../org/eclipse/jgit/lib/PersonIdent.html#PersonIdent">PersonIdent p = (PersonIdent) o;
376 return getName().equals(p.getName())
377 && getEmailAddress().equals(p.getEmailAddress())
378 && when / 1000L == p.when / 1000L;
379 }
380 return false;
381 }
382
383 /**
384 * Format for Git storage.
385 *
386 * @return a string in the git author format
387 */
388 public String toExternalString() {
389 final StringBuilder r = new StringBuilder();
390 appendSanitized(r, getName());
391 r.append(" <"); //$NON-NLS-1$
392 appendSanitized(r, getEmailAddress());
393 r.append("> "); //$NON-NLS-1$
394 r.append(when / 1000);
395 r.append(' ');
396 appendTimezone(r, tzOffset);
397 return r.toString();
398 }
399
400 /** {@inheritDoc} */
401 @Override
402 @SuppressWarnings("nls")
403 public String toString() {
404 final StringBuilder r = new StringBuilder();
405 final SimpleDateFormat dtfmt;
406 dtfmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US);
407 dtfmt.setTimeZone(getTimeZone());
408
409 r.append("PersonIdent[");
410 r.append(getName());
411 r.append(", ");
412 r.append(getEmailAddress());
413 r.append(", ");
414 r.append(dtfmt.format(Long.valueOf(when)));
415 r.append("]");
416
417 return r.toString();
418 }
419 }
420