View Javadoc
1   /*
2    * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.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  
11  package org.eclipse.jgit.internal.storage.file;
12  
13  import java.text.MessageFormat;
14  
15  import org.eclipse.jgit.errors.CorruptObjectException;
16  import org.eclipse.jgit.internal.JGitText;
17  import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
18  import org.eclipse.jgit.lib.ObjectId;
19  
20  /**
21   * <p>
22   * Reverse index for forward pack index. Provides operations based on offset
23   * instead of object id. Such offset-based reverse lookups are performed in
24   * O(log n) time.
25   * </p>
26   *
27   * @see PackIndex
28   * @see PackFile
29   */
30  public class PackReverseIndex {
31  	/** Index we were created from, and that has our ObjectId data. */
32  	private final PackIndex index;
33  
34  	/** The number of bytes per entry in the offsetIndex. */
35  	private final long bucketSize;
36  
37  	/**
38  	 * An index into the nth mapping, where the value is the position after the
39  	 * the last index that contains the values of the bucket. For example given
40  	 * offset o (and bucket = o / bucketSize), the offset will be contained in
41  	 * the range nth[offsetIndex[bucket - 1]] inclusive to
42  	 * nth[offsetIndex[bucket]] exclusive.
43  	 *
44  	 * See {@link #binarySearch}
45  	 */
46  	private final int[] offsetIndex;
47  
48  	/** Mapping from indices in offset order to indices in SHA-1 order. */
49  	private final int[] nth;
50  
51  	/**
52  	 * Create reverse index from straight/forward pack index, by indexing all
53  	 * its entries.
54  	 *
55  	 * @param packIndex
56  	 *            forward index - entries to (reverse) index.
57  	 */
58  	public PackReverseIndex(PackIndex packIndex) {
59  		index = packIndex;
60  
61  		final long cnt = index.getObjectCount();
62  		if (cnt + 1 > Integer.MAX_VALUE)
63  			throw new IllegalArgumentException(
64  					JGitText.get().hugeIndexesAreNotSupportedByJgitYet);
65  
66  		if (cnt == 0) {
67  			bucketSize = Long.MAX_VALUE;
68  			offsetIndex = new int[1];
69  			nth = new int[0];
70  			return;
71  		}
72  
73  		final long[] offsetsBySha1 = new long[(int) cnt];
74  
75  		long maxOffset = 0;
76  		int ith = 0;
77  		for (MutableEntry me : index) {
78  			final long o = me.getOffset();
79  			offsetsBySha1[ith++] = o;
80  			if (o > maxOffset)
81  				maxOffset = o;
82  		}
83  
84  		bucketSize = maxOffset / cnt + 1;
85  		int[] bucketIndex = new int[(int) cnt];
86  		int[] bucketValues = new int[(int) cnt + 1];
87  		for (int oi = 0; oi < offsetsBySha1.length; oi++) {
88  			final long o = offsetsBySha1[oi];
89  			final int bucket = (int) (o / bucketSize);
90  			final int bucketValuesPos = oi + 1;
91  			final int current = bucketIndex[bucket];
92  			bucketIndex[bucket] = bucketValuesPos;
93  			bucketValues[bucketValuesPos] = current;
94  		}
95  
96  		int nthByOffset = 0;
97  		nth = new int[offsetsBySha1.length];
98  		offsetIndex = bucketIndex; // Reuse the allocation
99  		for (int bi = 0; bi < bucketIndex.length; bi++) {
100 			final int start = nthByOffset;
101 			// Insertion sort of the values in the bucket.
102 			for (int vi = bucketIndex[bi]; vi > 0; vi = bucketValues[vi]) {
103 				final int nthBySha1 = vi - 1;
104 				final long o = offsetsBySha1[nthBySha1];
105 				int insertion = nthByOffset++;
106 				for (; start < insertion; insertion--) {
107 					if (o > offsetsBySha1[nth[insertion - 1]])
108 						break;
109 					nth[insertion] = nth[insertion - 1];
110 				}
111 				nth[insertion] = nthBySha1;
112 			}
113 			offsetIndex[bi] = nthByOffset;
114 		}
115 	}
116 
117 	/**
118 	 * Search for object id with the specified start offset in this pack
119 	 * (reverse) index.
120 	 *
121 	 * @param offset
122 	 *            start offset of object to find.
123 	 * @return object id for this offset, or null if no object was found.
124 	 */
125 	public ObjectId findObject(long offset) {
126 		final int ith = binarySearch(offset);
127 		if (ith < 0)
128 			return null;
129 		return index.getObjectId(nth[ith]);
130 	}
131 
132 	/**
133 	 * Search for the next offset to the specified offset in this pack (reverse)
134 	 * index.
135 	 *
136 	 * @param offset
137 	 *            start offset of previous object (must be valid-existing
138 	 *            offset).
139 	 * @param maxOffset
140 	 *            maximum offset in a pack (returned when there is no next
141 	 *            offset).
142 	 * @return offset of the next object in a pack or maxOffset if provided
143 	 *         offset was the last one.
144 	 * @throws org.eclipse.jgit.errors.CorruptObjectException
145 	 *             when there is no object with the provided offset.
146 	 */
147 	public long findNextOffset(long offset, long maxOffset)
148 			throws CorruptObjectException {
149 		final int ith = binarySearch(offset);
150 		if (ith < 0)
151 			throw new CorruptObjectException(
152 					MessageFormat.format(
153 							JGitText.get().cantFindObjectInReversePackIndexForTheSpecifiedOffset,
154 							Long.valueOf(offset)));
155 
156 		if (ith + 1 == nth.length)
157 			return maxOffset;
158 		return index.getOffset(nth[ith + 1]);
159 	}
160 
161 	int findPostion(long offset) {
162 		return binarySearch(offset);
163 	}
164 
165 	private int binarySearch(long offset) {
166 		int bucket = (int) (offset / bucketSize);
167 		int low = bucket == 0 ? 0 : offsetIndex[bucket - 1];
168 		int high = offsetIndex[bucket];
169 		while (low < high) {
170 			final int mid = (low + high) >>> 1;
171 			final long o = index.getOffset(nth[mid]);
172 			if (offset < o)
173 				high = mid;
174 			else if (offset == o)
175 				return mid;
176 			else
177 				low = mid + 1;
178 		}
179 		return -1;
180 	}
181 
182 	ObjectId findObjectByPosition(int nthPosition) {
183 		return index.getObjectId(nth[nthPosition]);
184 	}
185 }