1536db18cSMatthias Sohn /* 25c5f7c6bSMatthias Sohn * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com> and others 3536db18cSMatthias Sohn * 45c5f7c6bSMatthias Sohn * This program and the accompanying materials are made available under the 55c5f7c6bSMatthias Sohn * terms of the Eclipse Distribution License v. 1.0 which is available at 65c5f7c6bSMatthias Sohn * https://www.eclipse.org/org/documents/edl-v10.php. 7536db18cSMatthias Sohn * 85c5f7c6bSMatthias Sohn * SPDX-License-Identifier: BSD-3-Clause 9536db18cSMatthias Sohn */ 10536db18cSMatthias Sohn 11536db18cSMatthias Sohn package org.eclipse.jgit.lfs.lib; 12536db18cSMatthias Sohn 13536db18cSMatthias Sohn import java.io.Serializable; 14536db18cSMatthias Sohn import java.text.MessageFormat; 15536db18cSMatthias Sohn 16536db18cSMatthias Sohn import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException; 17536db18cSMatthias Sohn import org.eclipse.jgit.lfs.internal.LfsText; 18536db18cSMatthias Sohn import org.eclipse.jgit.util.NB; 19536db18cSMatthias Sohn import org.eclipse.jgit.util.RawParseUtils; 20536db18cSMatthias Sohn 21536db18cSMatthias Sohn /** 22e0332bfbSMatthias Sohn * A prefix abbreviation of an {@link org.eclipse.jgit.lfs.lib.LongObjectId}. 23536db18cSMatthias Sohn * <p> 24536db18cSMatthias Sohn * Enable abbreviating SHA-256 strings used by Git LFS, using sufficient leading 25536db18cSMatthias Sohn * digits from the LongObjectId name to still be unique within the repository 26536db18cSMatthias Sohn * the string was generated from. These ids are likely to be unique for a useful 27536db18cSMatthias Sohn * period of time, especially if they contain at least 6-10 hex digits. 28536db18cSMatthias Sohn * <p> 29536db18cSMatthias Sohn * This class converts the hex string into a binary form, to make it more 30536db18cSMatthias Sohn * efficient for matching against an object. 31536db18cSMatthias Sohn * 32e0332bfbSMatthias Sohn * Ported to SHA-256 from {@link org.eclipse.jgit.lib.AbbreviatedObjectId} 33536db18cSMatthias Sohn * 34536db18cSMatthias Sohn * @since 4.3 35536db18cSMatthias Sohn */ 36536db18cSMatthias Sohn public final class AbbreviatedLongObjectId implements Serializable { 37536db18cSMatthias Sohn private static final long serialVersionUID = 1L; 38536db18cSMatthias Sohn 39536db18cSMatthias Sohn /** 40536db18cSMatthias Sohn * Test a string of characters to verify it is a hex format. 41536db18cSMatthias Sohn * <p> 42536db18cSMatthias Sohn * If true the string can be parsed with {@link #fromString(String)}. 43536db18cSMatthias Sohn * 44536db18cSMatthias Sohn * @param id 45536db18cSMatthias Sohn * the string to test. 46536db18cSMatthias Sohn * @return true if the string can converted into an AbbreviatedObjectId. 47536db18cSMatthias Sohn */ isId(String id)486d370d83SHan-Wen Nienhuys public static final boolean isId(String id) { 49536db18cSMatthias Sohn if (id.length() < 2 50536db18cSMatthias Sohn || Constants.LONG_OBJECT_ID_STRING_LENGTH < id.length()) 51536db18cSMatthias Sohn return false; 52536db18cSMatthias Sohn try { 53536db18cSMatthias Sohn for (int i = 0; i < id.length(); i++) 54536db18cSMatthias Sohn RawParseUtils.parseHexInt4((byte) id.charAt(i)); 55536db18cSMatthias Sohn return true; 56536db18cSMatthias Sohn } catch (ArrayIndexOutOfBoundsException e) { 57536db18cSMatthias Sohn return false; 58536db18cSMatthias Sohn } 59536db18cSMatthias Sohn } 60536db18cSMatthias Sohn 61536db18cSMatthias Sohn /** 62536db18cSMatthias Sohn * Convert an AbbreviatedObjectId from hex characters (US-ASCII). 63536db18cSMatthias Sohn * 64536db18cSMatthias Sohn * @param buf 65536db18cSMatthias Sohn * the US-ASCII buffer to read from. 66536db18cSMatthias Sohn * @param offset 67536db18cSMatthias Sohn * position to read the first character from. 68536db18cSMatthias Sohn * @param end 69536db18cSMatthias Sohn * one past the last position to read (<code>end-offset</code> is 70536db18cSMatthias Sohn * the length of the string). 71536db18cSMatthias Sohn * @return the converted object id. 72536db18cSMatthias Sohn */ fromString(final byte[] buf, final int offset, final int end)73536db18cSMatthias Sohn public static final AbbreviatedLongObjectId fromString(final byte[] buf, 74536db18cSMatthias Sohn final int offset, final int end) { 75536db18cSMatthias Sohn if (end - offset > Constants.LONG_OBJECT_ID_STRING_LENGTH) 76536db18cSMatthias Sohn throw new IllegalArgumentException(MessageFormat.format( 77536db18cSMatthias Sohn LfsText.get().invalidLongIdLength, 78536db18cSMatthias Sohn Integer.valueOf(end - offset), 79536db18cSMatthias Sohn Integer.valueOf(Constants.LONG_OBJECT_ID_STRING_LENGTH))); 80536db18cSMatthias Sohn return fromHexString(buf, offset, end); 81536db18cSMatthias Sohn } 82536db18cSMatthias Sohn 83536db18cSMatthias Sohn /** 84e0332bfbSMatthias Sohn * Convert an AbbreviatedObjectId from an 85e0332bfbSMatthias Sohn * {@link org.eclipse.jgit.lib.AnyObjectId}. 86536db18cSMatthias Sohn * <p> 87536db18cSMatthias Sohn * This method copies over all bits of the Id, and is therefore complete 88536db18cSMatthias Sohn * (see {@link #isComplete()}). 89536db18cSMatthias Sohn * 90536db18cSMatthias Sohn * @param id 91e0332bfbSMatthias Sohn * the {@link org.eclipse.jgit.lib.ObjectId} to convert from. 92536db18cSMatthias Sohn * @return the converted object id. 93536db18cSMatthias Sohn */ fromLongObjectId( AnyLongObjectId id)94536db18cSMatthias Sohn public static final AbbreviatedLongObjectId fromLongObjectId( 95536db18cSMatthias Sohn AnyLongObjectId id) { 96536db18cSMatthias Sohn return new AbbreviatedLongObjectId( 97536db18cSMatthias Sohn Constants.LONG_OBJECT_ID_STRING_LENGTH, id.w1, id.w2, id.w3, 98536db18cSMatthias Sohn id.w4); 99536db18cSMatthias Sohn } 100536db18cSMatthias Sohn 101536db18cSMatthias Sohn /** 102536db18cSMatthias Sohn * Convert an AbbreviatedLongObjectId from hex characters. 103536db18cSMatthias Sohn * 104536db18cSMatthias Sohn * @param str 105536db18cSMatthias Sohn * the string to read from. Must be <= 64 characters. 106536db18cSMatthias Sohn * @return the converted object id. 107536db18cSMatthias Sohn */ fromString(String str)1086d370d83SHan-Wen Nienhuys public static final AbbreviatedLongObjectId fromString(String str) { 109536db18cSMatthias Sohn if (str.length() > Constants.LONG_OBJECT_ID_STRING_LENGTH) 110536db18cSMatthias Sohn throw new IllegalArgumentException( 111536db18cSMatthias Sohn MessageFormat.format(LfsText.get().invalidLongId, str)); 112536db18cSMatthias Sohn final byte[] b = org.eclipse.jgit.lib.Constants.encodeASCII(str); 113536db18cSMatthias Sohn return fromHexString(b, 0, b.length); 114536db18cSMatthias Sohn } 115536db18cSMatthias Sohn fromHexString(final byte[] bs, int ptr, final int end)116536db18cSMatthias Sohn private static final AbbreviatedLongObjectId fromHexString(final byte[] bs, 117536db18cSMatthias Sohn int ptr, final int end) { 118536db18cSMatthias Sohn try { 119536db18cSMatthias Sohn final long a = hexUInt64(bs, ptr, end); 120536db18cSMatthias Sohn final long b = hexUInt64(bs, ptr + 16, end); 121536db18cSMatthias Sohn final long c = hexUInt64(bs, ptr + 32, end); 122536db18cSMatthias Sohn final long d = hexUInt64(bs, ptr + 48, end); 123536db18cSMatthias Sohn return new AbbreviatedLongObjectId(end - ptr, a, b, c, d); 124*4cc13297SDavid Pursehouse } catch (ArrayIndexOutOfBoundsException e) { 125*4cc13297SDavid Pursehouse InvalidLongObjectIdException e1 = new InvalidLongObjectIdException( 126*4cc13297SDavid Pursehouse bs, ptr, end - ptr); 127*4cc13297SDavid Pursehouse e1.initCause(e); 128*4cc13297SDavid Pursehouse throw e1; 129536db18cSMatthias Sohn } 130536db18cSMatthias Sohn } 131536db18cSMatthias Sohn hexUInt64(final byte[] bs, int p, final int end)132536db18cSMatthias Sohn private static final long hexUInt64(final byte[] bs, int p, final int end) { 133536db18cSMatthias Sohn if (16 <= end - p) 134536db18cSMatthias Sohn return RawParseUtils.parseHexInt64(bs, p); 135536db18cSMatthias Sohn 136536db18cSMatthias Sohn long r = 0; 137536db18cSMatthias Sohn int n = 0; 138536db18cSMatthias Sohn while (n < 16 && p < end) { 139536db18cSMatthias Sohn r <<= 4; 140536db18cSMatthias Sohn r |= RawParseUtils.parseHexInt4(bs[p++]); 141536db18cSMatthias Sohn n++; 142536db18cSMatthias Sohn } 1434e8a3df6SDavid Pursehouse return r << ((16 - n) * 4); 144536db18cSMatthias Sohn } 145536db18cSMatthias Sohn mask(int nibbles, long word, long v)146f3ec7cf3SHan-Wen Nienhuys static long mask(int nibbles, long word, long v) { 147536db18cSMatthias Sohn final long b = (word - 1) * 16; 148536db18cSMatthias Sohn if (b + 16 <= nibbles) { 149536db18cSMatthias Sohn // We have all of the bits required for this word. 150536db18cSMatthias Sohn // 151536db18cSMatthias Sohn return v; 152536db18cSMatthias Sohn } 153536db18cSMatthias Sohn 154536db18cSMatthias Sohn if (nibbles <= b) { 155536db18cSMatthias Sohn // We have none of the bits required for this word. 156536db18cSMatthias Sohn // 157536db18cSMatthias Sohn return 0; 158536db18cSMatthias Sohn } 159536db18cSMatthias Sohn 160536db18cSMatthias Sohn final long s = 64 - (nibbles - b) * 4; 161536db18cSMatthias Sohn return (v >>> s) << s; 162536db18cSMatthias Sohn } 163536db18cSMatthias Sohn 164536db18cSMatthias Sohn /** Number of half-bytes used by this id. */ 165536db18cSMatthias Sohn final int nibbles; 166536db18cSMatthias Sohn 167536db18cSMatthias Sohn final long w1; 168536db18cSMatthias Sohn 169536db18cSMatthias Sohn final long w2; 170536db18cSMatthias Sohn 171536db18cSMatthias Sohn final long w3; 172536db18cSMatthias Sohn 173536db18cSMatthias Sohn final long w4; 174536db18cSMatthias Sohn AbbreviatedLongObjectId(final int n, final long new_1, final long new_2, final long new_3, final long new_4)175536db18cSMatthias Sohn AbbreviatedLongObjectId(final int n, final long new_1, final long new_2, 176536db18cSMatthias Sohn final long new_3, final long new_4) { 177536db18cSMatthias Sohn nibbles = n; 178536db18cSMatthias Sohn w1 = new_1; 179536db18cSMatthias Sohn w2 = new_2; 180536db18cSMatthias Sohn w3 = new_3; 181536db18cSMatthias Sohn w4 = new_4; 182536db18cSMatthias Sohn } 183536db18cSMatthias Sohn 184e0332bfbSMatthias Sohn /** 185e0332bfbSMatthias Sohn * Get length 186e0332bfbSMatthias Sohn * 187e0332bfbSMatthias Sohn * @return number of hex digits appearing in this id. 188e0332bfbSMatthias Sohn */ length()189536db18cSMatthias Sohn public int length() { 190536db18cSMatthias Sohn return nibbles; 191536db18cSMatthias Sohn } 192536db18cSMatthias Sohn 193e0332bfbSMatthias Sohn /** 194e0332bfbSMatthias Sohn * Check if this id is complete 195e0332bfbSMatthias Sohn * 196e0332bfbSMatthias Sohn * @return true if this ObjectId is actually a complete id. 197e0332bfbSMatthias Sohn */ isComplete()198536db18cSMatthias Sohn public boolean isComplete() { 199536db18cSMatthias Sohn return length() == Constants.LONG_OBJECT_ID_STRING_LENGTH; 200536db18cSMatthias Sohn } 201536db18cSMatthias Sohn 202e0332bfbSMatthias Sohn /** 203e0332bfbSMatthias Sohn * Convert to LongObjectId 204e0332bfbSMatthias Sohn * 205e0332bfbSMatthias Sohn * @return a complete ObjectId; null if {@link #isComplete()} is false. 206e0332bfbSMatthias Sohn */ toLongObjectId()207536db18cSMatthias Sohn public LongObjectId toLongObjectId() { 208536db18cSMatthias Sohn return isComplete() ? new LongObjectId(w1, w2, w3, w4) : null; 209536db18cSMatthias Sohn } 210536db18cSMatthias Sohn 211536db18cSMatthias Sohn /** 212536db18cSMatthias Sohn * Compares this abbreviation to a full object id. 213536db18cSMatthias Sohn * 214536db18cSMatthias Sohn * @param other 215536db18cSMatthias Sohn * the other object id. 216536db18cSMatthias Sohn * @return <0 if this abbreviation names an object that is less than 217536db18cSMatthias Sohn * <code>other</code>; 0 if this abbreviation exactly matches the 218536db18cSMatthias Sohn * first {@link #length()} digits of <code>other.name()</code>; 219536db18cSMatthias Sohn * >0 if this abbreviation names an object that is after 220536db18cSMatthias Sohn * <code>other</code>. 221536db18cSMatthias Sohn */ prefixCompare(AnyLongObjectId other)2226d370d83SHan-Wen Nienhuys public final int prefixCompare(AnyLongObjectId other) { 223536db18cSMatthias Sohn int cmp; 224536db18cSMatthias Sohn 225536db18cSMatthias Sohn cmp = NB.compareUInt64(w1, mask(1, other.w1)); 226536db18cSMatthias Sohn if (cmp != 0) 227536db18cSMatthias Sohn return cmp; 228536db18cSMatthias Sohn 229536db18cSMatthias Sohn cmp = NB.compareUInt64(w2, mask(2, other.w2)); 230536db18cSMatthias Sohn if (cmp != 0) 231536db18cSMatthias Sohn return cmp; 232536db18cSMatthias Sohn 233536db18cSMatthias Sohn cmp = NB.compareUInt64(w3, mask(3, other.w3)); 234536db18cSMatthias Sohn if (cmp != 0) 235536db18cSMatthias Sohn return cmp; 236536db18cSMatthias Sohn 237536db18cSMatthias Sohn return NB.compareUInt64(w4, mask(4, other.w4)); 238536db18cSMatthias Sohn } 239536db18cSMatthias Sohn 240536db18cSMatthias Sohn /** 241536db18cSMatthias Sohn * Compare this abbreviation to a network-byte-order LongObjectId. 242536db18cSMatthias Sohn * 243536db18cSMatthias Sohn * @param bs 244536db18cSMatthias Sohn * array containing the other LongObjectId in network byte order. 245536db18cSMatthias Sohn * @param p 246536db18cSMatthias Sohn * position within {@code bs} to start the compare at. At least 247536db18cSMatthias Sohn * 32 bytes, starting at this position are required. 248536db18cSMatthias Sohn * @return <0 if this abbreviation names an object that is less than 249536db18cSMatthias Sohn * <code>other</code>; 0 if this abbreviation exactly matches the 250536db18cSMatthias Sohn * first {@link #length()} digits of <code>other.name()</code>; 251536db18cSMatthias Sohn * >0 if this abbreviation names an object that is after 252536db18cSMatthias Sohn * <code>other</code>. 253536db18cSMatthias Sohn */ prefixCompare(byte[] bs, int p)2546d370d83SHan-Wen Nienhuys public final int prefixCompare(byte[] bs, int p) { 255536db18cSMatthias Sohn int cmp; 256536db18cSMatthias Sohn 257536db18cSMatthias Sohn cmp = NB.compareUInt64(w1, mask(1, NB.decodeInt64(bs, p))); 258536db18cSMatthias Sohn if (cmp != 0) 259536db18cSMatthias Sohn return cmp; 260536db18cSMatthias Sohn 261536db18cSMatthias Sohn cmp = NB.compareUInt64(w2, mask(2, NB.decodeInt64(bs, p + 8))); 262536db18cSMatthias Sohn if (cmp != 0) 263536db18cSMatthias Sohn return cmp; 264536db18cSMatthias Sohn 265536db18cSMatthias Sohn cmp = NB.compareUInt64(w3, mask(3, NB.decodeInt64(bs, p + 16))); 266536db18cSMatthias Sohn if (cmp != 0) 267536db18cSMatthias Sohn return cmp; 268536db18cSMatthias Sohn 269536db18cSMatthias Sohn return NB.compareUInt64(w4, mask(4, NB.decodeInt64(bs, p + 24))); 270536db18cSMatthias Sohn } 271536db18cSMatthias Sohn 272536db18cSMatthias Sohn /** 273536db18cSMatthias Sohn * Compare this abbreviation to a network-byte-order LongObjectId. 274536db18cSMatthias Sohn * 275536db18cSMatthias Sohn * @param bs 276536db18cSMatthias Sohn * array containing the other LongObjectId in network byte order. 277536db18cSMatthias Sohn * @param p 278536db18cSMatthias Sohn * position within {@code bs} to start the compare at. At least 4 279536db18cSMatthias Sohn * longs, starting at this position are required. 280536db18cSMatthias Sohn * @return <0 if this abbreviation names an object that is less than 281536db18cSMatthias Sohn * <code>other</code>; 0 if this abbreviation exactly matches the 282536db18cSMatthias Sohn * first {@link #length()} digits of <code>other.name()</code>; 283536db18cSMatthias Sohn * >0 if this abbreviation names an object that is after 284536db18cSMatthias Sohn * <code>other</code>. 285536db18cSMatthias Sohn */ prefixCompare(long[] bs, int p)2866d370d83SHan-Wen Nienhuys public final int prefixCompare(long[] bs, int p) { 287536db18cSMatthias Sohn int cmp; 288536db18cSMatthias Sohn 289536db18cSMatthias Sohn cmp = NB.compareUInt64(w1, mask(1, bs[p])); 290536db18cSMatthias Sohn if (cmp != 0) 291536db18cSMatthias Sohn return cmp; 292536db18cSMatthias Sohn 293536db18cSMatthias Sohn cmp = NB.compareUInt64(w2, mask(2, bs[p + 1])); 294536db18cSMatthias Sohn if (cmp != 0) 295536db18cSMatthias Sohn return cmp; 296536db18cSMatthias Sohn 297536db18cSMatthias Sohn cmp = NB.compareUInt64(w3, mask(3, bs[p + 2])); 298536db18cSMatthias Sohn if (cmp != 0) 299536db18cSMatthias Sohn return cmp; 300536db18cSMatthias Sohn 301536db18cSMatthias Sohn return NB.compareUInt64(w4, mask(4, bs[p + 3])); 302536db18cSMatthias Sohn } 303536db18cSMatthias Sohn 304e0332bfbSMatthias Sohn /** 305e0332bfbSMatthias Sohn * Get the first byte of this id 306e0332bfbSMatthias Sohn * 307e0332bfbSMatthias Sohn * @return value for a fan-out style map, only valid of length >= 2. 308e0332bfbSMatthias Sohn */ getFirstByte()309536db18cSMatthias Sohn public final int getFirstByte() { 310536db18cSMatthias Sohn return (int) (w1 >>> 56); 311536db18cSMatthias Sohn } 312536db18cSMatthias Sohn mask(long word, long v)3136d370d83SHan-Wen Nienhuys private long mask(long word, long v) { 314536db18cSMatthias Sohn return mask(nibbles, word, v); 315536db18cSMatthias Sohn } 316536db18cSMatthias Sohn 317e0332bfbSMatthias Sohn /** {@inheritDoc} */ 318536db18cSMatthias Sohn @Override hashCode()319536db18cSMatthias Sohn public int hashCode() { 320536db18cSMatthias Sohn return (int) (w1 >> 32); 321536db18cSMatthias Sohn } 322536db18cSMatthias Sohn 323e0332bfbSMatthias Sohn /** {@inheritDoc} */ 324536db18cSMatthias Sohn @Override equals(Object o)3256d370d83SHan-Wen Nienhuys public boolean equals(Object o) { 326536db18cSMatthias Sohn if (o instanceof AbbreviatedLongObjectId) { 327536db18cSMatthias Sohn final AbbreviatedLongObjectId b = (AbbreviatedLongObjectId) o; 328536db18cSMatthias Sohn return nibbles == b.nibbles && w1 == b.w1 && w2 == b.w2 329536db18cSMatthias Sohn && w3 == b.w3 && w4 == b.w4; 330536db18cSMatthias Sohn } 331536db18cSMatthias Sohn return false; 332536db18cSMatthias Sohn } 333536db18cSMatthias Sohn 334536db18cSMatthias Sohn /** 335e0332bfbSMatthias Sohn * <p>name.</p> 336e0332bfbSMatthias Sohn * 337536db18cSMatthias Sohn * @return string form of the abbreviation, in lower case hexadecimal. 338536db18cSMatthias Sohn */ name()339536db18cSMatthias Sohn public final String name() { 340536db18cSMatthias Sohn final char[] b = new char[Constants.LONG_OBJECT_ID_STRING_LENGTH]; 341536db18cSMatthias Sohn 342536db18cSMatthias Sohn AnyLongObjectId.formatHexChar(b, 0, w1); 343536db18cSMatthias Sohn if (nibbles <= 16) 344536db18cSMatthias Sohn return new String(b, 0, nibbles); 345536db18cSMatthias Sohn 346536db18cSMatthias Sohn AnyLongObjectId.formatHexChar(b, 16, w2); 347536db18cSMatthias Sohn if (nibbles <= 32) 348536db18cSMatthias Sohn return new String(b, 0, nibbles); 349536db18cSMatthias Sohn 350536db18cSMatthias Sohn AnyLongObjectId.formatHexChar(b, 32, w3); 351536db18cSMatthias Sohn if (nibbles <= 48) 352536db18cSMatthias Sohn return new String(b, 0, nibbles); 353536db18cSMatthias Sohn 354536db18cSMatthias Sohn AnyLongObjectId.formatHexChar(b, 48, w4); 355536db18cSMatthias Sohn return new String(b, 0, nibbles); 356536db18cSMatthias Sohn } 357536db18cSMatthias Sohn 358e0332bfbSMatthias Sohn /** {@inheritDoc} */ 359536db18cSMatthias Sohn @SuppressWarnings("nls") 360536db18cSMatthias Sohn @Override toString()361536db18cSMatthias Sohn public String toString() { 362536db18cSMatthias Sohn return "AbbreviatedLongObjectId[" + name() + "]"; //$NON-NLS-1$ 363536db18cSMatthias Sohn } 364536db18cSMatthias Sohn } 365