1 | /* ============================================================= |
2 | * SmallSQL : a free Java DBMS library for the Java(tm) platform |
3 | * ============================================================= |
4 | * |
5 | * (C) Copyright 2004-2006, by Volker Berlin. |
6 | * |
7 | * Project Info: http://www.smallsql.de/ |
8 | * |
9 | * This library is free software; you can redistribute it and/or modify it |
10 | * under the terms of the GNU Lesser General Public License as published by |
11 | * the Free Software Foundation; either version 2.1 of the License, or |
12 | * (at your option) any later version. |
13 | * |
14 | * This library is distributed in the hope that it will be useful, but |
15 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
16 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
17 | * License for more details. |
18 | * |
19 | * You should have received a copy of the GNU Lesser General Public |
20 | * License along with this library; if not, write to the Free Software |
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, |
22 | * USA. |
23 | * |
24 | * [Java is a trademark or registered trademark of Sun Microsystems, Inc. |
25 | * in the United States and other countries.] |
26 | * |
27 | * --------------- |
28 | * LongTreeList.java |
29 | * --------------- |
30 | * Author: Volker Berlin |
31 | * |
32 | */ |
33 | package smallsql.database; |
34 | |
35 | import java.sql.*; |
36 | |
37 | /** |
38 | * This class is used to save the row posistions (RowID) list for a not unique index. |
39 | * |
40 | * Te values for RowID are long (8 byte). The value differ around the row size. The |
41 | * minimum row size is 30 byte. We calculate a medium row size of 100 bytes. |
42 | * |
43 | * We used a tree to compress and sort the list. We save the long value in 4 levels |
44 | * a 2 bytes. The first tree levels has a pointer to the next level. The end point |
45 | * of a level is the value 0. A value of 0 at first in a level means the value 0. |
46 | * The end point can only occur at second or later position and not on first position. |
47 | * |
48 | * |
49 | * @author Volker Berlin |
50 | * |
51 | */ |
52 | final class LongTreeList { |
53 | |
54 | |
55 | /*void list(){ |
56 | System.out.println("=========== size:"+size); |
57 | LongTreeListEnum listEnum = new LongTreeListEnum(); |
58 | listEnum.reset(); |
59 | |
60 | long value; |
61 | do{ |
62 | value = getNext(listEnum); |
63 | System.out.println(value); |
64 | }while(value >0); |
65 | do{ |
66 | value = getPrevious(listEnum); |
67 | System.out.println(value); |
68 | }while(value >0); |
69 | } |
70 | static public void main1(String[] argc) throws Exception{ |
71 | LongTreeList list = new LongTreeList(); |
72 | list.add( Long.MAX_VALUE/2 ); |
73 | list.list(); |
74 | list.add( Long.MAX_VALUE ); |
75 | list.list(); |
76 | list.remove( Long.MAX_VALUE/2 ); |
77 | list.list(); |
78 | list.add( 12345L ); |
79 | list.list(); |
80 | list.add( 123L ); |
81 | list.list(); |
82 | list.add( 12345678L ); |
83 | list.list(); |
84 | list.add( 12L ); |
85 | list.list(); |
86 | list.add( 1234L ); |
87 | list.list(); |
88 | list.add( 123456L ); |
89 | list.list(); |
90 | list.add( 1234567L ); |
91 | list.list(); |
92 | list.add( 123456789L ); |
93 | list.list(); |
94 | list.add( 123456790L ); |
95 | list.list(); |
96 | list.add( 1L ); |
97 | list.list(); |
98 | } |
99 | |
100 | |
101 | static public void main(String[] argc) throws Exception{ |
102 | java.util.Random random = new java.util.Random(); |
103 | LongTreeList treeList = new LongTreeList(); |
104 | java.util.ArrayList plainList = new java.util.ArrayList(); |
105 | LongTreeListEnum listEnum = new LongTreeListEnum(); |
106 | |
107 | |
108 | for(int i=1; i<1000; i++){ |
109 | long value; |
110 | |
111 | value = Math.abs(random.nextLong()) >> 6; |
112 | //System.out.println(value+" "+treeList.size); |
113 | treeList.add(value); |
114 | plainList.add(new Long(value)); |
115 | |
116 | test(treeList, listEnum, plainList); |
117 | |
118 | if( i % 2 == 0){ |
119 | int idx = Math.abs(random.nextInt()) % plainList.size(); |
120 | value = ((Long)plainList.get( idx )).longValue(); |
121 | treeList.remove(value); |
122 | plainList.remove(idx); |
123 | |
124 | test(treeList, listEnum, plainList); |
125 | } |
126 | } |
127 | } |
128 | |
129 | static void test(LongTreeList treeList, LongTreeListEnum listEnum, java.util.ArrayList plainList){ |
130 | listEnum.reset(); |
131 | int size = plainList.size(); |
132 | int count = 0; |
133 | long value2, value = -1; |
134 | do{ |
135 | value2 = value; |
136 | value = treeList.getNext(listEnum); |
137 | if(value <0)break; |
138 | if(value <= value2) throw new RuntimeException("wrong sort order:"+value+" and:"+value2); |
139 | if(!plainList.contains(new Long(value))) throw new RuntimeException("wrong value:"+value); |
140 | count++; |
141 | }while(true); |
142 | if(count != size) throw new RuntimeException("soll count:"+size+" ist count:"+count); |
143 | |
144 | value = Long.MAX_VALUE; |
145 | do{ |
146 | value2 = value; |
147 | value = treeList.getPrevious(listEnum); |
148 | if(value <0)break; |
149 | if(value >= value2) throw new RuntimeException("wrong sort order:"+value+" and:"+value2); |
150 | if(!plainList.contains(new Long(value))) throw new RuntimeException("wrong value:"+value); |
151 | count--; |
152 | }while(true); |
153 | if(count != 0) throw new RuntimeException("Prevous count is wrong:"+count); |
154 | }*/ |
155 | |
156 | |
157 | private byte[] data; |
158 | private int size; |
159 | private int offset; |
160 | static final private int pointerSize = 3; //if change then also in resize() |
161 | |
162 | /** |
163 | * Create a empty LongTreeList. |
164 | * |
165 | */ |
166 | LongTreeList(){ |
167 | data = new byte[25]; |
168 | } |
169 | |
170 | /** |
171 | * Create a LongTreeList with a first value. |
172 | * @param value |
173 | */ |
174 | LongTreeList(long value) throws SQLException{ |
175 | this(); |
176 | add(value); |
177 | } |
178 | |
179 | /** |
180 | * Restore a LongTreeList from a MemoryStream. |
181 | */ |
182 | LongTreeList(MemoryStream input) throws SQLException{ |
183 | int size = input.readInt(); |
184 | data = input.readBytes(size); |
185 | } |
186 | |
187 | |
188 | /** |
189 | * Save this list to a serial stream. This can be used to save it on a harddisk. |
190 | * @param output |
191 | */ |
192 | final void save(MemoryStream output){ |
193 | output.writeInt(size); |
194 | output.writeBytes(data, 0, size); |
195 | } |
196 | |
197 | |
198 | /** |
199 | * Add a value to this list. |
200 | * @param value |
201 | * @throws SQLException |
202 | */ |
203 | final void add(long value) throws SQLException{ |
204 | offset = 0; |
205 | if(size == 0){ |
206 | writeShort( (int)(value >> 48) ); |
207 | writePointer ( offset+pointerSize+2 ); |
208 | writeShort( 0 ); |
209 | writeShort( (int)(value >> 32) ); |
210 | writePointer ( offset+pointerSize+2 ); |
211 | writeShort( 0 ); |
212 | writeShort( (int)(value >> 16) ); |
213 | writePointer ( offset+pointerSize+2 ); |
214 | writeShort( 0 ); |
215 | writeShort( (int)(value) ); |
216 | writeShort( 0 ); |
217 | size = offset; |
218 | return; |
219 | } |
220 | int shift = 48; |
221 | boolean firstNode = (size > 2); // if this the first node in this tree level (0 can be the first node and are the end of the level) |
222 | while(shift>=0){ |
223 | int octet = (int)(value >> shift) & 0xFFFF; |
224 | while(true){ |
225 | int nextEntry = getUnsignedShort(); |
226 | if(nextEntry == octet){ |
227 | if(shift == 0) return; //value exist allready, this case should not occur |
228 | offset = getPointer(); |
229 | firstNode = true; |
230 | break; |
231 | } |
232 | if((nextEntry == 0 && !firstNode) || nextEntry > octet){ |
233 | offset -= 2; |
234 | while(true){ |
235 | if(shift != 0){ |
236 | offset = insertNode(octet); |
237 | }else{ |
238 | insertNodeLastLevel(octet); |
239 | return; |
240 | } |
241 | shift -= 16; |
242 | octet = (int)(value >> shift) & 0xFFFF; |
243 | } |
244 | } |
245 | firstNode = false; |
246 | if(shift != 0) offset += pointerSize; |
247 | } |
248 | shift -= 16; |
249 | } |
250 | } |
251 | |
252 | |
253 | /** |
254 | * Remove a value from this list. |
255 | * @param value |
256 | * @throws SQLException |
257 | */ |
258 | final void remove(long value) throws SQLException{ |
259 | if(size == 0) return; |
260 | int offset1 = 0; |
261 | int offset2 = 0; |
262 | int offset3 = 0; |
263 | offset = 0; |
264 | int shift = 48; |
265 | boolean firstNode = true; // if this the first node in this tree level (0 can be the first node and are the end of the level) |
266 | boolean firstNode1 = true; |
267 | boolean firstNode2 = true; |
268 | boolean firstNode3 = true; |
269 | while(shift>=0){ |
270 | int octet = (int)(value >> shift) & 0xFFFF; |
271 | while(true){ |
272 | int nextEntry = getUnsignedShort(); |
273 | if(nextEntry == octet){ |
274 | if(shift == 0){ |
275 | //value find |
276 | offset -= 2; |
277 | removeNodeLastLevel(); |
278 | while(firstNode && getUnsignedShort() == 0){ |
279 | offset -= 2; |
280 | removeNodeLastLevel(); // the end 0 of a node |
281 | if(shift >= 3) |
282 | break; |
283 | offset = offset1; |
284 | offset1 = offset2; |
285 | offset2 = offset3; |
286 | firstNode = firstNode1; |
287 | firstNode1 = firstNode2; |
288 | firstNode2 = firstNode3; |
289 | removeNode(); |
290 | shift++; |
291 | } |
292 | return; |
293 | } |
294 | offset3 = offset2; |
295 | offset2 = offset1; |
296 | offset1 = offset -2; |
297 | offset = getPointer(); |
298 | firstNode3 = firstNode2; |
299 | firstNode2 = firstNode1; |
300 | firstNode1 = firstNode; |
301 | firstNode = true; |
302 | break; |
303 | } |
304 | if((nextEntry == 0 && !firstNode) || nextEntry > octet){ |
305 | //value is not in the list, this should not occur |
306 | return; |
307 | } |
308 | firstNode = false; |
309 | if(shift != 0) offset += pointerSize; |
310 | } |
311 | shift -= 16; |
312 | } |
313 | } |
314 | |
315 | |
316 | /** |
317 | * Get the next long value from this list. |
318 | * @return |
319 | */ |
320 | final long getNext(LongTreeListEnum listEnum){ |
321 | int shift = (3-listEnum.stack) << 4; |
322 | if(shift >= 64) return -1; //a previous call has return -1 |
323 | offset = listEnum.offsetStack[listEnum.stack]; |
324 | long result = listEnum.resultStack[listEnum.stack]; |
325 | boolean firstNode = (offset == 0); // true if it the first entry in a level |
326 | while(true){ |
327 | int nextEntry = getUnsignedShort(); |
328 | if(nextEntry != 0 || firstNode){ |
329 | //there are more entries in this node |
330 | result |= (((long)nextEntry) << shift); |
331 | if(listEnum.stack>=3){ |
332 | listEnum.offsetStack[listEnum.stack] = offset; |
333 | return result; |
334 | } |
335 | listEnum.offsetStack[listEnum.stack] = offset+pointerSize; |
336 | offset = getPointer(); |
337 | shift -= 16; |
338 | listEnum.stack++; |
339 | listEnum.resultStack[listEnum.stack] = result; |
340 | firstNode = true; |
341 | }else{ |
342 | //no more entries in this node |
343 | shift += 16; |
344 | listEnum.stack--; |
345 | if(listEnum.stack<0) return -1; // no more entries |
346 | result = listEnum.resultStack[listEnum.stack]; |
347 | offset = listEnum.offsetStack[listEnum.stack]; |
348 | firstNode = false; |
349 | } |
350 | } |
351 | } |
352 | |
353 | |
354 | /** |
355 | * Get the next long value from this list. |
356 | * @return |
357 | */ |
358 | final long getPrevious(LongTreeListEnum listEnum){ |
359 | int shift = (3-listEnum.stack) << 4; |
360 | if(shift >= 64){ //a previous call of getNext() has return -1 |
361 | shift = 48; |
362 | offset = 0; |
363 | listEnum.stack = 0; |
364 | listEnum.offsetStack[0] = 2 + pointerSize; |
365 | loopToEndOfNode(listEnum); |
366 | }else{ |
367 | setPreviousOffset(listEnum); |
368 | } |
369 | long result = listEnum.resultStack[listEnum.stack]; |
370 | while(true){ |
371 | int nextEntry = (offset < 0) ? -1 : getUnsignedShort(); |
372 | if(nextEntry >= 0){ |
373 | // there are more entries in this node |
374 | result |= (((long)nextEntry) << shift); |
375 | if(listEnum.stack>=3){ |
376 | listEnum.offsetStack[listEnum.stack] = offset; |
377 | return result; |
378 | } |
379 | listEnum.offsetStack[listEnum.stack] = offset+pointerSize; |
380 | offset = getPointer(); |
381 | shift -= 16; |
382 | listEnum.stack++; |
383 | listEnum.resultStack[listEnum.stack] = result; |
384 | loopToEndOfNode(listEnum); |
385 | }else{ |
386 | //no more entries in this node |
387 | shift += 16; |
388 | listEnum.stack--; |
389 | if(listEnum.stack<0) return -1; // no more entries |
390 | result = listEnum.resultStack[listEnum.stack]; |
391 | setPreviousOffset(listEnum); |
392 | } |
393 | } |
394 | } |
395 | |
396 | |
397 | /** |
398 | * Is used from getPrevious(). It set the offset of the previous entry. |
399 | * If there is no previous entry in this node then set it to -1. |
400 | * The problem is that "enum" point to the next postion to optimize getNext(). |
401 | * We need 2 steps forward to find the previous entry. It can occur that |
402 | * we are in onather node. We need to verify it with the startpoint of the current node. |
403 | */ |
404 | final private void setPreviousOffset(LongTreeListEnum listEnum){ |
405 | int previousOffset = listEnum.offsetStack[listEnum.stack] - 2*(2 + (listEnum.stack>=3 ? 0 : pointerSize)); |
406 | if(listEnum.stack == 0){ |
407 | offset = previousOffset; |
408 | return; |
409 | } |
410 | offset = listEnum.offsetStack[listEnum.stack-1] - pointerSize; |
411 | int pointer = getPointer(); |
412 | if(pointer <= previousOffset){ |
413 | offset = previousOffset; |
414 | return; |
415 | } |
416 | offset = -1; |
417 | } |
418 | |
419 | |
420 | /** |
421 | * Loop to the last entry in this node. Is used from getPrevious(). |
422 | */ |
423 | final private void loopToEndOfNode(LongTreeListEnum listEnum){ |
424 | int nextEntry; |
425 | int nextOffset1, nextOffset2; |
426 | nextOffset1 = offset; |
427 | offset += 2; |
428 | if(listEnum.stack<3) |
429 | offset += pointerSize; |
430 | do{ |
431 | nextOffset2 = nextOffset1; |
432 | nextOffset1 = offset; |
433 | nextEntry = getUnsignedShort(); |
434 | if(listEnum.stack<3) |
435 | offset += pointerSize; |
436 | }while(nextEntry != 0); |
437 | offset = nextOffset2; |
438 | } |
439 | |
440 | |
441 | |
442 | |
443 | /** |
444 | * Insert a octet entry on the current offset for one of the first 3 levels. |
445 | * After it create a new node at the end (simple two 0). |
446 | * Then set it the pointer in the new entry to the new node |
447 | * @param octet a short value |
448 | * @return the offset of the new node. |
449 | */ |
450 | final private int insertNode(int octet) throws SQLException{ |
451 | int oldOffset = offset; |
452 | |
453 | if(data.length < size + 4 + pointerSize) resize(); |
454 | System.arraycopy(data, oldOffset, data, oldOffset + 2+pointerSize, size-oldOffset); |
455 | size += 2+pointerSize; |
456 | |
457 | writeShort( octet ); |
458 | writePointer( size ); |
459 | |
460 | //correct all offset that point behind the new node |
461 | correctPointers( 0, oldOffset, 2+pointerSize, 0 ); |
462 | |
463 | data[size++] = (byte)0; |
464 | data[size++] = (byte)0; |
465 | return size-2; |
466 | } |
467 | |
468 | |
469 | /** |
470 | * Insert the octet of the last level (4 level) on the current offset. |
471 | * This level does not include a pointer to a next level. |
472 | * @param octet a short value |
473 | */ |
474 | final private void insertNodeLastLevel(int octet) throws SQLException{ |
475 | int oldOffset = offset; |
476 | |
477 | if(data.length < size + 2) resize(); |
478 | System.arraycopy(data, offset, data, offset + 2, size-offset); |
479 | size += 2; |
480 | writeShort( octet ); |
481 | |
482 | //correct all offset before this new node that point behind the new node |
483 | correctPointers( 0, oldOffset, 2, 0 ); |
484 | } |
485 | |
486 | |
487 | /** |
488 | * Remove a octet entry on the current offset for one of the first 3 levels. |
489 | * Then set it the pointer in the new entry to the new node |
490 | * @param octet a short value |
491 | */ |
492 | final private void removeNode() throws SQLException{ |
493 | int oldOffset = offset; |
494 | |
495 | //correct all offset that point behind the old node |
496 | correctPointers( 0, oldOffset, -(2+pointerSize), 0 ); |
497 | |
498 | size -= 2+pointerSize; |
499 | System.arraycopy(data, oldOffset + 2+pointerSize, data, oldOffset, size-oldOffset); |
500 | |
501 | offset = oldOffset; |
502 | } |
503 | |
504 | |
505 | /** |
506 | * Remove a octet entry on the current offset for one of the first 3 levels. |
507 | * Then set it the pointer in the new entry to the new node |
508 | * @param octet a short value |
509 | */ |
510 | final private void removeNodeLastLevel() throws SQLException{ |
511 | int oldOffset = offset; |
512 | |
513 | //correct all offset that point behind the old node |
514 | correctPointers( 0, oldOffset, -2, 0 ); |
515 | |
516 | size -= 2; |
517 | System.arraycopy(data, oldOffset + 2, data, oldOffset, size-oldOffset); |
518 | |
519 | offset = oldOffset; |
520 | } |
521 | |
522 | |
523 | /** |
524 | * Correct all pointers that point behind a new entry. |
525 | * @param startOffset the startoffset of the current node |
526 | * @param oldOffset the offset of the new entry, only pointer that point behind it need to correct. |
527 | * @param diff the differenz that need added to the pointers |
528 | * @param level the stack level. There are only 3 levels with pointers. |
529 | */ |
530 | final private void correctPointers(int startOffset, int oldOffset, int diff, int level){ |
531 | offset = startOffset; |
532 | boolean firstNode = true; |
533 | while(offset < size){ |
534 | if(offset == oldOffset){ |
535 | int absDiff = Math.abs(diff); |
536 | if(absDiff == 2) return; |
537 | offset += absDiff; |
538 | firstNode = false; |
539 | continue; |
540 | } |
541 | int value = getUnsignedShort(); |
542 | if(value != 0 || firstNode){ |
543 | int pointer = getPointer(); |
544 | if(pointer > oldOffset){ |
545 | offset -= pointerSize; |
546 | writePointer( pointer + diff ); |
547 | if(diff > 0) pointer += diff; |
548 | } |
549 | if(level < 2){ |
550 | startOffset = offset; |
551 | correctPointers( pointer, oldOffset, diff, level+1); |
552 | offset = startOffset; |
553 | } |
554 | firstNode = false; |
555 | }else{ |
556 | return; |
557 | } |
558 | } |
559 | } |
560 | |
561 | |
562 | /** |
563 | * Read a pointer to another node in de index. |
564 | */ |
565 | final private int getPointer(){ |
566 | int value = 0; |
567 | for(int i=0; i<pointerSize; i++){ |
568 | value <<= 8; |
569 | value += (data[offset++] & 0xFF); |
570 | } |
571 | return value; |
572 | } |
573 | |
574 | |
575 | /** |
576 | * Write a pointer to another node in the tree list. The size depends from the constant pointerSize. |
577 | */ |
578 | final private void writePointer(int value){ |
579 | for(int i=pointerSize-1; i>=0; i--){ |
580 | data[offset++] = (byte)(value >> (i*8)); |
581 | } |
582 | } |
583 | |
584 | |
585 | /** |
586 | * Read a short value from the index. |
587 | */ |
588 | final private int getUnsignedShort(){ |
589 | return ((data[ offset++ ] & 0xFF) << 8) | (data[ offset++ ] & 0xFF); |
590 | } |
591 | |
592 | |
593 | /** |
594 | * Save a short value in the index. The long values are saved in 4 short values- |
595 | * @param value |
596 | */ |
597 | final private void writeShort(int value){ |
598 | data[offset++] = (byte)(value >> 8); |
599 | data[offset++] = (byte)(value); |
600 | } |
601 | |
602 | |
603 | /** |
604 | * Increment the buffer size for the list. |
605 | */ |
606 | private final void resize() throws SQLException{ |
607 | int newsize = data.length << 1; |
608 | if(newsize > 0xFFFFFF){ //see pointerSize |
609 | newsize = 0xFFFFFF; |
610 | if(newsize == data.length) throw Utils.createSQLException("To many equals entry in Index."); |
611 | } |
612 | byte[] temp = new byte[newsize]; |
613 | System.arraycopy(data, 0, temp, 0, data.length); |
614 | data = temp; |
615 | } |
616 | |
617 | final int getSize() { |
618 | return size; |
619 | } |
620 | } |