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.LinkedList;
25  import java.util.NoSuchElementException;
26  
27  import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException;
28  
29  import static org.apache.directory.mavibot.btree.InternalUtil.*;
30  
31  /**
32   * A Cursor is used to fetch elements in a BTree and is returned by the
33   * @see BTree#browse method. The cursor <strng>must</strong> be closed
34   * when the user is done with it.
35   * <p>
36   * 
37   * @author <a href="mailto:labs@labs.apache.org">Mavibot labs Project</a>
38   *
39   * @param <K> The type for the Key
40   * @param <V> The type for the stored value
41   */
42  public class Cursor<K, V>
43  {
44      /** The transaction used for this cursor */
45      private Transaction<K, V> transaction;
46  
47      /** The Tuple used to return the results */
48      private Tuple<K, V> tuple = new Tuple<K, V>();
49  
50      /** The stack of pages from the root down to the leaf */
51      private LinkedList<ParentPos<K, V>> stack;
52  
53      /** The BTree we are walking */
54      private BTree<K, V> btree;
55  
56      private boolean allowDuplicates;
57      
58      /** a copy of the stack given at the time of initializing the cursor. This is used for moving the cursor to start position */
59      private LinkedList<ParentPos<K, V>> _initialStack;
60  
61      /**
62       * Creates a new instance of Cursor, starting on a page at a given position.
63       * 
64       * @param transaction The transaction this operation is protected by
65       * @param stack The stack of parent's from root to this page
66       */
67      /* No qualifier */Cursor( BTree<K, V> btree, Transaction<K, V> transaction, LinkedList<ParentPos<K, V>> stack )
68      {
69          this.transaction = transaction;
70          this.stack = stack;
71          this.btree = btree;
72          this.allowDuplicates = btree.isAllowDuplicates();
73          
74          _initialStack = new LinkedList<ParentPos<K,V>>();
75          
76          cloneStack( stack, _initialStack );
77      }
78      
79  
80      /**
81       * Find the next key/value
82       * 
83       * @return A Tuple containing the found key and value
84       * @throws IOException 
85       * @throws EndOfFileExceededException 
86       */
87      public Tuple<K, V> next() throws EndOfFileExceededException, IOException
88      {
89          ParentPos<K, V> parentPos = stack.getFirst();
90  
91          if ( parentPos.page == null )
92          {
93              // This is the end : no more value
94              throw new NoSuchElementException( "No more tuples present" );
95          }
96  
97          if ( parentPos.pos == parentPos.page.getNbElems() )
98          {
99              // End of the leaf. We have to go back into the stack up to the
100             // parent, and down to the leaf
101             parentPos = findNextParentPos();
102 
103             // we also need to check for the type of page cause
104             // findNextParentPos will never return a null ParentPos
105             if ( parentPos.page == null || ( parentPos.page instanceof Node ) )
106             {
107                 // This is the end : no more value
108                 throw new NoSuchElementException( "No more tuples present" );
109             }
110         }
111 
112         // can happen if next() is called after prev()
113         if ( parentPos.pos < 0 )
114         {
115             parentPos.pos = 0;
116         }
117         
118         Leaf<K, V> leaf = ( Leaf<K, V> ) ( parentPos.page );
119         tuple.setKey( leaf.keys[parentPos.pos] );
120         
121         if( allowDuplicates )
122         {
123             setDupsContainer( parentPos, btree );
124         
125             // can happen if next() is called after prev()
126             if ( parentPos.dupPos < 0 )
127             {
128                 parentPos.dupPos = 0;
129             }
130             
131             tuple.setValue( parentPos.dupsContainer.rootPage.getKey( parentPos.dupPos ) );
132             parentPos.dupPos++;
133             
134             if( parentPos.dupsContainer.getNbElems() ==  parentPos.dupPos )
135             {
136                 parentPos.pos++;
137                 changeNextDupsContainer( parentPos, btree );
138             }
139         }
140         else
141         {
142             tuple.setValue( leaf.values[parentPos.pos].getValue( btree ) );
143             parentPos.pos++;
144         }
145 
146         return tuple;
147     }
148 
149     
150     /**
151      * Find the leaf containing the following elements.
152      * 
153      * @return the new ParentPos instance, or null if we have no following leaf
154      * @throws IOException 
155      * @throws EndOfFileExceededException 
156      */
157     private ParentPos<K, V> findNextParentPos() throws EndOfFileExceededException, IOException
158     {
159         ParentPos<K, V> lastParentPos = null;
160         
161         while ( true )
162         {
163             // We first go up the tree, until we reach a page whose current position
164             // is not the last one
165             ParentPos<K, V> parentPos = stack.peek();
166 
167             if ( parentPos == null )
168             {
169                 stack.push( lastParentPos );
170                 return lastParentPos;
171             }
172 
173             if ( parentPos.pos == parentPos.page.getNbElems() )
174             {
175                 lastParentPos = stack.pop();
176                 continue;
177             }
178             else
179             {
180                 // Then we go down the tree until we find a leaf which position is not the last one.
181                 int newPos = ++parentPos.pos;
182                 ParentPos<K, V> newParentPos = parentPos;
183 
184                 while ( newParentPos.page instanceof Node )
185                 {
186                     Node<K, V> node = ( Node<K, V> ) newParentPos.page;
187 
188                     newParentPos = new ParentPos<K, V>( node.children[newPos].getValue( btree ), 0 );
189 
190                     stack.push( newParentPos );
191 
192                     newPos = 0;
193                 }
194 
195                 if( allowDuplicates )
196                 {
197                     changeNextDupsContainer( newParentPos, btree );
198                 }
199 
200                 return newParentPos;
201             }
202         }
203     }
204 
205 
206     /**
207      * Find the leaf containing the previous elements.
208      * 
209      * @return the new ParentPos instance, or null if we have no previous leaf
210      * @throws IOException 
211      * @throws EndOfFileExceededException 
212      */
213     private ParentPos<K, V> findPreviousParentPos() throws EndOfFileExceededException, IOException
214     {
215         ParentPos<K, V> lastParentPos = null;
216         
217         while ( true )
218         {
219             // We first go up the tree, until we reach a page which current position
220             // is not the first one
221             ParentPos<K, V> parentPos = stack.peek();
222 
223             if ( parentPos == null )
224             {
225                 stack.push( lastParentPos );
226                 return lastParentPos;
227             }
228 
229             if ( parentPos.pos == 0 )
230             {
231                 lastParentPos = stack.pop();
232                 continue;
233             }
234             else
235             {
236                 // Then we go down the tree until we find a leaf which position is not the first one.
237                 int newPos = --parentPos.pos;
238                 ParentPos<K, V> newParentPos = parentPos;
239 
240                 while ( newParentPos.page instanceof Node )
241                 {
242                     Node<K, V> node = ( Node<K, V> ) newParentPos.page;
243 
244                     newParentPos = new ParentPos<K, V>( node.children[newPos].getValue( btree ), node.children[newPos]
245                         .getValue( btree ).getNbElems() );
246 
247                     stack.push( newParentPos );
248 
249                     newPos = node.getNbElems();
250                 }
251 
252                 if( allowDuplicates )
253                 {
254                     changePrevDupsContainer( newParentPos, btree );
255                 }
256 
257                 return newParentPos;
258             }
259         }
260     }
261 
262 
263     /**
264      * Find the previous key/value
265      * 
266      * @return A Tuple containing the found key and value
267      * @throws IOException 
268      * @throws EndOfFileExceededException 
269      */
270     public Tuple<K, V> prev() throws EndOfFileExceededException, IOException
271     {
272         ParentPos<K, V> parentPos = stack.peek();
273 
274         if ( parentPos.page == null )
275         {
276             // This is the end : no more value
277             throw new NoSuchElementException( "No more tuples present" );
278         }
279 
280         if ( parentPos.pos == 0 && parentPos.dupPos == 0 )
281         {
282             // End of the leaf. We have to go back into the stack up to the
283             // parent, and down to the leaf
284             parentPos = findPreviousParentPos();
285 
286             // we also need to check for the type of page cause
287             // findPrevParentPos will never return a null ParentPos
288             if ( parentPos.page == null || ( parentPos.page instanceof Node ) )
289             {
290                 // This is the end : no more value
291                 throw new NoSuchElementException( "No more tuples present" );
292             }
293         }
294 
295         Leaf<K, V> leaf = ( Leaf<K, V> ) ( parentPos.page );
296         
297         if( allowDuplicates )
298         {
299             setDupsContainer( parentPos, btree );
300             
301             // can happen if prev() was called after next()
302             if( parentPos.pos == parentPos.page.getNbElems() )
303             {
304                 parentPos.pos--;
305             }
306             
307             if( parentPos.dupPos == parentPos.dupsContainer.getNbElems() )
308             {
309                 parentPos.dupPos--;
310             }
311             else if( parentPos.dupPos == 0 )
312             {
313                 changePrevDupsContainer( parentPos, btree );
314                 parentPos.pos--;
315                 parentPos.dupPos--;
316             }
317             else
318             {
319                 parentPos.dupPos--;
320             }
321             
322             tuple.setKey( leaf.keys[parentPos.pos] );
323             tuple.setValue( parentPos.dupsContainer.rootPage.getKey( parentPos.dupPos ) );
324         }
325         else
326         {
327             parentPos.pos--;
328             tuple.setKey( leaf.keys[parentPos.pos] );
329             tuple.setValue( leaf.values[parentPos.pos].getValue( btree ) );
330         }
331         
332         return tuple;
333     }
334 
335 
336     /**
337      * Tells if the cursor can return a next element
338      * @return true if there are some more elements
339      * @throws IOException 
340      * @throws EndOfFileExceededException 
341      */
342     public boolean hasNext() throws EndOfFileExceededException, IOException
343     {
344         ParentPos<K, V> parentPos = stack.peek();
345 
346         if ( parentPos.page == null )
347         {
348             return false;
349         }
350 
351         for( ParentPos<K, V> p : stack )
352         {
353             if( allowDuplicates && ( p.page instanceof Leaf ) )
354             {
355                 if ( ( p.dupPos != p.dupsContainer.getNbElems() ) 
356                      && ( p.pos != p.page.getNbElems() ) )
357                 {
358                     return true;
359                 }
360             }
361             else if ( p.pos != p.page.getNbElems() )
362             {
363                 return true;
364             }
365         }
366         
367         return false;
368     }
369 
370 
371     /**
372      * Tells if the cursor can return a previous element
373      * @return true if there are some more elements
374      * @throws IOException 
375      * @throws EndOfFileExceededException 
376      */
377     public boolean hasPrev() throws EndOfFileExceededException, IOException
378     {
379         ParentPos<K, V> parentPos = stack.peek();
380 
381         if ( parentPos.page == null )
382         {
383             return false;
384         }
385 
386         for( ParentPos<K, V> p : stack )
387         {
388             if( allowDuplicates && ( p.page instanceof Leaf ) )
389             {
390                 if( ( p.dupPos != 0 ) 
391                     || ( p.pos != 0 ) )
392                 {
393                     return true;    
394                 }
395             }
396             else if ( p.pos != 0 )
397             {
398                 return true;
399             }
400         }
401         
402         return false;
403     }
404 
405 
406     /**
407      * Closes the cursor, thus releases the associated transaction
408      */
409     public void close()
410     {
411         transaction.close();
412     }
413 
414 
415     /**
416      * @return The revision this cursor is based on
417      */
418     public long getRevision()
419     {
420         return transaction.getRevision();
421     }
422 
423 
424     /**
425      * @return The creation date for this cursor
426      */
427     public long getCreationDate()
428     {
429         return transaction.getCreationDate();
430     }
431     
432     
433     /**
434      * Moves the cursor to the next non-duplicate key.
435 
436      * If the BTree contains 
437      * 
438      *  <ul>
439      *    <li><1,0></li>
440      *    <li><1,1></li>
441      *    <li><2,0></li>
442      *    <li><2,1></li>
443      *  </ul>
444      *   
445      *  and cursor is present at <1,0> then the cursor will move to <2,0>
446      *  
447      * @throws EndOfFileExceededException
448      * @throws IOException
449      */
450     public void moveToNextNonDuplicateKey() throws EndOfFileExceededException, IOException
451     {
452         ParentPos<K, V> parentPos = stack.getFirst();
453 
454         if ( parentPos.page == null )
455         {
456             return;
457         }
458 
459         if ( parentPos.pos == ( parentPos.page.getNbElems() - 1 ) )
460         {
461             // End of the leaf. We have to go back into the stack up to the
462             // parent, and down to the leaf
463             // increment the position cause findNextParentPos checks "parentPos.pos == parentPos.page.getNbElems()"
464             parentPos.pos++;
465             ParentPos<K, V> nextPos = findNextParentPos();
466             
467             // if the returned value is a Node OR if it is same as the parentPos
468             // that means cursor is already at the last position
469             // call afterLast() to restore the stack with the path to the right most element
470             if( ( nextPos.page instanceof Node ) || ( nextPos == parentPos ) )
471             {
472                 afterLast();
473             }
474             else
475             {
476             	parentPos = nextPos;
477             }
478         }
479         else
480         {
481             parentPos.pos++;
482             changeNextDupsContainer( parentPos, btree );
483         }
484     }    
485 
486 
487     /**
488      * Moves the cursor to the previous non-duplicate key
489      * If the BTree contains 
490      * 
491      *  <ul>
492      *    <li><1,0></li>
493      *    <li><1,1></li>
494      *    <li><2,0></li>
495      *    <li><2,1></li>
496      *  </ul>
497      *   
498      *  and cursor is present at <2,1> then the cursor will move to <1,1>
499      * 
500      * @throws EndOfFileExceededException
501      * @throws IOException
502      */
503     public void moveToPrevNonDuplicateKey() throws EndOfFileExceededException, IOException
504     {
505         ParentPos<K, V> parentPos = stack.peek();
506 
507         if ( parentPos.page == null )
508         {
509             // This is the end : no more value
510             return;
511         }
512 
513         if ( parentPos.pos == 0 )
514         {
515             // End of the leaf. We have to go back into the stack up to the
516             // parent, and down to the leaf
517             parentPos = findPreviousParentPos();
518             
519             // if the returned value is a Node that means cursor is already at the first position
520             // call beforeFirst() to restore the stack to the initial state
521             if( parentPos.page instanceof Node )
522             {
523                 beforeFirst();
524             }
525         }
526         else
527         {
528             changePrevDupsContainer( parentPos, btree );
529             parentPos.pos--;
530         }
531     }
532     
533     
534     /**
535      * moves the cursor to the same position that was given at the time of instantiating the cursor.
536      * 
537      *  For example, if the cursor was created using browse() method, then beforeFirst() will
538      *  place the cursor before the 0th position.
539      *  
540      *  If the cursor was created using browseFrom(K), then calling beforeFirst() will reset the position
541      *  to the just before the position where K is present.
542      */
543     public void beforeFirst() throws IOException
544     {
545         cloneStack( _initialStack, stack );
546     }
547     
548     
549     /**
550      * Places the cursor at the end of the last position
551      * 
552      * @throws IOException
553      */
554     public void afterLast() throws IOException
555     {
556         stack.clear();
557         stack = ( LinkedList<ParentPos<K, V>> ) BTreeFactory.getPathToRightMostLeaf( btree );
558     }
559     
560     
561     /**
562      * clones the original stack of ParentPos objects
563      * 
564      * @param original the original stack
565      * @param clone the stack where the cloned ParentPos objects to be copied
566      */
567     private void cloneStack( LinkedList<ParentPos<K, V>> original, LinkedList<ParentPos<K, V>> clone )
568     {
569         clone.clear();
570         
571         // preserve the first position
572         for( ParentPos<K, V> o : original )
573         {
574             ParentPos<K, V> tmp = new ParentPos<K, V>( o.page, o.pos );
575             tmp.dupPos = o.dupPos;
576             tmp.dupsContainer = o.dupsContainer;
577             clone.add( tmp );
578         }
579     }
580     
581 }