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