AbbreviatedObjectId.java

  1. /*
  2.  * Copyright (C) 2008-2009, Google Inc.
  3.  * and other copyright owners as documented in the project's IP log.
  4.  *
  5.  * This program and the accompanying materials are made available
  6.  * under the terms of the Eclipse Distribution License v1.0 which
  7.  * accompanies this distribution, is reproduced below, and is
  8.  * available at http://www.eclipse.org/org/documents/edl-v10.php
  9.  *
  10.  * All rights reserved.
  11.  *
  12.  * Redistribution and use in source and binary forms, with or
  13.  * without modification, are permitted provided that the following
  14.  * conditions are met:
  15.  *
  16.  * - Redistributions of source code must retain the above copyright
  17.  *   notice, this list of conditions and the following disclaimer.
  18.  *
  19.  * - Redistributions in binary form must reproduce the above
  20.  *   copyright notice, this list of conditions and the following
  21.  *   disclaimer in the documentation and/or other materials provided
  22.  *   with the distribution.
  23.  *
  24.  * - Neither the name of the Eclipse Foundation, Inc. nor the
  25.  *   names of its contributors may be used to endorse or promote
  26.  *   products derived from this software without specific prior
  27.  *   written permission.
  28.  *
  29.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30.  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42.  */

  43. package org.eclipse.jgit.lib;

  44. import java.io.Serializable;
  45. import java.text.MessageFormat;

  46. import org.eclipse.jgit.errors.InvalidObjectIdException;
  47. import org.eclipse.jgit.internal.JGitText;
  48. import org.eclipse.jgit.util.NB;
  49. import org.eclipse.jgit.util.RawParseUtils;

  50. /**
  51.  * A prefix abbreviation of an {@link org.eclipse.jgit.lib.ObjectId}.
  52.  * <p>
  53.  * Sometimes Git produces abbreviated SHA-1 strings, using sufficient leading
  54.  * digits from the ObjectId name to still be unique within the repository the
  55.  * string was generated from. These ids are likely to be unique for a useful
  56.  * period of time, especially if they contain at least 6-10 hex digits.
  57.  * <p>
  58.  * This class converts the hex string into a binary form, to make it more
  59.  * efficient for matching against an object.
  60.  */
  61. public final class AbbreviatedObjectId implements Serializable {
  62.     private static final long serialVersionUID = 1L;

  63.     /**
  64.      * Test a string of characters to verify it is a hex format.
  65.      * <p>
  66.      * If true the string can be parsed with {@link #fromString(String)}.
  67.      *
  68.      * @param id
  69.      *            the string to test.
  70.      * @return true if the string can converted into an AbbreviatedObjectId.
  71.      */
  72.     public static final boolean isId(String id) {
  73.         if (id.length() < 2 || Constants.OBJECT_ID_STRING_LENGTH < id.length())
  74.             return false;
  75.         try {
  76.             for (int i = 0; i < id.length(); i++)
  77.                 RawParseUtils.parseHexInt4((byte) id.charAt(i));
  78.             return true;
  79.         } catch (ArrayIndexOutOfBoundsException e) {
  80.             return false;
  81.         }
  82.     }

  83.     /**
  84.      * Convert an AbbreviatedObjectId from hex characters (US-ASCII).
  85.      *
  86.      * @param buf
  87.      *            the US-ASCII buffer to read from.
  88.      * @param offset
  89.      *            position to read the first character from.
  90.      * @param end
  91.      *            one past the last position to read (<code>end-offset</code> is
  92.      *            the length of the string).
  93.      * @return the converted object id.
  94.      */
  95.     public static final AbbreviatedObjectId fromString(final byte[] buf,
  96.             final int offset, final int end) {
  97.         if (end - offset > Constants.OBJECT_ID_STRING_LENGTH)
  98.             throw new IllegalArgumentException(MessageFormat.format(
  99.                     JGitText.get().invalidIdLength,
  100.                     Integer.valueOf(end - offset),
  101.                     Integer.valueOf(Constants.OBJECT_ID_STRING_LENGTH)));
  102.         return fromHexString(buf, offset, end);
  103.     }

  104.     /**
  105.      * Convert an AbbreviatedObjectId from an
  106.      * {@link org.eclipse.jgit.lib.AnyObjectId}.
  107.      * <p>
  108.      * This method copies over all bits of the Id, and is therefore complete
  109.      * (see {@link #isComplete()}).
  110.      *
  111.      * @param id
  112.      *            the {@link org.eclipse.jgit.lib.ObjectId} to convert from.
  113.      * @return the converted object id.
  114.      */
  115.     public static final AbbreviatedObjectId fromObjectId(AnyObjectId id) {
  116.         return new AbbreviatedObjectId(Constants.OBJECT_ID_STRING_LENGTH,
  117.                 id.w1, id.w2, id.w3, id.w4, id.w5);
  118.     }

  119.     /**
  120.      * Convert an AbbreviatedObjectId from hex characters.
  121.      *
  122.      * @param str
  123.      *            the string to read from. Must be &lt;= 40 characters.
  124.      * @return the converted object id.
  125.      */
  126.     public static final AbbreviatedObjectId fromString(String str) {
  127.         if (str.length() > Constants.OBJECT_ID_STRING_LENGTH)
  128.             throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidId, str));
  129.         final byte[] b = Constants.encodeASCII(str);
  130.         return fromHexString(b, 0, b.length);
  131.     }

  132.     private static final AbbreviatedObjectId fromHexString(final byte[] bs,
  133.             int ptr, final int end) {
  134.         try {
  135.             final int a = hexUInt32(bs, ptr, end);
  136.             final int b = hexUInt32(bs, ptr + 8, end);
  137.             final int c = hexUInt32(bs, ptr + 16, end);
  138.             final int d = hexUInt32(bs, ptr + 24, end);
  139.             final int e = hexUInt32(bs, ptr + 32, end);
  140.             return new AbbreviatedObjectId(end - ptr, a, b, c, d, e);
  141.         } catch (ArrayIndexOutOfBoundsException e1) {
  142.             throw new InvalidObjectIdException(bs, ptr, end - ptr);
  143.         }
  144.     }

  145.     private static final int hexUInt32(final byte[] bs, int p, final int end) {
  146.         if (8 <= end - p)
  147.             return RawParseUtils.parseHexInt32(bs, p);

  148.         int r = 0, n = 0;
  149.         while (n < 8 && p < end) {
  150.             r <<= 4;
  151.             r |= RawParseUtils.parseHexInt4(bs[p++]);
  152.             n++;
  153.         }
  154.         return r << ((8 - n) * 4);
  155.     }

  156.     static int mask(int nibbles, int word, int v) {
  157.         final int b = (word - 1) * 8;
  158.         if (b + 8 <= nibbles) {
  159.             // We have all of the bits required for this word.
  160.             //
  161.             return v;
  162.         }

  163.         if (nibbles <= b) {
  164.             // We have none of the bits required for this word.
  165.             //
  166.             return 0;
  167.         }

  168.         final int s = 32 - (nibbles - b) * 4;
  169.         return (v >>> s) << s;
  170.     }

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

  173.     final int w1;

  174.     final int w2;

  175.     final int w3;

  176.     final int w4;

  177.     final int w5;

  178.     AbbreviatedObjectId(final int n, final int new_1, final int new_2,
  179.             final int new_3, final int new_4, final int new_5) {
  180.         nibbles = n;
  181.         w1 = new_1;
  182.         w2 = new_2;
  183.         w3 = new_3;
  184.         w4 = new_4;
  185.         w5 = new_5;
  186.     }

  187.     /**
  188.      * Get number of hex digits appearing in this id.
  189.      *
  190.      * @return number of hex digits appearing in this id.
  191.      */
  192.     public int length() {
  193.         return nibbles;
  194.     }

  195.     /**
  196.      * Whether this ObjectId is actually a complete id.
  197.      *
  198.      * @return true if this ObjectId is actually a complete id.
  199.      */
  200.     public boolean isComplete() {
  201.         return length() == Constants.OBJECT_ID_STRING_LENGTH;
  202.     }

  203.     /**
  204.      * A complete ObjectId; null if {@link #isComplete()} is false
  205.      *
  206.      * @return a complete ObjectId; null if {@link #isComplete()} is false
  207.      */
  208.     public ObjectId toObjectId() {
  209.         return isComplete() ? new ObjectId(w1, w2, w3, w4, w5) : null;
  210.     }

  211.     /**
  212.      * Compares this abbreviation to a full object id.
  213.      *
  214.      * @param other
  215.      *            the other object id.
  216.      * @return &lt;0 if this abbreviation names an object that is less than
  217.      *         <code>other</code>; 0 if this abbreviation exactly matches the
  218.      *         first {@link #length()} digits of <code>other.name()</code>;
  219.      *         &gt;0 if this abbreviation names an object that is after
  220.      *         <code>other</code>.
  221.      */
  222.     public final int prefixCompare(AnyObjectId other) {
  223.         int cmp;

  224.         cmp = NB.compareUInt32(w1, mask(1, other.w1));
  225.         if (cmp != 0)
  226.             return cmp;

  227.         cmp = NB.compareUInt32(w2, mask(2, other.w2));
  228.         if (cmp != 0)
  229.             return cmp;

  230.         cmp = NB.compareUInt32(w3, mask(3, other.w3));
  231.         if (cmp != 0)
  232.             return cmp;

  233.         cmp = NB.compareUInt32(w4, mask(4, other.w4));
  234.         if (cmp != 0)
  235.             return cmp;

  236.         return NB.compareUInt32(w5, mask(5, other.w5));
  237.     }

  238.     /**
  239.      * Compare this abbreviation to a network-byte-order ObjectId.
  240.      *
  241.      * @param bs
  242.      *            array containing the other ObjectId in network byte order.
  243.      * @param p
  244.      *            position within {@code bs} to start the compare at. At least
  245.      *            20 bytes, 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(byte[] bs, int p) {
  253.         int cmp;

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

  257.         cmp = NB.compareUInt32(w2, mask(2, NB.decodeInt32(bs, p + 4)));
  258.         if (cmp != 0)
  259.             return cmp;

  260.         cmp = NB.compareUInt32(w3, mask(3, NB.decodeInt32(bs, p + 8)));
  261.         if (cmp != 0)
  262.             return cmp;

  263.         cmp = NB.compareUInt32(w4, mask(4, NB.decodeInt32(bs, p + 12)));
  264.         if (cmp != 0)
  265.             return cmp;

  266.         return NB.compareUInt32(w5, mask(5, NB.decodeInt32(bs, p + 16)));
  267.     }

  268.     /**
  269.      * Compare this abbreviation to a network-byte-order ObjectId.
  270.      *
  271.      * @param bs
  272.      *            array containing the other ObjectId in network byte order.
  273.      * @param p
  274.      *            position within {@code bs} to start the compare at. At least 5
  275.      *            ints, starting at this position are required.
  276.      * @return &lt;0 if this abbreviation names an object that is less than
  277.      *         <code>other</code>; 0 if this abbreviation exactly matches the
  278.      *         first {@link #length()} digits of <code>other.name()</code>;
  279.      *         &gt;0 if this abbreviation names an object that is after
  280.      *         <code>other</code>.
  281.      */
  282.     public final int prefixCompare(int[] bs, int p) {
  283.         int cmp;

  284.         cmp = NB.compareUInt32(w1, mask(1, bs[p]));
  285.         if (cmp != 0)
  286.             return cmp;

  287.         cmp = NB.compareUInt32(w2, mask(2, bs[p + 1]));
  288.         if (cmp != 0)
  289.             return cmp;

  290.         cmp = NB.compareUInt32(w3, mask(3, bs[p + 2]));
  291.         if (cmp != 0)
  292.             return cmp;

  293.         cmp = NB.compareUInt32(w4, mask(4, bs[p + 3]));
  294.         if (cmp != 0)
  295.             return cmp;

  296.         return NB.compareUInt32(w5, mask(5, bs[p + 4]));
  297.     }

  298.     /**
  299.      * Get value for a fan-out style map, only valid of length &gt;= 2.
  300.      *
  301.      * @return value for a fan-out style map, only valid of length &gt;= 2.
  302.      */
  303.     public final int getFirstByte() {
  304.         return w1 >>> 24;
  305.     }

  306.     private int mask(int word, int v) {
  307.         return mask(nibbles, word, v);
  308.     }

  309.     /** {@inheritDoc} */
  310.     @Override
  311.     public int hashCode() {
  312.         return w1;
  313.     }

  314.     /** {@inheritDoc} */
  315.     @Override
  316.     public boolean equals(Object o) {
  317.         if (o instanceof AbbreviatedObjectId) {
  318.             final AbbreviatedObjectId b = (AbbreviatedObjectId) o;
  319.             return nibbles == b.nibbles && w1 == b.w1 && w2 == b.w2
  320.                     && w3 == b.w3 && w4 == b.w4 && w5 == b.w5;
  321.         }
  322.         return false;
  323.     }

  324.     /**
  325.      * Get string form of the abbreviation, in lower case hexadecimal.
  326.      *
  327.      * @return string form of the abbreviation, in lower case hexadecimal.
  328.      */
  329.     public final String name() {
  330.         final char[] b = new char[Constants.OBJECT_ID_STRING_LENGTH];

  331.         AnyObjectId.formatHexChar(b, 0, w1);
  332.         if (nibbles <= 8)
  333.             return new String(b, 0, nibbles);

  334.         AnyObjectId.formatHexChar(b, 8, w2);
  335.         if (nibbles <= 16)
  336.             return new String(b, 0, nibbles);

  337.         AnyObjectId.formatHexChar(b, 16, w3);
  338.         if (nibbles <= 24)
  339.             return new String(b, 0, nibbles);

  340.         AnyObjectId.formatHexChar(b, 24, w4);
  341.         if (nibbles <= 32)
  342.             return new String(b, 0, nibbles);

  343.         AnyObjectId.formatHexChar(b, 32, w5);
  344.         return new String(b, 0, nibbles);
  345.     }

  346.     /** {@inheritDoc} */
  347.     @SuppressWarnings("nls")
  348.     @Override
  349.     public String toString() {
  350.         return "AbbreviatedObjectId[" + name() + "]"; //$NON-NLS-1$
  351.     }
  352. }