blob: da19f688307e5fb60c9b37792d0330880672bc1d [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>
Stuart McCullochbb014372012-06-07 21:57:32 +000015 */
16public class Index implements Iterable<byte[]> {
17 final static int LEAF = 0;
18 final static int INDEX = 1;
19
20 final static int SIGNATURE = 0;
21 final static int MAGIC = 0x494C4458;
22 final static int KEYSIZE = 4;
23
24 private FileChannel file;
25 final int pageSize = 4096;
26 final int keySize;
27 final int valueSize = 8;
28 final int capacity;
29 public Page root;
Stuart McCulloch2286f232012-06-15 13:27:53 +000030 final LinkedHashMap<Integer,Page> cache = new LinkedHashMap<Integer,Index.Page>();
Stuart McCullochbb014372012-06-07 21:57:32 +000031 final MappedByteBuffer settings;
32
33 private int nextPage;
34
35 class Page {
36 final static int TYPE_OFFSET = 0;
37 final static int COUNT_OFFSET = 2;
38 final static int START_OFFSET = 4;
39 final int number;
40 boolean leaf;
41 final MappedByteBuffer buffer;
42 int n = 0;
43 boolean dirty;
44
45 Page(int number) throws IOException {
46 this.number = number;
47 buffer = file.map(MapMode.READ_WRITE, ((long) number) * pageSize, pageSize);
48 n = buffer.getShort(COUNT_OFFSET);
49 int type = buffer.getShort(TYPE_OFFSET);
50 leaf = type != 0;
51 }
52
53 Page(int number, boolean leaf) throws IOException {
54 this.number = number;
55 this.leaf = leaf;
56 this.n = 0;
57 buffer = file.map(MapMode.READ_WRITE, ((long) number) * pageSize, pageSize);
58 }
59
60 Iterator<byte[]> iterator() {
61 return new Iterator<byte[]>() {
62 Iterator<byte[]> i;
63 int rover = 0;
64
65 public byte[] next() {
66 if (leaf) {
67 return k(rover++);
68 }
69
70 return i.next();
71 }
72
73 public boolean hasNext() {
74 try {
75 if (leaf)
76 return rover < n;
77 while (i == null || i.hasNext() == false) {
78 int c = (int) c(rover++);
79 i = getPage(c).iterator();
80 }
81 return i.hasNext();
Stuart McCulloch2286f232012-06-15 13:27:53 +000082 }
83 catch (IOException e) {
Stuart McCullochbb014372012-06-07 21:57:32 +000084 throw new RuntimeException(e);
85 }
86
87 }
88
89 public void remove() {
90 throw new UnsupportedOperationException();
91 }
92
93 };
94 }
95
96 void write() throws IOException {
97 buffer.putShort(COUNT_OFFSET, (short) n);
98 buffer.put(TYPE_OFFSET, (byte) (leaf ? 1 : 0));
99 buffer.force();
100 }
101
102 int compare(byte[] key, int i) {
103 int index = pos(i);
104 for (int j = 0; j < keySize; j++, index++) {
105 int a = 0;
106 if (j < key.length)
107 a = key[j] & 0xFF;
108
109 int b = buffer.get(index) & 0xFF;
110 if (a == b)
111 continue;
112
113 return a > b ? 1 : -1;
114 }
115 return 0;
116 }
117
118 int pos(int i) {
119 return START_OFFSET + size(i);
120 }
121
122 int size(int n) {
123 return n * (keySize + valueSize);
124 }
125
126 void copyFrom(Page page, int start, int length) {
127 copy(page.buffer, pos(start), buffer, pos(0), size(length));
128 }
129
130 void copy(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int length) {
131 if (srcPos < dstPos) {
132 while (length-- > 0)
133 dst.put(dstPos + length, src.get(srcPos + length));
134
135 } else {
136 while (length-- > 0)
137 dst.put(dstPos++, src.get(srcPos++));
138 }
139 }
140
141 long search(byte[] k) throws Exception {
142 int cmp = 0;
143 int i = n - 1;
144 while (i >= 0 && (cmp = compare(k, i)) < 0)
145 i--;
146
147 if (leaf) {
148 if (cmp != 0)
149 return -1;
150 return c(i);
151 }
152 long value = c(i);
153 Page child = getPage((int) value);
154 return child.search(k);
155 }
156
157 void insert(byte[] k, long v) throws IOException {
158 if (n == capacity) {
159 int t = capacity / 2;
160 Page left = allocate(leaf);
161 Page right = allocate(leaf);
162 left.copyFrom(this, 0, t);
163 left.n = t;
164 right.copyFrom(this, t, capacity - t);
165 right.n = capacity - t;
166 leaf = false;
167 set(0, left.k(0), left.number);
168 set(1, right.k(0), right.number);
169 n = 2;
170 left.write();
171 right.write();
172 }
173 insertNonFull(k, v);
174 }
175
176 byte[] k(int i) {
177 buffer.position(pos(i));
178 byte[] key = new byte[keySize];
179 buffer.get(key);
180 return key;
181 }
182
183 long c(int i) {
184 if (i < 0) {
185 System.err.println("Arghhh");
186 }
187 int index = pos(i) + keySize;
188 return buffer.getLong(index);
189 }
190
191 void set(int i, byte[] k, long v) {
192 int index = pos(i);
193 for (int j = 0; j < keySize; j++) {
194 byte a = 0;
195 if (j < k.length)
196 a = k[j];
197 buffer.put(index + j, a);
198 }
199 buffer.putLong(index + keySize, v);
200 }
201
202 void insertNonFull(byte[] k, long v) throws IOException {
203 int cmp = 0;
204 int i = n - 1;
205 while (i >= 0 && (cmp = compare(k, i)) < 0)
206 i--;
207
208 if (leaf) {
209 if (cmp != 0) {
210 i++;
211 if (i != n)
212 copy(buffer, pos(i), buffer, pos(i + 1), size(n - i));
213 }
214 set(i, k, v);
215 n++;
216 dirty = true;
217 } else {
218 long value = c(i);
219 Page child = getPage((int) value);
220
221 if (child.n == capacity) {
222 Page left = child;
223 int t = capacity / 2;
224 Page right = allocate(child.leaf);
225 right.copyFrom(left, t, capacity - t);
226 right.n = capacity - t;
227 left.n = t;
228 i++; // place to insert
229 if (i < n) // ok if at end
230 copy(buffer, pos(i), buffer, pos(i + 1), size(n - i));
231 set(i, right.k(0), right.number);
232 n++;
233 assert i < n;
234 child = right.compare(k, 0) >= 0 ? right : left;
235 left.dirty = true;
236 right.dirty = true;
237 this.dirty = true;
238 }
239 child.insertNonFull(k, v);
240 }
241 write();
242 }
243
244 public String toString() {
245 StringBuilder sb = new StringBuilder();
246 try {
247 toString(sb, "");
Stuart McCulloch2286f232012-06-15 13:27:53 +0000248 }
249 catch (IOException e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000250 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++) {
Stuart McCulloch2286f232012-06-15 13:27:53 +0000257 sb.append(String.format("%s %02d:%02d %20s %s %d\n", indent, number, i, hex(k(i), 0, 4), leaf ? "=="
258 : "->", c(i)));
Stuart McCullochbb014372012-06-07 21:57:32 +0000259 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)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000306 throw new IllegalStateException("Invalid key size for Index file. The file is " + this.keySize
307 + " and was expected to be " + this.keySize);
Stuart McCullochbb014372012-06-07 21:57:32 +0000308
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}