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