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