blob: 4414f9d5ddfa64e316f8cf4ce56b6d921e4bd8e3 [file] [log] [blame]
Stuart McCullochbb014372012-06-07 21:57:32 +00001package aQute.lib.index;
2
3import java.io.*;
4import java.nio.*;
5import java.nio.channels.*;
6import java.nio.channels.FileChannel.MapMode;
7import java.util.*;
8
9/**
10 * <pre>
11 * 0 -> 0, 122 -> 1
12 * 123 -> 123, 244 -> 2
13 * 245 -> 245, ...
14 * </pre>
15 *
16 *
17 */
18public class Index implements Iterable<byte[]> {
19 final static int LEAF = 0;
20 final static int INDEX = 1;
21
22 final static int SIGNATURE = 0;
23 final static int MAGIC = 0x494C4458;
24 final static int KEYSIZE = 4;
25
26 private FileChannel file;
27 final int pageSize = 4096;
28 final int keySize;
29 final int valueSize = 8;
30 final int capacity;
31 public Page root;
32 final LinkedHashMap<Integer, Page> cache = new LinkedHashMap<Integer, Index.Page>();
33 final MappedByteBuffer settings;
34
35 private int nextPage;
36
37 class Page {
38 final static int TYPE_OFFSET = 0;
39 final static int COUNT_OFFSET = 2;
40 final static int START_OFFSET = 4;
41 final int number;
42 boolean leaf;
43 final MappedByteBuffer buffer;
44 int n = 0;
45 boolean dirty;
46
47 Page(int number) throws IOException {
48 this.number = number;
49 buffer = file.map(MapMode.READ_WRITE, ((long) number) * pageSize, pageSize);
50 n = buffer.getShort(COUNT_OFFSET);
51 int type = buffer.getShort(TYPE_OFFSET);
52 leaf = type != 0;
53 }
54
55 Page(int number, boolean leaf) throws IOException {
56 this.number = number;
57 this.leaf = leaf;
58 this.n = 0;
59 buffer = file.map(MapMode.READ_WRITE, ((long) number) * pageSize, pageSize);
60 }
61
62 Iterator<byte[]> iterator() {
63 return new Iterator<byte[]>() {
64 Iterator<byte[]> i;
65 int rover = 0;
66
67 public byte[] next() {
68 if (leaf) {
69 return k(rover++);
70 }
71
72 return i.next();
73 }
74
75 public boolean hasNext() {
76 try {
77 if (leaf)
78 return rover < n;
79 while (i == null || i.hasNext() == false) {
80 int c = (int) c(rover++);
81 i = getPage(c).iterator();
82 }
83 return i.hasNext();
84 } catch (IOException e) {
85 throw new RuntimeException(e);
86 }
87
88 }
89
90 public void remove() {
91 throw new UnsupportedOperationException();
92 }
93
94 };
95 }
96
97 void write() throws IOException {
98 buffer.putShort(COUNT_OFFSET, (short) n);
99 buffer.put(TYPE_OFFSET, (byte) (leaf ? 1 : 0));
100 buffer.force();
101 }
102
103 int compare(byte[] key, int i) {
104 int index = pos(i);
105 for (int j = 0; j < keySize; j++, index++) {
106 int a = 0;
107 if (j < key.length)
108 a = key[j] & 0xFF;
109
110 int b = buffer.get(index) & 0xFF;
111 if (a == b)
112 continue;
113
114 return a > b ? 1 : -1;
115 }
116 return 0;
117 }
118
119 int pos(int i) {
120 return START_OFFSET + size(i);
121 }
122
123 int size(int n) {
124 return n * (keySize + valueSize);
125 }
126
127 void copyFrom(Page page, int start, int length) {
128 copy(page.buffer, pos(start), buffer, pos(0), size(length));
129 }
130
131 void copy(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int length) {
132 if (srcPos < dstPos) {
133 while (length-- > 0)
134 dst.put(dstPos + length, src.get(srcPos + length));
135
136 } else {
137 while (length-- > 0)
138 dst.put(dstPos++, src.get(srcPos++));
139 }
140 }
141
142 long search(byte[] k) throws Exception {
143 int cmp = 0;
144 int i = n - 1;
145 while (i >= 0 && (cmp = compare(k, i)) < 0)
146 i--;
147
148 if (leaf) {
149 if (cmp != 0)
150 return -1;
151 return c(i);
152 }
153 long value = c(i);
154 Page child = getPage((int) value);
155 return child.search(k);
156 }
157
158 void insert(byte[] k, long v) throws IOException {
159 if (n == capacity) {
160 int t = capacity / 2;
161 Page left = allocate(leaf);
162 Page right = allocate(leaf);
163 left.copyFrom(this, 0, t);
164 left.n = t;
165 right.copyFrom(this, t, capacity - t);
166 right.n = capacity - t;
167 leaf = false;
168 set(0, left.k(0), left.number);
169 set(1, right.k(0), right.number);
170 n = 2;
171 left.write();
172 right.write();
173 }
174 insertNonFull(k, v);
175 }
176
177 byte[] k(int i) {
178 buffer.position(pos(i));
179 byte[] key = new byte[keySize];
180 buffer.get(key);
181 return key;
182 }
183
184 long c(int i) {
185 if (i < 0) {
186 System.err.println("Arghhh");
187 }
188 int index = pos(i) + keySize;
189 return buffer.getLong(index);
190 }
191
192 void set(int i, byte[] k, long v) {
193 int index = pos(i);
194 for (int j = 0; j < keySize; j++) {
195 byte a = 0;
196 if (j < k.length)
197 a = k[j];
198 buffer.put(index + j, a);
199 }
200 buffer.putLong(index + keySize, v);
201 }
202
203 void insertNonFull(byte[] k, long v) throws IOException {
204 int cmp = 0;
205 int i = n - 1;
206 while (i >= 0 && (cmp = compare(k, i)) < 0)
207 i--;
208
209 if (leaf) {
210 if (cmp != 0) {
211 i++;
212 if (i != n)
213 copy(buffer, pos(i), buffer, pos(i + 1), size(n - i));
214 }
215 set(i, k, v);
216 n++;
217 dirty = true;
218 } else {
219 long value = c(i);
220 Page child = getPage((int) value);
221
222 if (child.n == capacity) {
223 Page left = child;
224 int t = capacity / 2;
225 Page right = allocate(child.leaf);
226 right.copyFrom(left, t, capacity - t);
227 right.n = capacity - t;
228 left.n = t;
229 i++; // place to insert
230 if (i < n) // ok if at end
231 copy(buffer, pos(i), buffer, pos(i + 1), size(n - i));
232 set(i, right.k(0), right.number);
233 n++;
234 assert i < n;
235 child = right.compare(k, 0) >= 0 ? right : left;
236 left.dirty = true;
237 right.dirty = true;
238 this.dirty = true;
239 }
240 child.insertNonFull(k, v);
241 }
242 write();
243 }
244
245 public String toString() {
246 StringBuilder sb = new StringBuilder();
247 try {
248 toString(sb, "");
249 } catch (IOException e) {
250 e.printStackTrace();
251 }
252 return sb.toString();
253 }
254
255 public void toString(StringBuilder sb, String indent) throws IOException {
256 for (int i = 0; i < n; i++) {
257 sb.append(String.format("%s %02d:%02d %20s %s %d\n", indent, number, i,
258 hex(k(i), 0, 4), leaf ? "==" : "->", c(i)));
259 if (!leaf) {
260 long c = c(i);
261 Page sub = getPage((int) c);
262 sub.toString(sb, indent + " ");
263 }
264 }
265 }
266
267 private String hex(byte[] k, int i, int j) {
268 StringBuilder sb = new StringBuilder();
269
270 while (i < j) {
271 int b = 0xFF & k[i];
272 sb.append(nibble(b >> 4));
273 sb.append(nibble(b));
274 i++;
275 }
276 return sb.toString();
277 }
278
279 private char nibble(int i) {
280 i = i & 0xF;
281 return (char) (i >= 10 ? i + 'A' - 10 : i + '0');
282 }
283
284 }
285
286 public Index(File file, int keySize) throws IOException {
287 capacity = (pageSize - Page.START_OFFSET) / (keySize + valueSize);
288 RandomAccessFile raf = new RandomAccessFile(file, "rw");
289 this.file = raf.getChannel();
290 settings = this.file.map(MapMode.READ_WRITE, 0, pageSize);
291 if (this.file.size() == pageSize) {
292 this.keySize = keySize;
293 settings.putInt(SIGNATURE, MAGIC);
294 settings.putInt(KEYSIZE, keySize);
295 nextPage = 1;
296 root = allocate(true);
297 root.n = 1;
298 root.set(0, new byte[KEYSIZE], 0);
299 root.write();
300 } else {
301 if (settings.getInt(SIGNATURE) != MAGIC)
302 throw new IllegalStateException("No Index file, magic is not " + MAGIC);
303
304 this.keySize = settings.getInt(KEYSIZE);
305 if (keySize != 0 && this.keySize != keySize)
306 throw new IllegalStateException("Invalid key size for Index file. The file is "
307 + this.keySize + " and was expected to be " + this.keySize);
308
309 root = getPage(1);
310 nextPage = (int) (this.file.size() / pageSize);
311 }
312 }
313
314 public void insert(byte[] k, long v) throws Exception {
315 root.insert(k, v);
316 }
317
318 public long search(byte[] k) throws Exception {
319 return root.search(k);
320 }
321
322 Page allocate(boolean leaf) throws IOException {
323 Page page = new Page(nextPage++, leaf);
324 cache.put(page.number, page);
325 return page;
326 }
327
328 Page getPage(int number) throws IOException {
329 Page page = cache.get(number);
330 if (page == null) {
331 page = new Page(number);
332 cache.put(number, page);
333 }
334 return page;
335 }
336
337 public String toString() {
338 return root.toString();
339 }
340
341 public void close() throws IOException {
342 file.close();
343 cache.clear();
344 }
345
346 public Iterator<byte[]> iterator() {
347 return root.iterator();
348 }
349}