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.managed;
21  
22  
23  import java.io.IOException;
24  import java.lang.reflect.Array;
25  import java.util.Comparator;
26  import java.util.UUID;
27  
28  import org.apache.directory.mavibot.btree.TupleCursor;
29  import org.apache.directory.mavibot.btree.ValueCursor;
30  import org.apache.directory.mavibot.btree.exception.BTreeAlreadyManagedException;
31  import org.apache.directory.mavibot.btree.exception.EndOfFileExceededException;
32  import org.apache.directory.mavibot.btree.serializer.ElementSerializer;
33  import org.apache.directory.mavibot.btree.serializer.IntSerializer;
34  import org.apache.directory.mavibot.btree.util.Strings;
35  
36  
37  /**
38   * A holder to store the Values
39   * 
40   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
41   * @param <V> The value type
42   */
43  public class ValueHolder<V> implements Cloneable
44  {
45      /** The deserialized value */
46      private V[] valueArray;
47  
48      /** The BTree storing multiple value, if we have moe than a threashold values */
49      private BTree<V, V> valueBtree;
50  
51      /** The serialized value */
52      private byte[] raw;
53  
54      /** A flag set to true if the values are stored in a BTree */
55      private boolean isSubBtree = false;
56  
57      /** The RecordManager */
58      private RecordManager recordManager;
59  
60      /** The Value serializer */
61      private ElementSerializer<V> valueSerializer;
62  
63      /** An internal flag used when the values are not yet deserialized */
64      private boolean isRaw = true;
65  
66  
67      /**
68       * Creates a new instance of a ValueHolder, containing the serialized values
69       * 
70       * @param valueSerializer The Value's serializer
71       * @param raw The raw data containing the values
72       */
73      /* No qualifier */ValueHolder( RecordManager recordManager, ElementSerializer<V> valueSerializer,
74          boolean isSubBtree, int nbValues,
75          byte[] raw )
76      {
77          this.valueSerializer = valueSerializer;
78          this.recordManager = recordManager;
79          this.raw = raw;
80          this.isSubBtree = isSubBtree;
81  
82          if ( nbValues < BTree.valueThresholdUp )
83          {
84              // Keep an array
85              valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues );
86          }
87          else
88          {
89              // Use a sub btree
90  
91              //raw = ByteBuffer.wrap( valueSerializer.serialize( key ) );
92          }
93      }
94  
95  
96      /**
97       * Creates a new instance of a ValueHolder, containing the serialized values
98       * 
99       * @param valueSerializer The Value's serializer
100      * @param raw The raw data containing the values
101      */
102     /* No qualifier */ValueHolder( RecordManager recordManager, ElementSerializer<V> valueSerializer,
103         BTree<V, V> subBtree )
104     {
105         this.valueSerializer = valueSerializer;
106         this.recordManager = recordManager;
107         raw = null;
108         isRaw = false;
109         isSubBtree = true;
110         valueBtree = subBtree;
111     }
112 
113 
114     /**
115      * Creates a new instance of a ValueHolder, containing Values
116      * 
117      * @param valueSerializer The Value's serializer
118      * @param values The Values stored in the ValueHolder
119      */
120     /* No qualifier */ValueHolder( RecordManager recordManager, ElementSerializer<V> valueSerializer, V... values )
121     {
122         this.valueSerializer = valueSerializer;
123         this.recordManager = recordManager;
124 
125         if ( values != null )
126         {
127             int nbValues = values.length;
128 
129             if ( nbValues < BTree.valueThresholdUp )
130             {
131                 // Keep an array
132                 valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), nbValues );
133 
134                 try
135                 {
136                     System.arraycopy( values, 0, valueArray, 0, values.length );
137                 }
138                 catch ( ArrayStoreException ase )
139                 {
140                     ase.printStackTrace();
141                     throw ase;
142                 }
143 
144                 // Serialize the values
145                 byte[][] data = new byte[nbValues][];
146                 int pos = 0;
147                 int length = 0;
148 
149                 for ( V value : values )
150                 {
151                     byte[] serializedValue = valueSerializer.serialize( value );
152 
153                     data[pos++] = serializedValue;
154                     length += serializedValue.length;
155                 }
156 
157                 raw = new byte[length];
158                 pos = 0;
159 
160                 for ( byte[] bytes : data )
161                 {
162                     System.arraycopy( bytes, 0, raw, pos, bytes.length );
163                     pos += bytes.length;
164                 }
165             }
166             else
167             {
168                 // Use a sub btree, now that we have reached the threshold
169                 createSubTree();
170 
171                 // Now inject all the values into it
172                 for ( V value : values )
173                 {
174                     try
175                     {
176                         valueBtree.insert( value, value );
177                     }
178                     catch ( IOException e )
179                     {
180                         e.printStackTrace();
181                     }
182                 }
183             }
184         }
185         else
186         {
187             // No value, we create an empty array
188             valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), 0 );
189 
190             //raw = ByteBuffer.wrap( valueSerializer.serialize( key ) );
191         }
192 
193         isRaw = false;
194     }
195 
196 
197     /**
198      * @return a cursor on top of the values
199      */
200     public ValueCursor<V> getCursor()
201     {
202         checkRaw();
203 
204         ValueCursor<V> cursor;
205 
206         if ( isSubBtree )
207         {
208             cursor = new ValueBtreeCursor();
209         }
210         else
211         {
212             cursor = new ValueArrayCursor();
213         }
214 
215         return cursor;
216     }
217 
218     /**
219      * A class that encapsulate the values into an array
220      */
221     private class ValueArrayCursor implements ValueCursor<V>
222     {
223         /** Store the current position in the array or in the BTree */
224         private int currentPos;
225 
226 
227         /**
228          * Create an instance
229          */
230         private ValueArrayCursor()
231         {
232             // Start at -1 to be positioned before the first element
233             currentPos = -1;
234         }
235 
236 
237         /**
238          * {@inheritDoc}
239          */
240         @Override
241         public boolean hasNext()
242         {
243             if ( valueArray == null )
244             {
245                 // Load the array from the raw data
246                 return false;
247             }
248             else
249             {
250                 return ( valueArray != null ) && ( currentPos < valueArray.length );
251             }
252         }
253 
254 
255         /**
256          * {@inheritDoc}
257          */
258         public V next()
259         {
260             if ( valueArray == null )
261             {
262                 // Deserialize the array
263                 return null;
264             }
265             else
266             {
267                 currentPos++;
268 
269                 if ( currentPos == valueArray.length )
270                 {
271                     // We have reached the end of the array
272                     return null;
273                 }
274                 else
275                 {
276                     return valueArray[currentPos];
277                 }
278             }
279         }
280 
281 
282         /**
283          * {@inheritDoc}
284          */
285         @Override
286         public boolean hasPrev() throws EndOfFileExceededException, IOException
287         {
288             return false;
289         }
290 
291 
292         /**
293          * {@inheritDoc}
294          */
295         @Override
296         public void close()
297         {
298         }
299 
300 
301         /**
302          * {@inheritDoc}
303          */
304         @Override
305         public void beforeFirst() throws IOException
306         {
307         }
308 
309 
310         /**
311          * {@inheritDoc}
312          */
313         @Override
314         public void afterLast() throws IOException
315         {
316         }
317 
318 
319         /**
320          * {@inheritDoc}
321          */
322         @Override
323         public V prev() throws EndOfFileExceededException, IOException
324         {
325             return null;
326         }
327 
328 
329         /**
330          * {@inheritDoc}
331          */
332         @Override
333         public int size()
334         {
335             if ( valueArray != null )
336             {
337                 return valueArray.length;
338             }
339             else
340             {
341                 return 0;
342             }
343         }
344     }
345 
346     /**
347      * A class that encapsulate the values into an sub-btree
348      */
349     private class ValueBtreeCursor implements ValueCursor<V>
350     {
351         /** Store the current position in the array or in the BTree */
352         private TupleCursor<V, V> cursor;
353 
354 
355         /**
356          * Create an instance
357          */
358         private ValueBtreeCursor()
359         {
360             // Start at -1 to be positionned before the first element
361             try
362             {
363                 if ( valueBtree != null )
364                 {
365                     cursor = valueBtree.browse();
366                 }
367             }
368             catch ( IOException e )
369             {
370                 // TODO Auto-generated catch block
371                 e.printStackTrace();
372             }
373         }
374 
375 
376         /**
377          * {@inheritDoc}}
378          */
379         @Override
380         public boolean hasNext()
381         {
382             if ( cursor == null )
383             {
384                 return false;
385             }
386             else
387             {
388                 try
389                 {
390                     return cursor.hasNext();
391                 }
392                 catch ( EndOfFileExceededException e )
393                 {
394                     // TODO Auto-generated catch block
395                     e.printStackTrace();
396                     return false;
397                 }
398                 catch ( IOException e )
399                 {
400                     // TODO Auto-generated catch block
401                     e.printStackTrace();
402                     return false;
403                 }
404             }
405         }
406 
407 
408         /**
409          * {@inheritDoc}}
410          */
411         public V next()
412         {
413             try
414             {
415                 return cursor.next().getKey();
416             }
417             catch ( EndOfFileExceededException e )
418             {
419                 // TODO Auto-generated catch block
420                 e.printStackTrace();
421                 return null;
422             }
423             catch ( IOException e )
424             {
425                 // TODO Auto-generated catch block
426                 e.printStackTrace();
427                 return null;
428             }
429         }
430 
431 
432         /**
433          * {@inheritDoc}}
434          */
435         @Override
436         public boolean hasPrev() throws EndOfFileExceededException, IOException
437         {
438             if ( cursor == null )
439             {
440                 return false;
441             }
442             else
443             {
444                 try
445                 {
446                     return cursor.hasPrev();
447                 }
448                 catch ( EndOfFileExceededException e )
449                 {
450                     // TODO Auto-generated catch block
451                     e.printStackTrace();
452                     return false;
453                 }
454                 catch ( IOException e )
455                 {
456                     // TODO Auto-generated catch block
457                     e.printStackTrace();
458                     return false;
459                 }
460             }
461         }
462 
463 
464         /**
465          * {@inheritDoc}}
466          */
467         @Override
468         public void close()
469         {
470             if ( cursor != null )
471             {
472                 cursor.close();
473             }
474         }
475 
476 
477         /**
478          * {@inheritDoc}}
479          */
480         @Override
481         public void beforeFirst() throws IOException
482         {
483             if ( cursor != null )
484             {
485                 cursor.beforeFirst();
486             }
487         }
488 
489 
490         /**
491          * {@inheritDoc}}
492          */
493         @Override
494         public void afterLast() throws IOException
495         {
496             if ( cursor != null )
497             {
498                 cursor.afterLast();
499             }
500         }
501 
502 
503         /**
504          * {@inheritDoc}}
505          */
506         @Override
507         public V prev() throws EndOfFileExceededException, IOException
508         {
509             try
510             {
511                 return cursor.prev().getKey();
512             }
513             catch ( EndOfFileExceededException e )
514             {
515                 // TODO Auto-generated catch block
516                 e.printStackTrace();
517                 return null;
518             }
519             catch ( IOException e )
520             {
521                 // TODO Auto-generated catch block
522                 e.printStackTrace();
523                 return null;
524             }
525         }
526 
527 
528         /**
529          * {@inheritDoc}
530          */
531         @Override
532         public int size()
533         {
534             if ( valueBtree != null )
535             {
536                 return ( int ) valueBtree.getNbElems();
537             }
538             else
539             {
540                 return 0;
541             }
542         }
543     }
544 
545 
546     /**
547      * @return the raw representation of the value holder. 
548      */
549     public byte[] getRaw()
550     {
551         if ( isRaw )
552         {
553             // We don't have to serialize the ValueHolder, it has not been changed 
554             return raw;
555         }
556         else
557         {
558             // Ok, some values have been added/modified/removed, we have to serialize the ValueHolder
559             byte[][] valueBytes = new byte[valueArray.length * 2][];
560             int length = 0;
561             int pos = 0;
562 
563             for ( V value : valueArray )
564             {
565                 // Serialize the value
566                 byte[] bytes = valueSerializer.serialize( value );
567                 length += bytes.length;
568 
569                 // Serialize the value's length
570                 byte[] sizeBytes = IntSerializer.serialize( bytes.length );
571                 length += sizeBytes.length;
572 
573                 // And store the two byte[]
574                 valueBytes[pos++] = sizeBytes;
575                 valueBytes[pos++] = bytes;
576             }
577 
578             raw = new byte[length];
579             pos = 0;
580 
581             for ( byte[] bytes : valueBytes )
582             {
583                 System.arraycopy( bytes, 0, raw, pos, bytes.length );
584                 pos += bytes.length;
585             }
586 
587             return raw;
588         }
589     }
590 
591 
592     /**
593      * @return the isSubBtree
594      */
595     public boolean isSubBtree()
596     {
597         return isSubBtree;
598     }
599 
600 
601     /**
602      * @return the number of stored values
603      */
604     public int size()
605     {
606         if ( isSubBtree )
607         {
608             return ( int ) valueBtree.getNbElems();
609         }
610         else
611         {
612             return valueArray.length;
613         }
614     }
615 
616 
617     /**
618      * Create a new Sub-BTree to store the values.
619      */
620     private void createSubTree()
621     {
622         try
623         {
624             valueBtree = new BTree<V, V>( UUID.randomUUID().toString(), valueSerializer, valueSerializer );
625 
626             try
627             {
628                 recordManager.manage( valueBtree, true );
629                 isSubBtree = true;
630                 isRaw = false;
631                 raw = null;
632             }
633             catch ( BTreeAlreadyManagedException e )
634             {
635                 // should never happen
636                 throw new RuntimeException( e );
637             }
638         }
639         catch ( IOException e )
640         {
641             throw new RuntimeException( e );
642         }
643     }
644 
645 
646     /**
647      * Set the subBtree in the ValueHolder
648      */
649     /* No qualifier*/void setSubBtree( BTree<V, V> subBtree )
650     {
651         valueBtree = subBtree;
652         raw = null;
653         isRaw = false;
654         isSubBtree = true;
655         valueArray = null;
656     }
657 
658 
659     /**
660      * Add a new value in the ValueHolder
661      * 
662      * @param value The added value
663      */
664     public void add( V value )
665     {
666         checkRaw();
667 
668         if ( !isSubBtree )
669         {
670             // We have to check that we have reached the threshold or not
671             if ( valueArray.length + 1 > BTree.valueThresholdUp )
672             {
673                 // Ok, transform the array into a btree
674                 createSubTree();
675 
676                 try
677                 {
678                     for ( V val : valueArray )
679                     {
680                         // Here, we should insert all the values in one shot then 
681                         // write the btree on disk only once.
682                         valueBtree.insert( val, null );
683                     }
684 
685                     // We can delete the array now
686                     valueArray = null;
687 
688                     // And inject the new value
689                     valueBtree.insert( value, null );
690                 }
691                 catch ( IOException e )
692                 {
693                     // TODO Auto-generated catch block
694                     e.printStackTrace();
695                 }
696             }
697             else
698             {
699                 // First check that the value is not already present in the ValueHolder
700                 int pos = findPos( value );
701 
702                 if ( pos >= 0 )
703                 {
704                     // The value exists : nothing to do
705                     return;
706                 }
707 
708                 // Ok, we just have to insert the new element at the right position
709                 // We transform the position to a positive value 
710                 pos = -( pos + 1 );
711                 // First, copy the array
712                 V[] newValueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), valueArray.length + 1 );
713 
714                 System.arraycopy( valueArray, 0, newValueArray, 0, pos );
715                 newValueArray[pos] = value;
716                 System.arraycopy( valueArray, pos, newValueArray, pos + 1, valueArray.length - pos );
717 
718                 // And switch the arrays
719                 valueArray = newValueArray;
720             }
721         }
722         else
723         {
724             try
725             {
726                 valueBtree.insert( value, null );
727             }
728             catch ( IOException e )
729             {
730                 // TODO Auto-generated catch block
731                 e.printStackTrace();
732             }
733         }
734     }
735 
736 
737     /**
738      * Add a new value in the ValueHolder
739      * 
740      * @param value The added value
741      */
742     public void remove( V value )
743     {
744         checkRaw();
745 
746         if ( !isSubBtree )
747         {
748             // First check that the value is not already present in the ValueHolder
749             int pos = findPos( value );
750 
751             if ( pos < 0 )
752             {
753                 // The value does not exists : nothing to do
754                 return;
755             }
756 
757             // Ok, we just have to delete the new element at the right position
758             // First, copy the array
759             V[] newValueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), valueArray.length + 1 );
760 
761             System.arraycopy( valueArray, 0, newValueArray, 0, pos );
762             System.arraycopy( valueArray, pos + 1, newValueArray, pos, valueArray.length - pos - 1 );
763 
764             // And switch the arrays
765             valueArray = newValueArray;
766         }
767         else
768         {
769             try
770             {
771                 valueBtree.delete( value );
772             }
773             catch ( IOException e )
774             {
775                 // TODO Auto-generated catch block
776                 e.printStackTrace();
777             }
778         }
779     }
780 
781 
782     /**
783      * Add a new value in the ValueHolder
784      * 
785      * @param value The added value
786      */
787     public boolean contains( V value )
788     {
789         checkRaw();
790 
791         if ( isSubBtree )
792         {
793             try
794             {
795                 return valueBtree.hasKey( value );
796             }
797             catch ( IOException e )
798             {
799                 // TODO Auto-generated catch block
800                 e.printStackTrace();
801             }
802         }
803         else
804         {
805             if ( valueArray.length == 0 )
806             {
807                 return false;
808             }
809 
810             // Do a search using dichotomy
811             return findPos( value ) >= 0;
812         }
813 
814         return true;
815     }
816 
817 
818     /**
819      * Find the position of a given value in the array, or the position where we
820      * would insert the element (in this case, the position will be negative).
821      * As we use a 0-based array, the negative position for 0 is -1.
822      * -1 means the element can be added in position 0
823      * -2 means the element can be added in position 1
824      * ... 
825      */
826     private int findPos( V value )
827     {
828         if ( valueArray.length == 0 )
829         {
830             return -1;
831         }
832 
833         // Do a search using dichotomy
834         int pivot = valueArray.length / 2;
835         int low = 0;
836         int high = valueArray.length - 1;
837         Comparator<V> comparator = valueSerializer.getComparator();
838 
839         while ( high > low )
840         {
841             switch ( high - low )
842             {
843                 case 1:
844                     // We have 2 elements
845                     int result = comparator.compare( value, valueArray[pivot] );
846 
847                     if ( result == 0 )
848                     {
849                         return pivot;
850                     }
851 
852                     if ( result < 0 )
853                     {
854                         if ( pivot == low )
855                         {
856                             return -( low + 1 );
857                         }
858                         else
859                         {
860                             result = comparator.compare( value, valueArray[low] );
861 
862                             if ( result == 0 )
863                             {
864                                 return low;
865                             }
866 
867                             if ( result < 0 )
868                             {
869                                 return -( low + 1 );
870                             }
871                             else
872                             {
873                                 return -( low + 2 );
874                             }
875                         }
876                     }
877                     else
878                     {
879                         if ( pivot == high )
880                         {
881                             return -( high + 2 );
882                         }
883                         else
884                         {
885                             result = comparator.compare( value, valueArray[high] );
886 
887                             if ( result == 0 )
888                             {
889                                 return high;
890                             }
891 
892                             if ( result < 0 )
893                             {
894                                 return -( high + 1 );
895                             }
896                             else
897                             {
898                                 return -( high + 2 );
899                             }
900                         }
901                     }
902 
903                 default:
904                     // We have 3 elements
905                     result = comparator.compare( value, valueArray[pivot] );
906 
907                     if ( result == 0 )
908                     {
909                         return pivot;
910                     }
911 
912                     if ( result < 0 )
913                     {
914                         high = pivot - 1;
915                     }
916                     else
917                     {
918                         low = pivot + 1;
919                     }
920 
921                     pivot = ( high + low ) / 2;
922 
923                     continue;
924             }
925         }
926 
927         int result = comparator.compare( value, valueArray[pivot] );
928 
929         if ( result == 0 )
930         {
931             return pivot;
932         }
933 
934         if ( result < 0 )
935         {
936             return -( pivot + 1 );
937         }
938         else
939         {
940             return -( pivot + 2 );
941         }
942     }
943 
944 
945     /**
946      * Create a clone of this instance
947      */
948     public ValueHolder<V> clone() throws CloneNotSupportedException
949     {
950         ValueHolder<V> copy = ( ValueHolder<V> ) super.clone();
951 
952         //copy the valueArray if it's not null
953         // We don't clone the BTree, as we will create new revisions when 
954         //modifying it
955         if ( ( !isSubBtree ) && ( valueArray != null ) )
956         {
957             copy.valueArray = ( V[] ) Array.newInstance( valueSerializer.getType(), valueArray.length );
958             System.arraycopy( valueArray, 0, copy.valueArray, 0, valueArray.length );
959         }
960 
961         return copy;
962     }
963 
964 
965     /**
966      * Check if we haven't yet deserialized the values, and if so, do it
967      */
968     private void checkRaw()
969     {
970         if ( isRaw )
971         {
972             // We haven't yet deserialized the values. Let's do it now
973             if ( isSubBtree )
974             {
975                 // This is a sub BTree, we have to read the tree from the offsets
976 
977             }
978             else
979             {
980                 // We have to deserialize the array of values
981                 int index = 0;
982                 int pos = 0;
983 
984                 while ( pos < raw.length )
985                 {
986                     try
987                     {
988                         int size = IntSerializer.deserialize( raw, pos );
989                         pos += 4;
990                         V value = valueSerializer.fromBytes( raw, pos );
991                         pos += size;
992                         valueArray[index++] = value;
993                     }
994                     catch ( IOException e )
995                     {
996                         System.out.println( Strings.dumpBytes( raw ) );
997                     }
998                 }
999             }
1000 
1001             isRaw = false;
1002         }
1003     }
1004 
1005 
1006     /**
1007      * @return The sub-btree offset
1008      */
1009     /* No qualifier */long getOffset()
1010     {
1011         if ( isSubBtree )
1012         {
1013             return valueBtree.getBtreeOffset();
1014         }
1015         else
1016         {
1017             return -1L;
1018         }
1019     }
1020 
1021 
1022     /**
1023      * @see Object#toString()
1024      */
1025     public String toString()
1026     {
1027         StringBuilder sb = new StringBuilder();
1028 
1029         sb.append( "ValueHolder[" ).append( valueSerializer.getClass().getSimpleName() );
1030 
1031         if ( isRaw )
1032         {
1033             sb.append( ", isRaw[" ).append( raw.length ).append( "]" );
1034         }
1035         else
1036         {
1037             if ( isSubBtree )
1038             {
1039                 sb.append( ", SubBTree" );
1040             }
1041             else
1042             {
1043                 sb.append( ", array{" );
1044 
1045                 if ( valueArray == null )
1046                 {
1047                     sb.append( "}" );
1048                 }
1049                 else
1050                 {
1051                     boolean isFirst = true;
1052 
1053                     for ( V value : valueArray )
1054                     {
1055                         if ( isFirst )
1056                         {
1057                             isFirst = false;
1058                         }
1059                         else
1060                         {
1061                             sb.append( "/" );
1062                         }
1063 
1064                         sb.append( value );
1065                     }
1066 
1067                     sb.append( "}" );
1068                 }
1069             }
1070         }
1071 
1072         sb.append( "]" );
1073 
1074         return sb.toString();
1075     }
1076 }