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