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 static org.apache.directory.mavibot.btree.BTreeFactory.createLeaf;
24  import static org.apache.directory.mavibot.btree.BTreeFactory.createNode;
25  import static org.apache.directory.mavibot.btree.BTreeFactory.setKey;
26  
27  import java.io.IOException;
28  import java.lang.reflect.Array;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Comparator;
32  import java.util.List;
33  import java.util.UUID;
34  
35  import org.apache.directory.mavibot.btree.exception.BTreeAlreadyCreatedException;
36  import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException;
37  import org.apache.directory.mavibot.btree.exception.BTreeCreationException;
38  import org.apache.directory.mavibot.btree.exception.KeyNotFoundException;
39  import org.apache.directory.mavibot.btree.serializer.IntSerializer;
40  import org.apache.directory.mavibot.btree.serializer.LongSerializer;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  
45  /**
46   * A holder to store the Values
47   *
48   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
49   * @param <V> The value type
50   */
51  /* No qualifier */class PersistedValueHolder<V> extends AbstractValueHolder<V>
52  {
53      /** The LoggerFactory used by this class */
54      protected static final Logger LOG = LoggerFactory.getLogger( PersistedValueHolder.class );
55  
56      /** The parent BTree */
57      protected PersistedBTree<V, V> parentBtree;
58  
59      /** The serialized value */
60      private byte[] raw;
61  
62      /** A flag set to true when the raw value has been deserialized */
63      private boolean isDeserialized = false;
64  
65      /** A flag to signal that the raw value represent the serialized values in their last state */
66      private boolean isRawUpToDate = false;
67  
68  
69      /**
70       * Creates a new instance of a ValueHolder, containing the serialized values.
71       *
72       * @param parentBtree the parent BTree
73       * @param raw The raw data containing the values
74       * @param nbValues the number of stored values
75       * @param raw the byte[] containing either the serialized array of values or the sub-btree offset
76       */
77      PersistedValueHolder( BTree<?, V> parentBtree, int nbValues, byte[] raw )
78      {
79          this.parentBtree = ( PersistedBTree<V, V> ) parentBtree;
80          this.valueSerializer = parentBtree.getValueSerializer();
81          this.raw = raw;
82          isRawUpToDate = true;
83          valueThresholdUp = PersistedBTree.valueThresholdUp;
84          valueThresholdLow = PersistedBTree.valueThresholdLow;
85  
86          // We create the array of values if they fit in an array. If they are stored in a
87          // BTree, we do nothing atm.
88          if ( nbValues <= valueThresholdUp )
89          {
90              // The values are contained into an array
91              valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues );
92          }
93      }
94  
95  
96      /**
97       * Creates a new instance of a ValueHolder, containing Values. This constructor is called
98       * when we need to create a new ValueHolder with deserialized values.
99       *
100      * @param parentBtree The parent BTree
101      * @param values The Values stored in the ValueHolder
102      */
103     PersistedValueHolder( BTree<?, V> parentBtree, V... values )
104     {
105         this.parentBtree = ( PersistedBTree<V, V> ) parentBtree;
106         this.valueSerializer = parentBtree.getValueSerializer();
107         valueThresholdUp = PersistedBTree.valueThresholdUp;
108         valueThresholdLow = PersistedBTree.valueThresholdLow;
109 
110         if ( values != null )
111         {
112             int nbValues = values.length;
113 
114             if ( nbValues < PersistedBTree.valueThresholdUp )
115             {
116                 // Keep an array
117                 valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues );
118 
119                 try
120                 {
121                     System.arraycopy( values, 0, valueArray, 0, values.length );
122                 }
123                 catch ( ArrayStoreException ase )
124                 {
125                     ase.printStackTrace();
126                     throw ase;
127                 }
128             }
129             else
130             {
131                 // Use a sub btree, now that we have reached the threshold
132                 createSubTree();
133 
134                 //             // Now inject all the values into it
135                 //                for ( V value : values )
136                 //                {
137                 //                    try
138                 //                    {
139                 //                        valueBtree.insert( value, value );
140                 //                    }
141                 //                    catch ( IOException e )
142                 //                    {
143                 //                        e.printStackTrace();
144                 //                    }
145                 //                }
146                 try
147                 {
148                     build( ( PersistedBTree<V, V> ) valueBtree, values );
149                 }
150                 catch ( Exception e )
151                 {
152                     throw new RuntimeException( e );
153                 }
154             }
155         }
156         else
157         {
158             // No value, we create an empty array
159             valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), 0 );
160         }
161 
162         isDeserialized = true;
163     }
164 
165 
166     /**
167      * @return a cursor on top of the values
168      */
169     public ValueCursor<V> getCursor()
170     {
171         // Check that the values are deserialized before doing anything
172         checkAndDeserialize();
173 
174         return super.getCursor();
175     }
176 
177 
178     /**
179      * @return the raw representation of the value holder. The serialized value will not be the same
180      * if the values are stored in an array or in a btree. <br/>
181      * If they are stored in a BTree, the raw value will contain the offset of the btree, otherwise
182      * it will contain a byte[] which will contain each serialized value, prefixed by their length.
183      *
184      */
185     /* No qualifier*/byte[] getRaw()
186     {
187         if ( isRawUpToDate )
188         {
189             // Just have to return the raw value
190             return raw;
191         }
192 
193         if ( isSubBtree() )
194         {
195             // The values are stored into a subBtree, return the offset of this subBtree
196             long btreeOffset = ( ( PersistedBTree<V, V> ) valueBtree ).getBtreeOffset();
197             raw = LongSerializer.serialize( btreeOffset );
198         }
199         else
200         {
201             // Create as many byte[] as we have length and serialized values to store
202             byte[][] valueBytes = new byte[valueArray.length * 2][];
203             int length = 0;
204             int pos = 0;
205 
206             // Process each value now
207             for ( V value : valueArray )
208             {
209                 // Serialize the value
210                 byte[] bytes = valueSerializer.serialize( value );
211                 length += bytes.length;
212 
213                 // Serialize the value's length
214                 byte[] sizeBytes = IntSerializer.serialize( bytes.length );
215                 length += sizeBytes.length;
216 
217                 // And store the two byte[]
218                 valueBytes[pos++] = sizeBytes;
219                 valueBytes[pos++] = bytes;
220             }
221 
222             // Last, not least, create a buffer large enough to contain all the created byte[],
223             // and copy all those byte[] into this buffer
224             raw = new byte[length];
225             pos = 0;
226 
227             for ( byte[] bytes : valueBytes )
228             {
229                 System.arraycopy( bytes, 0, raw, pos, bytes.length );
230                 pos += bytes.length;
231             }
232         }
233 
234         // Update the flags
235         isRawUpToDate = true;
236 
237         return raw;
238     }
239 
240 
241     /**
242      * {@inheritDoc}
243      */
244     public int size()
245     {
246         checkAndDeserialize();
247 
248         if ( valueArray == null )
249         {
250             return ( int ) valueBtree.getNbElems();
251         }
252         else
253         {
254             return valueArray.length;
255         }
256     }
257 
258 
259     /**
260      * Create a new Sub-BTree to store the values.
261      */
262     protected void createSubTree()
263     {
264         try
265         {
266             PersistedBTreeConfiguration<V, V> configuration = new PersistedBTreeConfiguration<V, V>();
267             configuration.setAllowDuplicates( false );
268             configuration.setKeySerializer( valueSerializer );
269             configuration.setName( UUID.randomUUID().toString() );
270             configuration.setValueSerializer( valueSerializer );
271             configuration.setParentBTree( parentBtree );
272             configuration.setBtreeType( BTreeTypeEnum.PERSISTED_SUB );
273 
274             valueBtree = BTreeFactory.createPersistedBTree( configuration );
275 
276             try
277             {
278                 // The sub-btree will not be added into the BOB.
279                 parentBtree.getRecordManager().manage( valueBtree, RecordManager.INTERNAL_BTREE );
280                 raw = null;
281             }
282             catch ( BTreeAlreadyManagedException e )
283             {
284                 // should never happen
285                 throw new BTreeAlreadyCreatedException( e );
286             }
287         }
288         catch ( IOException e )
289         {
290             throw new BTreeCreationException( e );
291         }
292     }
293 
294 
295     /**
296      * Set the subBtree in the ValueHolder
297      */
298     /* No qualifier*/void setSubBtree( BTree<V, V> subBtree )
299     {
300         valueBtree = subBtree;
301         raw = null;
302         valueArray = null;
303         isDeserialized = true;
304         isRawUpToDate = false;
305     }
306 
307 
308     /**
309      * Check that the values are stored as raw value
310      */
311     private void checkAndDeserialize()
312     {
313         if ( !isDeserialized )
314         {
315             if ( valueArray == null )
316             {
317                 // the values are stored into a sub-btree. Read it now if it's not already done
318                 deserializeSubBtree();
319             }
320             else
321             {
322                 // The values are stored into an array. Deserialize it now
323                 deserializeArray();
324             }
325 
326             // Change the flag
327             isDeserialized = true;
328         }
329     }
330 
331 
332     /**
333      * {@inheritDoc}
334      */
335     public void add( V value )
336     {
337         // First check that we have a loaded BTree
338         checkAndDeserialize();
339 
340         super.add( value );
341 
342         // The raw value is not anymore up to date with the content
343         isRawUpToDate = false;
344         raw = null;
345     }
346 
347 
348     /**
349      * Remove a value from an array
350      */
351     private V removeFromArray( V value )
352     {
353         checkAndDeserialize();
354 
355         // First check that the value is not already present in the ValueHolder
356         int pos = findPos( value );
357 
358         if ( pos < 0 )
359         {
360             // The value does not exists : nothing to do
361             return null;
362         }
363 
364         // Ok, we just have to delete the new element at the right position
365         // First, copy the array
366         V[] newValueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), valueArray.length - 1 );
367 
368         System.arraycopy( valueArray, 0, newValueArray, 0, pos );
369         System.arraycopy( valueArray, pos + 1, newValueArray, pos, valueArray.length - pos - 1 );
370 
371         // Get the removed element
372         V removedValue = valueArray[pos];
373 
374         // And switch the arrays
375         valueArray = newValueArray;
376 
377         return removedValue;
378     }
379 
380 
381     /**
382      * Remove the value from a sub btree
383      */
384     private V removeFromBtree( V removedValue )
385     {
386         // First check that we have a loaded BTree
387         checkAndDeserialize();
388 
389         if ( btreeContains( removedValue ) )
390         {
391             try
392             {
393                 if ( valueBtree.getNbElems() - 1 < PersistedBTree.valueThresholdLow )
394                 {
395                     int nbValues = ( int ) ( valueBtree.getNbElems() - 1 );
396 
397                     // We have to switch to an Array of values
398                     valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues );
399 
400                     // Now copy all the value but the one we have removed
401                     TupleCursor<V, V> cursor = valueBtree.browse();
402                     V returnedValue = null;
403                     int pos = 0;
404 
405                     while ( cursor.hasNext() )
406                     {
407                         Tuple<V, V> tuple = cursor.next();
408 
409                         V value = tuple.getKey();
410 
411                         if ( valueSerializer.getComparator().compare( removedValue, value ) == 0 )
412                         {
413                             // This is the removed value : skip it
414                             returnedValue = value;
415                         }
416                         else
417                         {
418                             valueArray[pos++] = value;
419                         }
420                     }
421 
422                     cursor.close();
423 
424                     return returnedValue;
425                 }
426                 else
427                 {
428                     Tuple<V, V> removedTuple = valueBtree.delete( removedValue );
429 
430                     if ( removedTuple != null )
431                     {
432                         return removedTuple.getKey();
433                     }
434                     else
435                     {
436                         return null;
437                     }
438                 }
439             }
440             catch ( IOException e )
441             {
442                 // TODO Auto-generated catch block
443                 e.printStackTrace();
444                 return null;
445             }
446             catch ( KeyNotFoundException knfe )
447             {
448                 // TODO Auto-generated catch block
449                 knfe.printStackTrace();
450                 return null;
451             }
452         }
453         else
454         {
455             return null;
456         }
457     }
458 
459 
460     /**
461      * {@inheritDoc}
462      */
463     public V remove( V value )
464     {
465         V removedValue = null;
466 
467         if ( valueArray != null )
468         {
469             removedValue = removeFromArray( value );
470         }
471         else
472         {
473             removedValue = removeFromBtree( value );
474         }
475 
476         // The raw value is not anymore up to date wth the content
477         isRawUpToDate = false;
478         raw = null;
479 
480         return removedValue;
481     }
482 
483 
484     /**
485      * {@inheritDoc}
486      */
487     public boolean contains( V checkedValue )
488     {
489         // First, deserialize the value if it's still a byte[]
490         checkAndDeserialize();
491 
492         return super.contains( checkedValue );
493     }
494 
495 
496     /**
497      * Find the position of a given value in the array, or the position where we
498      * would insert the element (in this case, the position will be negative).
499      * As we use a 0-based array, the negative position for 0 is -1.
500      * -1 means the element can be added in position 0
501      * -2 means the element can be added in position 1
502      * ...
503      */
504     private int findPos( V value )
505     {
506         if ( valueArray.length == 0 )
507         {
508             return -1;
509         }
510 
511         // Do a search using dichotomy
512         int pivot = valueArray.length / 2;
513         int low = 0;
514         int high = valueArray.length - 1;
515         Comparator<V> comparator = valueSerializer.getComparator();
516 
517         while ( high > low )
518         {
519             switch ( high - low )
520             {
521                 case 1:
522                     // We have 2 elements
523                     int result = comparator.compare( value, valueArray[pivot] );
524 
525                     if ( result == 0 )
526                     {
527                         return pivot;
528                     }
529 
530                     if ( result < 0 )
531                     {
532                         if ( pivot == low )
533                         {
534                             return -( low + 1 );
535                         }
536                         else
537                         {
538                             result = comparator.compare( value, valueArray[low] );
539 
540                             if ( result == 0 )
541                             {
542                                 return low;
543                             }
544 
545                             if ( result < 0 )
546                             {
547                                 return -( low + 1 );
548                             }
549                             else
550                             {
551                                 return -( low + 2 );
552                             }
553                         }
554                     }
555                     else
556                     {
557                         if ( pivot == high )
558                         {
559                             return -( high + 2 );
560                         }
561                         else
562                         {
563                             result = comparator.compare( value, valueArray[high] );
564 
565                             if ( result == 0 )
566                             {
567                                 return high;
568                             }
569 
570                             if ( result < 0 )
571                             {
572                                 return -( high + 1 );
573                             }
574                             else
575                             {
576                                 return -( high + 2 );
577                             }
578                         }
579                     }
580 
581                 default:
582                     // We have 3 elements
583                     result = comparator.compare( value, valueArray[pivot] );
584 
585                     if ( result == 0 )
586                     {
587                         return pivot;
588                     }
589 
590                     if ( result < 0 )
591                     {
592                         high = pivot - 1;
593                     }
594                     else
595                     {
596                         low = pivot + 1;
597                     }
598 
599                     pivot = ( high + low ) / 2;
600 
601                     continue;
602             }
603         }
604 
605         int result = comparator.compare( value, valueArray[pivot] );
606 
607         if ( result == 0 )
608         {
609             return pivot;
610         }
611 
612         if ( result < 0 )
613         {
614             return -( pivot + 1 );
615         }
616         else
617         {
618             return -( pivot + 2 );
619         }
620     }
621 
622 
623     /**
624      * Create a clone of this instance
625      */
626     public ValueHolder<V> clone() throws CloneNotSupportedException
627     {
628         PersistedValueHolder<V> copy = ( PersistedValueHolder<V> ) super.clone();
629 
630         // copy the valueArray if it's not null
631         // We don't clone the BTree, as we will create new revisions when
632         // modifying it
633         if ( valueArray != null )
634         {
635             copy.valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), valueArray.length );
636             System.arraycopy( valueArray, 0, copy.valueArray, 0, valueArray.length );
637         }
638 
639         // Also clone the raw value if its up to date
640         if ( isRawUpToDate )
641         {
642             copy.raw = new byte[raw.length];
643             System.arraycopy( raw, 0, copy.raw, 0, raw.length );
644         }
645 
646         return copy;
647     }
648 
649 
650     @Override
651     public V replaceValueArray( V newValue )
652     {
653         V val = super.replaceValueArray( newValue );
654         // The raw value is not anymore up to date with the content
655         isRawUpToDate = false;
656 
657         return val;
658     }
659 
660 
661     /**
662      * Deserialize the values stored in an array
663      */
664     private void deserializeArray()
665     {
666         // We haven't yet deserialized the values. Let's do it now. The values are
667         // necessarily stored in an array at this point
668         int index = 0;
669         int pos = 0;
670 
671         while ( pos < raw.length )
672         {
673             try
674             {
675                 int size = IntSerializer.deserialize( raw, pos );
676                 pos += 4;
677 
678                 V value = valueSerializer.fromBytes( raw, pos );
679                 pos += size;
680                 valueArray[index++] = value;
681             }
682             catch ( IOException e )
683             {
684                 e.printStackTrace();
685             }
686         }
687     }
688 
689 
690     /**
691      * Deserialize the values stored in a sub-btree
692      */
693     private void deserializeSubBtree()
694     {
695         // Get the sub-btree offset
696         long offset = LongSerializer.deserialize( raw );
697 
698         // and reload the sub btree
699         valueBtree = parentBtree.getRecordManager().loadDupsBtree( offset, parentBtree );
700     }
701 
702 
703     /**
704      * @return The sub-btree offset
705      */
706     /* No qualifier */long getOffset()
707     {
708         if ( valueArray == null )
709         {
710             return ( ( PersistedBTree<V, V> ) valueBtree ).getBtreeOffset();
711         }
712         else
713         {
714             return -1L;
715         }
716     }
717 
718 
719     /**
720      * Constructs the sub-BTree using bulkload instead of performing sequential inserts.
721      * 
722      * @param btree the sub-BTtree to be constructed
723      * @param dupKeyValues the array of values to be inserted as keys
724      * @return
725      * @throws Exception
726      */
727     private BTree build( PersistedBTree<V, V> btree, V[] dupKeyValues ) throws Exception
728     {
729         long newRevision = btree.getRevision() + 1;
730 
731         int numKeysInNode = btree.getPageSize();
732 
733         RecordManager rm = btree.getRecordManager();
734 
735         List<Page<V, V>> lstLeaves = new ArrayList<Page<V, V>>();
736 
737         int totalTupleCount = 0;
738 
739         Page<V, V> leaf1 = BTreeFactory.createLeaf( btree, newRevision, numKeysInNode );
740         lstLeaves.add( leaf1 );
741 
742         int leafIndex = 0;
743 
744         for ( V v : dupKeyValues )
745         {
746             setKey( btree, leaf1, leafIndex, v );
747 
748             leafIndex++;
749             totalTupleCount++;
750             if ( ( totalTupleCount % numKeysInNode ) == 0 )
751             {
752                 leafIndex = 0;
753 
754                 PageHolder<V, V> pageHolder = ( PageHolder ) rm.writePage( btree, leaf1, newRevision );
755 
756                 leaf1 = createLeaf( btree, newRevision, numKeysInNode );
757                 lstLeaves.add( leaf1 );
758             }
759 
760             //TODO build the whole tree in chunks rather than processing *all* leaves at first
761         }
762 
763         if ( lstLeaves.isEmpty() )
764         {
765             return btree;
766         }
767 
768         // remove null keys and values from the last leaf and resize
769         PersistedLeaf lastLeaf = ( PersistedLeaf ) lstLeaves.get( lstLeaves.size() - 1 );
770         for ( int i = 0; i < lastLeaf.nbElems; i++ )
771         {
772             if ( lastLeaf.keys[i] == null )
773             {
774                 int n = i;
775                 lastLeaf.nbElems = n;
776                 KeyHolder[] keys = lastLeaf.keys;
777 
778                 lastLeaf.keys = ( KeyHolder[] ) Array.newInstance( KeyHolder.class, n );
779                 System.arraycopy( keys, 0, lastLeaf.keys, 0, n );
780 
781                 PageHolder pageHolder = ( PageHolder ) rm.writePage( btree, lastLeaf, newRevision );
782 
783                 break;
784             }
785         }
786 
787         if ( lastLeaf.keys.length == 0 )
788         {
789             lstLeaves.remove( lastLeaf );
790         }
791 
792         // make sure either one of the root pages is reclaimed, cause when we call rm.manage()
793         // there is already a root page created
794         Page rootPage = attachNodes( lstLeaves, btree, numKeysInNode, rm );
795 
796         Page oldRoot = btree.getRootPage();
797 
798         long newRootPageOffset = ( ( AbstractPage ) rootPage ).getOffset();
799         LOG.debug( "replacing old offset {} of the BTree {} with {}",
800             btree.getRootPageOffset(), btree.getName(), newRootPageOffset );
801 
802         BTreeHeader header = btree.getBtreeHeader();
803 
804         header.setRootPage( rootPage );
805         header.setRevision( newRevision );
806         header.setNbElems( totalTupleCount );
807 
808         long newBtreeHeaderOffset = rm.writeBtreeHeader( btree, header );
809 
810         header.setBTreeHeaderOffset( newBtreeHeaderOffset );
811 
812         rm.freePages( ( BTree ) btree, btree.getRevision(), ( List ) Arrays.asList( oldRoot ) );
813 
814         return btree;
815     }
816 
817 
818     /**
819      * Attaches the Nodes together
820      * 
821      * @param children the leaves
822      * @param btree the sub-BTree
823      * @param numKeysInNode number of keys per each node
824      * @param rm the RecordManager
825      * @return the new root page of the sub-BTree after attaching all the nodes
826      * @throws IOException
827      */
828     private Page attachNodes( List<Page<V, V>> children, BTree btree, int numKeysInNode, RecordManager rm )
829         throws IOException
830     {
831         if ( children.size() == 1 )
832         {
833             return children.get( 0 );
834         }
835 
836         List<Page<V, V>> lstNodes = new ArrayList<Page<V, V>>();
837 
838         int numChildren = numKeysInNode + 1;
839 
840         PersistedNode node = ( PersistedNode ) createNode( btree, btree.getRevision(), numKeysInNode );
841         lstNodes.add( node );
842         int i = 0;
843         int totalNodes = 0;
844 
845         for ( Page p : children )
846         {
847             if ( i != 0 )
848             {
849                 setKey( btree, node, i - 1, p.getLeftMostKey() );
850             }
851 
852             node.children[i] = new PersistedPageHolder( btree, p );
853 
854             i++;
855             totalNodes++;
856 
857             if ( ( totalNodes % numChildren ) == 0 )
858             {
859                 i = 0;
860 
861                 PageHolder pageHolder = ( PageHolder ) rm.writePage( btree, node, 1 );
862 
863                 node = ( PersistedNode ) createNode( btree, btree.getRevision(), numKeysInNode );
864                 lstNodes.add( node );
865             }
866         }
867 
868         // remove null keys and values from the last node and resize
869         AbstractPage lastNode = ( AbstractPage ) lstNodes.get( lstNodes.size() - 1 );
870 
871         for ( int j = 0; j < lastNode.nbElems; j++ )
872         {
873             if ( lastNode.keys[j] == null )
874             {
875                 int n = j;
876                 lastNode.nbElems = n;
877                 KeyHolder[] keys = lastNode.keys;
878 
879                 lastNode.keys = ( KeyHolder[] ) Array.newInstance( KeyHolder.class, n );
880                 System.arraycopy( keys, 0, lastNode.keys, 0, n );
881 
882                 PageHolder pageHolder = ( PageHolder ) rm.writePage( btree, lastNode, 1 );
883 
884                 break;
885             }
886         }
887 
888         if ( lastNode.keys.length == 0 )
889         {
890             lstNodes.remove( lastNode );
891         }
892 
893         return attachNodes( lstNodes, btree, numKeysInNode, rm );
894     }
895 
896 
897     /**
898      * @see Object#toString()
899      */
900     public String toString()
901     {
902         StringBuilder sb = new StringBuilder();
903 
904         sb.append( "ValueHolder[" ).append( valueSerializer.getClass().getSimpleName() );
905 
906         if ( !isDeserialized )
907         {
908             sb.append( ", isRaw[" ).append( raw.length ).append( "]" );
909         }
910         else
911         {
912             if ( valueArray == null )
913             {
914                 sb.append( ", SubBTree" );
915             }
916             else
917             {
918                 sb.append( ", array{" );
919 
920                 if ( valueArray == null )
921                 {
922                     sb.append( "}" );
923                 }
924                 else
925                 {
926                     boolean isFirst = true;
927 
928                     for ( V value : valueArray )
929                     {
930                         if ( isFirst )
931                         {
932                             isFirst = false;
933                         }
934                         else
935                         {
936                             sb.append( "/" );
937                         }
938 
939                         sb.append( value );
940                     }
941 
942                     sb.append( "}" );
943                 }
944             }
945         }
946 
947         sb.append( "]" );
948 
949         return sb.toString();
950     }
951 }