View Javadoc

1   /*
2   Copyright (c) 2008 Health Market Science, Inc.
3   
4   This library is free software; you can redistribute it and/or
5   modify it under the terms of the GNU Lesser General Public
6   License as published by the Free Software Foundation; either
7   version 2.1 of the License, or (at your option) any later version.
8   
9   This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  Lesser General Public License for more details.
13  
14  You should have received a copy of the GNU Lesser General Public
15  License along with this library; if not, write to the Free Software
16  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
17  USA
18  
19  You can contact Health Market Science at info@healthmarketscience.com
20  or at the following address:
21  
22  Health Market Science
23  2700 Horizon Drive
24  Suite 200
25  King of Prussia, PA 19406
26  */
27  
28  package com.healthmarketscience.jackcess;
29  
30  import java.io.IOException;
31  import java.lang.ref.Reference;
32  import java.lang.ref.SoftReference;
33  import java.util.AbstractList;
34  import java.util.ArrayList;
35  import java.util.Collections;
36  import java.util.HashMap;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.RandomAccess;
41  
42  
43  import static com.healthmarketscience.jackcess.Index.*;
44  
45  /**
46   * Manager of the index pages for a BigIndex.
47   * @author James Ahlborn
48   */
49  public class IndexPageCache
50  {
51    private enum UpdateType {
52      ADD, REMOVE, REPLACE;
53    }
54  
55    /** the index whose pages this cache is managing */
56    private final BigIndex _index;
57    /** the root page for the index */
58    private DataPageMain _rootPage;
59    /** the currently loaded pages for this index, pageNumber -> page */
60    private final Map<Integer, DataPageMain> _dataPages =
61      new HashMap<Integer, DataPageMain>();
62    /** the currently modified index pages */
63    private final List<CacheDataPage> _modifiedPages =
64      new ArrayList<CacheDataPage>();
65    
66    public IndexPageCache(BigIndex index) {
67      _index = index;
68    }
69  
70    public BigIndex getIndex() {
71      return _index;
72    }
73    
74    public PageChannel getPageChannel() {
75      return getIndex().getPageChannel();
76    }
77  
78    /**
79     * Sets the root page for this index, must be called before normal usage.
80     *
81     * @param pageNumber the root page number
82     */
83    public void setRootPageNumber(int pageNumber) throws IOException {
84      _rootPage = getDataPage(pageNumber);
85      // root page has no parent
86      _rootPage.initParentPage(INVALID_INDEX_PAGE_NUMBER, false);
87    }
88    
89    /**
90     * Writes any outstanding changes for this index to the file.
91     */
92    public void write()
93      throws IOException
94    {
95      // first discard any empty pages
96      handleEmptyPages();
97      // next, handle any necessary page splitting
98      preparePagesForWriting();
99      // finally, write all the modified pages (which are not being deleted)
100     writeDataPages();
101   }
102 
103   /**
104    * Handles any modified pages which are empty as the first pass during a
105    * {@link #write} call.  All empty pages are removed from the _modifiedPages
106    * collection by this method.
107    */
108   private void handleEmptyPages() throws IOException
109   {
110     for(Iterator<CacheDataPage> iter = _modifiedPages.iterator();
111         iter.hasNext(); ) {
112       CacheDataPage cacheDataPage = iter.next();
113       if(cacheDataPage._extra._entryView.isEmpty()) {
114         if(!cacheDataPage._main.isRoot()) {
115           deleteDataPage(cacheDataPage);
116         } else {
117           writeDataPage(cacheDataPage);
118         }
119         iter.remove();
120       }
121     }
122   }
123   
124   /**
125    * Prepares any non-empty modified pages for writing as the second pass
126    * during a {@link #write} call.  Updates entry prefixes, promotes/demotes
127    * tail pages, and splits pages as needed.
128    */
129   private void preparePagesForWriting() throws IOException
130   {
131     boolean splitPages = false;
132     int maxPageEntrySize = getIndex().getMaxPageEntrySize();
133 
134     // we need to continue looping through all the pages until we do not split
135     // any pages (because a split may cascade up the tree)
136     do {
137       splitPages = false;
138 
139       // we might be adding to this list while iterating, so we can't use an
140       // iterator
141       for(int i = 0; i < _modifiedPages.size(); ++i) {
142 
143         CacheDataPage cacheDataPage = _modifiedPages.get(i);
144 
145         if(!cacheDataPage.isLeaf()) {
146           // see if we need to update any child tail status
147           DataPageMain dpMain = cacheDataPage._main;
148           int size = cacheDataPage._extra._entryView.size();
149           if(dpMain.hasChildTail()) {
150             if(size == 1) {
151               demoteTail(cacheDataPage);
152             } 
153           } else {
154             if(size > 1) {
155               promoteTail(cacheDataPage);
156             }
157           }
158         }
159         
160         // look for pages with more entries than can fit on a page
161         if(cacheDataPage.getTotalEntrySize() > maxPageEntrySize) {
162 
163           // make sure the prefix is up-to-date (this may have gotten
164           // discarded by one of the update entry methods)
165           cacheDataPage._extra.updateEntryPrefix();
166 
167           // now, see if the page will fit when compressed
168           if(cacheDataPage.getCompressedEntrySize() > maxPageEntrySize) {
169             // need to split this page
170             splitPages = true;
171             splitDataPage(cacheDataPage);
172           }
173         }
174       }
175       
176     } while(splitPages);
177   }
178 
179   /**
180    * Writes any non-empty modified pages as the last pass during a
181    * {@link #write} call.  Clears the _modifiedPages collection when finised.
182    */
183   private void writeDataPages() throws IOException
184   {
185     for(CacheDataPage cacheDataPage : _modifiedPages) {
186       if(cacheDataPage._extra._entryView.isEmpty()) {
187         throw new IllegalStateException("Unexpected empty page " +
188                                         cacheDataPage);
189       }
190       writeDataPage(cacheDataPage);
191     }
192     _modifiedPages.clear();
193   }
194 
195   /**
196    * Returns a CacheDataPage for the given page number, may be {@code null} if
197    * the given page number is invalid.  Loads the given page if necessary.
198    */
199   public CacheDataPage getCacheDataPage(Integer pageNumber)
200     throws IOException
201   {
202     DataPageMain main = getDataPage(pageNumber);
203     return((main != null) ? new CacheDataPage(main) : null);
204   }
205   
206   /**
207    * Returns a DataPageMain for the given page number, may be {@code null} if
208    * the given page number is invalid.  Loads the given page if necessary.
209    */
210   private DataPageMain getDataPage(Integer pageNumber)
211     throws IOException
212   {
213     DataPageMain dataPage = _dataPages.get(pageNumber);
214     if((dataPage == null) && (pageNumber > INVALID_INDEX_PAGE_NUMBER)) {
215       dataPage = readDataPage(pageNumber)._main;
216       _dataPages.put(pageNumber, dataPage);
217     }
218     return dataPage;
219   }
220 
221   /**
222    * Writes the given index page to the file.
223    */
224   private void writeDataPage(CacheDataPage cacheDataPage)
225     throws IOException
226   {
227     getIndex().writeDataPage(cacheDataPage);
228 
229     // lastly, mark the page as no longer modified
230     cacheDataPage._extra._modified = false;    
231   }
232   
233   /**
234    * Deletes the given index page from the file (clears the page).
235    */
236   private void deleteDataPage(CacheDataPage cacheDataPage)
237     throws IOException
238   {
239     // free this database page
240     getPageChannel().deallocatePage(cacheDataPage._main._pageNumber);
241 
242     // discard from our cache
243     _dataPages.remove(cacheDataPage._main._pageNumber);
244     
245     // lastly, mark the page as no longer modified
246     cacheDataPage._extra._modified = false;    
247   }
248   
249   /**
250    * Reads the given index page from the file.
251    */
252   private CacheDataPage readDataPage(Integer pageNumber)
253     throws IOException
254   {
255     DataPageMain dataPage = new DataPageMain(pageNumber);
256     DataPageExtra extra = new DataPageExtra();
257     CacheDataPage cacheDataPage = new CacheDataPage(dataPage, extra);
258     getIndex().readDataPage(cacheDataPage);
259 
260     // associate the extra info with the main data page
261     dataPage.setExtra(extra);
262     
263     return cacheDataPage;
264   }  
265 
266   /**
267    * Removes the entry with the given index from the given page.
268    *
269    * @param cacheDataPage the page from which to remove the entry
270    * @param entryIdx the index of the entry to remove
271    */
272   private void removeEntry(CacheDataPage cacheDataPage, int entryIdx)
273     throws IOException
274   {
275     updateEntry(cacheDataPage, entryIdx, null, UpdateType.REMOVE);
276   }
277 
278   /**
279    * Adds the entry to the given page at the given index.
280    *
281    * @param cacheDataPage the page to which to add the entry
282    * @param entryIdx the index at which to add the entry
283    * @param newEntry the entry to add
284    */
285   private void addEntry(CacheDataPage cacheDataPage,
286                         int entryIdx,
287                         Entry newEntry)
288     throws IOException
289   {
290     updateEntry(cacheDataPage, entryIdx, newEntry, UpdateType.ADD);
291   }
292   
293   /**
294    * Updates the entries on the given page according to the given updateType.
295    *
296    * @param cacheDataPage the page to update
297    * @param entryIdx the index at which to add/remove/replace the entry
298    * @param newEntry the entry to add/replace
299    * @param upType the type of update to make
300    */
301   private void updateEntry(CacheDataPage cacheDataPage,
302                            int entryIdx,
303                            Entry newEntry,
304                            UpdateType upType)
305     throws IOException
306   {
307     DataPageMain dpMain = cacheDataPage._main;
308     DataPageExtra dpExtra = cacheDataPage._extra;
309 
310     if(newEntry != null) {
311       validateEntryForPage(dpMain, newEntry);
312     }
313 
314     // note, it's slightly ucky, but we need to load the parent page before we
315     // start mucking with our entries because our parent may use our entries.
316     CacheDataPage parentDataPage = (!dpMain.isRoot() ?
317                                     new CacheDataPage(dpMain.getParentPage()) :
318                                     null);
319     
320     Entry oldLastEntry = dpExtra._entryView.getLast();
321     Entry oldEntry = null;
322     int entrySizeDiff = 0;
323 
324     switch(upType) {
325     case ADD:
326       dpExtra._entryView.add(entryIdx, newEntry);
327       entrySizeDiff += newEntry.size();
328       break;
329 
330     case REPLACE:
331       oldEntry = dpExtra._entryView.set(entryIdx, newEntry);
332       entrySizeDiff += newEntry.size() - oldEntry.size();
333       break;
334 
335     case REMOVE: {
336       oldEntry = dpExtra._entryView.remove(entryIdx);
337       entrySizeDiff -= oldEntry.size();
338       break;
339     }
340     default:
341       throw new RuntimeException("unknown update type " + upType);
342     }
343 
344     boolean updateLast = (oldLastEntry != dpExtra._entryView.getLast());
345     
346     // child tail entry updates do not modify the page
347     if(!updateLast || !dpMain.hasChildTail()) {
348       dpExtra._totalEntrySize += entrySizeDiff;
349       setModified(cacheDataPage);
350 
351       // for now, just clear the prefix, we'll fix it later
352       dpExtra._entryPrefix = EMPTY_PREFIX;
353     }
354 
355     if(dpExtra._entryView.isEmpty()) {
356       // this page is dead
357       removeDataPage(parentDataPage, cacheDataPage, oldLastEntry);
358       return;
359     }
360 
361     // determine if we need to update our parent page 
362     if(!updateLast || dpMain.isRoot()) {
363       // no parent
364       return;
365     }
366 
367     // the update to the last entry needs to be propagated to our parent
368     replaceParentEntry(parentDataPage, cacheDataPage, oldLastEntry);
369   }
370 
371   /**
372    * Removes an index page which has become empty.  If this page is the root
373    * page, just clears it.
374    *
375    * @param parentDataPage the parent of the removed page
376    * @param cacheDataPage the page to remove
377    * @param oldLastEntry the last entry for this page (before it was removed)
378    */
379   private void removeDataPage(CacheDataPage parentDataPage,
380                               CacheDataPage cacheDataPage,
381                               Entry oldLastEntry)
382     throws IOException
383   {
384     DataPageMain dpMain = cacheDataPage._main;
385     DataPageExtra dpExtra = cacheDataPage._extra;
386 
387     if(dpMain.hasChildTail()) {
388       throw new IllegalStateException("Still has child tail?");
389     }
390 
391     if(dpExtra._totalEntrySize != 0) {
392       throw new IllegalStateException("Empty page but size is not 0? " +
393                                       dpExtra._totalEntrySize + ", " +
394                                       cacheDataPage);
395     }
396     
397     if(dpMain.isRoot()) {
398       // clear out this page (we don't actually remove it)
399       dpExtra._entryPrefix = EMPTY_PREFIX;
400       // when the root page becomes empty, it becomes a leaf page again
401       dpMain._leaf = true;
402       return;
403     }
404 
405     // remove this page from its parent page
406     updateParentEntry(parentDataPage, cacheDataPage, oldLastEntry, null,
407                       UpdateType.REMOVE);
408 
409     // remove this page from any next/prev pages
410     removeFromPeers(cacheDataPage);
411   }
412 
413   /**
414    * Removes a now empty index page from its next and previous peers.
415    *
416    * @param cacheDataPage the page to remove
417    */
418   private void removeFromPeers(CacheDataPage cacheDataPage)
419     throws IOException
420   {
421     DataPageMain dpMain = cacheDataPage._main;
422 
423     Integer prevPageNumber = dpMain._prevPageNumber;
424     Integer nextPageNumber = dpMain._nextPageNumber;
425     
426     DataPageMain prevMain = dpMain.getPrevPage();
427     if(prevMain != null) {
428       setModified(new CacheDataPage(prevMain));
429       prevMain._nextPageNumber = nextPageNumber;
430     }
431 
432     DataPageMain nextMain = dpMain.getNextPage();
433     if(nextMain != null) {
434       setModified(new CacheDataPage(nextMain));
435       nextMain._prevPageNumber = prevPageNumber;
436     }
437   }
438 
439   /**
440    * Adds an entry for the given child page to the given parent page.
441    *
442    * @param parentDataPage the parent page to which to add the entry
443    * @param childDataPage the child from which to get the entry to add
444    */
445   private void addParentEntry(CacheDataPage parentDataPage,
446                               CacheDataPage childDataPage)
447     throws IOException
448   {
449     DataPageExtra childExtra = childDataPage._extra;
450     updateParentEntry(parentDataPage, childDataPage, null,
451                       childExtra._entryView.getLast(), UpdateType.ADD);
452   }
453   
454   /**
455    * Replaces the entry for the given child page in the given parent page.
456    *
457    * @param parentDataPage the parent page in which to replace the entry
458    * @param childDataPage the child for which the entry is being replaced
459    * @param oldEntry the old child entry for the child page
460    */
461   private void replaceParentEntry(CacheDataPage parentDataPage,
462                                   CacheDataPage childDataPage,
463                                   Entry oldEntry)
464     throws IOException
465   {
466     DataPageExtra childExtra = childDataPage._extra;
467     updateParentEntry(parentDataPage, childDataPage, oldEntry,
468                       childExtra._entryView.getLast(), UpdateType.REPLACE);
469   }
470   
471   /**
472    * Updates the entry for the given child page in the given parent page
473    * according to the given updateType.
474    *
475    * @param parentDataPage the parent page in which to update the entry
476    * @param childDataPage the child for which the entry is being updated
477    * @param oldEntry the old child entry to remove/replace
478    * @param newEntry the new child entry to replace/add
479    * @param upType the type of update to make
480    */
481   private void updateParentEntry(CacheDataPage parentDataPage,
482                                  CacheDataPage childDataPage,
483                                  Entry oldEntry, Entry newEntry,
484                                  UpdateType upType)
485     throws IOException
486   {
487     DataPageMain childMain = childDataPage._main;
488     DataPageExtra parentExtra = parentDataPage._extra;
489 
490     if(childMain.isTail() && (upType != UpdateType.REMOVE)) {
491       // for add or replace, update the child tail info before updating the
492       // parent entries
493       updateParentTail(parentDataPage, childDataPage, upType);
494     }
495 
496     if(oldEntry != null) {
497       oldEntry = oldEntry.asNodeEntry(childMain._pageNumber);
498     }
499     if(newEntry != null) {
500       newEntry = newEntry.asNodeEntry(childMain._pageNumber);
501     }
502 
503     boolean expectFound = true;
504     int idx = 0;
505     
506     switch(upType) {
507     case ADD:
508       expectFound = false;
509       idx = parentExtra._entryView.find(newEntry);
510       break;
511 
512     case REPLACE:
513     case REMOVE:
514       idx = parentExtra._entryView.find(oldEntry);
515       break;
516     
517     default:
518       throw new RuntimeException("unknown update type " + upType);
519     }
520         
521     if(idx < 0) {
522       if(expectFound) {
523         throw new IllegalStateException(
524             "Could not find child entry in parent; childEntry " + oldEntry +
525             "; parent " + parentDataPage);
526       }
527       idx = missingIndexToInsertionPoint(idx);
528     } else {
529       if(!expectFound) {
530         throw new IllegalStateException(
531             "Unexpectedly found child entry in parent; childEntry " +
532             newEntry + "; parent " + parentDataPage);
533       }
534     }
535     updateEntry(parentDataPage, idx, newEntry, upType);
536 
537     if(childMain.isTail() && (upType == UpdateType.REMOVE)) {
538       // for remove, update the child tail info after updating the parent
539       // entries
540       updateParentTail(parentDataPage, childDataPage, upType);
541     }
542   }
543 
544   /**
545    * Updates the child tail info in the given parent page according to the
546    * given updateType.
547    *
548    * @param parentDataPage the parent page in which to update the child tail
549    * @param childDataPage the child to add/replace
550    * @param upType the type of update to make
551    */
552   private void updateParentTail(CacheDataPage parentDataPage,
553                                 CacheDataPage childDataPage,
554                                 UpdateType upType)
555     throws IOException
556   {
557     DataPageMain parentMain = parentDataPage._main;
558 
559     int newChildTailPageNumber =
560       ((upType == UpdateType.REMOVE) ?
561        INVALID_INDEX_PAGE_NUMBER :
562        childDataPage._main._pageNumber);
563     if(!parentMain.isChildTailPageNumber(newChildTailPageNumber)) {
564       setModified(parentDataPage);
565       parentMain._childTailPageNumber = newChildTailPageNumber;
566     }
567   }
568   
569   /**
570    * Verifies that the given entry type (node/leaf) is valid for the given
571    * page (node/leaf).
572    *
573    * @param dpMain the page to which the entry will be added
574    * @param entry the entry being added
575    * @throws IllegalStateException if the entry type does not match the page
576    *         type
577    */
578   private void validateEntryForPage(DataPageMain dpMain, Entry entry) {
579     if(dpMain._leaf != entry.isLeafEntry()) {
580       throw new IllegalStateException(
581           "Trying to update page with wrong entry type; pageLeaf " +
582           dpMain._leaf + ", entryLeaf " + entry.isLeafEntry());
583     }
584   }
585 
586   /**
587    * Splits an index page which has too many entries on it.
588    *
589    * @param origDataPage the page to split
590    */
591   private void splitDataPage(CacheDataPage origDataPage)
592     throws IOException
593   {
594     DataPageMain origMain = origDataPage._main;
595     DataPageExtra origExtra = origDataPage._extra;
596 
597     setModified(origDataPage);
598     
599     int numEntries = origExtra._entries.size();
600     if(numEntries < 2) {
601       throw new IllegalStateException(
602           "Cannot split page with less than 2 entries " + origDataPage);
603     }
604     
605     if(origMain.isRoot()) {
606       // we can't split the root page directly, so we need to put another page
607       // between the root page and its sub-pages, and then split that page.
608       CacheDataPage newDataPage = nestRootDataPage(origDataPage);
609 
610       // now, split this new page instead
611       origDataPage = newDataPage;
612       origMain = newDataPage._main;
613       origExtra = newDataPage._extra;
614     }
615 
616     // note, it's slightly ucky, but we need to load the parent page before we
617     // start mucking with our entries because our parent may use our entries.
618     DataPageMain parentMain = origMain.getParentPage();
619     CacheDataPage parentDataPage = new CacheDataPage(parentMain);
620     
621     // note, there are many, many ways this could be improved/tweaked.  for
622     // now, we just want it to be functional...
623     // so, we will naively move half the entries from one page to a new page.
624 
625     CacheDataPage newDataPage = allocateNewCacheDataPage(
626         parentMain._pageNumber, origMain._leaf);
627     DataPageMain newMain = newDataPage._main;
628     DataPageExtra newExtra = newDataPage._extra;
629     
630     List<Entry> headEntries =
631       origExtra._entries.subList(0, ((numEntries + 1) / 2));
632 
633     // move first half of the entries from old page to new page (so we do not
634     // need to muck with any tail entries)
635     for(Entry headEntry : headEntries) {
636       newExtra._totalEntrySize += headEntry.size();
637       newExtra._entries.add(headEntry);
638     }
639     newExtra.setEntryView(newMain);
640 
641     // remove the moved entries from the old page
642     headEntries.clear();
643     origExtra._entryPrefix = EMPTY_PREFIX;
644     origExtra._totalEntrySize -= newExtra._totalEntrySize;
645 
646     // insert this new page between the old page and any previous page
647     addToPeersBefore(newDataPage, origDataPage);
648 
649     if(!newMain._leaf) {
650       // reparent the children pages of the new page
651       reparentChildren(newDataPage);
652 
653       // if the children of this page are also node pages, then the next/prev
654       // links should not cross parent boundaries (the leaf pages are linked
655       // from beginning to end, but child node pages are only linked within
656       // the same parent)
657       DataPageMain childMain = newMain.getChildPage(
658           newExtra._entryView.getLast());
659       if(!childMain._leaf) {
660         separateFromNextPeer(new CacheDataPage(childMain));
661       }
662     }
663 
664     // lastly, we need to add the new page to the parent page's entries
665     addParentEntry(parentDataPage, newDataPage);
666   }
667 
668   /**
669    * Copies the current root page info into a new page and nests this page
670    * under the root page.  This must be done when the root page needs to be
671    * split.
672    *
673    * @param rootDataPage the root data page
674    * 
675    * @return the newly created page nested under the root page
676    */
677   private CacheDataPage nestRootDataPage(CacheDataPage rootDataPage)
678     throws IOException
679   {
680     DataPageMain rootMain = rootDataPage._main;
681     DataPageExtra rootExtra = rootDataPage._extra;
682 
683     if(!rootMain.isRoot()) {
684       throw new IllegalArgumentException("should be called with root, duh");
685     }
686     
687     CacheDataPage newDataPage =
688       allocateNewCacheDataPage(rootMain._pageNumber, rootMain._leaf);
689     DataPageMain newMain = newDataPage._main;
690     DataPageExtra newExtra = newDataPage._extra;
691 
692     // move entries to new page
693     newMain._childTailPageNumber = rootMain._childTailPageNumber;
694     newExtra._entries = rootExtra._entries;
695     newExtra._entryPrefix = rootExtra._entryPrefix;
696     newExtra._totalEntrySize = rootExtra._totalEntrySize;
697     newExtra.setEntryView(newMain);
698 
699     if(!newMain._leaf) {
700       // we need to re-parent all the child pages
701       reparentChildren(newDataPage);
702     }
703       
704     // clear the root page
705     rootMain._leaf = false;
706     rootMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
707     rootExtra._entries = new ArrayList<Entry>();
708     rootExtra._entryPrefix = EMPTY_PREFIX;
709     rootExtra._totalEntrySize = 0;
710     rootExtra.setEntryView(rootMain);
711 
712     // add the new page as the first child of the root page
713     addParentEntry(rootDataPage, newDataPage);
714 
715     return newDataPage;
716   }
717   
718   /**
719    * Allocates a new index page with the given parent page and type.
720    *
721    * @param parentPageNumber the parent page for the new page
722    * @param isLeaf whether or not the new page is a leaf page
723    *
724    * @return the newly created page
725    */
726   private CacheDataPage allocateNewCacheDataPage(Integer parentPageNumber,
727                                                  boolean isLeaf)
728     throws IOException
729   {
730     DataPageMain dpMain = new DataPageMain(getPageChannel().allocateNewPage());
731     DataPageExtra dpExtra = new DataPageExtra();
732     dpMain.initParentPage(parentPageNumber, false);
733     dpMain._leaf = isLeaf;
734     dpMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
735     dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
736     dpMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
737     dpExtra._entries = new ArrayList<Entry>();
738     dpExtra._entryPrefix = EMPTY_PREFIX;
739     dpMain.setExtra(dpExtra);
740 
741     // add to our page cache
742     _dataPages.put(dpMain._pageNumber, dpMain);
743 
744     // needs to be written out
745     CacheDataPage cacheDataPage = new CacheDataPage(dpMain, dpExtra);
746     setModified(cacheDataPage);
747 
748     return cacheDataPage;
749   }
750 
751   /**
752    * Inserts the new page as a peer between the given original page and any
753    * previous peer page.
754    *
755    * @param newDataPage the new index page
756    * @param origDataPage the current index page
757    */
758   private void addToPeersBefore(CacheDataPage newDataPage,
759                                 CacheDataPage origDataPage)
760     throws IOException
761   {
762     DataPageMain origMain = origDataPage._main;
763     DataPageMain newMain = newDataPage._main;
764 
765     DataPageMain prevMain = origMain.getPrevPage();
766 
767     newMain._nextPageNumber = origMain._pageNumber;
768     newMain._prevPageNumber = origMain._prevPageNumber;
769     origMain._prevPageNumber = newMain._pageNumber;
770     
771     if(prevMain != null) {
772       setModified(new CacheDataPage(prevMain));
773       prevMain._nextPageNumber = newMain._pageNumber;
774     }
775   }
776 
777   /**
778    * Separates the given index page from any next peer page.
779    *
780    * @param cacheDataPage the index page to be separated
781    */
782   private void separateFromNextPeer(CacheDataPage cacheDataPage)
783     throws IOException
784   {
785     DataPageMain dpMain = cacheDataPage._main;
786 
787     setModified(cacheDataPage);
788 
789     DataPageMain nextMain = dpMain.getNextPage();
790     setModified(new CacheDataPage(nextMain));
791 
792     nextMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
793     dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
794   }
795   
796   /**
797    * Sets the parent info for the children of the given page to the given
798    * page.
799    *
800    * @param cacheDataPage the page whose children need to be updated
801    */
802   private void reparentChildren(CacheDataPage cacheDataPage)
803     throws IOException
804   {
805     DataPageMain dpMain = cacheDataPage._main;
806     DataPageExtra dpExtra = cacheDataPage._extra;
807 
808     // note, the "parent" page number is not actually persisted, so we do not
809     // need to mark any updated pages as modified.  for the same reason, we
810     // don't need to load the pages if not already loaded
811     for(Entry entry : dpExtra._entryView) {
812       Integer childPageNumber = entry.getSubPageNumber();
813       DataPageMain childMain = _dataPages.get(childPageNumber);
814       if(childMain != null) {
815         childMain.setParentPage(dpMain._pageNumber,
816                                 dpMain.isChildTailPageNumber(childPageNumber));
817       }
818     }
819   }
820 
821   /**
822    * Makes the tail entry of the given page a normal entry on that page, done
823    * when there is only one entry left on a page, and it is the tail.
824    *
825    * @param cacheDataPage the page whose tail must be updated
826    */
827   private void demoteTail(CacheDataPage cacheDataPage)
828     throws IOException
829   {
830     // there's only one entry on the page, and it's the tail.  make it a
831     // normal entry
832     DataPageMain dpMain = cacheDataPage._main;
833     DataPageExtra dpExtra = cacheDataPage._extra;
834 
835     setModified(cacheDataPage);
836     
837     DataPageMain tailMain = dpMain.getChildTailPage();
838     CacheDataPage tailDataPage = new CacheDataPage(tailMain);
839 
840     // move the tail entry to the last normal entry
841     updateParentTail(cacheDataPage, tailDataPage, UpdateType.REMOVE);
842     Entry tailEntry = dpExtra._entryView.demoteTail();
843     dpExtra._totalEntrySize += tailEntry.size();
844     dpExtra._entryPrefix = EMPTY_PREFIX;
845     
846     tailMain.setParentPage(dpMain._pageNumber, false);
847   }
848   
849   /**
850    * Makes the last normal entry of the given page the tail entry on that
851    * page, done when there are multiple entries on a page and no tail entry.
852    *
853    * @param cacheDataPage the page whose tail must be updated
854    */
855   private void promoteTail(CacheDataPage cacheDataPage)
856     throws IOException
857   {
858     // there's not tail currently on this page, make last entry a tail
859     DataPageMain dpMain = cacheDataPage._main;
860     DataPageExtra dpExtra = cacheDataPage._extra;
861 
862     setModified(cacheDataPage);
863     
864     DataPageMain lastMain = dpMain.getChildPage(dpExtra._entryView.getLast());
865     CacheDataPage lastDataPage = new CacheDataPage(lastMain);
866 
867     // move the "last" normal entry to the tail entry
868     updateParentTail(cacheDataPage, lastDataPage, UpdateType.ADD);
869     Entry lastEntry = dpExtra._entryView.promoteTail();
870     dpExtra._totalEntrySize -= lastEntry.size();
871     dpExtra._entryPrefix = EMPTY_PREFIX;
872 
873     lastMain.setParentPage(dpMain._pageNumber, true);
874   }
875   
876   /**
877    * Finds the index page on which the given entry does or should reside.
878    *
879    * @param e the entry to find
880    */
881   public CacheDataPage findCacheDataPage(Entry e)
882     throws IOException
883   {
884     DataPageMain curPage = _rootPage;
885     while(true) {
886 
887       if(curPage._leaf) {
888         // nowhere to go from here
889         return new CacheDataPage(curPage);
890       }
891       
892       DataPageExtra extra = curPage.getExtra();
893 
894       // need to descend
895       int idx = extra._entryView.find(e);
896       if(idx < 0) {
897         idx = missingIndexToInsertionPoint(idx);
898         if(idx == extra._entryView.size()) {
899           // just move to last child page
900           --idx;
901         }
902       }
903 
904       Entry nodeEntry = extra._entryView.get(idx);
905       curPage = curPage.getChildPage(nodeEntry);
906     }
907   }
908 
909   /**
910    * Marks the given index page as modified and saves it for writing, if
911    * necessary (if the page is already marked, does nothing).
912    *
913    * @param cacheDataPage the modified index page
914    */
915   private void setModified(CacheDataPage cacheDataPage)
916   {
917     if(!cacheDataPage._extra._modified) {
918       _modifiedPages.add(cacheDataPage);
919       cacheDataPage._extra._modified = true;
920     }
921   }
922 
923   /**
924    * Finds the valid entry prefix given the first/last entries on an index
925    * page.
926    *
927    * @param e1 the first entry on the page
928    * @param e2 the last entry on the page
929    *
930    * @return a valid entry prefix for the page
931    */
932   private static byte[] findCommonPrefix(Entry e1, Entry e2)
933   {
934     byte[] b1 = e1.getEntryBytes();
935     byte[] b2 = e2.getEntryBytes();
936     
937     int maxLen = b1.length;
938     byte[] prefix = b1;
939     if(b1.length > b2.length) {
940       maxLen = b2.length;
941       prefix = b2;
942     }
943     
944     int len = 0;
945     while((len < maxLen) && (b1[len] == b2[len])) {
946       ++len;
947     }
948     
949     if(len < prefix.length) {
950       if(len == 0) {
951         return EMPTY_PREFIX;
952       }
953       
954       // need new prefix
955       byte[] tmpPrefix = new byte[len];
956       System.arraycopy(prefix, 0, tmpPrefix, 0, len);
957       prefix = tmpPrefix;
958     }
959 
960     return prefix;
961   }
962 
963   /**
964    * Used by unit tests to validate the internal status of the index.
965    */
966   void validate() throws IOException {
967     for(DataPageMain dpMain : _dataPages.values()) {
968       DataPageExtra dpExtra = dpMain.getExtra();
969       validateEntries(dpExtra);
970       validateChildren(dpMain, dpExtra);
971       validatePeers(dpMain);
972     }
973   }
974 
975   /**
976    * Validates the entries for an index page
977    *
978    * @param dpExtra the entries to validate
979    */
980   private void validateEntries(DataPageExtra dpExtra) throws IOException {
981     int entrySize = 0;
982     Entry prevEntry = Index.FIRST_ENTRY;
983     for(Entry e : dpExtra._entries) {
984       entrySize += e.size();
985       if(prevEntry.compareTo(e) >= 0) {
986         throw new IOException("Unexpected order in index entries, " +
987                               prevEntry + " >= " + e);
988       }
989       prevEntry = e;
990     }
991     if(entrySize != dpExtra._totalEntrySize) {
992       throw new IllegalStateException("Expected size " + entrySize +
993                                       " but was " + dpExtra._totalEntrySize);
994     }
995   }
996 
997   /**
998    * Validates the children for an index page
999    *
1000    * @param dpMain the index page
1001    * @param dpExtra the child entries to validate
1002    */
1003   private void validateChildren(DataPageMain dpMain,
1004                                 DataPageExtra dpExtra) throws IOException {
1005     int childTailPageNumber = dpMain._childTailPageNumber;
1006     if(dpMain._leaf) {
1007       if(childTailPageNumber != INVALID_INDEX_PAGE_NUMBER) {
1008         throw new IllegalStateException("Leaf page has tail " + dpMain);
1009       }
1010       return;
1011     }
1012     if((dpExtra._entryView.size() == 1) && dpMain.hasChildTail()) {
1013       throw new IllegalStateException("Single child is tail " + dpMain);
1014     }
1015     for(Entry e : dpExtra._entryView) {
1016       validateEntryForPage(dpMain, e);
1017       Integer subPageNumber = e.getSubPageNumber();
1018       DataPageMain childMain = _dataPages.get(subPageNumber);
1019       if(childMain != null) {
1020         if(childMain._parentPageNumber != null) {
1021           if((int)childMain._parentPageNumber != dpMain._pageNumber) {
1022             throw new IllegalStateException("Child's parent is incorrect " +
1023                                             childMain);
1024           }
1025           boolean expectTail = ((int)subPageNumber == childTailPageNumber);
1026           if(expectTail != childMain._tail) {
1027             throw new IllegalStateException("Child tail status incorrect " +
1028                                             childMain);
1029           }
1030         }
1031         Entry lastEntry = childMain.getExtra()._entryView.getLast();
1032         if(e.compareTo(lastEntry) != 0) {
1033           throw new IllegalStateException("Invalid entry " + e +
1034                                           " but child is " + lastEntry);
1035         }
1036       }
1037     }
1038   }
1039 
1040   /**
1041    * Validates the peer pages for an index page.
1042    *
1043    * @param dpMain the index page
1044    */
1045   private void validatePeers(DataPageMain dpMain) throws IOException {
1046     DataPageMain prevMain = _dataPages.get(dpMain._prevPageNumber);
1047     if(prevMain != null) {
1048       if((int)prevMain._nextPageNumber != dpMain._pageNumber) {
1049         throw new IllegalStateException("Prev page " + prevMain +
1050                                         " does not ref " + dpMain);
1051       }
1052       validatePeerStatus(dpMain, prevMain);
1053     }
1054     
1055     DataPageMain nextMain = _dataPages.get(dpMain._nextPageNumber);
1056     if(nextMain != null) {
1057       if((int)nextMain._prevPageNumber != dpMain._pageNumber) {
1058         throw new IllegalStateException("Next page " + nextMain +
1059                                         " does not ref " + dpMain);
1060       }
1061       validatePeerStatus(dpMain, nextMain);
1062     }
1063   }
1064 
1065   /**
1066    * Validates the given peer page against the given index page
1067    *
1068    * @param dpMain the index page
1069    * @param peerMain the peer index page
1070    */
1071   private void validatePeerStatus(DataPageMain dpMain, DataPageMain peerMain)
1072     throws IOException
1073   {
1074     if(dpMain._leaf != peerMain._leaf) {
1075       throw new IllegalStateException("Mismatched peer status " +
1076                                       dpMain._leaf + " " + peerMain._leaf);
1077     }
1078     if(!dpMain._leaf) {
1079       if((dpMain._parentPageNumber != null) &&
1080          (peerMain._parentPageNumber != null) &&
1081          ((int)dpMain._parentPageNumber != (int)peerMain._parentPageNumber)) {
1082         throw new IllegalStateException("Mismatched node parents " +
1083                                         dpMain._parentPageNumber + " " +
1084                                         peerMain._parentPageNumber);
1085       }
1086     }
1087   }
1088   
1089   /**
1090    * Dumps the given index page to a StringBuilder
1091    *
1092    * @param rtn the StringBuilder to update
1093    * @param dpMain the index page to dump
1094    */
1095   private void dumpPage(StringBuilder rtn, DataPageMain dpMain) {
1096     try {
1097       CacheDataPage cacheDataPage = new CacheDataPage(dpMain);
1098       rtn.append(cacheDataPage).append("\n");
1099       if(!dpMain._leaf) {
1100         for(Entry e : cacheDataPage._extra._entryView) {
1101           DataPageMain childMain = dpMain.getChildPage(e);
1102           dumpPage(rtn, childMain);
1103         }
1104       }
1105     } catch(IOException e) {
1106       rtn.append("Page[" + dpMain._pageNumber + "]: " + e);
1107     }
1108   }
1109   
1110   @Override
1111   public String toString() {
1112     if(_rootPage == null) {
1113       return "Cache: (uninitialized)";
1114     }
1115     
1116     StringBuilder rtn = new StringBuilder("Cache: \n");
1117     dumpPage(rtn, _rootPage);
1118     return rtn.toString();
1119   }
1120 
1121   /**
1122    * Keeps track of the main info for an index page.
1123    */
1124   private class DataPageMain
1125   {
1126     public final int _pageNumber;
1127     public Integer _prevPageNumber;
1128     public Integer _nextPageNumber;
1129     public Integer _childTailPageNumber;
1130     public Integer _parentPageNumber;
1131     public boolean _leaf;
1132     public boolean _tail;
1133     private Reference<DataPageExtra> _extra;
1134 
1135     private DataPageMain(int pageNumber) {
1136       _pageNumber = pageNumber;
1137     }
1138 
1139     public IndexPageCache getCache() {
1140       return IndexPageCache.this;
1141     }
1142     
1143     public boolean isRoot() {
1144       return(this == _rootPage);
1145     }
1146     
1147     public boolean isTail() throws IOException
1148     {
1149       resolveParent();
1150       return _tail;
1151     }
1152 
1153     public boolean hasChildTail() {
1154       return((int)_childTailPageNumber != INVALID_INDEX_PAGE_NUMBER);
1155     }
1156 
1157     public boolean isChildTailPageNumber(int pageNumber) {
1158       return((int)_childTailPageNumber == pageNumber);
1159     }
1160     
1161     public DataPageMain getParentPage() throws IOException
1162     {
1163       resolveParent();
1164       return IndexPageCache.this.getDataPage(_parentPageNumber);
1165     }
1166 
1167     public void initParentPage(Integer parentPageNumber, boolean isTail) {
1168       // only set if not already set
1169       if(_parentPageNumber == null) {
1170         setParentPage(parentPageNumber, isTail);
1171       }
1172     }
1173     
1174     public void setParentPage(Integer parentPageNumber, boolean isTail) {
1175       _parentPageNumber = parentPageNumber;
1176       _tail = isTail;
1177     }
1178 
1179     public DataPageMain getPrevPage() throws IOException
1180     {
1181       return IndexPageCache.this.getDataPage(_prevPageNumber);
1182     }
1183     
1184     public DataPageMain getNextPage() throws IOException
1185     {
1186       return IndexPageCache.this.getDataPage(_nextPageNumber);
1187     }
1188     
1189     public DataPageMain getChildPage(Entry e) throws IOException
1190     {
1191       Integer childPageNumber = e.getSubPageNumber();
1192       return getChildPage(childPageNumber,
1193                           isChildTailPageNumber(childPageNumber));
1194     }
1195     
1196     public DataPageMain getChildTailPage() throws IOException
1197     {
1198       return getChildPage(_childTailPageNumber, true);
1199     }
1200 
1201     /**
1202      * Returns a child page for the given page number, updating its parent
1203      * info if necessary.
1204      */
1205     private DataPageMain getChildPage(Integer childPageNumber, boolean isTail)
1206       throws IOException
1207     {
1208       DataPageMain child = getDataPage(childPageNumber);
1209       if(child != null) {
1210         // set the parent info for this child (if necessary)
1211         child.initParentPage(_pageNumber, isTail);
1212       }
1213       return child;
1214     }
1215     
1216     public DataPageExtra getExtra() throws IOException
1217     {
1218       DataPageExtra extra = _extra.get();
1219       if(extra == null) {
1220         extra = readDataPage(_pageNumber)._extra;
1221         setExtra(extra);
1222       }
1223       
1224       return extra;
1225     }
1226 
1227     public void setExtra(DataPageExtra extra) throws IOException
1228     {
1229       extra.setEntryView(this);
1230       _extra = new SoftReference<DataPageExtra>(extra);
1231     }
1232 
1233     private void resolveParent() throws IOException {
1234       if(_parentPageNumber == null) {
1235         // the act of searching for the last entry should resolve any parent
1236         // pages along the path
1237         findCacheDataPage(getExtra()._entryView.getLast());
1238         if(_parentPageNumber == null) {
1239           throw new IllegalStateException("Parent was not resolved");
1240         }
1241       }
1242     }
1243 
1244     @Override
1245     public String toString() {
1246       return (_leaf ? "Leaf" : "Node") + "DPMain[" + _pageNumber +
1247         "] " + _prevPageNumber + ", " + _nextPageNumber + ", (" +
1248         _childTailPageNumber + ")";
1249     }
1250   }
1251 
1252   /**
1253    * Keeps track of the extra info for an index page.  This info (if
1254    * unmodified) may be re-read from disk as necessary.
1255    */
1256   private static class DataPageExtra
1257   {
1258     /** sorted collection of index entries.  this is kept in a list instead of
1259         a SortedSet because the SortedSet has lame traversal utilities */
1260     public List<Entry> _entries;
1261     public EntryListView _entryView;
1262     public byte[] _entryPrefix;
1263     public int _totalEntrySize;
1264     public boolean _modified;
1265 
1266     private DataPageExtra()
1267     {
1268     }
1269 
1270     public void setEntryView(DataPageMain main) throws IOException {
1271       _entryView = new EntryListView(main, this);
1272     }
1273     
1274     public void updateEntryPrefix() {
1275       if(_entryPrefix.length == 0) {
1276         // prefix is only related to *real* entries, tail not included
1277         _entryPrefix = findCommonPrefix(_entries.get(0),
1278                                         _entries.get(_entries.size() - 1));
1279       }
1280     }
1281     
1282     @Override
1283     public String toString() {
1284       return "DPExtra: " + _entryView;
1285     }    
1286   }
1287 
1288   /**
1289    * IndexPageCache implementation of an Index {@link DataPage}.
1290    */
1291   public static final class CacheDataPage
1292     extends Index.DataPage
1293   {
1294     public final DataPageMain _main;
1295     public final DataPageExtra _extra;
1296 
1297     private CacheDataPage(DataPageMain dataPage) throws IOExc