View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.mavibot.btree;
21  
22  
23  import java.io.IOException;
24  import java.util.NoSuchElementException;
25  
26  import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException;
27  
28  
29  /**
30   * A Cursor is used to fetch only keys in a BTree and is returned by the
31   * @see BTree#browseKeys method. The cursor <strng>must</strong> be closed
32   * when the user is done with it.
33   * <p>
34   *
35   * @param <K> The type for the Key
36   *
37   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
38   */
39  public class KeyCursor<K>
40  {
41      /** A marker to tell that we are before the first element */
42      private static final int BEFORE_FIRST = -1;
43  
44      /** A marker to tell that we are after the last element */
45      private static final int AFTER_LAST = -2;
46  
47      /** The stack of pages from the root down to the leaf */
48      protected ParentPos<K, K>[] stack;
49  
50      /** The stack's depth */
51      protected int depth = 0;
52  
53      /** The transaction used for this cursor */
54      protected ReadTransaction<K, K> transaction;
55  
56  
57      /**
58       * Creates a new instance of Cursor.
59       */
60      protected KeyCursor()
61      {
62      }
63  
64  
65      /**
66       * Creates a new instance of Cursor, starting on a page at a given position.
67       *
68       * @param transaction The transaction this operation is protected by
69       * @param stack The stack of parent's from root to this page
70       */
71      public KeyCursor( ReadTransaction<K, K> transaction, ParentPos<K, K>[] stack, int depth )
72      {
73          this.transaction = transaction;
74          this.stack = stack;
75          this.depth = depth;
76      }
77  
78  
79      /**
80       * Change the position in the current cursor to set it after the last key
81       */
82      public void afterLast() throws IOException
83      {
84          // First check that we have elements in the BTree
85          if ( ( stack == null ) || ( stack.length == 0 ) )
86          {
87              return;
88          }
89  
90          Page<K, K> child = null;
91  
92          for ( int i = 0; i < depth; i++ )
93          {
94              ParentPos<K, K> parentPos = stack[i];
95  
96              if ( child != null )
97              {
98                  parentPos.page = child;
99                  parentPos.pos = child.getNbElems();
100             }
101             else
102             {
103                 // We have N+1 children if the page is a Node, so we don't decrement the nbElems field
104                 parentPos.pos = parentPos.page.getNbElems();
105             }
106 
107             child = ( ( AbstractPage<K, K> ) parentPos.page ).getPage( parentPos.pos );
108         }
109 
110         // and leaf
111         ParentPos<K, K> parentPos = stack[depth];
112 
113         if ( child == null )
114         {
115             parentPos.pos = parentPos.page.getNbElems() - 1;
116         }
117         else
118         {
119             parentPos.page = child;
120             parentPos.pos = child.getNbElems() - 1;
121         }
122 
123         parentPos.pos = AFTER_LAST;
124     }
125 
126 
127     /**
128      * Change the position in the current cursor before the first key
129      */
130     public void beforeFirst() throws IOException
131     {
132         // First check that we have elements in the BTree
133         if ( ( stack == null ) || ( stack.length == 0 ) )
134         {
135             return;
136         }
137 
138         Page<K, K> child = null;
139 
140         for ( int i = 0; i < depth; i++ )
141         {
142             ParentPos<K, K> parentPos = stack[i];
143             parentPos.pos = 0;
144 
145             if ( child != null )
146             {
147                 parentPos.page = child;
148             }
149 
150             child = ( ( AbstractPage<K, K> ) parentPos.page ).getPage( 0 );
151         }
152 
153         // and leaf
154         ParentPos<K, K> parentPos = stack[depth];
155         parentPos.pos = BEFORE_FIRST;
156 
157         if ( child != null )
158         {
159             parentPos.page = child;
160         }
161     }
162 
163 
164     /**
165      * Tells if the cursor can return a next element
166      *
167      * @return true if there are some more elements
168      * @throws IOException
169      * @throws EndOfFileExceededException
170      */
171     public boolean hasNext() throws EndOfFileExceededException, IOException
172     {
173         // First check that we have elements in the BTree
174         if ( ( stack == null ) || ( stack.length == 0 ) )
175         {
176             return false;
177         }
178 
179         // Take the leaf and check if we have no mare keys
180         ParentPos<K, K> parentPos = stack[depth];
181 
182         if ( parentPos.page == null )
183         {
184             // Empty BTree, get out
185             return false;
186         }
187 
188         if ( parentPos.pos == AFTER_LAST )
189         {
190             return false;
191         }
192 
193         if ( parentPos.pos == BEFORE_FIRST )
194         {
195             return true;
196         }
197 
198         if ( parentPos.pos < parentPos.page.getNbElems() - 1 )
199         {
200             // Not the last position, we have a next key
201             return true;
202         }
203         else
204         {
205             // Ok, here, we have reached the last key in the leaf. We have to go up and
206             // see if we have some remaining keys
207             int currentDepth = depth - 1;
208 
209             while ( currentDepth >= 0 )
210             {
211                 parentPos = stack[currentDepth];
212 
213                 if ( parentPos.pos < parentPos.page.getNbElems() )
214                 {
215                     // The parent has some remaining keys on the right, get out
216                     return true;
217                 }
218                 else
219                 {
220                     currentDepth--;
221                 }
222             }
223 
224             // We are done, there are no more key left
225             return false;
226         }
227     }
228 
229 
230     /**
231      * Find the next key
232      *
233      * @return the found key
234      * @throws IOException
235      * @throws EndOfFileExceededException
236      */
237     public K next() throws EndOfFileExceededException, IOException
238     {
239         // First check that we have elements in the BTree
240         if ( ( stack == null ) || ( stack.length == 0 ) )
241         {
242             throw new NoSuchElementException( "No Key is present" );
243         }
244 
245         ParentPos<K, K> parentPos = stack[depth];
246 
247         if ( ( parentPos.page == null ) || ( parentPos.pos == AFTER_LAST ) )
248         {
249             // This is the end : no more keys
250             throw new NoSuchElementException( "No more keys present" );
251         }
252 
253         if ( parentPos.pos == parentPos.page.getNbElems() )
254         {
255             // End of the leaf. We have to go back into the stack up to the
256             // parent, and down to the leaf
257             parentPos = findNextParentPos();
258 
259             // we also need to check for the type of page cause
260             // findNextParentPos will never return a null ParentPos
261             if ( ( parentPos == null ) || ( parentPos.page == null ) )
262             {
263                 // This is the end : no more keys
264                 throw new NoSuchElementException( "No more keys present" );
265             }
266         }
267 
268         // Deal with the BeforeFirst case
269         if ( parentPos.pos == BEFORE_FIRST )
270         {
271             parentPos.pos++;
272         }
273         else
274         {
275             if ( parentPos.pos == parentPos.page.getNbElems() - 1 )
276             {
277                 parentPos = findNextParentPos();
278                 
279                 if ( ( parentPos == null ) || ( parentPos.page == null ) )
280                 {
281                     // This is the end : no more keys
282                     throw new NoSuchElementException( "No more keys present" );
283                 }
284             }
285             else
286             {
287                 parentPos.pos++;
288             }
289         }
290         
291         AbstractPage<K, K> leaf = ( AbstractPage<K, K> ) ( parentPos.page );
292 
293         return leaf.getKey( parentPos.pos );
294     }
295 
296 
297     /**
298      * Get the next key.
299      */
300     public K nextKey() throws EndOfFileExceededException, IOException
301     {
302         return next();
303     }
304 
305 
306     /**
307      * Tells if the cursor can return a next key
308      *
309      * @return true if there are some more keys
310      * @throws IOException
311      * @throws EndOfFileExceededException
312      */
313     public boolean hasNextKey() throws EndOfFileExceededException, IOException
314     {
315         // First check that we have elements in the BTree
316         if ( ( stack == null ) || ( stack.length == 0 ) )
317         {
318             // This is the end : no more key
319             return false;
320         }
321 
322         ParentPos<K, K> parentPos = stack[depth];
323 
324         if ( parentPos.page == null )
325         {
326             // This is the end : no more key
327             return false;
328         }
329 
330         if ( parentPos.pos == ( parentPos.page.getNbElems() - 1 ) )
331         {
332             // End of the leaf. We have to go back into the stack up to the
333             // parent, and down to the next leaf
334             return hasNextParentPos();
335         }
336         else
337         {
338             return true;
339         }
340     }
341 
342 
343     /**
344      * Tells if the cursor can return a previous element
345      *
346      * @return true if there are some more elements
347      * @throws IOException
348      * @throws EndOfFileExceededException
349      */
350     public boolean hasPrev() throws EndOfFileExceededException, IOException
351     {
352         // First check that we have elements in the BTree
353         if ( ( stack == null ) || ( stack.length == 0 ) )
354         {
355             return false;
356         }
357 
358         // Take the leaf and check if we have no mare keys
359         ParentPos<K, K> parentPos = stack[depth];
360 
361         if ( parentPos.page == null )
362         {
363             // Empty BTree, get out
364             return false;
365         }
366 
367         if ( parentPos.pos > 0 )
368         {
369             // get out, we have keys on the left
370             return true;
371         }
372         else
373         {
374             // Check that we are not before the first key
375             if ( parentPos.pos == BEFORE_FIRST )
376             {
377                 return false;
378             }
379 
380             if ( parentPos.pos == AFTER_LAST )
381             {
382                 return true;
383             }
384 
385             // Ok, here, we have reached the first key in the leaf. We have to go up and
386             // see if we have some remaining keys
387             int currentDepth = depth - 1;
388 
389             while ( currentDepth >= 0 )
390             {
391                 parentPos = stack[currentDepth];
392 
393                 if ( parentPos.pos > 0 )
394                 {
395                     // The parent has some remaining keys on the right, get out
396                     return true;
397                 }
398                 else
399                 {
400                     currentDepth--;
401                 }
402             }
403 
404             return false;
405         }
406     }
407 
408 
409     /**
410      * Find the previous key
411      *
412      * @return the found key
413      * @throws IOException
414      * @throws EndOfFileExceededException
415      */
416     public K prev() throws EndOfFileExceededException, IOException
417     {
418         // First check that we have elements in the BTree
419         if ( ( stack == null ) || ( stack.length == 0 ) )
420         {
421             throw new NoSuchElementException( "No more keys present" );
422         }
423 
424         ParentPos<K, K> parentPos = stack[depth];
425 
426         if ( ( parentPos.page == null ) || ( parentPos.pos == BEFORE_FIRST ) )
427         {
428             // This is the end : no more keys
429             throw new NoSuchElementException( "No more keys present" );
430         }
431 
432         // Deal with the AfterLast case
433         if ( parentPos.pos == AFTER_LAST )
434         {
435             parentPos.pos = parentPos.page.getNbElems() - 1;
436         }
437         else
438         {
439             if ( parentPos.pos == 0 )
440             {
441                 parentPos = findPrevParentPos();
442                 
443                 if ( ( parentPos == null ) || ( parentPos.page == null ) )
444                 {
445                     // This is the end : no more keys
446                     throw new NoSuchElementException( "No more keys present" );
447                 }
448             }
449             else
450             {
451                 parentPos.pos--;
452             }
453         }
454 
455         AbstractPage<K, K> leaf = ( AbstractPage<K, K> ) ( parentPos.page );
456 
457         return leaf.getKey( parentPos.pos );
458     }
459 
460 
461     /**
462      * Get the previous key.
463      * 
464      * @return the found key
465      * @throws EndOfFileExceededException
466      * @throws IOException
467      */
468     public K prevKey() throws EndOfFileExceededException, IOException
469     {
470         return prev();
471     }
472 
473 
474     /**
475      * Tells if the cursor can return a previous key
476      *
477      * @return true if there are some more keys
478      * @throws IOException
479      * @throws EndOfFileExceededException
480      */
481     public boolean hasPrevKey() throws EndOfFileExceededException, IOException
482     {
483         // First check that we have elements in the BTree
484         if ( ( stack == null ) || ( stack.length == 0 ) )
485         {
486             // This is the end : no more key
487             return false;
488         }
489 
490         ParentPos<K, K> parentPos = stack[depth];
491 
492         if ( parentPos.page == null )
493         {
494             // This is the end : no more key
495             return false;
496         }
497 
498         switch ( parentPos.pos )
499         {
500             case 0 :
501                 // Beginning of the leaf. We have to go back into the stack up to the
502                 // parent, and down to the leaf
503                 return hasPrevParentPos();
504                 
505             case -1 :
506                 // no previous key
507                 return false;
508                 
509             default :
510                 // we have a previous key
511                 return true;
512         }
513     }
514 
515 
516     /**
517      * Tells if there is a next ParentPos
518      *
519      * @return the new ParentPos instance, or null if we have no following leaf
520      * @throws IOException
521      * @throws EndOfFileExceededException
522      */
523     private boolean hasNextParentPos() throws EndOfFileExceededException, IOException
524     {
525         if ( depth == 0 )
526         {
527             // No need to go any further, there is only one leaf in the btree
528             return false;
529         }
530 
531         int currentDepth = depth - 1;
532         Page<K, K> child = null;
533 
534         // First, go up the tree until we find a Node which has some element on the right
535         while ( currentDepth >= 0 )
536         {
537             // We first go up the tree, until we reach a page whose current position
538             // is not the last one
539             ParentPos<K, K> parentPos = stack[currentDepth];
540 
541             if ( parentPos.pos + 1 > parentPos.page.getNbElems() )
542             {
543                 // No more element on the right : go up
544                 currentDepth--;
545             }
546             else
547             {
548                 // We can pick the next element at this level
549                 child = ( ( AbstractPage<K, K> ) parentPos.page ).getPage( parentPos.pos + 1 );
550 
551                 // and go down the tree through the nodes
552                 while ( currentDepth < depth - 1 )
553                 {
554                     currentDepth++;
555                     child = ( ( AbstractPage<K, K> ) child ).getPage( 0 );
556                 }
557 
558                 return true;
559             }
560         }
561 
562         return false;
563     }
564 
565 
566     /**
567      * Find the leaf containing the following elements.
568      *
569      * @return the new ParentPos instance, or null if we have no following leaf
570      * @throws IOException
571      * @throws EndOfFileExceededException
572      */
573     private ParentPos<K, K> findNextParentPos() throws EndOfFileExceededException, IOException
574     {
575         if ( depth == 0 )
576         {
577             // No need to go any further, there is only one leaf in the btree
578             return null;
579         }
580 
581         int currentDepth = depth - 1;
582         Page<K, K> child = null;
583 
584         // First, go up the tree until we find a Node which has some element on the right
585         while ( currentDepth >= 0 )
586         {
587             // We first go up the tree, until we reach a page whose current position
588             // is not the last one
589             ParentPos<K, K> parentPos = stack[currentDepth];
590 
591             if ( parentPos.pos + 1 > parentPos.page.getNbElems() )
592             {
593                 // No more element on the right : go up
594                 currentDepth--;
595             }
596             else
597             {
598                 // We can pick the next element at this level
599                 parentPos.pos++;
600                 child = ( ( AbstractPage<K, K> ) parentPos.page ).getPage( parentPos.pos );
601 
602                 // and go down the tree through the nodes
603                 while ( currentDepth < depth - 1 )
604                 {
605                     currentDepth++;
606                     parentPos = stack[currentDepth];
607                     parentPos.pos = 0;
608                     parentPos.page = child;
609                     child = ( ( AbstractPage<K, K> ) child ).getPage( 0 );
610                 }
611 
612                 // and the leaf
613                 parentPos = stack[depth];
614                 parentPos.page = child;
615                 parentPos.pos = 0;
616 
617                 return parentPos;
618             }
619         }
620 
621         return null;
622     }
623 
624 
625     /**
626      * Find the leaf containing the previous elements.
627      *
628      * @return the new ParentPos instance, or null if we have no previous leaf
629      * @throws IOException
630      * @throws EndOfFileExceededException
631      */
632     private ParentPos<K, K> findPrevParentPos() throws EndOfFileExceededException, IOException
633     {
634         if ( depth == 0 )
635         {
636             // No need to go any further, there is only one leaf in the btree
637             return null;
638         }
639 
640         int currentDepth = depth - 1;
641         Page<K, K> child = null;
642 
643         // First, go up the tree until we find a Node which has some element on the left
644         while ( currentDepth >= 0 )
645         {
646             // We first go up the tree, until we reach a page whose current position
647             // is not the last one
648             ParentPos<K, K> parentPos = stack[currentDepth];
649 
650             if ( parentPos.pos == 0 )
651             {
652                 // No more element on the right : go up
653                 currentDepth--;
654             }
655             else
656             {
657                 // We can pick the next element at this level
658                 parentPos.pos--;
659                 child = ( ( AbstractPage<K, K> ) parentPos.page ).getPage( parentPos.pos );
660 
661                 // and go down the tree through the nodes
662                 while ( currentDepth < depth - 1 )
663                 {
664                     currentDepth++;
665                     parentPos = stack[currentDepth];
666                     parentPos.pos = child.getNbElems();
667                     parentPos.page = child;
668                     child = ( ( AbstractPage<K, K> ) parentPos.page ).getPage( parentPos.page.getNbElems() );
669                 }
670 
671                 // and the leaf
672                 parentPos = stack[depth];
673                 parentPos.pos = child.getNbElems() - 1;
674                 parentPos.page = child;
675 
676                 return parentPos;
677             }
678         }
679 
680         return null;
681     }
682 
683 
684     /**
685      * Tells if there is a prev ParentPos
686      *
687      * @return the new ParentPos instance, or null if we have no previous leaf
688      * @throws IOException
689      * @throws EndOfFileExceededException
690      */
691     private boolean hasPrevParentPos() throws EndOfFileExceededException, IOException
692     {
693         if ( depth == 0 )
694         {
695             // No need to go any further, there is only one leaf in the btree
696             return false;
697         }
698 
699         int currentDepth = depth - 1;
700         Page<K, K> child = null;
701 
702         // First, go up the tree until we find a Node which has some element on the right
703         while ( currentDepth >= 0 )
704         {
705             // We first go up the tree, until we reach a page whose current position
706             // is not the last one
707             ParentPos<K, K> parentPos = stack[currentDepth];
708 
709             if ( parentPos.pos == 0 )
710             {
711                 // No more element on the left : go up
712                 currentDepth--;
713             }
714             else
715             {
716                 // We can pick the previous element at this level
717                 child = ( ( AbstractPage<K, K> ) parentPos.page ).getPage( parentPos.pos - 1 );
718 
719                 // and go down the tree through the nodes
720                 while ( currentDepth < depth - 1 )
721                 {
722                     currentDepth++;
723                     child = ( ( AbstractPage<K, K> ) child ).getPage( child.getNbElems() );
724                 }
725 
726                 return true;
727             }
728         }
729 
730         return false;
731     }
732 
733 
734     /**
735      * {@inheritDoc}
736      */
737     public void close()
738     {
739         transaction.close();
740     }
741 
742 
743     /**
744      * Get the creation date
745      * @return The creation date for this cursor
746      */
747     public long getCreationDate()
748     {
749         return transaction.getCreationDate();
750     }
751 
752 
753     /**
754      * Get the current revision
755      *
756      * @return The revision this cursor is based on
757      */
758     public long getRevision()
759     {
760         return transaction.getRevision();
761     }
762 
763 
764     public String toString()
765     {
766         StringBuilder sb = new StringBuilder();
767 
768         sb.append( "KeyCursor, depth = " ).append( depth ).append( "\n" );
769 
770         for ( int i = 0; i <= depth; i++ )
771         {
772             sb.append( "    " ).append( stack[i] ).append( "\n" );
773         }
774 
775         return sb.toString();
776     }
777 }