001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/dbaseapi/DBaseIndex.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036
037 package org.deegree.io.dbaseapi;
038
039 import java.io.File;
040 import java.io.FileNotFoundException;
041 import java.io.IOException;
042 import java.io.RandomAccessFile;
043 import java.util.ArrayList;
044 import java.util.Hashtable;
045 import java.util.LinkedList;
046 import java.util.ListIterator;
047 import java.util.Stack;
048
049 import org.deegree.model.spatialschema.ByteUtils;
050
051 /**
052 * <p>
053 * A class for reading from and writing to DBase index files (*.ndx), maybe not 100% xbase
054 * compatible!
055 * </p>
056 *
057 * <p>
058 * The fileformat is described at http://www.e-bachmann.dk/computing/databases/xbase/index.html
059 * </p>
060 *
061 * <p>
062 * This index is suitable for indexing both unique and non-unique columns. Unique indexing is much
063 * faster than non-unique because it use a faster algorithm.
064 * </p>
065 *
066 * <p>
067 * The index file is a B+tree (sometimes called a paged B-tree) that consist of pages. There are two
068 * page types, leaves and non-leafs. The starting page (eg. the page the search algorithm starts) is
069 * the root page.
070 * </p>
071 *
072 * <p>
073 * <b>Searching goes as follows:</b>
074 * <ul>
075 * <li>load the root page (eg. the starting page)</li>
076 * <li>if the page is a leaf
077 * <ul>
078 * <li>search for the requested key</li>
079 * </ul>
080 * </li>
081 * <li>if the page is not a leaf (eg. the page has subpages)
082 * <ul>
083 * <li>search for a key that is equal to or bigger than the requested key</li>
084 * <li>load the lower page</li>
085 * <li>continue searching inside the lower page</li>
086 * </ul>
087 * </li>
088 * </ul>
089 * </p>
090 *
091 * <p>
092 * Above algorithm is implemented in two different methods, one for unique indexes and one for
093 * non-unique indexes. Searching unique indexes is easier because the algorithm is finished as soon
094 * as it has found a key, the non-unique version of the algorithm has to find all keys present in
095 * the index.
096 * <p>
097 *
098 * <p>
099 * <b>Inserting goes as follows:</b>
100 * <ul>
101 * <li>find the leaf page the key has to insert in</li>
102 * <li>insert the key in the leaf page</li>
103 * <li>if the leaf page is full (eg. validEntries > noOfKeysPerPage)
104 * <ul>
105 * <li>split the leaf page (results in two leaf pages)</li>
106 * <li>add the first item of the new page to the parent page</li>
107 * </ul>
108 * </li>
109 * <li>if the parent page is also full
110 * <ul>
111 * <li>split the parent page
112 * <li>
113 * <li>add the first item of the new page to it's parent page</li>
114 * <li>etc.</li>
115 * </ul>
116 * </li>
117 * </ul>
118 * </p>
119 *
120 * <p>
121 * If a page that splits does not have a parent page then a new page is created. This page is the
122 * new starting page
123 * </p>
124 *
125 * <p>
126 * Handling different data types: The index can handle strings and numbers. Numbers are always
127 * stored als IEEE doubles. The method addKey checks the given key and throws an exception if the
128 * datatype of the key doesn't suit the index
129 * </p>
130 *
131 * @author Reijer Copier, email: reijer.copier@idgis.nl
132 */
133
134 public class DBaseIndex {
135 // The filename we use (this variable is used by toString)
136 private String fileName;
137
138 // The random access file we use
139 protected RandomAccessFile file;
140
141 // Attributes stored in the .ndx header
142 protected int startingPageNo;
143
144 // Attributes stored in the .ndx header
145 protected int numberOfPages;
146
147 // Attributes stored in the .ndx header
148 protected int sizeOfKeyRecord;
149
150 // Attributes stored in the .ndx header
151 protected int keyLength;
152
153 // Attributes stored in the .ndx header
154 protected int noOfKeysPerPage;
155
156 // Attributes stored in the .ndx header
157 protected int keyType;
158
159 private boolean uniqueFlag;
160
161 // Buffers
162 protected byte[] b = new byte[4];
163
164 // Buffers
165 protected byte[] page = new byte[512];
166
167 // Buffers
168 protected byte[] keyBytes;
169
170 // Cache size
171 protected int cacheSize = 20;
172
173 // Cache
174 private Cache cache = new Cache();
175
176 /**
177 * Inner class for the cache. The cache remembers recently used pages.
178 */
179 public class Cache {
180
181 /**
182 * Inner class for the cache items
183 */
184
185 class Item implements Comparable {
186 /**
187 * Create a new item with the given page
188 */
189 Item( Page p ) {
190 this.p = p;
191 timeStamp = System.currentTimeMillis();
192 }
193
194 /**
195 * Mark the item as used (eg. create a new time stamp)
196 */
197 void use() {
198 timeStamp = System.currentTimeMillis();
199 }
200
201 long timeStamp;
202
203 Page p;
204
205 /**
206 * Compare the time stamp from this object to the time stamp of another object
207 */
208 public int compareTo( Object o ) {
209 return new Long( timeStamp ).compareTo( new Long( ( (Item) o ).timeStamp ) );
210 }
211 }
212
213 private Hashtable<Integer, Item> pages;
214
215 private LinkedList<Item> cacheItems;
216
217 /**
218 * Create a new cache
219 */
220 public Cache() {
221 pages = new Hashtable<Integer, Item>();
222 cacheItems = new LinkedList<Item>();
223 }
224
225 /**
226 * Remove an item from the cache (this method searches for the last used item)
227 */
228 void removeItem()
229 throws IOException {
230 Item i = cacheItems.removeFirst();
231
232 if ( i.p.onStoreList )
233 i.p.write();
234
235 pages.remove( new Integer( i.p.number ) );
236 }
237
238 /**
239 * Insert a new item into the cache
240 */
241 public void insert( int number, Page p )
242 throws IOException {
243 Item i = new Item( p );
244
245 pages.put( new Integer( number ), i );
246 cacheItems.addLast( i );
247
248 if ( cacheItems.size() > cacheSize )
249 removeItem();
250 }
251
252 /**
253 * Get a page form the cache
254 *
255 * @return returns the addressed page or <code>null</code>
256 */
257 public Page get( int number ) {
258 Item item = pages.get( new Integer( number ) );
259
260 if ( item != null ) {
261 cacheItems.remove( item );
262 item.use();
263 cacheItems.addLast( item );
264
265 return item.p;
266 }
267 return null;
268 }
269
270 /**
271 * Flush the cache (eg. store modified pages)
272 */
273 public void flush() {
274 ListIterator i = cacheItems.listIterator();
275
276 while ( i.hasNext() ) {
277 Item item = (Item) i.next();
278
279 try {
280 if ( item.p.onStoreList ) {
281 item.p.write();
282 }
283 } catch ( IOException e ) {
284 e.printStackTrace();
285 }
286 }
287
288 cacheItems.clear();
289 pages.clear();
290 }
291 }
292
293 /**
294 * Inner class for the key entries
295 */
296 private class KeyEntry {
297 // Lower pointer and record number
298 int lower;
299
300 // Lower pointer and record number
301 int record;
302
303 // Data
304 Comparable data;
305
306 /**
307 * Construct a new KeyEntry
308 */
309 KeyEntry( int lower, int record, Comparable data ) {
310 this.lower = lower;
311 this.record = record;
312 this.data = data;
313 }
314
315 /**
316 * Read an existing KeyEntry
317 */
318 KeyEntry( int lower, int record ) throws IOException {
319 this.lower = lower;
320 this.record = record;
321 read();
322 }
323
324 /**
325 * Compare this key entry to another key
326 */
327 int compareTo( Comparable key ) {
328 return this.data.compareTo( key );
329 }
330
331 /**
332 * Read data from current file position
333 */
334 void read()
335 throws IOException {
336 if ( keyType == 0 ) {
337 file.read( keyBytes );
338 data = new String( keyBytes ).trim();
339 } else {
340 data = new Double( file.readDouble() );
341 }
342 }
343
344 /**
345 * Write data to current file position
346 */
347 void write()
348 throws IOException {
349 if ( keyType == 0 ) {
350 byte[] currentKeyBytes = ( (String) data ).getBytes();
351 file.write( currentKeyBytes );
352 file.write( new byte[keyLength - currentKeyBytes.length] );
353 } else {
354 file.writeDouble( ( (Double) data ).doubleValue() );
355 }
356 }
357 }
358
359 /**
360 * Inner class for the pages
361 */
362
363 private class Page {
364 /**
365 * Page numer, number of valid entries and the last lower pointer
366 */
367 int number;
368
369 /**
370 * Page numer, number of valid entries and the last lower pointer
371 */
372 int validEntries;
373
374 /**
375 * Page numer, number of valid entries and the last lower pointer
376 */
377 int lastLower;
378
379 /**
380 * An array with the key entries;
381 */
382 KeyEntry[] entries = new KeyEntry[noOfKeysPerPage + 1];
383
384 /**
385 * Is this page on the store list?
386 */
387 boolean onStoreList;
388
389 /**
390 * This constructor is only used by newPage(), it creates an empty page
391 */
392 Page() {
393 validEntries = 0;
394 lastLower = 0;
395 onStoreList = true;
396 }
397
398 /**
399 * This constructor is only used by getPage(), it loads a page from the file
400 */
401 Page( int number ) throws IOException {
402 this.number = number;
403 onStoreList = false;
404
405 // Seek to the page
406 file.seek( number * 512 );
407 // Read the number of valid entries
408 file.read( b );
409 validEntries = ByteUtils.readLEInt( b, 0 );
410
411 // Read the key entries
412 for ( int i = 0; i < validEntries; i++ ) {
413 int lower, record;
414
415 // Read the lower pointer
416 file.read( b );
417 lower = ByteUtils.readLEInt( b, 0 );
418
419 // Read the record number
420 file.read( b );
421 record = ByteUtils.readLEInt( b, 0 );
422
423 // Store the key in the array
424 entries[i] = new KeyEntry( lower, record );
425
426 // Skip some unused bytes
427 file.skipBytes( sizeOfKeyRecord - ( keyLength + 8 ) );
428 }
429 // Read the last lower pointer
430 file.read( b );
431 lastLower = ByteUtils.readLEInt( b, 0 );
432 }
433
434 /**
435 * Write the page to disk
436 */
437 void write()
438 throws IOException {
439 file.seek( number * 512 );
440 // Write the number of valid entries
441 ByteUtils.writeLEInt( b, 0, validEntries );
442 file.write( b );
443
444 // Write all the key entries
445 for ( int i = 0; i < validEntries; i++ ) {
446 // Write the lower pointer
447 ByteUtils.writeLEInt( b, 0, entries[i].lower );
448 file.write( b );
449
450 // Write the the recordnumber
451 ByteUtils.writeLEInt( b, 0, entries[i].record );
452 file.write( b );
453
454 // Write the key
455 entries[i].write();
456
457 for ( int j = 0; j < keyLength - keyBytes.length; j++ )
458 file.write( 0x20 );
459
460 file.skipBytes( sizeOfKeyRecord - ( keyLength + 8 ) );
461 }
462 // Write the last lower pointer
463 ByteUtils.writeLEInt( b, 0, lastLower );
464 file.write( b );
465
466 long size = ( ( number + 1 ) * 512 ) - file.getFilePointer();
467 file.write( new byte[(int) size] );
468 }
469
470 /**
471 * This method is called if saving is needed
472 */
473 void store() {
474 onStoreList = true;
475 }
476
477 /**
478 * Search in this page (and lower pages)
479 */
480 int search( Comparable key, Stack<Integer> searchStack )
481 throws IOException {
482 if ( validEntries == 0 ) // Page is empty
483 {
484 return -number;
485 }
486
487 if ( entries[0].lower == 0 ) // This page is a leaf
488 {
489 for ( int i = 0; i < validEntries; i++ ) {
490 if ( entries[i].compareTo( key ) == 0 )
491
492 return entries[i].record;
493 }
494
495 return -number;
496 }
497
498 for ( int i = 0; i < validEntries; i++ ) {
499 int compare = entries[i].compareTo( key );
500
501 if ( compare == 0 || compare > 0 ) {
502 Page lowerPage = getPage( entries[i].lower );
503 if ( searchStack != null )
504 searchStack.push( new Integer( number ) );
505 return lowerPage.search( key, searchStack );
506 }
507 }
508
509 Page lowerPage = getPage( lastLower );
510 if ( searchStack != null )
511 searchStack.push( new Integer( number ) );
512 return lowerPage.search( key, searchStack );
513
514 }
515
516 /**
517 * Search in this page (and lower pages), duplicates allowed
518 */
519 ArrayList<Integer> searchDup( Comparable key )
520 throws IOException {
521 ArrayList<Integer> found = new ArrayList<Integer>( 100 );
522
523 if ( validEntries != 0 ) // Page is not emtpy
524 {
525 if ( entries[0].lower == 0 ) // Page is a leaf
526 {
527 for ( int i = 0; i < validEntries; i++ ) {
528 if ( entries[i].compareTo( key ) == 0 ) {
529 found.add( new Integer( entries[i].record ) );
530 }
531 }
532 } else {
533 for ( int i = 0; i < validEntries; i++ ) {
534 if ( entries[i].compareTo( key ) >= 0 ) {
535 ArrayList<Integer> lowerFound = getPage( entries[i].lower ).searchDup( key );
536 if ( lowerFound.size() != 0 )
537 found.addAll( lowerFound );
538 else
539 return found;
540 }
541 }
542
543 found.addAll( getPage( lastLower ).searchDup( key ) );
544 }
545 }
546
547 return found;
548 }
549
550 /**
551 * Find the insert position for a key
552 */
553 int searchDupPos( Comparable key, Stack<Integer> searchStack )
554 throws IOException {
555 if ( validEntries == 0 ) // Page is empty
556 return number;
557
558 if ( entries[0].lower == 0 ) // Page is a leaf
559 return number;
560
561 for ( int i = 0; i < validEntries; i++ ) {
562 if ( entries[i].compareTo( key ) >= 0 ) {
563 Page lowerPage = getPage( entries[i].lower );
564 searchStack.push( new Integer( number ) );
565 return lowerPage.searchDupPos( key, searchStack );
566 }
567 }
568
569 Page lowerPage = getPage( lastLower );
570 searchStack.push( new Integer( number ) );
571 return lowerPage.searchDupPos( key, searchStack );
572 }
573
574 /**
575 * Add a node to this page, this method is only called if page is non-leaf page
576 */
577 void addNode( Comparable key, int left, int right, Stack searchStack )
578 throws IOException {
579 for ( int i = 0; i < validEntries + 1; i++ ) {
580 if ( i == validEntries ) {
581 entries[i] = new KeyEntry( left, 0, key );
582 lastLower = right;
583 break;
584 }
585
586 if ( left == entries[i].lower ) {
587 for ( int j = validEntries - 1; j >= i; j-- ) {
588 entries[j + 1] = entries[j];
589 }
590 entries[i] = new KeyEntry( left, 0, key );
591 entries[i + 1].lower = right;
592 break;
593 }
594 }
595
596 validEntries++;
597
598 if ( validEntries > noOfKeysPerPage ) // Split
599 {
600 Page newPage = newPage();
601
602 int firstEntry = validEntries / 2;
603
604 KeyEntry parentKey = entries[firstEntry];
605 firstEntry++;
606
607 int j = 0;
608 for ( int i = firstEntry; i < validEntries; i++ ) {
609 newPage.entries[j] = entries[i];
610 j++;
611 }
612
613 newPage.validEntries = j;
614 validEntries -= newPage.validEntries + 1;
615
616 newPage.lastLower = lastLower;
617 lastLower = parentKey.lower;
618
619 Page parent;
620 if ( searchStack.size() == 0 ) {
621 parent = newPage();
622 setRoot( parent );
623 } else
624 parent = getPage( ( (Integer) searchStack.pop() ).intValue() );
625
626 parent.addNode( parentKey.data, number, newPage.number, searchStack );
627 }
628 store();
629 }
630
631 /**
632 * Add a key to this page, only for leaf nodes
633 */
634 void addKey( Comparable key, int record, Stack searchStack )
635 throws IOException {
636 for ( int i = 0; i < validEntries + 1; i++ ) {
637 if ( i == validEntries ) {
638 entries[validEntries] = new KeyEntry( 0, record, key );
639 break;
640 }
641
642 if ( entries[i].compareTo( key ) >= 0 ) {
643 for ( int j = validEntries - 1; j >= i; j-- ) {
644 entries[j + 1] = entries[j];
645 }
646 entries[i] = new KeyEntry( 0, record, key );
647 break;
648 }
649 }
650
651 validEntries++;
652
653 if ( validEntries == noOfKeysPerPage ) // Split
654 {
655 Page newPage = newPage();
656
657 int firstEntry = validEntries / 2 + 1;
658 if ( ( validEntries % 2 ) != 0 )
659 firstEntry++;
660
661 int j = 0;
662 for ( int i = firstEntry; i < validEntries; i++ ) {
663 newPage.entries[j] = entries[i];
664 j++;
665 }
666
667 newPage.validEntries = validEntries - firstEntry;
668 validEntries -= newPage.validEntries;
669
670 Page parent;
671 if ( searchStack.size() == 0 ) {
672 parent = newPage();
673 setRoot( parent );
674 } else
675 parent = getPage( ( (Integer) searchStack.pop() ).intValue() );
676 parent.addNode( entries[validEntries - 1].data, number, newPage.number, searchStack );
677 }
678
679 store();
680 }
681
682 /**
683 * Calculate the depth for this page
684 */
685 int getDepth()
686 throws IOException {
687 if ( validEntries == 0 ) {
688 throw new IOException( "valid entries must be > 0" );
689 }
690
691 if ( entries[0].lower == 0 ) {
692 return 1;
693 }
694 Page lowerPage = getPage( entries[0].lower );
695 return lowerPage.getDepth() + 1;
696
697 }
698
699 /**
700 * Convert the page to a string (for debugging)
701 */
702 public String toString() {
703 String s = "Number: " + number + "\nValidEntries: " + validEntries + "\n";
704
705 for ( int i = 0; i < validEntries; i++ ) {
706 s += "entry: " + i + "\n";
707
708 KeyEntry key = entries[i];
709 s += " lower: " + key.lower + "\n";
710 s += " record: " + key.record + "\n";
711 s += " data: " + key.data + "\n";
712 }
713 s += "lower: " + lastLower;
714 return s;
715 }
716 }
717
718 /**
719 * Open an existing .ndx file
720 */
721 public DBaseIndex( String name ) throws IOException {
722 File f = new File( name + ".ndx" );
723
724 if ( !f.exists() )
725 throw new FileNotFoundException();
726
727 fileName = name;
728 file = new RandomAccessFile( f, "rw" );
729
730 file.read( b );
731 startingPageNo = ByteUtils.readLEInt( b, 0 );
732
733 file.read( b );
734 numberOfPages = ByteUtils.readLEInt( b, 0 );
735
736 file.skipBytes( 4 ); // Reserved
737 file.read( b, 0, 2 );
738 keyLength = ByteUtils.readLEShort( b, 0 );
739
740 file.read( b, 0, 2 );
741 noOfKeysPerPage = ByteUtils.readLEShort( b, 0 );
742
743 file.read( b, 0, 2 );
744 keyType = ByteUtils.readLEShort( b, 0 );
745
746 file.read( b );
747 sizeOfKeyRecord = ByteUtils.readLEInt( b, 0 );
748
749 file.skipBytes( 1 ); // Reserved
750 uniqueFlag = file.readBoolean();
751
752 keyBytes = new byte[keyLength];
753 }
754
755 /**
756 * Used by createIndex()
757 */
758 private DBaseIndex( String name, int startingPageNo, int numberOfPages, int sizeOfKeyRecord, int keyLength,
759 int noOfKeysPerPage, int keyType, boolean uniqueFlag, RandomAccessFile file ) {
760 fileName = name;
761 this.startingPageNo = startingPageNo;
762 this.numberOfPages = numberOfPages;
763 this.sizeOfKeyRecord = sizeOfKeyRecord;
764 this.keyLength = keyLength;
765 this.noOfKeysPerPage = noOfKeysPerPage;
766 this.keyType = keyType;
767 this.uniqueFlag = uniqueFlag;
768 this.file = file;
769
770 keyBytes = new byte[keyLength];
771 }
772
773 /**
774 * Get a page
775 */
776 protected Page getPage( int number )
777 throws IOException {
778 Page p;
779
780 synchronized ( cache ) {
781 p = cache.get( number );
782
783 if ( p == null ) {
784 p = new Page( number );
785 cache.insert( number, p );
786 }
787 }
788
789 if ( p.validEntries != 0 ) {
790 if ( p.entries[0].lower != 0 ) {
791 Hashtable<Integer, String> test = new Hashtable<Integer, String>();
792 for ( int i = 0; i < p.validEntries; i++ ) {
793 test.put( new Integer( p.entries[i].lower ), "" );
794 }
795 test.put( new Integer( p.lastLower ), "" );
796 if ( test.size() != p.validEntries + 1 ) {
797 throw new IOException( "Error in page " + p.number );
798 }
799 }
800 }
801
802 return p;
803 }
804
805 /**
806 * Create a new page
807 */
808 protected Page newPage()
809 throws IOException {
810 Page p;
811
812 synchronized ( cache ) {
813 numberOfPages++;
814
815 p = new Page();
816 p.number = numberOfPages;
817
818 cache.insert( p.number, p );
819 p.write();
820 }
821 return p;
822 }
823
824 /**
825 * Set the root page
826 */
827 protected synchronized void setRoot( Page page ) {
828 startingPageNo = page.number;
829 }
830
831 /**
832 * Create a new index
833 */
834 public static DBaseIndex createIndex( String name, String column, int keyLength, boolean uniqueFlag, boolean numbers )
835 throws IOException {
836 RandomAccessFile file = new RandomAccessFile( name + ".ndx", "rw" );
837
838 int startingPageNo = 1, numberOfPages = 1, sizeOfKeyRecord, noOfKeysPerPage, keyType = numbers ? 1 : 0;
839
840 if ( numbers )
841 keyLength = 8;
842
843 sizeOfKeyRecord = 8 + keyLength;
844
845 while ( ( sizeOfKeyRecord % 4 ) != 0 )
846 sizeOfKeyRecord++;
847
848 noOfKeysPerPage = 504 / sizeOfKeyRecord;
849
850 byte[] b = new byte[4];
851
852 ByteUtils.writeLEInt( b, 0, startingPageNo );
853 file.write( b );
854
855 ByteUtils.writeLEInt( b, 0, numberOfPages );
856 file.write( b );
857
858 file.writeInt( 0 ); // Reserved
859
860 ByteUtils.writeLEShort( b, 0, keyLength );
861 file.write( b, 0, 2 );
862
863 ByteUtils.writeLEShort( b, 0, noOfKeysPerPage );
864 file.write( b, 0, 2 );
865
866 ByteUtils.writeLEShort( b, 0, keyType );
867 file.write( b, 0, 2 );
868
869 ByteUtils.writeLEInt( b, 0, sizeOfKeyRecord );
870 file.write( b );
871
872 file.write( 0 ); // Reserved
873
874 file.writeBoolean( uniqueFlag );
875
876 file.write( column.getBytes() );
877
878 for ( int i = 0; i < 180 - column.length(); i++ )
879 file.write( 0x20 );
880
881 for ( int i = 0; i < 820; i++ )
882 file.write( 0 );
883
884 return new DBaseIndex( name, startingPageNo, numberOfPages, sizeOfKeyRecord, keyLength, noOfKeysPerPage,
885 keyType, uniqueFlag, file );
886 }
887
888 /**
889 * Flush all the buffers
890 */
891 public void flush()
892 throws IOException {
893 file.seek( 0 );
894 ByteUtils.writeLEInt( b, 0, startingPageNo );
895 file.write( b );
896
897 ByteUtils.writeLEInt( b, 0, numberOfPages );
898 file.write( b );
899
900 cache.flush();
901 }
902
903 /**
904 * Close the index file
905 */
906 public void close()
907 throws IOException {
908 flush();
909 file.close();
910 }
911
912 public int[] search( Comparable key )
913 throws IOException, KeyNotFoundException, InvalidKeyTypeException {
914 if ( key == null )
915 throw new NullPointerException();
916
917 if ( ( keyType == 0 && !( key instanceof String ) ) || ( keyType == 1 && !( key instanceof Number ) ) ) {
918 throw new InvalidKeyTypeException( key, this );
919 }
920
921 if ( keyType == 1 && !( key instanceof Double ) ) {
922 key = new Double( ( (Number) key ).doubleValue() );
923 }
924
925 Page root = getPage( startingPageNo );
926
927 if ( uniqueFlag ) {
928 int[] retval = new int[1];
929 retval[0] = root.search( key, null );
930 if ( retval[0] < 0 ) {
931 throw new KeyNotFoundException( key, this );
932 }
933 return retval;
934 }
935 ArrayList searchResult = root.searchDup( key );
936 if ( searchResult.size() == 0 ) {
937 throw new KeyNotFoundException( key, this );
938 }
939 int[] retval = new int[searchResult.size()];
940 for ( int i = 0; i < retval.length; i++ )
941 retval[i] = ( (Integer) searchResult.get( i ) ).intValue();
942 return retval;
943
944 }
945
946 /**
947 * Add a key to the index
948 */
949 public void addKey( Comparable key, int record )
950 throws IOException, KeyAlreadyExistException, InvalidKeyTypeException, KeyTooLongException {
951 if ( key == null )
952 throw new NullPointerException();
953
954 if ( ( keyType == 0 && !( key instanceof String ) ) || ( keyType == 1 && !( key instanceof Number ) ) ) {
955 throw new InvalidKeyTypeException( key, this );
956 }
957
958 if ( keyType == 1 && !( key instanceof Double ) ) {
959 key = new Double( ( (Number) key ).doubleValue() );
960 }
961
962 if ( key instanceof String ) {
963 if ( ( (String) key ).length() > keyLength ) {
964 throw new KeyTooLongException( key, this );
965 }
966 }
967
968 Page root = getPage( startingPageNo );
969 Stack<Integer> stack = new Stack<Integer>();
970
971 if ( uniqueFlag ) {
972 int searchResult = root.search( key, stack );
973 if ( searchResult >= 0 ) {
974 throw new KeyAlreadyExistException( key, this );
975 }
976
977 getPage( -searchResult ).addKey( key, record, stack );
978 } else {
979 int searchResult = root.searchDupPos( key, stack );
980 getPage( searchResult ).addKey( key, record, stack );
981 }
982 }
983
984 /**
985 * Calculate the depth for the index
986 */
987 public int getDepth()
988 throws IOException {
989 Page root = getPage( startingPageNo );
990 return root.getDepth();
991 }
992
993 /**
994 * Contains this index unique values?
995 */
996 public boolean isUnique() {
997 return uniqueFlag;
998 }
999
1000 public String toString() {
1001 return fileName;
1002 }
1003 }