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