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