PersonIdent.java

  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. package org.eclipse.jgit.lib;

  13. import java.io.Serializable;
  14. import java.text.SimpleDateFormat;
  15. import java.util.Date;
  16. import java.util.Locale;
  17. import java.util.TimeZone;

  18. import org.eclipse.jgit.internal.JGitText;
  19. import org.eclipse.jgit.util.SystemReader;
  20. import org.eclipse.jgit.util.time.ProposedTimestamp;

  21. /**
  22.  * A combination of a person identity and time in Git.
  23.  *
  24.  * Git combines Name + email + time + time zone to specify who wrote or
  25.  * committed something.
  26.  */
  27. public class PersonIdent implements Serializable {
  28.     private static final long serialVersionUID = 1L;

  29.     /**
  30.      * Get timezone object for the given offset.
  31.      *
  32.      * @param tzOffset
  33.      *            timezone offset as in {@link #getTimeZoneOffset()}.
  34.      * @return time zone object for the given offset.
  35.      * @since 4.1
  36.      */
  37.     public static TimeZone getTimeZone(int tzOffset) {
  38.         StringBuilder tzId = new StringBuilder(8);
  39.         tzId.append("GMT"); //$NON-NLS-1$
  40.         appendTimezone(tzId, tzOffset);
  41.         return TimeZone.getTimeZone(tzId.toString());
  42.     }

  43.     /**
  44.      * Format a timezone offset.
  45.      *
  46.      * @param r
  47.      *            string builder to append to.
  48.      * @param offset
  49.      *            timezone offset as in {@link #getTimeZoneOffset()}.
  50.      * @since 4.1
  51.      */
  52.     public static void appendTimezone(StringBuilder r, int offset) {
  53.         final char sign;
  54.         final int offsetHours;
  55.         final int offsetMins;

  56.         if (offset < 0) {
  57.             sign = '-';
  58.             offset = -offset;
  59.         } else {
  60.             sign = '+';
  61.         }

  62.         offsetHours = offset / 60;
  63.         offsetMins = offset % 60;

  64.         r.append(sign);
  65.         if (offsetHours < 10) {
  66.             r.append('0');
  67.         }
  68.         r.append(offsetHours);
  69.         if (offsetMins < 10) {
  70.             r.append('0');
  71.         }
  72.         r.append(offsetMins);
  73.     }

  74.     /**
  75.      * Sanitize the given string for use in an identity and append to output.
  76.      * <p>
  77.      * Trims whitespace from both ends and special characters {@code \n < >} that
  78.      * interfere with parsing; appends all other characters to the output.
  79.      * Analogous to the C git function {@code strbuf_addstr_without_crud}.
  80.      *
  81.      * @param r
  82.      *            string builder to append to.
  83.      * @param str
  84.      *            input string.
  85.      * @since 4.4
  86.      */
  87.     public static void appendSanitized(StringBuilder r, String str) {
  88.         // Trim any whitespace less than \u0020 as in String#trim().
  89.         int i = 0;
  90.         while (i < str.length() && str.charAt(i) <= ' ') {
  91.             i++;
  92.         }
  93.         int end = str.length();
  94.         while (end > i && str.charAt(end - 1) <= ' ') {
  95.             end--;
  96.         }

  97.         for (; i < end; i++) {
  98.             char c = str.charAt(i);
  99.             switch (c) {
  100.                 case '\n':
  101.                 case '<':
  102.                 case '>':
  103.                     continue;
  104.                 default:
  105.                     r.append(c);
  106.                     break;
  107.             }
  108.         }
  109.     }

  110.     private final String name;

  111.     private final String emailAddress;

  112.     private final long when;

  113.     private final int tzOffset;

  114.     /**
  115.      * Creates new PersonIdent from config info in repository, with current time.
  116.      * This new PersonIdent gets the info from the default committer as available
  117.      * from the configuration.
  118.      *
  119.      * @param repo a {@link org.eclipse.jgit.lib.Repository} object.
  120.      */
  121.     public PersonIdent(Repository repo) {
  122.         this(repo.getConfig().get(UserConfig.KEY));
  123.     }

  124.     /**
  125.      * Copy a {@link org.eclipse.jgit.lib.PersonIdent}.
  126.      *
  127.      * @param pi
  128.      *            Original {@link org.eclipse.jgit.lib.PersonIdent}
  129.      */
  130.     public PersonIdent(PersonIdent pi) {
  131.         this(pi.getName(), pi.getEmailAddress());
  132.     }

  133.     /**
  134.      * Construct a new {@link org.eclipse.jgit.lib.PersonIdent} with current
  135.      * time.
  136.      *
  137.      * @param aName
  138.      *            a {@link java.lang.String} object.
  139.      * @param aEmailAddress
  140.      *            a {@link java.lang.String} object.
  141.      */
  142.     public PersonIdent(String aName, String aEmailAddress) {
  143.         this(aName, aEmailAddress, SystemReader.getInstance().getCurrentTime());
  144.     }

  145.     /**
  146.      * Construct a new {@link org.eclipse.jgit.lib.PersonIdent} with current
  147.      * time.
  148.      *
  149.      * @param aName
  150.      *            a {@link java.lang.String} object.
  151.      * @param aEmailAddress
  152.      *            a {@link java.lang.String} object.
  153.      * @param when
  154.      *            a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object.
  155.      * @since 4.6
  156.      */
  157.     public PersonIdent(String aName, String aEmailAddress,
  158.             ProposedTimestamp when) {
  159.         this(aName, aEmailAddress, when.millis());
  160.     }

  161.     /**
  162.      * Copy a PersonIdent, but alter the clone's time stamp
  163.      *
  164.      * @param pi
  165.      *            original {@link org.eclipse.jgit.lib.PersonIdent}
  166.      * @param when
  167.      *            local time
  168.      * @param tz
  169.      *            time zone
  170.      */
  171.     public PersonIdent(PersonIdent pi, Date when, TimeZone tz) {
  172.         this(pi.getName(), pi.getEmailAddress(), when, tz);
  173.     }

  174.     /**
  175.      * Copy a {@link org.eclipse.jgit.lib.PersonIdent}, but alter the clone's
  176.      * time stamp
  177.      *
  178.      * @param pi
  179.      *            original {@link org.eclipse.jgit.lib.PersonIdent}
  180.      * @param aWhen
  181.      *            local time
  182.      */
  183.     public PersonIdent(PersonIdent pi, Date aWhen) {
  184.         this(pi.getName(), pi.getEmailAddress(), aWhen.getTime(), pi.tzOffset);
  185.     }

  186.     /**
  187.      * Construct a PersonIdent from simple data
  188.      *
  189.      * @param aName a {@link java.lang.String} object.
  190.      * @param aEmailAddress a {@link java.lang.String} object.
  191.      * @param aWhen
  192.      *            local time stamp
  193.      * @param aTZ
  194.      *            time zone
  195.      */
  196.     public PersonIdent(final String aName, final String aEmailAddress,
  197.             final Date aWhen, final TimeZone aTZ) {
  198.         this(aName, aEmailAddress, aWhen.getTime(), aTZ.getOffset(aWhen
  199.                 .getTime()) / (60 * 1000));
  200.     }

  201.     /**
  202.      * Copy a PersonIdent, but alter the clone's time stamp
  203.      *
  204.      * @param pi
  205.      *            original {@link org.eclipse.jgit.lib.PersonIdent}
  206.      * @param aWhen
  207.      *            local time stamp
  208.      * @param aTZ
  209.      *            time zone
  210.      */
  211.     public PersonIdent(PersonIdent pi, long aWhen, int aTZ) {
  212.         this(pi.getName(), pi.getEmailAddress(), aWhen, aTZ);
  213.     }

  214.     private PersonIdent(final String aName, final String aEmailAddress,
  215.             long when) {
  216.         this(aName, aEmailAddress, when, SystemReader.getInstance()
  217.                 .getTimezone(when));
  218.     }

  219.     private PersonIdent(UserConfig config) {
  220.         this(config.getCommitterName(), config.getCommitterEmail());
  221.     }

  222.     /**
  223.      * Construct a {@link org.eclipse.jgit.lib.PersonIdent}.
  224.      * <p>
  225.      * Whitespace in the name and email is preserved for the lifetime of this
  226.      * object, but are trimmed by {@link #toExternalString()}. This means that
  227.      * parsing the result of {@link #toExternalString()} may not return an
  228.      * equivalent instance.
  229.      *
  230.      * @param aName
  231.      *            a {@link java.lang.String} object.
  232.      * @param aEmailAddress
  233.      *            a {@link java.lang.String} object.
  234.      * @param aWhen
  235.      *            local time stamp
  236.      * @param aTZ
  237.      *            time zone
  238.      */
  239.     public PersonIdent(final String aName, final String aEmailAddress,
  240.             final long aWhen, final int aTZ) {
  241.         if (aName == null)
  242.             throw new IllegalArgumentException(
  243.                     JGitText.get().personIdentNameNonNull);
  244.         if (aEmailAddress == null)
  245.             throw new IllegalArgumentException(
  246.                     JGitText.get().personIdentEmailNonNull);
  247.         name = aName;
  248.         emailAddress = aEmailAddress;
  249.         when = aWhen;
  250.         tzOffset = aTZ;
  251.     }

  252.     /**
  253.      * Get name of person
  254.      *
  255.      * @return Name of person
  256.      */
  257.     public String getName() {
  258.         return name;
  259.     }

  260.     /**
  261.      * Get email address of person
  262.      *
  263.      * @return email address of person
  264.      */
  265.     public String getEmailAddress() {
  266.         return emailAddress;
  267.     }

  268.     /**
  269.      * Get timestamp
  270.      *
  271.      * @return timestamp
  272.      */
  273.     public Date getWhen() {
  274.         return new Date(when);
  275.     }

  276.     /**
  277.      * Get this person's declared time zone
  278.      *
  279.      * @return this person's declared time zone; null if time zone is unknown.
  280.      */
  281.     public TimeZone getTimeZone() {
  282.         return getTimeZone(tzOffset);
  283.     }

  284.     /**
  285.      * Get this person's declared time zone as minutes east of UTC.
  286.      *
  287.      * @return this person's declared time zone as minutes east of UTC. If the
  288.      *         timezone is to the west of UTC it is negative.
  289.      */
  290.     public int getTimeZoneOffset() {
  291.         return tzOffset;
  292.     }

  293.     /**
  294.      * {@inheritDoc}
  295.      * <p>
  296.      * Hashcode is based only on the email address and timestamp.
  297.      */
  298.     @Override
  299.     public int hashCode() {
  300.         int hc = getEmailAddress().hashCode();
  301.         hc *= 31;
  302.         hc += (int) (when / 1000L);
  303.         return hc;
  304.     }

  305.     /** {@inheritDoc} */
  306.     @Override
  307.     public boolean equals(Object o) {
  308.         if (o instanceof PersonIdent) {
  309.             final PersonIdent p = (PersonIdent) o;
  310.             return getName().equals(p.getName())
  311.                     && getEmailAddress().equals(p.getEmailAddress())
  312.                     && when / 1000L == p.when / 1000L;
  313.         }
  314.         return false;
  315.     }

  316.     /**
  317.      * Format for Git storage.
  318.      *
  319.      * @return a string in the git author format
  320.      */
  321.     public String toExternalString() {
  322.         final StringBuilder r = new StringBuilder();
  323.         appendSanitized(r, getName());
  324.         r.append(" <"); //$NON-NLS-1$
  325.         appendSanitized(r, getEmailAddress());
  326.         r.append("> "); //$NON-NLS-1$
  327.         r.append(when / 1000);
  328.         r.append(' ');
  329.         appendTimezone(r, tzOffset);
  330.         return r.toString();
  331.     }

  332.     /** {@inheritDoc} */
  333.     @Override
  334.     @SuppressWarnings("nls")
  335.     public String toString() {
  336.         final StringBuilder r = new StringBuilder();
  337.         final SimpleDateFormat dtfmt;
  338.         dtfmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US);
  339.         dtfmt.setTimeZone(getTimeZone());

  340.         r.append("PersonIdent[");
  341.         r.append(getName());
  342.         r.append(", ");
  343.         r.append(getEmailAddress());
  344.         r.append(", ");
  345.         r.append(dtfmt.format(Long.valueOf(when)));
  346.         r.append("]");

  347.         return r.toString();
  348.     }
  349. }