View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with this
4    * work for additional information regarding copyright ownership. The ASF
5    * licenses this file to you under the Apache License, Version 2.0 (the
6    * "License"); you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14   * License for the specific language governing permissions and limitations
15   * under the License.
16   */
17  package org.apache.hadoop.hbase.io.encoding;
18  
19  import java.io.DataInputStream;
20  import java.io.DataOutputStream;
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
23  
24  import org.apache.hadoop.hbase.Cell;
25  import org.apache.hadoop.hbase.CellComparator;
26  import org.apache.hadoop.hbase.CellUtil;
27  import org.apache.hadoop.hbase.HConstants;
28  import org.apache.hadoop.hbase.KeyValue;
29  import org.apache.hadoop.hbase.KeyValue.KVComparator;
30  import org.apache.hadoop.hbase.KeyValue.SamePrefixComparator;
31  import org.apache.hadoop.hbase.KeyValue.Type;
32  import org.apache.hadoop.hbase.KeyValueUtil;
33  import org.apache.hadoop.hbase.SettableSequenceId;
34  import org.apache.hadoop.hbase.classification.InterfaceAudience;
35  import org.apache.hadoop.hbase.io.HeapSize;
36  import org.apache.hadoop.hbase.io.TagCompressionContext;
37  import org.apache.hadoop.hbase.io.util.LRUDictionary;
38  import org.apache.hadoop.hbase.util.ByteBufferUtils;
39  import org.apache.hadoop.hbase.util.Bytes;
40  import org.apache.hadoop.hbase.util.ClassSize;
41  import org.apache.hadoop.io.WritableUtils;
42  
43  /**
44   * Base class for all data block encoders that use a buffer.
45   */
46  @InterfaceAudience.Private
47  abstract class BufferedDataBlockEncoder extends AbstractDataBlockEncoder {
48    /**
49     * TODO: This datablockencoder is dealing in internals of hfileblocks. Purge reference to HFBs
50     */
51    private static int INITIAL_KEY_BUFFER_SIZE = 512;
52  
53    @Override
54    public ByteBuffer decodeKeyValues(DataInputStream source,
55        HFileBlockDecodingContext blkDecodingCtx) throws IOException {
56      if (blkDecodingCtx.getClass() != HFileBlockDefaultDecodingContext.class) {
57        throw new IOException(this.getClass().getName() + " only accepts "
58            + HFileBlockDefaultDecodingContext.class.getName() + " as the decoding context.");
59      }
60  
61      HFileBlockDefaultDecodingContext decodingCtx =
62          (HFileBlockDefaultDecodingContext) blkDecodingCtx;
63      if (decodingCtx.getHFileContext().isIncludesTags()
64          && decodingCtx.getHFileContext().isCompressTags()) {
65        if (decodingCtx.getTagCompressionContext() != null) {
66          // It will be overhead to create the TagCompressionContext again and again for every block
67          // decoding.
68          decodingCtx.getTagCompressionContext().clear();
69        } else {
70          try {
71            TagCompressionContext tagCompressionContext = new TagCompressionContext(
72                LRUDictionary.class, Byte.MAX_VALUE);
73            decodingCtx.setTagCompressionContext(tagCompressionContext);
74          } catch (Exception e) {
75            throw new IOException("Failed to initialize TagCompressionContext", e);
76          }
77        }
78      }
79      return internalDecodeKeyValues(source, 0, 0, decodingCtx);
80    }
81  
82    protected static class SeekerState implements Cell {
83      protected ByteBuffer currentBuffer;
84      protected TagCompressionContext tagCompressionContext;
85      protected int valueOffset = -1;
86      protected int keyLength;
87      protected int valueLength;
88      protected int lastCommonPrefix;
89      protected int tagsLength = 0;
90      protected int tagsOffset = -1;
91      protected int tagsCompressedLength = 0;
92      protected boolean uncompressTags = true;
93  
94      /** We need to store a copy of the key. */
95      protected byte[] keyBuffer = HConstants.EMPTY_BYTE_ARRAY;
96      protected byte[] tagsBuffer = HConstants.EMPTY_BYTE_ARRAY;
97  
98      protected long memstoreTS;
99      protected int nextKvOffset;
100     protected KeyValue.KeyOnlyKeyValue currentKey = new KeyValue.KeyOnlyKeyValue();
101 
102     public SeekerState() {
103     }
104 
105     protected boolean isValid() {
106       return valueOffset != -1;
107     }
108 
109     protected void invalidate() {
110       valueOffset = -1;
111       tagsCompressedLength = 0;
112       currentKey = new KeyValue.KeyOnlyKeyValue();
113       uncompressTags = true;
114       currentBuffer = null;
115     }
116 
117     protected void ensureSpaceForKey() {
118       if (keyLength > keyBuffer.length) {
119         int newKeyBufferLength = Integer.highestOneBit(Math.max(
120             INITIAL_KEY_BUFFER_SIZE, keyLength) - 1) << 1;
121         byte[] newKeyBuffer = new byte[newKeyBufferLength];
122         System.arraycopy(keyBuffer, 0, newKeyBuffer, 0, keyBuffer.length);
123         keyBuffer = newKeyBuffer;
124       }
125     }
126 
127     protected void ensureSpaceForTags() {
128       if (tagsLength > tagsBuffer.length) {
129         int newTagsBufferLength = Integer.highestOneBit(Math.max(
130             INITIAL_KEY_BUFFER_SIZE, tagsLength) - 1) << 1;
131         byte[] newTagsBuffer = new byte[newTagsBufferLength];
132         System.arraycopy(tagsBuffer, 0, newTagsBuffer, 0, tagsBuffer.length);
133         tagsBuffer = newTagsBuffer;
134       }
135     }
136 
137     protected void setKey(byte[] keyBuffer, long memTS) {
138       currentKey.setKey(keyBuffer, 0, keyLength);
139       memstoreTS = memTS;
140     }
141 
142     /**
143      * Copy the state from the next one into this instance (the previous state
144      * placeholder). Used to save the previous state when we are advancing the
145      * seeker to the next key/value.
146      */
147     protected void copyFromNext(SeekerState nextState) {
148       if (keyBuffer.length != nextState.keyBuffer.length) {
149         keyBuffer = nextState.keyBuffer.clone();
150       } else if (!isValid()) {
151         // Note: we can only call isValid before we override our state, so this
152         // comes before all the assignments at the end of this method.
153         System.arraycopy(nextState.keyBuffer, 0, keyBuffer, 0,
154              nextState.keyLength);
155       } else {
156         // don't copy the common prefix between this key and the previous one
157         System.arraycopy(nextState.keyBuffer, nextState.lastCommonPrefix,
158             keyBuffer, nextState.lastCommonPrefix, nextState.keyLength
159                 - nextState.lastCommonPrefix);
160       }
161       currentKey = nextState.currentKey;
162 
163       valueOffset = nextState.valueOffset;
164       keyLength = nextState.keyLength;
165       valueLength = nextState.valueLength;
166       lastCommonPrefix = nextState.lastCommonPrefix;
167       nextKvOffset = nextState.nextKvOffset;
168       memstoreTS = nextState.memstoreTS;
169       currentBuffer = nextState.currentBuffer;
170       tagsOffset = nextState.tagsOffset;
171       tagsLength = nextState.tagsLength;
172       if (nextState.tagCompressionContext != null) {
173         tagCompressionContext = nextState.tagCompressionContext;
174       }
175     }
176 
177     @Override
178     public byte[] getRowArray() {
179       return currentKey.getRowArray();
180     }
181 
182     @Override
183     public int getRowOffset() {
184       return Bytes.SIZEOF_SHORT;
185     }
186 
187     @Override
188     public short getRowLength() {
189       return currentKey.getRowLength();
190     }
191 
192     @Override
193     public byte[] getFamilyArray() {
194       return currentKey.getFamilyArray();
195     }
196 
197     @Override
198     public int getFamilyOffset() {
199       return currentKey.getFamilyOffset();
200     }
201 
202     @Override
203     public byte getFamilyLength() {
204       return currentKey.getFamilyLength();
205     }
206 
207     @Override
208     public byte[] getQualifierArray() {
209       return currentKey.getQualifierArray();
210     }
211 
212     @Override
213     public int getQualifierOffset() {
214       return currentKey.getQualifierOffset();
215     }
216 
217     @Override
218     public int getQualifierLength() {
219       return currentKey.getQualifierLength();
220     }
221 
222     @Override
223     public long getTimestamp() {
224       return currentKey.getTimestamp();
225     }
226 
227     @Override
228     public byte getTypeByte() {
229       return currentKey.getTypeByte();
230     }
231 
232     @Override
233     public long getMvccVersion() {
234       return memstoreTS;
235     }
236 
237     @Override
238     public long getSequenceId() {
239       return memstoreTS;
240     }
241 
242     @Override
243     public byte[] getValueArray() {
244       return currentBuffer.array();
245     }
246 
247     @Override
248     public int getValueOffset() {
249       return currentBuffer.arrayOffset() + valueOffset;
250     }
251 
252     @Override
253     public int getValueLength() {
254       return valueLength;
255     }
256 
257     @Override
258     public byte[] getTagsArray() {
259       if (tagCompressionContext != null) {
260         return tagsBuffer;
261       }
262       return currentBuffer.array();
263     }
264 
265     @Override
266     public int getTagsOffset() {
267       if (tagCompressionContext != null) {
268         return 0;
269       }
270       return currentBuffer.arrayOffset() + tagsOffset;
271     }
272 
273     @Override
274     public int getTagsLength() {
275       return tagsLength;
276     }
277 
278     @Override
279     @Deprecated
280     public byte[] getValue() {
281       throw new UnsupportedOperationException("getValue() not supported");
282     }
283 
284     @Override
285     @Deprecated
286     public byte[] getFamily() {
287       throw new UnsupportedOperationException("getFamily() not supported");
288     }
289 
290     @Override
291     @Deprecated
292     public byte[] getQualifier() {
293       throw new UnsupportedOperationException("getQualifier() not supported");
294     }
295 
296     @Override
297     @Deprecated
298     public byte[] getRow() {
299       throw new UnsupportedOperationException("getRow() not supported");
300     }
301 
302     @Override
303     public String toString() {
304       return KeyValue.keyToString(this.keyBuffer, 0, KeyValueUtil.keyLength(this)) + "/vlen="
305           + getValueLength() + "/seqid=" + memstoreTS;
306     }
307 
308     public Cell shallowCopy() {
309       return new ClonedSeekerState(currentBuffer, keyBuffer, currentKey.getRowLength(),
310           currentKey.getFamilyOffset(), currentKey.getFamilyLength(), keyLength, 
311           currentKey.getQualifierOffset(), currentKey.getQualifierLength(),
312           currentKey.getTimestamp(), currentKey.getTypeByte(), valueLength, valueOffset,
313           memstoreTS, tagsOffset, tagsLength, tagCompressionContext, tagsBuffer);
314     }
315   }
316 
317   /**
318    * Copies only the key part of the keybuffer by doing a deep copy and passes the 
319    * seeker state members for taking a clone.
320    * Note that the value byte[] part is still pointing to the currentBuffer and the 
321    * represented by the valueOffset and valueLength
322    */
323   // We return this as a Cell to the upper layers of read flow and might try setting a new SeqId
324   // there. So this has to be an instance of SettableSequenceId. SeekerState need not be
325   // SettableSequenceId as we never return that to top layers. When we have to, we make
326   // ClonedSeekerState from it.
327   protected static class ClonedSeekerState implements Cell, HeapSize, SettableSequenceId {
328     private static final long FIXED_OVERHEAD = ClassSize.align(ClassSize.OBJECT
329         + (4 * ClassSize.REFERENCE) + (2 * Bytes.SIZEOF_LONG) + (7 * Bytes.SIZEOF_INT)
330         + (Bytes.SIZEOF_SHORT) + (2 * Bytes.SIZEOF_BYTE) + (2 * ClassSize.ARRAY));
331     private byte[] keyOnlyBuffer;
332     private ByteBuffer currentBuffer;
333     private short rowLength;
334     private int familyOffset;
335     private byte familyLength;
336     private int qualifierOffset;
337     private int qualifierLength;
338     private long timestamp;
339     private byte typeByte;
340     private int valueOffset;
341     private int valueLength;
342     private int tagsLength;
343     private int tagsOffset;
344     private byte[] cloneTagsBuffer;
345     private long seqId;
346     private TagCompressionContext tagCompressionContext;
347     
348     protected ClonedSeekerState(ByteBuffer currentBuffer, byte[] keyBuffer, short rowLength,
349         int familyOffset, byte familyLength, int keyLength, int qualOffset, int qualLength,
350         long timeStamp, byte typeByte, int valueLen, int valueOffset, long seqId,
351         int tagsOffset, int tagsLength, TagCompressionContext tagCompressionContext,
352         byte[] tagsBuffer) {
353       this.currentBuffer = currentBuffer;
354       keyOnlyBuffer = new byte[keyLength];
355       this.tagCompressionContext = tagCompressionContext;
356       this.rowLength = rowLength;
357       this.familyOffset = familyOffset;
358       this.familyLength = familyLength;
359       this.qualifierOffset = qualOffset;
360       this.qualifierLength = qualLength;
361       this.timestamp = timeStamp;
362       this.typeByte = typeByte;
363       this.valueLength = valueLen;
364       this.valueOffset = valueOffset;
365       this.tagsOffset = tagsOffset;
366       this.tagsLength = tagsLength;
367       System.arraycopy(keyBuffer, 0, keyOnlyBuffer, 0, keyLength);
368       if (tagCompressionContext != null) {
369         this.cloneTagsBuffer = new byte[tagsLength];
370         System.arraycopy(tagsBuffer, 0, this.cloneTagsBuffer, 0, tagsLength);
371       }
372       setSequenceId(seqId);
373     }
374 
375     @Override
376     public byte[] getRowArray() {
377       return keyOnlyBuffer;
378     }
379 
380     @Override
381     public byte[] getFamilyArray() {
382       return keyOnlyBuffer;
383     }
384 
385     @Override
386     public byte[] getQualifierArray() {
387       return keyOnlyBuffer;
388     }
389 
390     @Override
391     public int getRowOffset() {
392       return Bytes.SIZEOF_SHORT;
393     }
394 
395     @Override
396     public short getRowLength() {
397       return rowLength;
398     }
399 
400     @Override
401     public int getFamilyOffset() {
402       return familyOffset;
403     }
404 
405     @Override
406     public byte getFamilyLength() {
407       return familyLength;
408     }
409 
410     @Override
411     public int getQualifierOffset() {
412       return qualifierOffset;
413     }
414 
415     @Override
416     public int getQualifierLength() {
417       return qualifierLength;
418     }
419 
420     @Override
421     public long getTimestamp() {
422       return timestamp;
423     }
424 
425     @Override
426     public byte getTypeByte() {
427       return typeByte;
428     }
429 
430     @Override
431     @Deprecated
432     public long getMvccVersion() {
433       return getSequenceId();
434     }
435 
436     @Override
437     public long getSequenceId() {
438       return seqId;
439     }
440 
441     @Override
442     public byte[] getValueArray() {
443       return currentBuffer.array();
444     }
445 
446     @Override
447     public int getValueOffset() {
448       return currentBuffer.arrayOffset() + valueOffset;
449     }
450 
451     @Override
452     public int getValueLength() {
453       return valueLength;
454     }
455 
456     @Override
457     public byte[] getTagsArray() {
458       if (tagCompressionContext != null) {
459         return cloneTagsBuffer;
460       }
461       return currentBuffer.array();
462     }
463 
464     @Override
465     public int getTagsOffset() {
466       if (tagCompressionContext != null) {
467         return 0;
468       }
469       return currentBuffer.arrayOffset() + tagsOffset;
470     }
471 
472     @Override
473     public int getTagsLength() {
474       return tagsLength;
475     }
476 
477     @Override
478     @Deprecated
479     public byte[] getValue() {
480       return CellUtil.cloneValue(this);
481     }
482 
483     @Override
484     @Deprecated
485     public byte[] getFamily() {
486       return CellUtil.cloneFamily(this);
487     }
488 
489     @Override
490     @Deprecated
491     public byte[] getQualifier() {
492       return CellUtil.cloneQualifier(this);
493     }
494 
495     @Override
496     @Deprecated
497     public byte[] getRow() {
498       return CellUtil.cloneRow(this);
499     }
500 
501     @Override
502     public String toString() {
503       return KeyValue.keyToString(this.keyOnlyBuffer, 0, KeyValueUtil.keyLength(this)) + "/vlen="
504           + getValueLength() + "/seqid=" + seqId;
505     }
506 
507     @Override
508     public void setSequenceId(long seqId) {
509       this.seqId = seqId;
510     }
511 
512     @Override
513     public long heapSize() {
514       return FIXED_OVERHEAD + rowLength + familyLength + qualifierLength + valueLength + tagsLength;
515     }
516   }
517 
518   protected abstract static class BufferedEncodedSeeker<STATE extends SeekerState>
519       extends AbstractEncodedSeeker {
520     protected final SamePrefixComparator<byte[]> samePrefixComparator;
521     protected ByteBuffer currentBuffer;
522     protected STATE current, previous;
523     protected TagCompressionContext tagCompressionContext = null;
524 
525     public BufferedEncodedSeeker(KVComparator comparator,
526         HFileBlockDecodingContext decodingCtx) {
527       super(comparator, decodingCtx);
528       this.samePrefixComparator = comparator;
529       if (decodingCtx.getHFileContext().isCompressTags()) {
530         try {
531           tagCompressionContext = new TagCompressionContext(LRUDictionary.class, Byte.MAX_VALUE);
532         } catch (Exception e) {
533           throw new RuntimeException("Failed to initialize TagCompressionContext", e);
534         }
535       }
536       current = createSeekerState(); // always valid
537       previous = createSeekerState(); // may not be valid
538     }
539 
540     @Override
541     public int compareKey(KVComparator comparator, byte[] key, int offset, int length) {
542       return comparator.compareFlatKey(key, offset, length,
543           current.keyBuffer, 0, current.keyLength);
544     }
545 
546     @Override
547     public int compareKey(KVComparator comparator, Cell key) {
548       return comparator.compareOnlyKeyPortion(key,
549           new KeyValue.KeyOnlyKeyValue(current.keyBuffer, 0, current.keyLength));
550     }
551 
552     @Override
553     public void setCurrentBuffer(ByteBuffer buffer) {
554       if (this.tagCompressionContext != null) {
555         this.tagCompressionContext.clear();
556       }
557       currentBuffer = buffer;
558       current.currentBuffer = currentBuffer;
559       if(tagCompressionContext != null) {
560         current.tagCompressionContext = tagCompressionContext;
561       }
562       decodeFirst();
563       current.setKey(current.keyBuffer, current.memstoreTS);
564       previous.invalidate();
565     }
566 
567     @Override
568     public ByteBuffer getKeyDeepCopy() {
569       ByteBuffer keyBuffer = ByteBuffer.allocate(current.keyLength);
570       keyBuffer.put(current.keyBuffer, 0, current.keyLength);
571       keyBuffer.rewind();
572       return keyBuffer;
573     }
574 
575     @Override
576     public ByteBuffer getValueShallowCopy() {
577       ByteBuffer dup = currentBuffer.duplicate();
578       dup.position(current.valueOffset);
579       dup.limit(current.valueOffset + current.valueLength);
580       return dup.slice();
581     }
582 
583     @Override
584     public Cell getKeyValue() {
585       return current.shallowCopy();
586     }
587 
588     @Override
589     public void rewind() {
590       currentBuffer.rewind();
591       if (tagCompressionContext != null) {
592         tagCompressionContext.clear();
593       }
594       decodeFirst();
595       current.setKey(current.keyBuffer, current.memstoreTS);
596       previous.invalidate();
597     }
598 
599     @Override
600     public boolean next() {
601       if (!currentBuffer.hasRemaining()) {
602         return false;
603       }
604       decodeNext();
605       current.setKey(current.keyBuffer, current.memstoreTS);
606       previous.invalidate();
607       return true;
608     }
609 
610     protected void decodeTags() {
611       current.tagsLength = ByteBufferUtils.readCompressedInt(currentBuffer);
612       if (tagCompressionContext != null) {
613         if (current.uncompressTags) {
614           // Tag compression is been used. uncompress it into tagsBuffer
615           current.ensureSpaceForTags();
616           try {
617             current.tagsCompressedLength = tagCompressionContext.uncompressTags(currentBuffer,
618                 current.tagsBuffer, 0, current.tagsLength);
619           } catch (IOException e) {
620             throw new RuntimeException("Exception while uncompressing tags", e);
621           }
622         } else {
623           ByteBufferUtils.skip(currentBuffer, current.tagsCompressedLength);
624           current.uncompressTags = true;// Reset this.
625         }
626         current.tagsOffset = -1;
627       } else {
628         // When tag compress is not used, let us not do copying of tags bytes into tagsBuffer.
629         // Just mark the tags Offset so as to create the KV buffer later in getKeyValueBuffer()
630         current.tagsOffset = currentBuffer.position();
631         ByteBufferUtils.skip(currentBuffer, current.tagsLength);
632       }
633     }
634 
635     @Override
636     public int seekToKeyInBlock(byte[] key, int offset, int length, boolean seekBefore) {
637       return seekToKeyInBlock(new KeyValue.KeyOnlyKeyValue(key, offset, length), seekBefore);
638     }
639 
640     @Override
641     public int seekToKeyInBlock(Cell seekCell, boolean seekBefore) {
642       int rowCommonPrefix = 0;
643       int familyCommonPrefix = 0;
644       int qualCommonPrefix = 0;
645       previous.invalidate();
646       KeyValue.KeyOnlyKeyValue currentCell = new KeyValue.KeyOnlyKeyValue();
647       do {
648         int comp;
649         if (samePrefixComparator != null) {
650           currentCell.setKey(current.keyBuffer, 0, current.keyLength);
651           if (current.lastCommonPrefix != 0) {
652             // The KV format has row key length also in the byte array. The
653             // common prefix
654             // includes it. So we need to subtract to find out the common prefix
655             // in the
656             // row part alone
657             rowCommonPrefix = Math.min(rowCommonPrefix, current.lastCommonPrefix - 2);
658           }
659           if (current.lastCommonPrefix <= 2) {
660             rowCommonPrefix = 0;
661           }
662           rowCommonPrefix += CellComparator.findCommonPrefixInRowPart(seekCell, currentCell,
663               rowCommonPrefix);
664           comp = CellComparator.compareCommonRowPrefix(seekCell, currentCell, rowCommonPrefix);
665           if (comp == 0) {
666             comp = compareTypeBytes(seekCell, currentCell);
667             if (comp == 0) {
668               // Subtract the fixed row key length and the family key fixed length
669               familyCommonPrefix = Math.max(
670                   0,
671                   Math.min(familyCommonPrefix,
672                       current.lastCommonPrefix - (3 + currentCell.getRowLength())));
673               familyCommonPrefix += CellComparator.findCommonPrefixInFamilyPart(seekCell,
674                   currentCell, familyCommonPrefix);
675               comp = CellComparator.compareCommonFamilyPrefix(seekCell, currentCell,
676                   familyCommonPrefix);
677               if (comp == 0) {
678                 // subtract the rowkey fixed length and the family key fixed
679                 // length
680                 qualCommonPrefix = Math.max(
681                     0,
682                     Math.min(
683                         qualCommonPrefix,
684                         current.lastCommonPrefix
685                             - (3 + currentCell.getRowLength() + currentCell.getFamilyLength())));
686                 qualCommonPrefix += CellComparator.findCommonPrefixInQualifierPart(seekCell,
687                     currentCell, qualCommonPrefix);
688                 comp = CellComparator.compareCommonQualifierPrefix(seekCell, currentCell,
689                     qualCommonPrefix);
690                 if (comp == 0) {
691                   comp = CellComparator.compareTimestamps(seekCell, currentCell);
692                   if (comp == 0) {
693                     // Compare types. Let the delete types sort ahead of puts;
694                     // i.e. types
695                     // of higher numbers sort before those of lesser numbers.
696                     // Maximum
697                     // (255)
698                     // appears ahead of everything, and minimum (0) appears
699                     // after
700                     // everything.
701                     comp = (0xff & currentCell.getTypeByte()) - (0xff & seekCell.getTypeByte());
702                   }
703                 }
704               }
705             }
706           }
707         } else {
708           Cell r = new KeyValue.KeyOnlyKeyValue(current.keyBuffer, 0, current.keyLength);
709           comp = comparator.compareOnlyKeyPortion(seekCell, r);
710         }
711         if (comp == 0) { // exact match
712           if (seekBefore) {
713             if (!previous.isValid()) {
714               // The caller (seekBefore) has to ensure that we are not at the
715               // first key in the block.
716               throw new IllegalStateException("Cannot seekBefore if "
717                   + "positioned at the first key in the block: key="
718                   + Bytes.toStringBinary(seekCell.getRowArray()));
719             }
720             moveToPrevious();
721             return 1;
722           }
723           return 0;
724         }
725 
726         if (comp < 0) { // already too large, check previous
727           if (previous.isValid()) {
728             moveToPrevious();
729           } else {
730             return HConstants.INDEX_KEY_MAGIC; // using optimized index key
731           }
732           return 1;
733         }
734 
735         // move to next, if more data is available
736         if (currentBuffer.hasRemaining()) {
737           previous.copyFromNext(current);
738           decodeNext();
739           current.setKey(current.keyBuffer, current.memstoreTS);
740         } else {
741           break;
742         }
743       } while (true);
744 
745       // we hit the end of the block, not an exact match
746       return 1;
747     }
748 
749     private int compareTypeBytes(Cell key, Cell right) {
750       if (key.getFamilyLength() + key.getQualifierLength() == 0
751           && key.getTypeByte() == Type.Minimum.getCode()) {
752         // left is "bigger", i.e. it appears later in the sorted order
753         return 1;
754       }
755       if (right.getFamilyLength() + right.getQualifierLength() == 0
756           && right.getTypeByte() == Type.Minimum.getCode()) {
757         return -1;
758       }
759       return 0;
760     }
761 
762 
763     private void moveToPrevious() {
764       if (!previous.isValid()) {
765         throw new IllegalStateException(
766             "Can move back only once and not in first key in the block.");
767       }
768 
769       STATE tmp = previous;
770       previous = current;
771       current = tmp;
772 
773       // move after last key value
774       currentBuffer.position(current.nextKvOffset);
775       // Already decoded the tag bytes. We cache this tags into current state and also the total
776       // compressed length of the tags bytes. For the next time decodeNext() we don't need to decode
777       // the tags again. This might pollute the Data Dictionary what we use for the compression.
778       // When current.uncompressTags is false, we will just reuse the current.tagsBuffer and skip
779       // 'tagsCompressedLength' bytes of source stream.
780       // See in decodeTags()
781       current.tagsBuffer = previous.tagsBuffer;
782       current.tagsCompressedLength = previous.tagsCompressedLength;
783       current.uncompressTags = false;
784       current.setKey(current.keyBuffer, current.memstoreTS);
785       previous.invalidate();
786     }
787 
788     @SuppressWarnings("unchecked")
789     protected STATE createSeekerState() {
790       // This will fail for non-default seeker state if the subclass does not
791       // override this method.
792       return (STATE) new SeekerState();
793     }
794 
795     abstract protected void decodeFirst();
796     abstract protected void decodeNext();
797   }
798 
799   /**
800    * @param cell
801    * @param out
802    * @param encodingCtx
803    * @return unencoded size added
804    * @throws IOException
805    */
806   protected final int afterEncodingKeyValue(Cell cell, DataOutputStream out,
807       HFileBlockDefaultEncodingContext encodingCtx) throws IOException {
808     int size = 0;
809     if (encodingCtx.getHFileContext().isIncludesTags()) {
810       int tagsLength = cell.getTagsLength();
811       ByteBufferUtils.putCompressedInt(out, tagsLength);
812       // There are some tags to be written
813       if (tagsLength > 0) {
814         TagCompressionContext tagCompressionContext = encodingCtx.getTagCompressionContext();
815         // When tag compression is enabled, tagCompressionContext will have a not null value. Write
816         // the tags using Dictionary compression in such a case
817         if (tagCompressionContext != null) {
818           tagCompressionContext
819               .compressTags(out, cell.getTagsArray(), cell.getTagsOffset(), tagsLength);
820         } else {
821           out.write(cell.getTagsArray(), cell.getTagsOffset(), tagsLength);
822         }
823       }
824       size += tagsLength + KeyValue.TAGS_LENGTH_SIZE;
825     }
826     if (encodingCtx.getHFileContext().isIncludesMvcc()) {
827       // Copy memstore timestamp from the byte buffer to the output stream.
828       long memstoreTS = cell.getSequenceId();
829       WritableUtils.writeVLong(out, memstoreTS);
830       // TODO use a writeVLong which returns the #bytes written so that 2 time parsing can be
831       // avoided.
832       size += WritableUtils.getVIntSize(memstoreTS);
833     }
834     return size;
835   }
836 
837   protected final void afterDecodingKeyValue(DataInputStream source,
838       ByteBuffer dest, HFileBlockDefaultDecodingContext decodingCtx) throws IOException {
839     if (decodingCtx.getHFileContext().isIncludesTags()) {
840       int tagsLength = ByteBufferUtils.readCompressedInt(source);
841       // Put as unsigned short
842       dest.put((byte) ((tagsLength >> 8) & 0xff));
843       dest.put((byte) (tagsLength & 0xff));
844       if (tagsLength > 0) {
845         TagCompressionContext tagCompressionContext = decodingCtx.getTagCompressionContext();
846         // When tag compression is been used in this file, tagCompressionContext will have a not
847         // null value passed.
848         if (tagCompressionContext != null) {
849           tagCompressionContext.uncompressTags(source, dest, tagsLength);
850         } else {
851           ByteBufferUtils.copyFromStreamToBuffer(dest, source, tagsLength);
852         }
853       }
854     }
855     if (decodingCtx.getHFileContext().isIncludesMvcc()) {
856       long memstoreTS = -1;
857       try {
858         // Copy memstore timestamp from the data input stream to the byte
859         // buffer.
860         memstoreTS = WritableUtils.readVLong(source);
861         ByteBufferUtils.writeVLong(dest, memstoreTS);
862       } catch (IOException ex) {
863         throw new RuntimeException("Unable to copy memstore timestamp " +
864             memstoreTS + " after decoding a key/value");
865       }
866     }
867   }
868 
869   protected abstract ByteBuffer internalDecodeKeyValues(DataInputStream source,
870       int allocateHeaderLength, int skipLastBytes, HFileBlockDefaultDecodingContext decodingCtx)
871       throws IOException;
872 
873   /**
874    * Asserts that there is at least the given amount of unfilled space
875    * remaining in the given buffer.
876    * @param out typically, the buffer we are writing to
877    * @param length the required space in the buffer
878    * @throws EncoderBufferTooSmallException If there are no enough bytes.
879    */
880   protected static void ensureSpace(ByteBuffer out, int length)
881       throws EncoderBufferTooSmallException {
882     if (out.position() + length > out.limit()) {
883       throw new EncoderBufferTooSmallException(
884           "Buffer position=" + out.position() +
885           ", buffer limit=" + out.limit() +
886           ", length to be written=" + length);
887     }
888   }
889 
890   @Override
891   public void startBlockEncoding(HFileBlockEncodingContext blkEncodingCtx, DataOutputStream out)
892       throws IOException {
893     if (blkEncodingCtx.getClass() != HFileBlockDefaultEncodingContext.class) {
894       throw new IOException (this.getClass().getName() + " only accepts "
895           + HFileBlockDefaultEncodingContext.class.getName() + " as the " +
896           "encoding context.");
897     }
898 
899     HFileBlockDefaultEncodingContext encodingCtx =
900         (HFileBlockDefaultEncodingContext) blkEncodingCtx;
901     encodingCtx.prepareEncoding(out);
902     if (encodingCtx.getHFileContext().isIncludesTags()
903         && encodingCtx.getHFileContext().isCompressTags()) {
904       if (encodingCtx.getTagCompressionContext() != null) {
905         // It will be overhead to create the TagCompressionContext again and again for every block
906         // encoding.
907         encodingCtx.getTagCompressionContext().clear();
908       } else {
909         try {
910           TagCompressionContext tagCompressionContext = new TagCompressionContext(
911               LRUDictionary.class, Byte.MAX_VALUE);
912           encodingCtx.setTagCompressionContext(tagCompressionContext);
913         } catch (Exception e) {
914           throw new IOException("Failed to initialize TagCompressionContext", e);
915         }
916       }
917     }
918     ByteBufferUtils.putInt(out, 0); // DUMMY length. This will be updated in endBlockEncoding()
919     blkEncodingCtx.setEncodingState(new BufferedDataBlockEncodingState());
920   }
921 
922   private static class BufferedDataBlockEncodingState extends EncodingState {
923     int unencodedDataSizeWritten = 0;
924   }
925 
926   @Override
927   public int encode(Cell cell, HFileBlockEncodingContext encodingCtx, DataOutputStream out)
928       throws IOException {
929     BufferedDataBlockEncodingState state = (BufferedDataBlockEncodingState) encodingCtx
930         .getEncodingState();
931     int encodedKvSize = internalEncode(cell, (HFileBlockDefaultEncodingContext) encodingCtx, out);
932     state.unencodedDataSizeWritten += encodedKvSize;
933     return encodedKvSize;
934   }
935 
936   public abstract int internalEncode(Cell cell, HFileBlockDefaultEncodingContext encodingCtx,
937       DataOutputStream out) throws IOException;
938 
939   @Override
940   public void endBlockEncoding(HFileBlockEncodingContext encodingCtx, DataOutputStream out,
941       byte[] uncompressedBytesWithHeader) throws IOException {
942     BufferedDataBlockEncodingState state = (BufferedDataBlockEncodingState) encodingCtx
943         .getEncodingState();
944     // Write the unencodedDataSizeWritten (with header size)
945     Bytes.putInt(uncompressedBytesWithHeader,
946       HConstants.HFILEBLOCK_HEADER_SIZE + DataBlockEncoding.ID_SIZE, state.unencodedDataSizeWritten
947         );
948     postEncoding(encodingCtx);
949   }
950 }