View Javadoc
1   /*
2   Copyright (c) 2008 Health Market Science, Inc.
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess.impl;
18  
19  import java.io.IOException;
20  import java.lang.ref.Reference;
21  import java.lang.ref.SoftReference;
22  import java.util.AbstractList;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.LinkedHashMap;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Queue;
32  import java.util.RandomAccess;
33  
34  import static com.healthmarketscience.jackcess.impl.IndexData.*;
35  import com.healthmarketscience.jackcess.impl.IndexData.DataPage;
36  
37  /**
38   * Manager of the index pages for a IndexData.
39   * @author James Ahlborn
40   */
41  public class IndexPageCache
42  {
43    private enum UpdateType {
44      ADD, REMOVE, REPLACE;
45    }
46  
47    /** max number of pages to cache (unless a write operation is in
48        progress) */
49    private static final int MAX_CACHE_SIZE = 25;
50  
51    /** the index whose pages this cache is managing */
52    private final IndexData _indexData;
53    /** the root page for the index */
54    private DataPageMain _rootPage;
55    /** the currently loaded pages for this index, pageNumber -> page */
56    private final Map<Integer, DataPageMain> _dataPages =
57      new LinkedHashMap<Integer, DataPageMain>(16, 0.75f, true) {
58      private static final long serialVersionUID = 0L;
59      @Override
60      protected boolean removeEldestEntry(Map.Entry<Integer, DataPageMain> e) {
61        // only purge when the size is too big and a logical write operation is
62        // not in progress (while an update is happening, the pages can be in
63        // flux and removing pages from the cache can cause problems)
64        if((size() > MAX_CACHE_SIZE) && !getPageChannel().isWriting()) {
65          purgeOldPages();
66        }
67        return false;
68      }
69    };
70    /** the currently modified index pages */
71    private final List<CacheDataPage> _modifiedPages =
72      new ArrayList<>();
73  
74    public IndexPageCache(IndexData indexData) {
75      _indexData = indexData;
76    }
77  
78    public IndexData getIndexData() {
79      return _indexData;
80    }
81  
82    public PageChannel getPageChannel() {
83      return getIndexData().getPageChannel();
84    }
85  
86    /**
87     * Sets the root page for this index, must be called before normal usage.
88     *
89     * @param pageNumber the root page number
90     */
91    public void setRootPageNumber(int pageNumber) throws IOException {
92      _rootPage = getDataPage(pageNumber);
93      // root page has no parent
94      _rootPage.initParentPage(INVALID_INDEX_PAGE_NUMBER, false);
95    }
96  
97    /**
98     * Writes any outstanding changes for this index to the file.
99     */
100   public void write()
101     throws IOException
102   {
103     // first discard any empty pages
104     handleEmptyPages();
105     // next, handle any necessary page splitting
106     preparePagesForWriting();
107     // finally, write all the modified pages (which are not being deleted)
108     writeDataPages();
109     // after we write everything, we can purge our cache if necessary
110     if(_dataPages.size() > MAX_CACHE_SIZE) {
111       purgeOldPages();
112     }
113   }
114 
115   /**
116    * Handles any modified pages which are empty as the first pass during a
117    * {@link #write} call.  All empty pages are removed from the _modifiedPages
118    * collection by this method.
119    */
120   private void handleEmptyPages() throws IOException
121   {
122     for(Iterator<CacheDataPage> iter = _modifiedPages.iterator();
123         iter.hasNext(); ) {
124       CacheDataPage cacheDataPage = iter.next();
125       if(cacheDataPage._extra._entryView.isEmpty()) {
126         if(!cacheDataPage._main.isRoot()) {
127           deleteDataPage(cacheDataPage);
128         } else {
129           writeDataPage(cacheDataPage);
130         }
131         iter.remove();
132       }
133     }
134   }
135 
136   /**
137    * Prepares any non-empty modified pages for writing as the second pass
138    * during a {@link #write} call.  Updates entry prefixes, promotes/demotes
139    * tail pages, and splits pages as needed.
140    */
141   private void preparePagesForWriting() throws IOException
142   {
143     boolean splitPages = false;
144     int maxPageEntrySize = getIndexData().getMaxPageEntrySize();
145 
146     // we need to continue looping through all the pages until we do not split
147     // any pages (because a split may cascade up the tree)
148     do {
149       splitPages = false;
150 
151       // we might be adding to this list while iterating, so we can't use an
152       // iterator
153       for(int i = 0; i < _modifiedPages.size(); ++i) {
154 
155         CacheDataPage cacheDataPage = _modifiedPages.get(i);
156 
157         if(!cacheDataPage.isLeaf()) {
158           // see if we need to update any child tail status
159           DataPageMain dpMain = cacheDataPage._main;
160           int size = cacheDataPage._extra._entryView.size();
161           if(dpMain.hasChildTail()) {
162             if(size == 1) {
163               demoteTail(cacheDataPage);
164             }
165           } else {
166             if(size > 1) {
167               // only a leaf page can become a tail page
168               DataPageMain lastChild = dpMain.getChildPage(
169                   cacheDataPage._extra._entryView.getLast());
170               if(lastChild._leaf) {
171                 promoteTail(cacheDataPage, lastChild);
172               }
173             }
174           }
175         }
176 
177         // look for pages with more entries than can fit on a page
178         if(cacheDataPage.getTotalEntrySize() > maxPageEntrySize) {
179 
180           // make sure the prefix is up-to-date (this may have gotten
181           // discarded by one of the update entry methods)
182           cacheDataPage._extra.updateEntryPrefix();
183 
184           // now, see if the page will fit when compressed
185           if(cacheDataPage.getCompressedEntrySize() > maxPageEntrySize) {
186             // need to split this page
187             splitPages = true;
188             splitDataPage(cacheDataPage);
189           }
190         }
191       }
192 
193     } while(splitPages);
194   }
195 
196   /**
197    * Writes any non-empty modified pages as the last pass during a
198    * {@link #write} call.  Clears the _modifiedPages collection when finised.
199    */
200   private void writeDataPages() throws IOException
201   {
202     for(CacheDataPage cacheDataPage : _modifiedPages) {
203       if(cacheDataPage._extra._entryView.isEmpty()) {
204         throw new IllegalStateException(withErrorContext(
205                 "Unexpected empty page " + cacheDataPage));
206       }
207       writeDataPage(cacheDataPage);
208     }
209     _modifiedPages.clear();
210   }
211 
212   /**
213    * Returns a CacheDataPage for the given page number, may be {@code null} if
214    * the given page number is invalid.  Loads the given page if necessary.
215    */
216   public CacheDataPage getCacheDataPage(Integer pageNumber)
217     throws IOException
218   {
219     DataPageMain main = getDataPage(pageNumber);
220     return((main != null) ? new CacheDataPage(main) : null);
221   }
222 
223   /**
224    * Returns a DataPageMain for the given page number, may be {@code null} if
225    * the given page number is invalid.  Loads the given page if necessary.
226    */
227   private DataPageMain getDataPage(Integer pageNumber)
228     throws IOException
229   {
230     DataPageMain dataPage = _dataPages.get(pageNumber);
231     if((dataPage == null) && (pageNumber > INVALID_INDEX_PAGE_NUMBER)) {
232       dataPage = readDataPage(pageNumber)._main;
233       _dataPages.put(pageNumber, dataPage);
234     }
235     return dataPage;
236   }
237 
238   /**
239    * Writes the given index page to the file.
240    */
241   private void writeDataPage(CacheDataPage cacheDataPage)
242     throws IOException
243   {
244     getIndexData().writeDataPage(cacheDataPage);
245 
246     // lastly, mark the page as no longer modified
247     cacheDataPage._extra._modified = false;
248   }
249 
250   /**
251    * Deletes the given index page from the file (clears the page).
252    */
253   private void deleteDataPage(CacheDataPage cacheDataPage)
254     throws IOException
255   {
256     // the index gives up the page before the database offers it for re-use,
257     // otherwise the index still claims a page which something else can take
258     getIndexData().removeOwnedPage(cacheDataPage._main._pageNumber);
259 
260     // free this database page
261     getPageChannel().deallocatePage(cacheDataPage._main._pageNumber);
262 
263     // discard from our cache
264     _dataPages.remove(cacheDataPage._main._pageNumber);
265 
266     // lastly, mark the page as no longer modified
267     cacheDataPage._extra._modified = false;
268   }
269 
270   /**
271    * Reads the given index page from the file.
272    */
273   private CacheDataPage readDataPage(Integer pageNumber)
274     throws IOException
275   {
276     DataPageMain dataPage = new DataPageMain(pageNumber);
277     DataPageExtra extra = new DataPageExtra();
278     CacheDataPage cacheDataPage = new CacheDataPage(dataPage, extra);
279     getIndexData().readDataPage(cacheDataPage);
280 
281     // associate the extra info with the main data page
282     dataPage.setExtra(extra);
283 
284     return cacheDataPage;
285   }
286 
287   /**
288    * Removes the entry with the given index from the given page.
289    *
290    * @param cacheDataPage the page from which to remove the entry
291    * @param entryIdx the index of the entry to remove
292    */
293   private Entry removeEntry(CacheDataPage cacheDataPage, int entryIdx)
294     throws IOException
295   {
296     return updateEntry(cacheDataPage, entryIdx, null, UpdateType.REMOVE);
297   }
298 
299   /**
300    * Adds the entry to the given page at the given index.
301    *
302    * @param cacheDataPage the page to which to add the entry
303    * @param entryIdx the index at which to add the entry
304    * @param newEntry the entry to add
305    */
306   private void addEntry(CacheDataPage cacheDataPage,
307                         int entryIdx,
308                         Entry newEntry)
309     throws IOException
310   {
311     updateEntry(cacheDataPage, entryIdx, newEntry, UpdateType.ADD);
312   }
313 
314   /**
315    * Updates the entries on the given page according to the given updateType.
316    *
317    * @param cacheDataPage the page to update
318    * @param entryIdx the index at which to add/remove/replace the entry
319    * @param newEntry the entry to add/replace
320    * @param upType the type of update to make
321    */
322   private Entry updateEntry(CacheDataPage cacheDataPage,
323                             int entryIdx,
324                             Entry newEntry,
325                             UpdateType upType)
326     throws IOException
327   {
328     DataPageMain dpMain = cacheDataPage._main;
329     DataPageExtra dpExtra = cacheDataPage._extra;
330 
331     if(newEntry != null) {
332       validateEntryForPage(dpMain, newEntry);
333     }
334 
335     // note, it's slightly ucky, but we need to load the parent page before we
336     // start mucking with our entries because our parent may use our entries.
337     CacheDataPage parentDataPage = (!dpMain.isRoot() ?
338                                     new CacheDataPage(dpMain.getParentPage()) :
339                                     null);
340 
341     Entry oldLastEntry = dpExtra._entryView.getLast();
342     Entry oldEntry = null;
343     int entrySizeDiff = 0;
344 
345     switch(upType) {
346     case ADD:
347       dpExtra._entryView.add(entryIdx, newEntry);
348       entrySizeDiff += newEntry.size();
349       break;
350 
351     case REPLACE:
352       oldEntry = dpExtra._entryView.set(entryIdx, newEntry);
353       entrySizeDiff += newEntry.size() - oldEntry.size();
354       break;
355 
356     case REMOVE: {
357       oldEntry = dpExtra._entryView.remove(entryIdx);
358       entrySizeDiff -= oldEntry.size();
359       break;
360     }
361     default:
362       throw new RuntimeException(withErrorContext(
363               "unknown update type " + upType));
364     }
365 
366     boolean updateLast = (oldLastEntry != dpExtra._entryView.getLast());
367 
368     // child tail entry updates do not modify the page
369     if(!updateLast || !dpMain.hasChildTail()) {
370       dpExtra._totalEntrySize += entrySizeDiff;
371       setModified(cacheDataPage);
372 
373       // for now, just clear the prefix, we'll fix it later
374       dpExtra._entryPrefix = EMPTY_PREFIX;
375     }
376 
377     if(dpExtra._entryView.isEmpty()) {
378       // this page is dead
379       removeDataPage(parentDataPage, cacheDataPage, oldLastEntry);
380       return oldEntry;
381     }
382 
383     // determine if we need to update our parent page
384     if(!updateLast || dpMain.isRoot()) {
385       // no parent
386       return oldEntry;
387     }
388 
389     // the update to the last entry needs to be propagated to our parent
390     replaceParentEntry(parentDataPage, cacheDataPage, oldLastEntry);
391     return oldEntry;
392   }
393 
394   /**
395    * Removes an index page which has become empty.  If this page is the root
396    * page, just clears it.
397    *
398    * @param parentDataPage the parent of the removed page
399    * @param cacheDataPage the page to remove
400    * @param oldLastEntry the last entry for this page (before it was removed)
401    */
402   private void removeDataPage(CacheDataPage parentDataPage,
403                               CacheDataPage cacheDataPage,
404                               Entry oldLastEntry)
405     throws IOException
406   {
407     DataPageMain dpMain = cacheDataPage._main;
408     DataPageExtra dpExtra = cacheDataPage._extra;
409 
410     if(dpMain.hasChildTail()) {
411       throw new IllegalStateException(withErrorContext("Still has child tail?"));
412     }
413 
414     if(dpExtra._totalEntrySize != 0) {
415       throw new IllegalStateException(withErrorContext(
416               "Empty page but size is not 0? " + dpExtra._totalEntrySize + ", " +
417               cacheDataPage));
418     }
419 
420     if(dpMain.isRoot()) {
421       // clear out this page (we don't actually remove it)
422       dpExtra._entryPrefix = EMPTY_PREFIX;
423       // when the root page becomes empty, it becomes a leaf page again
424       dpMain._leaf = true;
425       dpMain._level = 0;
426       return;
427     }
428 
429     // remove this page from its parent page
430     updateParentEntry(parentDataPage, cacheDataPage, oldLastEntry, null,
431                       UpdateType.REMOVE);
432 
433     // remove this page from any next/prev pages
434     removeFromPeers(cacheDataPage);
435   }
436 
437   /**
438    * Removes a now empty index page from its next and previous peers.
439    *
440    * @param cacheDataPage the page to remove
441    */
442   private void removeFromPeers(CacheDataPage cacheDataPage)
443     throws IOException
444   {
445     DataPageMain dpMain = cacheDataPage._main;
446 
447     Integer prevPageNumber = dpMain._prevPageNumber;
448     Integer nextPageNumber = dpMain._nextPageNumber;
449 
450     DataPageMain prevMain = dpMain.getPrevPage();
451     if(prevMain != null) {
452       setModified(new CacheDataPage(prevMain));
453       prevMain._nextPageNumber = nextPageNumber;
454     }
455 
456     DataPageMain nextMain = dpMain.getNextPage();
457     if(nextMain != null) {
458       setModified(new CacheDataPage(nextMain));
459       nextMain._prevPageNumber = prevPageNumber;
460     }
461   }
462 
463   /**
464    * Adds an entry for the given child page to the given parent page.
465    *
466    * @param parentDataPage the parent page to which to add the entry
467    * @param childDataPage the child from which to get the entry to add
468    */
469   private void addParentEntry(CacheDataPage parentDataPage,
470                               CacheDataPage childDataPage)
471     throws IOException
472   {
473     DataPageExtra childExtra = childDataPage._extra;
474     updateParentEntry(parentDataPage, childDataPage, null,
475                       childExtra._entryView.getLast(), UpdateType.ADD);
476   }
477 
478   /**
479    * Replaces the entry for the given child page in the given parent page.
480    *
481    * @param parentDataPage the parent page in which to replace the entry
482    * @param childDataPage the child for which the entry is being replaced
483    * @param oldEntry the old child entry for the child page
484    */
485   private void replaceParentEntry(CacheDataPage parentDataPage,
486                                   CacheDataPage childDataPage,
487                                   Entry oldEntry)
488     throws IOException
489   {
490     DataPageExtra childExtra = childDataPage._extra;
491     updateParentEntry(parentDataPage, childDataPage, oldEntry,
492                       childExtra._entryView.getLast(), UpdateType.REPLACE);
493   }
494 
495   /**
496    * Updates the entry for the given child page in the given parent page
497    * according to the given updateType.
498    *
499    * @param parentDataPage the parent page in which to update the entry
500    * @param childDataPage the child for which the entry is being updated
501    * @param oldEntry the old child entry to remove/replace
502    * @param newEntry the new child entry to replace/add
503    * @param upType the type of update to make
504    */
505   private void updateParentEntry(CacheDataPage parentDataPage,
506                                  CacheDataPage childDataPage,
507                                  Entry oldEntry, Entry newEntry,
508                                  UpdateType upType)
509     throws IOException
510   {
511     DataPageMain childMain = childDataPage._main;
512     DataPageExtra parentExtra = parentDataPage._extra;
513 
514     if(childMain.isTail() && (upType != UpdateType.REMOVE)) {
515       // for add or replace, update the child tail info before updating the
516       // parent entries
517       updateParentTail(parentDataPage, childDataPage, upType);
518     }
519 
520     if(oldEntry != null) {
521       oldEntry = oldEntry.asNodeEntry(childMain._pageNumber);
522     }
523     if(newEntry != null) {
524       newEntry = newEntry.asNodeEntry(childMain._pageNumber);
525     }
526 
527     boolean expectFound = true;
528     int idx = 0;
529 
530     switch(upType) {
531     case ADD:
532       expectFound = false;
533       idx = parentExtra._entryView.find(newEntry);
534       break;
535 
536     case REPLACE:
537     case REMOVE:
538       idx = parentExtra._entryView.find(oldEntry);
539       break;
540 
541     default:
542       throw new RuntimeException(withErrorContext(
543               "unknown update type " + upType));
544     }
545 
546     if(idx < 0) {
547       if(expectFound) {
548         throw new IllegalStateException(withErrorContext(
549             "Could not find child entry in parent; childEntry " + oldEntry +
550             "; parent " + parentDataPage));
551       }
552       idx = missingIndexToInsertionPoint(idx);
553     } else {
554       if(!expectFound) {
555         throw new IllegalStateException(withErrorContext(
556             "Unexpectedly found child entry in parent; childEntry " +
557             newEntry + "; parent " + parentDataPage));
558       }
559     }
560     updateEntry(parentDataPage, idx, newEntry, upType);
561 
562     if(childMain.isTail() && (upType == UpdateType.REMOVE)) {
563       // for remove, update the child tail info after updating the parent
564       // entries
565       updateParentTail(parentDataPage, childDataPage, upType);
566     }
567   }
568 
569   /**
570    * Updates the child tail info in the given parent page according to the
571    * given updateType.
572    *
573    * @param parentDataPage the parent page in which to update the child tail
574    * @param childDataPage the child to add/replace
575    * @param upType the type of update to make
576    */
577   private void updateParentTail(CacheDataPage parentDataPage,
578                                 CacheDataPage childDataPage,
579                                 UpdateType upType)
580   {
581     DataPageMain parentMain = parentDataPage._main;
582 
583     int newChildTailPageNumber =
584       ((upType == UpdateType.REMOVE) ?
585        INVALID_INDEX_PAGE_NUMBER :
586        childDataPage._main._pageNumber);
587     if(!parentMain.isChildTailPageNumber(newChildTailPageNumber)) {
588       setModified(parentDataPage);
589       parentMain._childTailPageNumber = newChildTailPageNumber;
590     }
591   }
592 
593   /**
594    * Verifies that the given entry type (node/leaf) is valid for the given
595    * page (node/leaf).
596    *
597    * @param dpMain the page to which the entry will be added
598    * @param entry the entry being added
599    * @throws IllegalStateException if the entry type does not match the page
600    *         type
601    */
602   private void validateEntryForPage(DataPageMain dpMain, Entry entry) {
603     if(dpMain._leaf != entry.isLeafEntry()) {
604       throw new IllegalStateException(withErrorContext(
605           "Trying to update page with wrong entry type; pageLeaf " +
606           dpMain._leaf + ", entryLeaf " + entry.isLeafEntry()));
607     }
608   }
609 
610   /**
611    * Splits an index page which has too many entries on it.
612    *
613    * @param origDataPage the page to split
614    */
615   private void splitDataPage(CacheDataPage origDataPage)
616     throws IOException
617   {
618     DataPageMain origMain = origDataPage._main;
619     DataPageExtra origExtra = origDataPage._extra;
620 
621     setModified(origDataPage);
622 
623     int numEntries = origExtra._entries.size();
624     if(numEntries < 2) {
625       throw new IllegalStateException(withErrorContext(
626               "Cannot split page with less than 2 entries " + origDataPage));
627     }
628 
629     if(origMain.isRoot()) {
630       // we can't split the root page directly, so we need to put another page
631       // between the root page and its sub-pages, and then split that page.
632       CacheDataPage newDataPage = nestRootDataPage(origDataPage);
633 
634       // now, split this new page instead
635       origDataPage = newDataPage;
636       origMain = newDataPage._main;
637       origExtra = newDataPage._extra;
638     }
639 
640     // note, it's slightly ucky, but we need to load the parent page before we
641     // start mucking with our entries because our parent may use our entries.
642     DataPageMain parentMain = origMain.getParentPage();
643     CacheDataPage parentDataPage = new CacheDataPage(parentMain);
644 
645     // note, there are many, many ways this could be improved/tweaked.  for
646     // now, we just want it to be functional...
647     // so, we will naively move half the entries from one page to a new page.
648 
649     CacheDataPage newDataPage = allocateNewCacheDataPage(
650         parentMain._pageNumber, origMain._leaf, origMain.getLevel());
651     DataPageMain newMain = newDataPage._main;
652     DataPageExtra newExtra = newDataPage._extra;
653 
654     List<Entry> headEntries =
655       origExtra._entries.subList(0, ((numEntries + 1) / 2));
656 
657     // move first half of the entries from old page to new page (so we do not
658     // need to muck with any tail entries)
659     for(Entry headEntry : headEntries) {
660       newExtra._totalEntrySize += headEntry.size();
661       newExtra._entries.add(headEntry);
662     }
663     newExtra.setEntryView(newMain);
664 
665     // remove the moved entries from the old page
666     headEntries.clear();
667     origExtra._entryPrefix = EMPTY_PREFIX;
668     origExtra._totalEntrySize -= newExtra._totalEntrySize;
669 
670     // insert this new page between the old page and any previous page
671     addToPeersBefore(newDataPage, origDataPage);
672 
673     if(!newMain._leaf) {
674       // reparent the children pages of the new page
675       reparentChildren(newDataPage);
676 
677       // if the children of this page are also node pages, then the next/prev
678       // links should not cross parent boundaries (the leaf pages are linked
679       // from beginning to end, but child node pages are only linked within
680       // the same parent)
681       DataPageMain childMain = newMain.getChildPage(
682           newExtra._entryView.getLast());
683       if(!childMain._leaf) {
684         separateFromNextPeer(new CacheDataPage(childMain));
685       }
686     }
687 
688     // lastly, we need to add the new page to the parent page's entries
689     addParentEntry(parentDataPage, newDataPage);
690   }
691 
692   /**
693    * Copies the current root page info into a new page and nests this page
694    * under the root page.  This must be done when the root page needs to be
695    * split.
696    *
697    * @param rootDataPage the root data page
698    *
699    * @return the newly created page nested under the root page
700    */
701   private CacheDataPage nestRootDataPage(CacheDataPage rootDataPage)
702     throws IOException
703   {
704     DataPageMain rootMain = rootDataPage._main;
705     DataPageExtra rootExtra = rootDataPage._extra;
706 
707     if(!rootMain.isRoot()) {
708       throw new IllegalArgumentException(withErrorContext(
709               "should be called with root, duh"));
710     }
711 
712     CacheDataPage newDataPage =
713       allocateNewCacheDataPage(rootMain._pageNumber, rootMain._leaf,
714                                rootMain.getLevel());
715     DataPageMain newMain = newDataPage._main;
716     DataPageExtra newExtra = newDataPage._extra;
717 
718     // move entries to new page
719     newMain._childTailPageNumber = rootMain._childTailPageNumber;
720     newExtra._entries = rootExtra._entries;
721     newExtra._entryPrefix = rootExtra._entryPrefix;
722     newExtra._totalEntrySize = rootExtra._totalEntrySize;
723     newExtra.setEntryView(newMain);
724 
725     if(!newMain._leaf) {
726       // we need to re-parent all the child pages
727       reparentChildren(newDataPage);
728     }
729 
730     // clear the root page, which now sits one level above the new page
731     // holding its entries
732     rootMain._leaf = false;
733     rootMain._level = newMain._level + 1;
734     rootMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
735     rootExtra._entries = new ArrayList<>();
736     rootExtra._entryPrefix = EMPTY_PREFIX;
737     rootExtra._totalEntrySize = 0;
738     rootExtra.setEntryView(rootMain);
739 
740     // add the new page as the first child of the root page
741     addParentEntry(rootDataPage, newDataPage);
742 
743     return newDataPage;
744   }
745 
746   /**
747    * Allocates a new index page with the given parent page and type.
748    *
749    * @param parentPageNumber the parent page for the new page
750    * @param isLeaf whether or not the new page is a leaf page
751    * @param level the level of the new page in the index tree
752    *
753    * @return the newly created page
754    */
755   private CacheDataPage allocateNewCacheDataPage(Integer parentPageNumber,
756                                                  boolean isLeaf, int level)
757     throws IOException
758   {
759     DataPageMain dpMain = new DataPageMain(getPageChannel().allocateNewPage());
760     DataPageExtra dpExtra = new DataPageExtra();
761     dpMain.initParentPage(parentPageNumber, false);
762     dpMain._leaf = isLeaf;
763     dpMain._level = level;
764     dpMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
765     dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
766     dpMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
767     dpExtra._entries = new ArrayList<>();
768     dpExtra._entryPrefix = EMPTY_PREFIX;
769     dpMain.setExtra(dpExtra);
770 
771     // add to our page cache
772     _dataPages.put(dpMain._pageNumber, dpMain);
773 
774     // update owned pages cache
775     _indexData.addOwnedPage(dpMain._pageNumber);
776 
777     // needs to be written out
778     CacheDataPage cacheDataPage = new CacheDataPage(dpMain, dpExtra);
779     setModified(cacheDataPage);
780 
781     return cacheDataPage;
782   }
783 
784   /**
785    * Inserts the new page as a peer between the given original page and any
786    * previous peer page.
787    *
788    * @param newDataPage the new index page
789    * @param origDataPage the current index page
790    */
791   private void addToPeersBefore(CacheDataPage newDataPage,
792                                 CacheDataPage origDataPage)
793     throws IOException
794   {
795     DataPageMain origMain = origDataPage._main;
796     DataPageMain newMain = newDataPage._main;
797 
798     DataPageMain prevMain = origMain.getPrevPage();
799 
800     newMain._nextPageNumber = origMain._pageNumber;
801     newMain._prevPageNumber = origMain._prevPageNumber;
802     origMain._prevPageNumber = newMain._pageNumber;
803 
804     if(prevMain != null) {
805       setModified(new CacheDataPage(prevMain));
806       prevMain._nextPageNumber = newMain._pageNumber;
807     }
808   }
809 
810   /**
811    * Separates the given index page from any next peer page.
812    *
813    * @param cacheDataPage the index page to be separated
814    */
815   private void separateFromNextPeer(CacheDataPage cacheDataPage)
816     throws IOException
817   {
818     DataPageMain dpMain = cacheDataPage._main;
819 
820     setModified(cacheDataPage);
821 
822     DataPageMain nextMain = dpMain.getNextPage();
823     setModified(new CacheDataPage(nextMain));
824 
825     nextMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
826     dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
827   }
828 
829   /**
830    * Sets the parent info for the children of the given page to the given
831    * page.
832    *
833    * @param cacheDataPage the page whose children need to be updated
834    */
835   private void reparentChildren(CacheDataPage cacheDataPage)
836   {
837     DataPageMain dpMain = cacheDataPage._main;
838     DataPageExtra dpExtra = cacheDataPage._extra;
839 
840     // note, the "parent" page number is not actually persisted, so we do not
841     // need to mark any updated pages as modified.  for the same reason, we
842     // don't need to load the pages if not already loaded
843     for(Entry entry : dpExtra._entryView) {
844       Integer childPageNumber = entry.getSubPageNumber();
845       DataPageMain childMain = _dataPages.get(childPageNumber);
846       if(childMain != null) {
847         childMain.setParentPage(dpMain._pageNumber,
848                                 dpMain.isChildTailPageNumber(childPageNumber));
849       }
850     }
851   }
852 
853   /**
854    * Makes the tail entry of the given page a normal entry on that page, done
855    * when there is only one entry left on a page, and it is the tail.
856    *
857    * @param cacheDataPage the page whose tail must be updated
858    */
859   private void demoteTail(CacheDataPage cacheDataPage)
860     throws IOException
861   {
862     // there's only one entry on the page, and it's the tail.  make it a
863     // normal entry
864     DataPageMain dpMain = cacheDataPage._main;
865     DataPageExtra dpExtra = cacheDataPage._extra;
866 
867     setModified(cacheDataPage);
868 
869     DataPageMain tailMain = dpMain.getChildTailPage();
870     CacheDataPage tailDataPage = new CacheDataPage(tailMain);
871 
872     // move the tail entry to the last normal entry
873     updateParentTail(cacheDataPage, tailDataPage, UpdateType.REMOVE);
874     Entry tailEntry = dpExtra._entryView.demoteTail();
875     dpExtra._totalEntrySize += tailEntry.size();
876     dpExtra._entryPrefix = EMPTY_PREFIX;
877 
878     tailMain.setParentPage(dpMain._pageNumber, false);
879   }
880 
881   /**
882    * Makes the last normal entry of the given page the tail entry on that
883    * page, done when there are multiple entries on a page and no tail entry.
884    *
885    * @param cacheDataPage the page whose tail must be updated
886    */
887   private void promoteTail(CacheDataPage cacheDataPage, DataPageMain lastMain)
888     throws IOException
889   {
890     // there's not tail currently on this page, make last entry a tail
891     DataPageMain dpMain = cacheDataPage._main;
892     DataPageExtra dpExtra = cacheDataPage._extra;
893 
894     setModified(cacheDataPage);
895 
896     CacheDataPage lastDataPage = new CacheDataPage(lastMain);
897 
898     // move the "last" normal entry to the tail entry
899     updateParentTail(cacheDataPage, lastDataPage, UpdateType.ADD);
900     Entry lastEntry = dpExtra._entryView.promoteTail();
901     dpExtra._totalEntrySize -= lastEntry.size();
902     dpExtra._entryPrefix = EMPTY_PREFIX;
903 
904     lastMain.setParentPage(dpMain._pageNumber, true);
905   }
906 
907   /**
908    * Finds the index page on which the given entry does or should reside.
909    *
910    * @param e the entry to find
911    */
912   public CacheDataPage findCacheDataPage(Entry e)
913     throws IOException
914   {
915     DataPageMain curPage = _rootPage;
916     while(true) {
917 
918       if(curPage._leaf) {
919         // nowhere to go from here
920         return new CacheDataPage(curPage);
921       }
922 
923       DataPageExtra extra = curPage.getExtra();
924 
925       // need to descend
926       int idx = extra._entryView.find(e);
927       if(idx < 0) {
928         idx = missingIndexToInsertionPoint(idx);
929         if(idx == extra._entryView.size()) {
930           // just move to last child page
931           --idx;
932         }
933       }
934 
935       Entry nodeEntry = extra._entryView.get(idx);
936       curPage = curPage.getChildPage(nodeEntry);
937     }
938   }
939 
940   /**
941    * Marks the given index page as modified and saves it for writing, if
942    * necessary (if the page is already marked, does nothing).
943    *
944    * @param cacheDataPage the modified index page
945    */
946   private void setModified(CacheDataPage cacheDataPage)
947   {
948     if(!cacheDataPage._extra._modified) {
949       _modifiedPages.add(cacheDataPage);
950       cacheDataPage._extra._modified = true;
951     }
952   }
953 
954   /**
955    * Finds the valid entry prefix given the first/last entries on an index
956    * page.
957    *
958    * @param e1 the first entry on the page
959    * @param e2 the last entry on the page
960    *
961    * @return a valid entry prefix for the page
962    */
963   private static byte[] findCommonPrefix(Entry e1, Entry e2)
964   {
965     byte[] b1 = e1.getEntryBytes();
966     byte[] b2 = e2.getEntryBytes();
967 
968     int maxLen = b1.length;
969     byte[] prefix = b1;
970     if(b1.length > b2.length) {
971       maxLen = b2.length;
972       prefix = b2;
973     }
974 
975     int len = 0;
976     while((len < maxLen) && (b1[len] == b2[len])) {
977       ++len;
978     }
979 
980     if(len < prefix.length) {
981       if(len == 0) {
982         return EMPTY_PREFIX;
983       }
984 
985       // need new prefix
986       prefix = ByteUtil.copyOf(prefix, len);
987     }
988 
989     return prefix;
990   }
991 
992   /**
993    * Used by unit tests to validate the internal status of the index.
994    */
995   void validate(boolean forceLoad) throws IOException {
996     new Validator(forceLoad).validate();
997   }
998 
999   /**
1000    * Collects all the cache pages in the cache.
1001    *
1002    * @param pages the List to update
1003    * @param dpMain the index page to collect
1004    */
1005   private List<Object> collectPages(List<Object> pages, DataPageMain dpMain) {
1006     try {
1007       CacheDataPage cacheDataPage = new CacheDataPage(dpMain);
1008       pages.add(cacheDataPage);
1009       if(!dpMain._leaf) {
1010         for(Entry e : cacheDataPage._extra._entryView) {
1011           DataPageMain childMain = dpMain.getChildPage(e);
1012           collectPages(pages, childMain);
1013         }
1014       }
1015     } catch(IOException e) {
1016       pages.add("DataPage[" + dpMain._pageNumber + "]: <" + e + ">");
1017     }
1018     return pages;
1019   }
1020 
1021   /**
1022    * Trims the size of the _dataPages cache appropriately (assuming caller has
1023    * already verified that the cache needs trimming).
1024    */
1025   private void purgeOldPages() {
1026     Iterator<DataPageMain> iter = _dataPages.values().iterator();
1027     while(iter.hasNext()) {
1028       DataPageMain dpMain = iter.next();
1029       // note, we never purge the root page
1030       if(dpMain != _rootPage) {
1031         iter.remove();
1032         if(_dataPages.size() <= MAX_CACHE_SIZE) {
1033           break;
1034         }
1035       }
1036     }
1037   }
1038 
1039   @Override
1040   public String toString() {
1041     ToStringBuilder sb = ToStringBuilder.builder(this);
1042     if(_rootPage == null) {
1043       sb.append("pages", "(uninitialized)");
1044     } else {
1045       sb.append("pages", collectPages(new ArrayList<Object>(), _rootPage));
1046     }
1047     return sb.toString();
1048   }
1049 
1050   private String withErrorContext(String msg) {
1051     return _indexData.withErrorContext(msg);
1052   }
1053 
1054 
1055   /**
1056    * Keeps track of the main info for an index page.
1057    */
1058   private class DataPageMain
1059   {
1060     public final int _pageNumber;
1061     public Integer _prevPageNumber;
1062     public Integer _nextPageNumber;
1063     public Integer _childTailPageNumber;
1064     public Integer _parentPageNumber;
1065     public boolean _leaf;
1066     /** depth below the leaves of the index tree, 0 for a leaf page */
1067     public int _level;
1068     public boolean _tail;
1069     private Reference<DataPageExtra> _extra;
1070 
1071     private DataPageMain(int pageNumber) {
1072       _pageNumber = pageNumber;
1073     }
1074 
1075     public IndexPageCache getCache() {
1076       return IndexPageCache.this;
1077     }
1078 
1079     public boolean isRoot() {
1080       return(this == _rootPage);
1081     }
1082 
1083     public boolean isTail() throws IOException
1084     {
1085       resolveParent();
1086       return _tail;
1087     }
1088 
1089     public boolean hasChildTail() {
1090       return(_childTailPageNumber != INVALID_INDEX_PAGE_NUMBER);
1091     }
1092 
1093     public boolean isChildTailPageNumber(int pageNumber) {
1094       return(_childTailPageNumber == pageNumber);
1095     }
1096 
1097     public DataPageMain getParentPage() throws IOException
1098     {
1099       resolveParent();
1100       return IndexPageCache.this.getDataPage(_parentPageNumber);
1101     }
1102 
1103     public void initParentPage(Integer parentPageNumber, boolean isTail) {
1104       // only set if not already set
1105       if(_parentPageNumber == null) {
1106         setParentPage(parentPageNumber, isTail);
1107       }
1108     }
1109 
1110     public void setParentPage(Integer parentPageNumber, boolean isTail) {
1111       _parentPageNumber = parentPageNumber;
1112       _tail = isTail;
1113     }
1114 
1115     public DataPageMain getPrevPage() throws IOException
1116     {
1117       return IndexPageCache.this.getDataPage(_prevPageNumber);
1118     }
1119 
1120     public DataPageMain getNextPage() throws IOException
1121     {
1122       return IndexPageCache.this.getDataPage(_nextPageNumber);
1123     }
1124 
1125     public DataPageMain getChildPage(Entry e) throws IOException
1126     {
1127       Integer childPageNumber = e.getSubPageNumber();
1128       return getChildPage(childPageNumber,
1129                           isChildTailPageNumber(childPageNumber));
1130     }
1131 
1132     public DataPageMain getChildTailPage() throws IOException
1133     {
1134       return getChildPage(_childTailPageNumber, true);
1135     }
1136 
1137     /**
1138      * Returns a child page for the given page number, updating its parent
1139      * info if necessary.
1140      */
1141     private DataPageMain getChildPage(Integer childPageNumber, boolean isTail)
1142       throws IOException
1143     {
1144       DataPageMain child = getDataPage(childPageNumber);
1145       if(child != null) {
1146         // set the parent info for this child (if necessary)
1147         child.initParentPage(_pageNumber, isTail);
1148       }
1149       return child;
1150     }
1151 
1152     public int getLevel() throws IOException
1153     {
1154       if(!_leaf && (_level == 0)) {
1155         // a node page is never at level 0, so this one has a level which was
1156         // never filled in.  take it from a child instead
1157         _level = getChildPage(getExtra()._entries.get(0)).getLevel() + 1;
1158       }
1159       return _level;
1160     }
1161 
1162     public DataPageExtra getExtra() throws IOException
1163     {
1164       DataPageExtra extra = _extra.get();
1165       if(extra == null) {
1166         extra = readDataPage(_pageNumber)._extra;
1167         setExtra(extra);
1168       }
1169 
1170       return extra;
1171     }
1172 
1173     public void setExtra(DataPageExtra extra) throws IOException
1174     {
1175       extra.setEntryView(this);
1176       _extra = new SoftReference<>(extra);
1177     }
1178 
1179     private void resolveParent() throws IOException {
1180       if(_parentPageNumber == null) {
1181         // the act of searching for the last entry should resolve any parent
1182         // pages along the path
1183         findCacheDataPage(getExtra()._entryView.getLast());
1184         if(_parentPageNumber == null) {
1185           throw new IllegalStateException(withErrorContext(
1186                   "Parent was not resolved"));
1187         }
1188       }
1189     }
1190 
1191     @Override
1192     public String toString() {
1193       return (_leaf ? "Leaf" : "Node") + "DPMain[" + _pageNumber +
1194         "] " + _prevPageNumber + ", " + _nextPageNumber + ", (" +
1195         _childTailPageNumber + ")";
1196     }
1197   }
1198 
1199   /**
1200    * Keeps track of the extra info for an index page.  This info (if
1201    * unmodified) may be re-read from disk as necessary.
1202    */
1203   private static class DataPageExtra
1204   {
1205     /** sorted collection of index entries.  this is kept in a list instead of
1206         a SortedSet because the SortedSet has lame traversal utilities */
1207     public List<Entry> _entries;
1208     public EntryListView _entryView;
1209     public byte[] _entryPrefix;
1210     public int _totalEntrySize;
1211     public boolean _modified;
1212 
1213     private DataPageExtra()
1214     {
1215     }
1216 
1217     public void setEntryView(DataPageMain main) throws IOException {
1218       _entryView = new EntryListView(main, this);
1219     }
1220 
1221     public void updateEntryPrefix() {
1222       if(_entryPrefix.length == 0) {
1223         // prefix is only related to *real* entries, tail not included
1224         _entryPrefix = findCommonPrefix(_entries.get(0),
1225                                         _entries.get(_entries.size() - 1));
1226       }
1227     }
1228 
1229     @Override
1230     public String toString() {
1231       return ToStringBuilder.builder("DPExtra")
1232         .append(null, _entryView)
1233         .toString();
1234     }
1235   }
1236 
1237   /**
1238    * IndexPageCache implementation of an Index {@link DataPage}.
1239    */
1240   private static final class CacheDataPage extends DataPage
1241   {
1242     public final DataPageMain _main;
1243     public final DataPageExtra _extra;
1244 
1245     private CacheDataPage(DataPageMain dataPage) throws IOException {
1246       this(dataPage, dataPage.getExtra());
1247     }
1248 
1249     private CacheDataPage(DataPageMain dataPage, DataPageExtra extra) {
1250       _main = dataPage;
1251       _extra = extra;
1252     }
1253 
1254     @Override
1255     public int getPageNumber() {
1256       return _main._pageNumber;
1257     }
1258 
1259     @Override
1260     public boolean isLeaf() {
1261       return _main._leaf;
1262     }
1263 
1264     @Override
1265     public void setLeaf(boolean isLeaf) {
1266       _main._leaf = isLeaf;
1267     }
1268 
1269     @Override
1270     public int getLevel() throws IOException {
1271       return _main.getLevel();
1272     }
1273 
1274     @Override
1275     public void setLevel(int level) {
1276       _main._level = level;
1277     }
1278 
1279     @Override
1280     public int getPrevPageNumber() {
1281       return _main._prevPageNumber;
1282     }
1283 
1284     @Override
1285     public void setPrevPageNumber(int pageNumber) {
1286       _main._prevPageNumber = pageNumber;
1287     }
1288 
1289     @Override
1290     public int getNextPageNumber() {
1291       return _main._nextPageNumber;
1292     }
1293 
1294     @Override
1295     public void setNextPageNumber(int pageNumber) {
1296       _main._nextPageNumber = pageNumber;
1297     }
1298 
1299     @Override
1300     public int getChildTailPageNumber() {
1301       return _main._childTailPageNumber;
1302     }
1303 
1304     @Override
1305     public void setChildTailPageNumber(int pageNumber) {
1306       _main._childTailPageNumber = pageNumber;
1307     }
1308 
1309 
1310     @Override
1311     public int getTotalEntrySize() {
1312       return _extra._totalEntrySize;
1313     }
1314 
1315     @Override
1316     public void setTotalEntrySize(int totalSize) {
1317       _extra._totalEntrySize = totalSize;
1318     }
1319 
1320     @Override
1321     public byte[] getEntryPrefix() {
1322       return _extra._entryPrefix;
1323     }
1324 
1325     @Override
1326     public void setEntryPrefix(byte[] entryPrefix) {
1327       _extra._entryPrefix = entryPrefix;
1328     }
1329 
1330 
1331     @Override
1332     public List<Entry> getEntries() {
1333       return _extra._entries;
1334     }
1335 
1336     @Override
1337     public void setEntries(List<Entry> entries) {
1338       _extra._entries = entries;
1339     }
1340 
1341     @Override
1342     public void addEntry(int idx, Entry entry) throws IOException {
1343       _main.getCache().addEntry(this, idx, entry);
1344     }
1345 
1346     @Override
1347     public Entry removeEntry(int idx) throws IOException {
1348       return _main.getCache().removeEntry(this, idx);
1349     }
1350 
1351   }
1352 
1353   /**
1354    * A view of an index page's entries which combines the normal entries and
1355    * tail entry into one collection.
1356    */
1357   private static class EntryListView extends AbstractList<Entry>
1358     implements RandomAccess
1359   {
1360     private final DataPageExtra _extra;
1361     private Entry _childTailEntry;
1362 
1363     private EntryListView(DataPageMain main, DataPageExtra extra)
1364       throws IOException
1365     {
1366       if(main.hasChildTail()) {
1367         _childTailEntry = main.getChildTailPage().getExtra()._entryView
1368           .getLast().asNodeEntry(main._childTailPageNumber);
1369       }
1370       _extra = extra;
1371     }
1372 
1373     private List<Entry> getEntries() {
1374       return _extra._entries;
1375     }
1376 
1377     @Override
1378     public int size() {
1379       int size = getEntries().size();
1380       if(hasChildTail()) {
1381         ++size;
1382       }
1383       return size;
1384     }
1385 
1386     @Override
1387     public Entry get(int idx) {
1388       return (isCurrentChildTailIndex(idx) ?
1389               _childTailEntry :
1390               getEntries().get(idx));
1391     }
1392 
1393     @Override
1394     public Entry set(int idx, Entry newEntry) {
1395       return (isCurrentChildTailIndex(idx) ?
1396               setChildTailEntry(newEntry) :
1397               getEntries().set(idx, newEntry));
1398     }
1399 
1400     @Override
1401     public void add(int idx, Entry newEntry) {
1402       // note, we will never add to the "tail" entry, that will always be
1403       // handled through promoteTail
1404       getEntries().add(idx, newEntry);
1405     }
1406 
1407     @Override
1408     public Entry remove(int idx) {
1409       return (isCurrentChildTailIndex(idx) ?
1410               setChildTailEntry(null) :
1411               getEntries().remove(idx));
1412     }
1413 
1414     public Entry setChildTailEntry(Entry newEntry) {
1415       Entry old = _childTailEntry;
1416       _childTailEntry = newEntry;
1417       return old;
1418     }
1419 
1420     private boolean hasChildTail() {
1421       return(_childTailEntry != null);
1422     }
1423 
1424     private boolean isCurrentChildTailIndex(int idx) {
1425       return(idx == getEntries().size());
1426     }
1427 
1428     public Entry getLast() {
1429       return(hasChildTail() ? _childTailEntry :
1430              (!getEntries().isEmpty() ?
1431               getEntries().get(getEntries().size() - 1) : null));
1432     }
1433 
1434     public Entry demoteTail() {
1435       Entry tail = _childTailEntry;
1436       _childTailEntry = null;
1437       getEntries().add(tail);
1438       return tail;
1439     }
1440 
1441     public Entry promoteTail() {
1442       Entry last = getEntries().remove(getEntries().size() - 1);
1443       _childTailEntry = last;
1444       return last;
1445     }
1446 
1447     public int find(Entry e) {
1448       return Collections.binarySearch(this, e);
1449     }
1450 
1451   }
1452 
1453   /**
1454    * Utility class for running index validation.
1455    */
1456   private final class Validator {
1457     private final boolean _forceLoad;
1458     private final Map<Integer,DataPageMain> _knownPages = new HashMap<>();
1459     private final Queue<DataPageMain> _pendingPages = new LinkedList<>();
1460 
1461     private Validator(boolean forceLoad) {
1462       _forceLoad = forceLoad;
1463       _knownPages.putAll(_dataPages);
1464       _pendingPages.addAll(_knownPages.values());
1465     }
1466 
1467     void validate() throws IOException {
1468       DataPageMain dpMain = null;
1469       while((dpMain = _pendingPages.poll()) != null) {
1470         DataPageExtra dpExtra = dpMain.getExtra();
1471         validateEntries(dpExtra);
1472         validateChildren(dpMain, dpExtra);
1473         validatePeers(dpMain);
1474       }
1475     }
1476 
1477     /**
1478      * Validates the entries for an index page
1479      *
1480      * @param dpExtra the entries to validate
1481      */
1482     private void validateEntries(DataPageExtra dpExtra) throws IOException {
1483       int entrySize = 0;
1484       Entry prevEntry = FIRST_ENTRY;
1485       for(Entry e : dpExtra._entries) {
1486         entrySize += e.size();
1487         if(prevEntry.compareTo(e) >= 0) {
1488           throw new IOException(withErrorContext(
1489                   "Unexpected order in index entries, " + prevEntry +
1490                   " >= " + e));
1491         }
1492         prevEntry = e;
1493       }
1494 
1495       if(dpExtra._entryView.hasChildTail()) {
1496         Entry tailE = dpExtra._entryView.getLast();
1497         if(prevEntry.compareTo(tailE) >= 0) {
1498           throw new IOException(withErrorContext(
1499                   "Unexpected order in index entries, " + prevEntry +
1500                   " >= " + tailE));
1501         }
1502       }
1503 
1504       if(entrySize != dpExtra._totalEntrySize) {
1505         throw new IllegalStateException(withErrorContext(
1506                 "Expected size " + entrySize +
1507                 " but was " + dpExtra._totalEntrySize));
1508       }
1509     }
1510 
1511     /**
1512      * Validates the children for an index page
1513      *
1514      * @param dpMain the index page
1515      * @param dpExtra the child entries to validate
1516      */
1517     private void validateChildren(DataPageMain dpMain,
1518                                   DataPageExtra dpExtra) throws IOException {
1519       int childTailPageNumber = dpMain._childTailPageNumber;
1520       if(dpMain._leaf) {
1521         if(childTailPageNumber != INVALID_INDEX_PAGE_NUMBER) {
1522           throw new IllegalStateException(
1523               withErrorContext("Leaf page has tail " + dpMain));
1524         }
1525         return;
1526       }
1527       if((dpExtra._entryView.size() == 1) && dpMain.hasChildTail()) {
1528         throw new IllegalStateException(
1529             withErrorContext("Single child is tail " + dpMain));
1530       }
1531       Integer prevPageNumber = null;
1532       Integer nextPageNumber = null;
1533       Entry prevLastEntry = FIRST_ENTRY;
1534       for(Entry e : dpExtra._entryView) {
1535         validateEntryForPage(dpMain, e);
1536         Integer subPageNumber = e.getSubPageNumber();
1537         DataPageMain childMain = getPageForValidate(subPageNumber);
1538         if(childMain != null) {
1539           if((prevPageNumber != null) &&
1540              ((int)childMain._prevPageNumber != prevPageNumber)) {
1541             throw new IllegalStateException(withErrorContext(
1542                     "Child's prevPageNumber is not the previous child for " +
1543                     childMain + " " + dpExtra._entryView + " " +
1544                     prevPageNumber));
1545           }
1546           if((nextPageNumber != null) &&
1547              (childMain._pageNumber != nextPageNumber)) {
1548             throw new IllegalStateException(withErrorContext(
1549                     "Child's pageNumber is not the expected next child for " +
1550                     childMain));
1551           }
1552           if(childMain.getLevel() != (dpMain.getLevel() - 1)) {
1553             throw new IllegalStateException(withErrorContext(
1554                     "Child's level " + childMain.getLevel() +
1555                     " is not one below its parent's " + dpMain.getLevel() +
1556                     " for " + childMain));
1557           }
1558           if(childMain._parentPageNumber != null) {
1559             if(childMain._parentPageNumber != dpMain._pageNumber) {
1560               throw new IllegalStateException(
1561                   withErrorContext("Child's parent is incorrect " + childMain));
1562             }
1563             boolean expectTail = (subPageNumber == childTailPageNumber);
1564             if(expectTail != childMain._tail) {
1565               throw new IllegalStateException(withErrorContext(
1566                       "Child tail status incorrect " + childMain));
1567             }
1568           }
1569           DataPageExtra childExtra = childMain.getExtra();
1570           Entry lastEntry = childExtra._entryView.getLast();
1571           if(e.compareTo(lastEntry) != 0) {
1572             throw new IllegalStateException(withErrorContext(
1573                     "Invalid last entry " + e + " but child is " + lastEntry));
1574           }
1575           Entry firstEntry = childExtra._entries.get(0);
1576           if(prevLastEntry.compareTo(firstEntry) >= 0) {
1577             throw new IllegalStateException(withErrorContext(
1578                     "Invalid first entry " + firstEntry + " but prev last is " +
1579                     prevLastEntry));
1580           }
1581           nextPageNumber = childMain._nextPageNumber;
1582           prevPageNumber = childMain._pageNumber;
1583           prevLastEntry = lastEntry;
1584         } else {
1585           // if we aren't force loading, we may have gaps in the children so we
1586           // can't validate these for the current child
1587           nextPageNumber = null;
1588           prevPageNumber = null;
1589         }
1590       }
1591     }
1592 
1593     /**
1594      * Validates the peer pages for an index page.
1595      *
1596      * @param dpMain the index page
1597      */
1598     private void validatePeers(DataPageMain dpMain)
1599       throws IOException {
1600 
1601       DataPageMain prevMain = getPageForValidate(dpMain._prevPageNumber);
1602       if(prevMain != null) {
1603         if(prevMain._nextPageNumber != dpMain._pageNumber) {
1604           throw new IllegalStateException(withErrorContext(
1605                   "Prev page " + prevMain + " does not ref " + dpMain));
1606         }
1607         validatePeerStatus(dpMain, prevMain);
1608         validatePeerEntries(prevMain, dpMain);
1609       }
1610 
1611       DataPageMain nextMain =
1612         getPageForValidate(dpMain._nextPageNumber);
1613       if(nextMain != null) {
1614         if(nextMain._prevPageNumber != dpMain._pageNumber) {
1615           throw new IllegalStateException(withErrorContext(
1616                   "Next page " + nextMain + " does not ref " + dpMain));
1617         }
1618         validatePeerStatus(dpMain, nextMain);
1619         validatePeerEntries(dpMain, nextMain);
1620       }
1621     }
1622 
1623     /**
1624      * Validates the given peer page against the given index page
1625      *
1626      * @param dpMain the index page
1627      * @param peerMain the peer index page
1628      */
1629     private void validatePeerStatus(DataPageMain dpMain, DataPageMain peerMain)
1630       throws IOException
1631     {
1632       if(dpMain._leaf != peerMain._leaf) {
1633         throw new IllegalStateException(withErrorContext(
1634                 "Mismatched peer status " + dpMain._leaf + " " +
1635                 peerMain._leaf));
1636       }
1637       if(dpMain.getLevel() != peerMain.getLevel()) {
1638         throw new IllegalStateException(withErrorContext(
1639                 "Mismatched peer level " + dpMain.getLevel() + " " +
1640                 peerMain.getLevel()));
1641       }
1642       if(!dpMain._leaf) {
1643         if((dpMain._parentPageNumber != null) &&
1644            (peerMain._parentPageNumber != null) &&
1645            ((int)dpMain._parentPageNumber != (int)peerMain._parentPageNumber)) {
1646           throw new IllegalStateException(withErrorContext(
1647                   "Mismatched node parents " + dpMain._parentPageNumber + " " +
1648                   peerMain._parentPageNumber));
1649         }
1650       }
1651     }
1652 
1653     /**
1654      * Validates the order of the entries of the peers.
1655      */
1656     private void validatePeerEntries(DataPageMain prevMain, DataPageMain nextMain)
1657       throws IOException {
1658       Entry lastE = prevMain.getExtra()._entryView.getLast();
1659       Entry firstE = nextMain.getExtra()._entries.get(0);
1660       if(lastE.compareTo(firstE) >= 0) {
1661           throw new IOException(withErrorContext(
1662                   "Unexpected peer order in index entries, " + lastE +
1663                   " >= " + firstE));
1664       }
1665     }
1666 
1667     private DataPageMain getPageForValidate(
1668         Integer pageNumber) throws IOException {
1669       DataPageMain dpMain = _knownPages.get(pageNumber);
1670       if((dpMain == null) && _forceLoad &&
1671          (pageNumber != INVALID_INDEX_PAGE_NUMBER)) {
1672         dpMain = getDataPage(pageNumber);
1673         if(dpMain != null) {
1674           _knownPages.put(pageNumber, dpMain);
1675           _pendingPages.add(dpMain);
1676         } else {
1677           throw new IllegalStateException(
1678               withErrorContext("Could not find index page " + pageNumber));
1679         }
1680       }
1681       return dpMain;
1682     }
1683   }
1684 
1685 }