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               // only a leaf page can become a tail page
169               DataPageMain lastChild = dpMain.getChildPage(
170                   cacheDataPage._extra._entryView.getLast());
171               if(lastChild._leaf) {
172                 promoteTail(cacheDataPage, lastChild);
173               }
174             }
175           }
176         }
177 
178         // look for pages with more entries than can fit on a page
179         if(cacheDataPage.getTotalEntrySize() > maxPageEntrySize) {
180 
181           // make sure the prefix is up-to-date (this may have gotten
182           // discarded by one of the update entry methods)
183           cacheDataPage._extra.updateEntryPrefix();
184 
185           // now, see if the page will fit when compressed
186           if(cacheDataPage.getCompressedEntrySize() > maxPageEntrySize) {
187             // need to split this page
188             splitPages = true;
189             splitDataPage(cacheDataPage);
190           }
191         }
192       }
193 
194     } while(splitPages);
195   }
196 
197   /**
198    * Writes any non-empty modified pages as the last pass during a
199    * {@link #write} call.  Clears the _modifiedPages collection when finised.
200    */
201   private void writeDataPages() throws IOException
202   {
203     for(CacheDataPage cacheDataPage : _modifiedPages) {
204       if(cacheDataPage._extra._entryView.isEmpty()) {
205         throw new IllegalStateException(withErrorContext(
206                 "Unexpected empty page " + cacheDataPage));
207       }
208       writeDataPage(cacheDataPage);
209     }
210     _modifiedPages.clear();
211   }
212 
213   /**
214    * Returns a CacheDataPage for the given page number, may be {@code null} if
215    * the given page number is invalid.  Loads the given page if necessary.
216    */
217   public CacheDataPage getCacheDataPage(Integer pageNumber)
218     throws IOException
219   {
220     DataPageMain main = getDataPage(pageNumber);
221     return((main != null) ? new CacheDataPage(main) : null);
222   }
223 
224   /**
225    * Returns a DataPageMain for the given page number, may be {@code null} if
226    * the given page number is invalid.  Loads the given page if necessary.
227    */
228   private DataPageMain getDataPage(Integer pageNumber)
229     throws IOException
230   {
231     DataPageMain dataPage = _dataPages.get(pageNumber);
232     if((dataPage == null) && (pageNumber > INVALID_INDEX_PAGE_NUMBER)) {
233       dataPage = readDataPage(pageNumber)._main;
234       _dataPages.put(pageNumber, dataPage);
235     }
236     return dataPage;
237   }
238 
239   /**
240    * Writes the given index page to the file.
241    */
242   private void writeDataPage(CacheDataPage cacheDataPage)
243     throws IOException
244   {
245     getIndexData().writeDataPage(cacheDataPage);
246 
247     // lastly, mark the page as no longer modified
248     cacheDataPage._extra._modified = false;
249   }
250 
251   /**
252    * Deletes the given index page from the file (clears the page).
253    */
254   private void deleteDataPage(CacheDataPage cacheDataPage)
255     throws IOException
256   {
257     // free this database page
258     getPageChannel().deallocatePage(cacheDataPage._main._pageNumber);
259 
260     // discard from our cache
261     _dataPages.remove(cacheDataPage._main._pageNumber);
262 
263     // lastly, mark the page as no longer modified
264     cacheDataPage._extra._modified = false;
265   }
266 
267   /**
268    * Reads the given index page from the file.
269    */
270   private CacheDataPage readDataPage(Integer pageNumber)
271     throws IOException
272   {
273     DataPageMain dataPage = new DataPageMain(pageNumber);
274     DataPageExtra extra = new DataPageExtra();
275     CacheDataPage cacheDataPage = new CacheDataPage(dataPage, extra);
276     getIndexData().readDataPage(cacheDataPage);
277 
278     // associate the extra info with the main data page
279     dataPage.setExtra(extra);
280 
281     return cacheDataPage;
282   }
283 
284   /**
285    * Removes the entry with the given index from the given page.
286    *
287    * @param cacheDataPage the page from which to remove the entry
288    * @param entryIdx the index of the entry to remove
289    */
290   private Entry removeEntry(CacheDataPage cacheDataPage, int entryIdx)
291     throws IOException
292   {
293     return updateEntry(cacheDataPage, entryIdx, null, UpdateType.REMOVE);
294   }
295 
296   /**
297    * Adds the entry to the given page at the given index.
298    *
299    * @param cacheDataPage the page to which to add the entry
300    * @param entryIdx the index at which to add the entry
301    * @param newEntry the entry to add
302    */
303   private void addEntry(CacheDataPage cacheDataPage,
304                         int entryIdx,
305                         Entry newEntry)
306     throws IOException
307   {
308     updateEntry(cacheDataPage, entryIdx, newEntry, UpdateType.ADD);
309   }
310 
311   /**
312    * Updates the entries on the given page according to the given updateType.
313    *
314    * @param cacheDataPage the page to update
315    * @param entryIdx the index at which to add/remove/replace the entry
316    * @param newEntry the entry to add/replace
317    * @param upType the type of update to make
318    */
319   private Entry updateEntry(CacheDataPage cacheDataPage,
320                             int entryIdx,
321                             Entry newEntry,
322                             UpdateType upType)
323     throws IOException
324   {
325     DataPageMain dpMain = cacheDataPage._main;
326     DataPageExtra dpExtra = cacheDataPage._extra;
327 
328     if(newEntry != null) {
329       validateEntryForPage(dpMain, newEntry);
330     }
331 
332     // note, it's slightly ucky, but we need to load the parent page before we
333     // start mucking with our entries because our parent may use our entries.
334     CacheDataPage parentDataPage = (!dpMain.isRoot() ?
335                                     new CacheDataPage(dpMain.getParentPage()) :
336                                     null);
337 
338     Entry oldLastEntry = dpExtra._entryView.getLast();
339     Entry oldEntry = null;
340     int entrySizeDiff = 0;
341 
342     switch(upType) {
343     case ADD:
344       dpExtra._entryView.add(entryIdx, newEntry);
345       entrySizeDiff += newEntry.size();
346       break;
347 
348     case REPLACE:
349       oldEntry = dpExtra._entryView.set(entryIdx, newEntry);
350       entrySizeDiff += newEntry.size() - oldEntry.size();
351       break;
352 
353     case REMOVE: {
354       oldEntry = dpExtra._entryView.remove(entryIdx);
355       entrySizeDiff -= oldEntry.size();
356       break;
357     }
358     default:
359       throw new RuntimeException(withErrorContext(
360               "unknown update type " + upType));
361     }
362 
363     boolean updateLast = (oldLastEntry != dpExtra._entryView.getLast());
364 
365     // child tail entry updates do not modify the page
366     if(!updateLast || !dpMain.hasChildTail()) {
367       dpExtra._totalEntrySize += entrySizeDiff;
368       setModified(cacheDataPage);
369 
370       // for now, just clear the prefix, we'll fix it later
371       dpExtra._entryPrefix = EMPTY_PREFIX;
372     }
373 
374     if(dpExtra._entryView.isEmpty()) {
375       // this page is dead
376       removeDataPage(parentDataPage, cacheDataPage, oldLastEntry);
377       return oldEntry;
378     }
379 
380     // determine if we need to update our parent page
381     if(!updateLast || dpMain.isRoot()) {
382       // no parent
383       return oldEntry;
384     }
385 
386     // the update to the last entry needs to be propagated to our parent
387     replaceParentEntry(parentDataPage, cacheDataPage, oldLastEntry);
388     return oldEntry;
389   }
390 
391   /**
392    * Removes an index page which has become empty.  If this page is the root
393    * page, just clears it.
394    *
395    * @param parentDataPage the parent of the removed page
396    * @param cacheDataPage the page to remove
397    * @param oldLastEntry the last entry for this page (before it was removed)
398    */
399   private void removeDataPage(CacheDataPage parentDataPage,
400                               CacheDataPage cacheDataPage,
401                               Entry oldLastEntry)
402     throws IOException
403   {
404     DataPageMain dpMain = cacheDataPage._main;
405     DataPageExtra dpExtra = cacheDataPage._extra;
406 
407     if(dpMain.hasChildTail()) {
408       throw new IllegalStateException(withErrorContext("Still has child tail?"));
409     }
410 
411     if(dpExtra._totalEntrySize != 0) {
412       throw new IllegalStateException(withErrorContext(
413               "Empty page but size is not 0? " + dpExtra._totalEntrySize + ", " +
414               cacheDataPage));
415     }
416 
417     if(dpMain.isRoot()) {
418       // clear out this page (we don't actually remove it)
419       dpExtra._entryPrefix = EMPTY_PREFIX;
420       // when the root page becomes empty, it becomes a leaf page again
421       dpMain._leaf = true;
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);
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     DataPageMain newMain = newDataPage._main;
711     DataPageExtra newExtra = newDataPage._extra;
712 
713     // move entries to new page
714     newMain._childTailPageNumber = rootMain._childTailPageNumber;
715     newExtra._entries = rootExtra._entries;
716     newExtra._entryPrefix = rootExtra._entryPrefix;
717     newExtra._totalEntrySize = rootExtra._totalEntrySize;
718     newExtra.setEntryView(newMain);
719 
720     if(!newMain._leaf) {
721       // we need to re-parent all the child pages
722       reparentChildren(newDataPage);
723     }
724 
725     // clear the root page
726     rootMain._leaf = false;
727     rootMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
728     rootExtra._entries = new ArrayList<Entry>();
729     rootExtra._entryPrefix = EMPTY_PREFIX;
730     rootExtra._totalEntrySize = 0;
731     rootExtra.setEntryView(rootMain);
732 
733     // add the new page as the first child of the root page
734     addParentEntry(rootDataPage, newDataPage);
735 
736     return newDataPage;
737   }
738 
739   /**
740    * Allocates a new index page with the given parent page and type.
741    *
742    * @param parentPageNumber the parent page for the new page
743    * @param isLeaf whether or not the new page is a leaf page
744    *
745    * @return the newly created page
746    */
747   private CacheDataPage allocateNewCacheDataPage(Integer parentPageNumber,
748                                                  boolean isLeaf)
749     throws IOException
750   {
751     DataPageMain dpMain = new DataPageMain(getPageChannel().allocateNewPage());
752     DataPageExtra dpExtra = new DataPageExtra();
753     dpMain.initParentPage(parentPageNumber, false);
754     dpMain._leaf = isLeaf;
755     dpMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
756     dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
757     dpMain._childTailPageNumber = INVALID_INDEX_PAGE_NUMBER;
758     dpExtra._entries = new ArrayList<Entry>();
759     dpExtra._entryPrefix = EMPTY_PREFIX;
760     dpMain.setExtra(dpExtra);
761 
762     // add to our page cache
763     _dataPages.put(dpMain._pageNumber, dpMain);
764 
765     // update owned pages cache
766     _indexData.addOwnedPage(dpMain._pageNumber);
767 
768     // needs to be written out
769     CacheDataPage cacheDataPage = new CacheDataPage(dpMain, dpExtra);
770     setModified(cacheDataPage);
771 
772     return cacheDataPage;
773   }
774 
775   /**
776    * Inserts the new page as a peer between the given original page and any
777    * previous peer page.
778    *
779    * @param newDataPage the new index page
780    * @param origDataPage the current index page
781    */
782   private void addToPeersBefore(CacheDataPage newDataPage,
783                                 CacheDataPage origDataPage)
784     throws IOException
785   {
786     DataPageMain origMain = origDataPage._main;
787     DataPageMain newMain = newDataPage._main;
788 
789     DataPageMain prevMain = origMain.getPrevPage();
790 
791     newMain._nextPageNumber = origMain._pageNumber;
792     newMain._prevPageNumber = origMain._prevPageNumber;
793     origMain._prevPageNumber = newMain._pageNumber;
794 
795     if(prevMain != null) {
796       setModified(new CacheDataPage(prevMain));
797       prevMain._nextPageNumber = newMain._pageNumber;
798     }
799   }
800 
801   /**
802    * Separates the given index page from any next peer page.
803    *
804    * @param cacheDataPage the index page to be separated
805    */
806   private void separateFromNextPeer(CacheDataPage cacheDataPage)
807     throws IOException
808   {
809     DataPageMain dpMain = cacheDataPage._main;
810 
811     setModified(cacheDataPage);
812 
813     DataPageMain nextMain = dpMain.getNextPage();
814     setModified(new CacheDataPage(nextMain));
815 
816     nextMain._prevPageNumber = INVALID_INDEX_PAGE_NUMBER;
817     dpMain._nextPageNumber = INVALID_INDEX_PAGE_NUMBER;
818   }
819 
820   /**
821    * Sets the parent info for the children of the given page to the given
822    * page.
823    *
824    * @param cacheDataPage the page whose children need to be updated
825    */
826   private void reparentChildren(CacheDataPage cacheDataPage)
827   {
828     DataPageMain dpMain = cacheDataPage._main;
829     DataPageExtra dpExtra = cacheDataPage._extra;
830 
831     // note, the "parent" page number is not actually persisted, so we do not
832     // need to mark any updated pages as modified.  for the same reason, we
833     // don't need to load the pages if not already loaded
834     for(Entry entry : dpExtra._entryView) {
835       Integer childPageNumber = entry.getSubPageNumber();
836       DataPageMain childMain = _dataPages.get(childPageNumber);
837       if(childMain != null) {
838         childMain.setParentPage(dpMain._pageNumber,
839                                 dpMain.isChildTailPageNumber(childPageNumber));
840       }
841     }
842   }
843 
844   /**
845    * Makes the tail entry of the given page a normal entry on that page, done
846    * when there is only one entry left on a page, and it is the tail.
847    *
848    * @param cacheDataPage the page whose tail must be updated
849    */
850   private void demoteTail(CacheDataPage cacheDataPage)
851     throws IOException
852   {
853     // there's only one entry on the page, and it's the tail.  make it a
854     // normal entry
855     DataPageMain dpMain = cacheDataPage._main;
856     DataPageExtra dpExtra = cacheDataPage._extra;
857 
858     setModified(cacheDataPage);
859 
860     DataPageMain tailMain = dpMain.getChildTailPage();
861     CacheDataPage tailDataPage = new CacheDataPage(tailMain);
862 
863     // move the tail entry to the last normal entry
864     updateParentTail(cacheDataPage, tailDataPage, UpdateType.REMOVE);
865     Entry tailEntry = dpExtra._entryView.demoteTail();
866     dpExtra._totalEntrySize += tailEntry.size();
867     dpExtra._entryPrefix = EMPTY_PREFIX;
868 
869     tailMain.setParentPage(dpMain._pageNumber, false);
870   }
871 
872   /**
873    * Makes the last normal entry of the given page the tail entry on that
874    * page, done when there are multiple entries on a page and no tail entry.
875    *
876    * @param cacheDataPage the page whose tail must be updated
877    */
878   private void promoteTail(CacheDataPage cacheDataPage, DataPageMain lastMain)
879     throws IOException
880   {
881     // there's not tail currently on this page, make last entry a tail
882     DataPageMain dpMain = cacheDataPage._main;
883     DataPageExtra dpExtra = cacheDataPage._extra;
884 
885     setModified(cacheDataPage);
886 
887     CacheDataPage lastDataPage = new CacheDataPage(lastMain);
888 
889     // move the "last" normal entry to the tail entry
890     updateParentTail(cacheDataPage, lastDataPage, UpdateType.ADD);
891     Entry lastEntry = dpExtra._entryView.promoteTail();
892     dpExtra._totalEntrySize -= lastEntry.size();
893     dpExtra._entryPrefix = EMPTY_PREFIX;
894 
895     lastMain.setParentPage(dpMain._pageNumber, true);
896   }
897 
898   /**
899    * Finds the index page on which the given entry does or should reside.
900    *
901    * @param e the entry to find
902    */
903   public CacheDataPage findCacheDataPage(Entry e)
904     throws IOException
905   {
906     DataPageMain curPage = _rootPage;
907     while(true) {
908 
909       if(curPage._leaf) {
910         // nowhere to go from here
911         return new CacheDataPage(curPage);
912       }
913 
914       DataPageExtra extra = curPage.getExtra();
915 
916       // need to descend
917       int idx = extra._entryView.find(e);
918       if(idx < 0) {
919         idx = missingIndexToInsertionPoint(idx);
920         if(idx == extra._entryView.size()) {
921           // just move to last child page
922           --idx;
923         }
924       }
925 
926       Entry nodeEntry = extra._entryView.get(idx);
927       curPage = curPage.getChildPage(nodeEntry);
928     }
929   }
930 
931   /**
932    * Marks the given index page as modified and saves it for writing, if
933    * necessary (if the page is already marked, does nothing).
934    *
935    * @param cacheDataPage the modified index page
936    */
937   private void setModified(CacheDataPage cacheDataPage)
938   {
939     if(!cacheDataPage._extra._modified) {
940       _modifiedPages.add(cacheDataPage);
941       cacheDataPage._extra._modified = true;
942     }
943   }
944 
945   /**
946    * Finds the valid entry prefix given the first/last entries on an index
947    * page.
948    *
949    * @param e1 the first entry on the page
950    * @param e2 the last entry on the page
951    *
952    * @return a valid entry prefix for the page
953    */
954   private static byte[] findCommonPrefix(Entry e1, Entry e2)
955   {
956     byte[] b1 = e1.getEntryBytes();
957     byte[] b2 = e2.getEntryBytes();
958 
959     int maxLen = b1.length;
960     byte[] prefix = b1;
961     if(b1.length > b2.length) {
962       maxLen = b2.length;
963       prefix = b2;
964     }
965 
966     int len = 0;
967     while((len < maxLen) && (b1[len] == b2[len])) {
968       ++len;
969     }
970 
971     if(len < prefix.length) {
972       if(len == 0) {
973         return EMPTY_PREFIX;
974       }
975 
976       // need new prefix
977       prefix = ByteUtil.copyOf(prefix, len);
978     }
979 
980     return prefix;
981   }
982 
983   /**
984    * Used by unit tests to validate the internal status of the index.
985    */
986   void validate(boolean forceLoad) throws IOException {
987     new Validator(forceLoad).validate();
988   }
989 
990   /**
991    * Collects all the cache pages in the cache.
992    *
993    * @param pages the List to update
994    * @param dpMain the index page to collect
995    */
996   private List<Object> collectPages(List<Object> pages, DataPageMain dpMain) {
997     try {
998       CacheDataPage cacheDataPage = new CacheDataPage(dpMain);
999       pages.add(cacheDataPage);
1000       if(!dpMain._leaf) {
1001         for(Entry e : cacheDataPage._extra._entryView) {
1002           DataPageMain childMain = dpMain.getChildPage(e);
1003           collectPages(pages, childMain);
1004         }
1005       }
1006     } catch(IOException e) {
1007       pages.add("DataPage[" + dpMain._pageNumber + "]: <" + e + ">");
1008     }
1009     return pages;
1010   }
1011 
1012   /**
1013    * Trims the size of the _dataPages cache appropriately (assuming caller has
1014    * already verified that the cache needs trimming).
1015    */
1016   private void purgeOldPages() {
1017     Iterator<DataPageMain> iter = _dataPages.values().iterator();
1018     while(iter.hasNext()) {
1019       DataPageMain dpMain = iter.next();
1020       // note, we never purge the root page
1021       if(dpMain != _rootPage) {
1022         iter.remove();
1023         if(_dataPages.size() <= MAX_CACHE_SIZE) {
1024           break;
1025         }
1026       }
1027     }
1028   }
1029 
1030   @Override
1031   public String toString() {
1032     ToStringBuilder sb = CustomToStringStyle.builder(this);
1033     if(_rootPage == null) {
1034       sb.append("pages", "(uninitialized)");
1035     } else {
1036       sb.append("pages", collectPages(new ArrayList<Object>(), _rootPage));
1037     }
1038     return sb.toString();
1039   }
1040 
1041   private String withErrorContext(String msg) {
1042     return _indexData.withErrorContext(msg);
1043   }
1044 
1045 
1046   /**
1047    * Keeps track of the main info for an index page.
1048    */
1049   private class DataPageMain
1050   {
1051     public final int _pageNumber;
1052     public Integer _prevPageNumber;
1053     public Integer _nextPageNumber;
1054     public Integer _childTailPageNumber;
1055     public Integer _parentPageNumber;
1056     public boolean _leaf;
1057     public boolean _tail;
1058     private Reference<DataPageExtra> _extra;
1059 
1060     private DataPageMain(int pageNumber) {
1061       _pageNumber = pageNumber;
1062     }
1063 
1064     public IndexPageCache getCache() {
1065       return IndexPageCache.this;
1066     }
1067 
1068     public boolean isRoot() {
1069       return(this == _rootPage);
1070     }
1071 
1072     public boolean isTail() throws IOException
1073     {
1074       resolveParent();
1075       return _tail;
1076     }
1077 
1078     public boolean hasChildTail() {
1079       return(_childTailPageNumber != INVALID_INDEX_PAGE_NUMBER);
1080     }
1081 
1082     public boolean isChildTailPageNumber(int pageNumber) {
1083       return(_childTailPageNumber == pageNumber);
1084     }
1085 
1086     public DataPageMain getParentPage() throws IOException
1087     {
1088       resolveParent();
1089       return IndexPageCache.this.getDataPage(_parentPageNumber);
1090     }
1091 
1092     public void initParentPage(Integer parentPageNumber, boolean isTail) {
1093       // only set if not already set
1094       if(_parentPageNumber == null) {
1095         setParentPage(parentPageNumber, isTail);
1096       }
1097     }
1098 
1099     public void setParentPage(Integer parentPageNumber, boolean isTail) {
1100       _parentPageNumber = parentPageNumber;
1101       _tail = isTail;
1102     }
1103 
1104     public DataPageMain getPrevPage() throws IOException
1105     {
1106       return IndexPageCache.this.getDataPage(_prevPageNumber);
1107     }
1108 
1109     public DataPageMain getNextPage() throws IOException
1110     {
1111       return IndexPageCache.this.getDataPage(_nextPageNumber);
1112     }
1113 
1114     public DataPageMain getChildPage(Entry e) throws IOException
1115     {
1116       Integer childPageNumber = e.getSubPageNumber();
1117       return getChildPage(childPageNumber,
1118                           isChildTailPageNumber(childPageNumber));
1119     }
1120 
1121     public DataPageMain getChildTailPage() throws IOException
1122     {
1123       return getChildPage(_childTailPageNumber, true);
1124     }
1125 
1126     /**
1127      * Returns a child page for the given page number, updating its parent
1128      * info if necessary.
1129      */
1130     private DataPageMain getChildPage(Integer childPageNumber, boolean isTail)
1131       throws IOException
1132     {
1133       DataPageMain child = getDataPage(childPageNumber);
1134       if(child != null) {
1135         // set the parent info for this child (if necessary)
1136         child.initParentPage(_pageNumber, isTail);
1137       }
1138       return child;
1139     }
1140 
1141     public DataPageExtra getExtra() throws IOException
1142     {
1143       DataPageExtra extra = _extra.get();
1144       if(extra == null) {
1145         extra = readDataPage(_pageNumber)._extra;
1146         setExtra(extra);
1147       }
1148 
1149       return extra;
1150     }
1151 
1152     public void setExtra(DataPageExtra extra) throws IOException
1153     {
1154       extra.setEntryView(this);
1155       _extra = new SoftReference<DataPageExtra>(extra);
1156     }
1157 
1158     private void resolveParent() throws IOException {
1159       if(_parentPageNumber == null) {
1160         // the act of searching for the last entry should resolve any parent
1161         // pages along the path
1162         findCacheDataPage(getExtra()._entryView.getLast());
1163         if(_parentPageNumber == null) {
1164           throw new IllegalStateException(withErrorContext(
1165                   "Parent was not resolved"));
1166         }
1167       }
1168     }
1169 
1170     @Override
1171     public String toString() {
1172       return (_leaf ? "Leaf" : "Node") + "DPMain[" + _pageNumber +
1173         "] " + _prevPageNumber + ", " + _nextPageNumber + ", (" +
1174         _childTailPageNumber + ")";
1175     }
1176   }
1177 
1178   /**
1179    * Keeps track of the extra info for an index page.  This info (if
1180    * unmodified) may be re-read from disk as necessary.
1181    */
1182   private static class DataPageExtra
1183   {
1184     /** sorted collection of index entries.  this is kept in a list instead of
1185         a SortedSet because the SortedSet has lame traversal utilities */
1186     public List<Entry> _entries;
1187     public EntryListView _entryView;
1188     public byte[] _entryPrefix;
1189     public int _totalEntrySize;
1190     public boolean _modified;
1191 
1192     private DataPageExtra()
1193     {
1194     }
1195 
1196     public void setEntryView(DataPageMain main) throws IOException {
1197       _entryView = new EntryListView(main, this);
1198     }
1199 
1200     public void updateEntryPrefix() {
1201       if(_entryPrefix.length == 0) {
1202         // prefix is only related to *real* entries, tail not included
1203         _entryPrefix = findCommonPrefix(_entries.get(0),
1204                                         _entries.get(_entries.size() - 1));
1205       }
1206     }
1207 
1208     @Override
1209     public String toString() {
1210       return CustomToStringStyle.builder("DPExtra")
1211         .append(null, _entryView)
1212         .toString();
1213     }
1214   }
1215 
1216   /**
1217    * IndexPageCache implementation of an Index {@link DataPage}.
1218    */
1219   private static final class CacheDataPage extends DataPage
1220   {
1221     public final DataPageMain _main;
1222     public final DataPageExtra _extra;
1223 
1224     private CacheDataPage(DataPageMain dataPage) throws IOException {
1225       this(dataPage, dataPage.getExtra());
1226     }
1227 
1228     private CacheDataPage(DataPageMain dataPage, DataPageExtra extra) {
1229       _main = dataPage;
1230       _extra = extra;
1231     }
1232 
1233     @Override
1234     public int getPageNumber() {
1235       return _main._pageNumber;
1236     }
1237 
1238     @Override
1239     public boolean isLeaf() {
1240       return _main._leaf;
1241     }
1242 
1243     @Override
1244     public void setLeaf(boolean isLeaf) {
1245       _main._leaf = isLeaf;
1246     }
1247 
1248 
1249     @Override
1250     public int getPrevPageNumber() {
1251       return _main._prevPageNumber;
1252     }
1253 
1254     @Override
1255     public void setPrevPageNumber(int pageNumber) {
1256       _main._prevPageNumber = pageNumber;
1257     }
1258 
1259     @Override
1260     public int getNextPageNumber() {
1261       return _main._nextPageNumber;
1262     }
1263 
1264     @Override
1265     public void setNextPageNumber(int pageNumber) {
1266       _main._nextPageNumber = pageNumber;
1267     }
1268 
1269     @Override
1270     public int getChildTailPageNumber() {
1271       return _main._childTailPageNumber;
1272     }
1273 
1274     @Override
1275     public void setChildTailPageNumber(int pageNumber) {
1276       _main._childTailPageNumber = pageNumber;
1277     }
1278 
1279 
1280     @Override
1281     public int getTotalEntrySize() {
1282       return _extra._totalEntrySize;
1283     }
1284 
1285     @Override
1286     public void setTotalEntrySize(int totalSize) {
1287       _extra._totalEntrySize = totalSize;
1288     }
1289 
1290     @Override
1291     public byte[] getEntryPrefix() {
1292       return _extra._entryPrefix;
1293     }
1294 
1295     @Override
1296     public void setEntryPrefix(byte[] entryPrefix) {
1297       _extra._entryPrefix = entryPrefix;
1298     }
1299 
1300 
1301     @Override
1302     public List<Entry> getEntries() {
1303       return _extra._entries;
1304     }
1305 
1306     @Override
1307     public void setEntries(List<Entry> entries) {
1308       _extra._entries = entries;
1309     }
1310 
1311     @Override
1312     public void addEntry(int idx, Entry entry) throws IOException {
1313       _main.getCache().addEntry(this, idx, entry);
1314     }
1315 
1316     @Override
1317     public Entry removeEntry(int idx) throws IOException {
1318       return _main.getCache().removeEntry(this, idx);
1319     }
1320 
1321   }
1322 
1323   /**
1324    * A view of an index page's entries which combines the normal entries and
1325    * tail entry into one collection.
1326    */
1327   private static class EntryListView extends AbstractList<Entry>
1328     implements RandomAccess
1329   {
1330     private final DataPageExtra _extra;
1331     private Entry _childTailEntry;
1332 
1333     private EntryListView(DataPageMain main, DataPageExtra extra)
1334       throws IOException
1335     {
1336       if(main.hasChildTail()) {
1337         _childTailEntry = main.getChildTailPage().getExtra()._entryView
1338           .getLast().asNodeEntry(main._childTailPageNumber);
1339       }
1340       _extra = extra;
1341     }
1342 
1343     private List<Entry> getEntries() {
1344       return _extra._entries;
1345     }
1346 
1347     @Override
1348     public int size() {
1349       int size = getEntries().size();
1350       if(hasChildTail()) {
1351         ++size;
1352       }
1353       return size;
1354     }
1355 
1356     @Override
1357     public Entry get(int idx) {
1358       return (isCurrentChildTailIndex(idx) ?
1359               _childTailEntry :
1360               getEntries().get(idx));
1361     }
1362 
1363     @Override
1364     public Entry set(int idx, Entry newEntry) {
1365       return (isCurrentChildTailIndex(idx) ?
1366               setChildTailEntry(newEntry) :
1367               getEntries().set(idx, newEntry));
1368     }
1369 
1370     @Override
1371     public void add(int idx, Entry newEntry) {
1372       // note, we will never add to the "tail" entry, that will always be
1373       // handled through promoteTail
1374       getEntries().add(idx, newEntry);
1375     }
1376 
1377     @Override
1378     public Entry remove(int idx) {
1379       return (isCurrentChildTailIndex(idx) ?
1380               setChildTailEntry(null) :
1381               getEntries().remove(idx));
1382     }
1383 
1384     public Entry setChildTailEntry(Entry newEntry) {
1385       Entry old = _childTailEntry;
1386       _childTailEntry = newEntry;
1387       return old;
1388     }
1389 
1390     private boolean hasChildTail() {
1391       return(_childTailEntry != null);
1392     }
1393 
1394     private boolean isCurrentChildTailIndex(int idx) {
1395       return(idx == getEntries().size());
1396     }
1397 
1398     public Entry getLast() {
1399       return(hasChildTail() ? _childTailEntry :
1400              (!getEntries().isEmpty() ?
1401               getEntries().get(getEntries().size() - 1) : null));
1402     }
1403 
1404     public Entry demoteTail() {
1405       Entry tail = _childTailEntry;
1406       _childTailEntry = null;
1407       getEntries().add(tail);
1408       return tail;
1409     }
1410 
1411     public Entry promoteTail() {
1412       Entry last = getEntries().remove(getEntries().size() - 1);
1413       _childTailEntry = last;
1414       return last;
1415     }
1416 
1417     public int find(Entry e) {
1418       return Collections.binarySearch(this, e);
1419     }
1420 
1421   }
1422 
1423   /**
1424    * Utility class for running index validation.
1425    */
1426   private final class Validator {
1427     private final boolean _forceLoad;
1428     private final Map<Integer,DataPageMain> _knownPages = new HashMap<>();
1429     private final Queue<DataPageMain> _pendingPages = new LinkedList<>();
1430 
1431     private Validator(boolean forceLoad) {
1432       _forceLoad = forceLoad;
1433       _knownPages.putAll(_dataPages);
1434       _pendingPages.addAll(_knownPages.values());
1435     }
1436 
1437     void validate() throws IOException {
1438       DataPageMain dpMain = null;
1439       while((dpMain = _pendingPages.poll()) != null) {
1440         DataPageExtra dpExtra = dpMain.getExtra();
1441         validateEntries(dpExtra);
1442         validateChildren(dpMain, dpExtra);
1443         validatePeers(dpMain);
1444       }
1445     }
1446 
1447     /**
1448      * Validates the entries for an index page
1449      *
1450      * @param dpExtra the entries to validate
1451      */
1452     private void validateEntries(DataPageExtra dpExtra) throws IOException {
1453       int entrySize = 0;
1454       Entry prevEntry = FIRST_ENTRY;
1455       for(Entry e : dpExtra._entries) {
1456         entrySize += e.size();
1457         if(prevEntry.compareTo(e) >= 0) {
1458           throw new IOException(withErrorContext(
1459                   "Unexpected order in index entries, " + prevEntry +
1460                   " >= " + e));
1461         }
1462         prevEntry = e;
1463       }
1464 
1465       if(dpExtra._entryView.hasChildTail()) {
1466         Entry tailE = dpExtra._entryView.getLast();
1467         if(prevEntry.compareTo(tailE) >= 0) {
1468           throw new IOException(withErrorContext(
1469                   "Unexpected order in index entries, " + prevEntry +
1470                   " >= " + tailE));
1471         }
1472       }
1473 
1474       if(entrySize != dpExtra._totalEntrySize) {
1475         throw new IllegalStateException(withErrorContext(
1476                 "Expected size " + entrySize +
1477                 " but was " + dpExtra._totalEntrySize));
1478       }
1479     }
1480 
1481     /**
1482      * Validates the children for an index page
1483      *
1484      * @param dpMain the index page
1485      * @param dpExtra the child entries to validate
1486      */
1487     private void validateChildren(DataPageMain dpMain,
1488                                   DataPageExtra dpExtra) throws IOException {
1489       int childTailPageNumber = dpMain._childTailPageNumber;
1490       if(dpMain._leaf) {
1491         if(childTailPageNumber != INVALID_INDEX_PAGE_NUMBER) {
1492           throw new IllegalStateException(
1493               withErrorContext("Leaf page has tail " + dpMain));
1494         }
1495         return;
1496       }
1497       if((dpExtra._entryView.size() == 1) && dpMain.hasChildTail()) {
1498         throw new IllegalStateException(
1499             withErrorContext("Single child is tail " + dpMain));
1500       }
1501       Integer prevPageNumber = null;
1502       Integer nextPageNumber = null;
1503       Entry prevLastEntry = FIRST_ENTRY;
1504       for(Entry e : dpExtra._entryView) {
1505         validateEntryForPage(dpMain, e);
1506         Integer subPageNumber = e.getSubPageNumber();
1507         DataPageMain childMain = getPageForValidate(subPageNumber);
1508         if(childMain != null) {
1509           if((prevPageNumber != null) &&
1510              ((int)childMain._prevPageNumber != prevPageNumber)) {
1511             throw new IllegalStateException(withErrorContext(
1512                     "Child's prevPageNumber is not the previous child for " +
1513                     childMain + " " + dpExtra._entryView + " " +
1514                     prevPageNumber));
1515           }
1516           if((nextPageNumber != null) &&
1517              (childMain._pageNumber != nextPageNumber)) {
1518             throw new IllegalStateException(withErrorContext(
1519                     "Child's pageNumber is not the expected next child for " +
1520                     childMain));
1521           }
1522           if(childMain._parentPageNumber != null) {
1523             if(childMain._parentPageNumber != dpMain._pageNumber) {
1524               throw new IllegalStateException(
1525                   withErrorContext("Child's parent is incorrect " + childMain));
1526             }
1527             boolean expectTail = (subPageNumber == childTailPageNumber);
1528             if(expectTail != childMain._tail) {
1529               throw new IllegalStateException(withErrorContext(
1530                       "Child tail status incorrect " + childMain));
1531             }
1532           }
1533           DataPageExtra childExtra = childMain.getExtra();
1534           Entry lastEntry = childExtra._entryView.getLast();
1535           if(e.compareTo(lastEntry) != 0) {
1536             throw new IllegalStateException(withErrorContext(
1537                     "Invalid last entry " + e + " but child is " + lastEntry));
1538           }
1539           Entry firstEntry = childExtra._entries.get(0);
1540           if(prevLastEntry.compareTo(firstEntry) >= 0) {
1541             throw new IllegalStateException(withErrorContext(
1542                     "Invalid first entry " + firstEntry + " but prev last is " +
1543                     prevLastEntry));
1544           }
1545           nextPageNumber = childMain._nextPageNumber;
1546           prevPageNumber = childMain._pageNumber;
1547           prevLastEntry = lastEntry;
1548         } else {
1549           // if we aren't force loading, we may have gaps in the children so we
1550           // can't validate these for the current child
1551           nextPageNumber = null;
1552           prevPageNumber = null;
1553         }
1554       }
1555     }
1556 
1557     /**
1558      * Validates the peer pages for an index page.
1559      *
1560      * @param dpMain the index page
1561      */
1562     private void validatePeers(DataPageMain dpMain)
1563       throws IOException {
1564 
1565       DataPageMain prevMain = getPageForValidate(dpMain._prevPageNumber);
1566       if(prevMain != null) {
1567         if(prevMain._nextPageNumber != dpMain._pageNumber) {
1568           throw new IllegalStateException(withErrorContext(
1569                   "Prev page " + prevMain + " does not ref " + dpMain));
1570         }
1571         validatePeerStatus(dpMain, prevMain);
1572         validatePeerEntries(prevMain, dpMain);
1573       }
1574 
1575       DataPageMain nextMain =
1576         getPageForValidate(dpMain._nextPageNumber);
1577       if(nextMain != null) {
1578         if(nextMain._prevPageNumber != dpMain._pageNumber) {
1579           throw new IllegalStateException(withErrorContext(
1580                   "Next page " + nextMain + " does not ref " + dpMain));
1581         }
1582         validatePeerStatus(dpMain, nextMain);
1583         validatePeerEntries(dpMain, nextMain);
1584       }
1585     }
1586 
1587     /**
1588      * Validates the given peer page against the given index page
1589      *
1590      * @param dpMain the index page
1591      * @param peerMain the peer index page
1592      */
1593     private void validatePeerStatus(DataPageMain dpMain, DataPageMain peerMain)
1594     {
1595       if(dpMain._leaf != peerMain._leaf) {
1596         throw new IllegalStateException(withErrorContext(
1597                 "Mismatched peer status " + dpMain._leaf + " " +
1598                 peerMain._leaf));
1599       }
1600       if(!dpMain._leaf) {
1601         if((dpMain._parentPageNumber != null) &&
1602            (peerMain._parentPageNumber != null) &&
1603            ((int)dpMain._parentPageNumber != (int)peerMain._parentPageNumber)) {
1604           throw new IllegalStateException(withErrorContext(
1605                   "Mismatched node parents " + dpMain._parentPageNumber + " " +
1606                   peerMain._parentPageNumber));
1607         }
1608       }
1609     }
1610 
1611     /**
1612      * Validates the order of the entries of the peers.
1613      */
1614     private void validatePeerEntries(DataPageMain prevMain, DataPageMain nextMain)
1615       throws IOException {
1616       Entry lastE = prevMain.getExtra()._entryView.getLast();
1617       Entry firstE = nextMain.getExtra()._entries.get(0);
1618       if(lastE.compareTo(firstE) >= 0) {
1619           throw new IOException(withErrorContext(
1620                   "Unexpected peer order in index entries, " + lastE +
1621                   " >= " + firstE));
1622       }
1623     }
1624 
1625     private DataPageMain getPageForValidate(
1626         Integer pageNumber) throws IOException {
1627       DataPageMain dpMain = _knownPages.get(pageNumber);
1628       if((dpMain == null) && _forceLoad &&
1629          (pageNumber != INVALID_INDEX_PAGE_NUMBER)) {
1630         dpMain = getDataPage(pageNumber);
1631         if(dpMain != null) {
1632           _knownPages.put(pageNumber, dpMain);
1633           _pendingPages.add(dpMain);
1634         } else {
1635           throw new IllegalStateException(
1636               withErrorContext("Could not find index page " + pageNumber));
1637         }
1638       }
1639       return dpMain;
1640     }
1641   }
1642 
1643 }