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