AbbreviatedLongObjectId.java

  1. /*
  2.  * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.lfs.lib;

  11. import java.io.Serializable;
  12. import java.text.MessageFormat;

  13. import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException;
  14. import org.eclipse.jgit.lfs.internal.LfsText;
  15. import org.eclipse.jgit.util.NB;
  16. import org.eclipse.jgit.util.RawParseUtils;

  17. /**
  18.  * A prefix abbreviation of an {@link org.eclipse.jgit.lfs.lib.LongObjectId}.
  19.  * <p>
  20.  * Enable abbreviating SHA-256 strings used by Git LFS, using sufficient leading
  21.  * digits from the LongObjectId name to still be unique within the repository
  22.  * the string was generated from. These ids are likely to be unique for a useful
  23.  * period of time, especially if they contain at least 6-10 hex digits.
  24.  * <p>
  25.  * This class converts the hex string into a binary form, to make it more
  26.  * efficient for matching against an object.
  27.  *
  28.  * Ported to SHA-256 from {@link org.eclipse.jgit.lib.AbbreviatedObjectId}
  29.  *
  30.  * @since 4.3
  31.  */
  32. public final class AbbreviatedLongObjectId implements Serializable {
  33.     private static final long serialVersionUID = 1L;

  34.     /**
  35.      * Test a string of characters to verify it is a hex format.
  36.      * <p>
  37.      * If true the string can be parsed with {@link #fromString(String)}.
  38.      *
  39.      * @param id
  40.      *            the string to test.
  41.      * @return true if the string can converted into an AbbreviatedObjectId.
  42.      */
  43.     public static final boolean isId(String id) {
  44.         if (id.length() < 2
  45.                 || Constants.LONG_OBJECT_ID_STRING_LENGTH < id.length())
  46.             return false;
  47.         try {
  48.             for (int i = 0; i < id.length(); i++)
  49.                 RawParseUtils.parseHexInt4((byte) id.charAt(i));
  50.             return true;
  51.         } catch (ArrayIndexOutOfBoundsException e) {
  52.             return false;
  53.         }
  54.     }

  55.     /**
  56.      * Convert an AbbreviatedObjectId from hex characters (US-ASCII).
  57.      *
  58.      * @param buf
  59.      *            the US-ASCII buffer to read from.
  60.      * @param offset
  61.      *            position to read the first character from.
  62.      * @param end
  63.      *            one past the last position to read (<code>end-offset</code> is
  64.      *            the length of the string).
  65.      * @return the converted object id.
  66.      */
  67.     public static final AbbreviatedLongObjectId fromString(final byte[] buf,
  68.             final int offset, final int end) {
  69.         if (end - offset > Constants.LONG_OBJECT_ID_STRING_LENGTH)
  70.             throw new IllegalArgumentException(MessageFormat.format(
  71.                             LfsText.get().invalidLongIdLength,
  72.                     Integer.valueOf(end - offset),
  73.                     Integer.valueOf(Constants.LONG_OBJECT_ID_STRING_LENGTH)));
  74.         return fromHexString(buf, offset, end);
  75.     }

  76.     /**
  77.      * Convert an AbbreviatedObjectId from an
  78.      * {@link org.eclipse.jgit.lib.AnyObjectId}.
  79.      * <p>
  80.      * This method copies over all bits of the Id, and is therefore complete
  81.      * (see {@link #isComplete()}).
  82.      *
  83.      * @param id
  84.      *            the {@link org.eclipse.jgit.lib.ObjectId} to convert from.
  85.      * @return the converted object id.
  86.      */
  87.     public static final AbbreviatedLongObjectId fromLongObjectId(
  88.             AnyLongObjectId id) {
  89.         return new AbbreviatedLongObjectId(
  90.                 Constants.LONG_OBJECT_ID_STRING_LENGTH, id.w1, id.w2, id.w3,
  91.                 id.w4);
  92.     }

  93.     /**
  94.      * Convert an AbbreviatedLongObjectId from hex characters.
  95.      *
  96.      * @param str
  97.      *            the string to read from. Must be &lt;= 64 characters.
  98.      * @return the converted object id.
  99.      */
  100.     public static final AbbreviatedLongObjectId fromString(String str) {
  101.         if (str.length() > Constants.LONG_OBJECT_ID_STRING_LENGTH)
  102.             throw new IllegalArgumentException(
  103.                     MessageFormat.format(LfsText.get().invalidLongId, str));
  104.         final byte[] b = org.eclipse.jgit.lib.Constants.encodeASCII(str);
  105.         return fromHexString(b, 0, b.length);
  106.     }

  107.     private static final AbbreviatedLongObjectId fromHexString(final byte[] bs,
  108.             int ptr, final int end) {
  109.         try {
  110.             final long a = hexUInt64(bs, ptr, end);
  111.             final long b = hexUInt64(bs, ptr + 16, end);
  112.             final long c = hexUInt64(bs, ptr + 32, end);
  113.             final long d = hexUInt64(bs, ptr + 48, end);
  114.             return new AbbreviatedLongObjectId(end - ptr, a, b, c, d);
  115.         } catch (ArrayIndexOutOfBoundsException e) {
  116.             InvalidLongObjectIdException e1 = new InvalidLongObjectIdException(
  117.                     bs, ptr, end - ptr);
  118.             e1.initCause(e);
  119.             throw e1;
  120.         }
  121.     }

  122.     private static final long hexUInt64(final byte[] bs, int p, final int end) {
  123.         if (16 <= end - p)
  124.             return RawParseUtils.parseHexInt64(bs, p);

  125.         long r = 0;
  126.         int n = 0;
  127.         while (n < 16 && p < end) {
  128.             r <<= 4;
  129.             r |= RawParseUtils.parseHexInt4(bs[p++]);
  130.             n++;
  131.         }
  132.         return r << ((16 - n) * 4);
  133.     }

  134.     static long mask(int nibbles, long word, long v) {
  135.         final long b = (word - 1) * 16;
  136.         if (b + 16 <= nibbles) {
  137.             // We have all of the bits required for this word.
  138.             //
  139.             return v;
  140.         }

  141.         if (nibbles <= b) {
  142.             // We have none of the bits required for this word.
  143.             //
  144.             return 0;
  145.         }

  146.         final long s = 64 - (nibbles - b) * 4;
  147.         return (v >>> s) << s;
  148.     }

  149.     /** Number of half-bytes used by this id. */
  150.     final int nibbles;

  151.     final long w1;

  152.     final long w2;

  153.     final long w3;

  154.     final long w4;

  155.     AbbreviatedLongObjectId(final int n, final long new_1, final long new_2,
  156.             final long new_3, final long new_4) {
  157.         nibbles = n;
  158.         w1 = new_1;
  159.         w2 = new_2;
  160.         w3 = new_3;
  161.         w4 = new_4;
  162.     }

  163.     /**
  164.      * Get length
  165.      *
  166.      * @return number of hex digits appearing in this id.
  167.      */
  168.     public int length() {
  169.         return nibbles;
  170.     }

  171.     /**
  172.      * Check if this id is complete
  173.      *
  174.      * @return true if this ObjectId is actually a complete id.
  175.      */
  176.     public boolean isComplete() {
  177.         return length() == Constants.LONG_OBJECT_ID_STRING_LENGTH;
  178.     }

  179.     /**
  180.      * Convert to LongObjectId
  181.      *
  182.      * @return a complete ObjectId; null if {@link #isComplete()} is false.
  183.      */
  184.     public LongObjectId toLongObjectId() {
  185.         return isComplete() ? new LongObjectId(w1, w2, w3, w4) : null;
  186.     }

  187.     /**
  188.      * Compares this abbreviation to a full object id.
  189.      *
  190.      * @param other
  191.      *            the other object id.
  192.      * @return &lt;0 if this abbreviation names an object that is less than
  193.      *         <code>other</code>; 0 if this abbreviation exactly matches the
  194.      *         first {@link #length()} digits of <code>other.name()</code>;
  195.      *         &gt;0 if this abbreviation names an object that is after
  196.      *         <code>other</code>.
  197.      */
  198.     public final int prefixCompare(AnyLongObjectId other) {
  199.         int cmp;

  200.         cmp = NB.compareUInt64(w1, mask(1, other.w1));
  201.         if (cmp != 0)
  202.             return cmp;

  203.         cmp = NB.compareUInt64(w2, mask(2, other.w2));
  204.         if (cmp != 0)
  205.             return cmp;

  206.         cmp = NB.compareUInt64(w3, mask(3, other.w3));
  207.         if (cmp != 0)
  208.             return cmp;

  209.         return NB.compareUInt64(w4, mask(4, other.w4));
  210.     }

  211.     /**
  212.      * Compare this abbreviation to a network-byte-order LongObjectId.
  213.      *
  214.      * @param bs
  215.      *            array containing the other LongObjectId in network byte order.
  216.      * @param p
  217.      *            position within {@code bs} to start the compare at. At least
  218.      *            32 bytes, starting at this position are required.
  219.      * @return &lt;0 if this abbreviation names an object that is less than
  220.      *         <code>other</code>; 0 if this abbreviation exactly matches the
  221.      *         first {@link #length()} digits of <code>other.name()</code>;
  222.      *         &gt;0 if this abbreviation names an object that is after
  223.      *         <code>other</code>.
  224.      */
  225.     public final int prefixCompare(byte[] bs, int p) {
  226.         int cmp;

  227.         cmp = NB.compareUInt64(w1, mask(1, NB.decodeInt64(bs, p)));
  228.         if (cmp != 0)
  229.             return cmp;

  230.         cmp = NB.compareUInt64(w2, mask(2, NB.decodeInt64(bs, p + 8)));
  231.         if (cmp != 0)
  232.             return cmp;

  233.         cmp = NB.compareUInt64(w3, mask(3, NB.decodeInt64(bs, p + 16)));
  234.         if (cmp != 0)
  235.             return cmp;

  236.         return NB.compareUInt64(w4, mask(4, NB.decodeInt64(bs, p + 24)));
  237.     }

  238.     /**
  239.      * Compare this abbreviation to a network-byte-order LongObjectId.
  240.      *
  241.      * @param bs
  242.      *            array containing the other LongObjectId in network byte order.
  243.      * @param p
  244.      *            position within {@code bs} to start the compare at. At least 4
  245.      *            longs, starting at this position are required.
  246.      * @return &lt;0 if this abbreviation names an object that is less than
  247.      *         <code>other</code>; 0 if this abbreviation exactly matches the
  248.      *         first {@link #length()} digits of <code>other.name()</code>;
  249.      *         &gt;0 if this abbreviation names an object that is after
  250.      *         <code>other</code>.
  251.      */
  252.     public final int prefixCompare(long[] bs, int p) {
  253.         int cmp;

  254.         cmp = NB.compareUInt64(w1, mask(1, bs[p]));
  255.         if (cmp != 0)
  256.             return cmp;

  257.         cmp = NB.compareUInt64(w2, mask(2, bs[p + 1]));
  258.         if (cmp != 0)
  259.             return cmp;

  260.         cmp = NB.compareUInt64(w3, mask(3, bs[p + 2]));
  261.         if (cmp != 0)
  262.             return cmp;

  263.         return NB.compareUInt64(w4, mask(4, bs[p + 3]));
  264.     }

  265.     /**
  266.      * Get the first byte of this id
  267.      *
  268.      * @return value for a fan-out style map, only valid of length &gt;= 2.
  269.      */
  270.     public final int getFirstByte() {
  271.         return (int) (w1 >>> 56);
  272.     }

  273.     private long mask(long word, long v) {
  274.         return mask(nibbles, word, v);
  275.     }

  276.     /** {@inheritDoc} */
  277.     @Override
  278.     public int hashCode() {
  279.         return (int) (w1 >> 32);
  280.     }

  281.     /** {@inheritDoc} */
  282.     @Override
  283.     public boolean equals(Object o) {
  284.         if (o instanceof AbbreviatedLongObjectId) {
  285.             final AbbreviatedLongObjectId b = (AbbreviatedLongObjectId) o;
  286.             return nibbles == b.nibbles && w1 == b.w1 && w2 == b.w2
  287.                     && w3 == b.w3 && w4 == b.w4;
  288.         }
  289.         return false;
  290.     }

  291.     /**
  292.      * <p>name.</p>
  293.      *
  294.      * @return string form of the abbreviation, in lower case hexadecimal.
  295.      */
  296.     public final String name() {
  297.         final char[] b = new char[Constants.LONG_OBJECT_ID_STRING_LENGTH];

  298.         AnyLongObjectId.formatHexChar(b, 0, w1);
  299.         if (nibbles <= 16)
  300.             return new String(b, 0, nibbles);

  301.         AnyLongObjectId.formatHexChar(b, 16, w2);
  302.         if (nibbles <= 32)
  303.             return new String(b, 0, nibbles);

  304.         AnyLongObjectId.formatHexChar(b, 32, w3);
  305.         if (nibbles <= 48)
  306.             return new String(b, 0, nibbles);

  307.         AnyLongObjectId.formatHexChar(b, 48, w4);
  308.         return new String(b, 0, nibbles);
  309.     }

  310.     /** {@inheritDoc} */
  311.     @SuppressWarnings("nls")
  312.     @Override
  313.     public String toString() {
  314.         return "AbbreviatedLongObjectId[" + name() + "]"; //$NON-NLS-1$
  315.     }
  316. }