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.time.Instant;
  16. import java.time.ZoneId;
  17. import java.util.Date;
  18. import java.util.Locale;
  19. import java.util.TimeZone;

  20. import org.eclipse.jgit.internal.JGitText;
  21. import org.eclipse.jgit.util.SystemReader;
  22. import org.eclipse.jgit.util.time.ProposedTimestamp;

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

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

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

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

  64.         offsetHours = offset / 60;
  65.         offsetMins = offset % 60;

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

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

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

  112.     private final String name;

  113.     private final String emailAddress;

  114.     private final long when;

  115.     private final int tzOffset;

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

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

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

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

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

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

  188.     /**
  189.      * Copy a {@link org.eclipse.jgit.lib.PersonIdent}, but alter the clone's
  190.      * time stamp
  191.      *
  192.      * @param pi
  193.      *            original {@link org.eclipse.jgit.lib.PersonIdent}
  194.      * @param aWhen
  195.      *            local time as Instant
  196.      * @since 6.1
  197.      */
  198.     public PersonIdent(PersonIdent pi, Instant aWhen) {
  199.         this(pi.getName(), pi.getEmailAddress(), aWhen.toEpochMilli(), pi.tzOffset);
  200.     }

  201.     /**
  202.      * Construct a PersonIdent from simple data
  203.      *
  204.      * @param aName a {@link java.lang.String} object.
  205.      * @param aEmailAddress a {@link java.lang.String} object.
  206.      * @param aWhen
  207.      *            local time stamp
  208.      * @param aTZ
  209.      *            time zone
  210.      */
  211.     public PersonIdent(final String aName, final String aEmailAddress,
  212.             final Date aWhen, final TimeZone aTZ) {
  213.         this(aName, aEmailAddress, aWhen.getTime(), aTZ.getOffset(aWhen
  214.                 .getTime()) / (60 * 1000));
  215.     }

  216.     /**
  217.      * Construct a PersonIdent from simple data
  218.      *
  219.      * @param aName
  220.      *            a {@link java.lang.String} object.
  221.      * @param aEmailAddress
  222.      *            a {@link java.lang.String} object.
  223.      * @param aWhen
  224.      *            local time stamp
  225.      * @param zoneId
  226.      *            time zone id
  227.      * @since 6.1
  228.      */
  229.     public PersonIdent(final String aName, String aEmailAddress, Instant aWhen,
  230.             ZoneId zoneId) {
  231.         this(aName, aEmailAddress, aWhen.toEpochMilli(),
  232.                 TimeZone.getTimeZone(zoneId)
  233.                         .getOffset(aWhen
  234.                 .toEpochMilli()) / (60 * 1000));
  235.     }

  236.     /**
  237.      * Copy a PersonIdent, but alter the clone's time stamp
  238.      *
  239.      * @param pi
  240.      *            original {@link org.eclipse.jgit.lib.PersonIdent}
  241.      * @param aWhen
  242.      *            local time stamp
  243.      * @param aTZ
  244.      *            time zone
  245.      */
  246.     public PersonIdent(PersonIdent pi, long aWhen, int aTZ) {
  247.         this(pi.getName(), pi.getEmailAddress(), aWhen, aTZ);
  248.     }

  249.     private PersonIdent(final String aName, final String aEmailAddress,
  250.             long when) {
  251.         this(aName, aEmailAddress, when, SystemReader.getInstance()
  252.                 .getTimezone(when));
  253.     }

  254.     private PersonIdent(UserConfig config) {
  255.         this(config.getCommitterName(), config.getCommitterEmail());
  256.     }

  257.     /**
  258.      * Construct a {@link org.eclipse.jgit.lib.PersonIdent}.
  259.      * <p>
  260.      * Whitespace in the name and email is preserved for the lifetime of this
  261.      * object, but are trimmed by {@link #toExternalString()}. This means that
  262.      * parsing the result of {@link #toExternalString()} may not return an
  263.      * equivalent instance.
  264.      *
  265.      * @param aName
  266.      *            a {@link java.lang.String} object.
  267.      * @param aEmailAddress
  268.      *            a {@link java.lang.String} object.
  269.      * @param aWhen
  270.      *            local time stamp
  271.      * @param aTZ
  272.      *            time zone
  273.      */
  274.     public PersonIdent(final String aName, final String aEmailAddress,
  275.             final long aWhen, final int aTZ) {
  276.         if (aName == null)
  277.             throw new IllegalArgumentException(
  278.                     JGitText.get().personIdentNameNonNull);
  279.         if (aEmailAddress == null)
  280.             throw new IllegalArgumentException(
  281.                     JGitText.get().personIdentEmailNonNull);
  282.         name = aName;
  283.         emailAddress = aEmailAddress;
  284.         when = aWhen;
  285.         tzOffset = aTZ;
  286.     }

  287.     /**
  288.      * Get name of person
  289.      *
  290.      * @return Name of person
  291.      */
  292.     public String getName() {
  293.         return name;
  294.     }

  295.     /**
  296.      * Get email address of person
  297.      *
  298.      * @return email address of person
  299.      */
  300.     public String getEmailAddress() {
  301.         return emailAddress;
  302.     }

  303.     /**
  304.      * Get timestamp
  305.      *
  306.      * @return timestamp
  307.      */
  308.     public Date getWhen() {
  309.         return new Date(when);
  310.     }

  311.     /**
  312.      * Get when attribute as instant
  313.      *
  314.      * @return timestamp
  315.      * @since 6.1
  316.      */
  317.     public Instant getWhenAsInstant() {
  318.         return Instant.ofEpochMilli(when);
  319.     }

  320.     /**
  321.      * Get this person's declared time zone
  322.      *
  323.      * @return this person's declared time zone; null if time zone is unknown.
  324.      */
  325.     public TimeZone getTimeZone() {
  326.         return getTimeZone(tzOffset);
  327.     }

  328.     /**
  329.      * Get the time zone id
  330.      *
  331.      * @return the time zone id
  332.      * @since 6.1
  333.      */
  334.     public ZoneId getZoneId() {
  335.         return getTimeZone().toZoneId();
  336.     }

  337.     /**
  338.      * Get this person's declared time zone as minutes east of UTC.
  339.      *
  340.      * @return this person's declared time zone as minutes east of UTC. If the
  341.      *         timezone is to the west of UTC it is negative.
  342.      */
  343.     public int getTimeZoneOffset() {
  344.         return tzOffset;
  345.     }

  346.     /**
  347.      * {@inheritDoc}
  348.      * <p>
  349.      * Hashcode is based only on the email address and timestamp.
  350.      */
  351.     @Override
  352.     public int hashCode() {
  353.         int hc = getEmailAddress().hashCode();
  354.         hc *= 31;
  355.         hc += (int) (when / 1000L);
  356.         return hc;
  357.     }

  358.     /** {@inheritDoc} */
  359.     @Override
  360.     public boolean equals(Object o) {
  361.         if (o instanceof PersonIdent) {
  362.             final PersonIdent p = (PersonIdent) o;
  363.             return getName().equals(p.getName())
  364.                     && getEmailAddress().equals(p.getEmailAddress())
  365.                     && when / 1000L == p.when / 1000L;
  366.         }
  367.         return false;
  368.     }

  369.     /**
  370.      * Format for Git storage.
  371.      *
  372.      * @return a string in the git author format
  373.      */
  374.     public String toExternalString() {
  375.         final StringBuilder r = new StringBuilder();
  376.         appendSanitized(r, getName());
  377.         r.append(" <"); //$NON-NLS-1$
  378.         appendSanitized(r, getEmailAddress());
  379.         r.append("> "); //$NON-NLS-1$
  380.         r.append(when / 1000);
  381.         r.append(' ');
  382.         appendTimezone(r, tzOffset);
  383.         return r.toString();
  384.     }

  385.     /** {@inheritDoc} */
  386.     @Override
  387.     @SuppressWarnings("nls")
  388.     public String toString() {
  389.         final StringBuilder r = new StringBuilder();
  390.         final SimpleDateFormat dtfmt;
  391.         dtfmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US);
  392.         dtfmt.setTimeZone(getTimeZone());

  393.         r.append("PersonIdent[");
  394.         r.append(getName());
  395.         r.append(", ");
  396.         r.append(getEmailAddress());
  397.         r.append(", ");
  398.         r.append(dtfmt.format(Long.valueOf(when)));
  399.         r.append("]");

  400.         return r.toString();
  401.     }
  402. }