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, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase;
20  
21  import java.io.DataInput;
22  import java.io.DataOutput;
23  import java.io.EOFException;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.nio.ByteBuffer;
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  import org.apache.hadoop.hbase.KeyValue.Type;
32  import org.apache.hadoop.hbase.classification.InterfaceAudience;
33  import org.apache.hadoop.hbase.io.util.StreamUtils;
34  import org.apache.hadoop.hbase.util.ByteBufferUtils;
35  import org.apache.hadoop.hbase.util.Bytes;
36  import org.apache.hadoop.hbase.util.IterableUtils;
37  import org.apache.hadoop.io.IOUtils;
38  import org.apache.hadoop.io.WritableUtils;
39  
40  import com.google.common.base.Function;
41  import com.google.common.collect.Lists;
42  
43  /**
44   * static convenience methods for dealing with KeyValues and collections of KeyValues
45   */
46  @InterfaceAudience.Private
47  public class KeyValueUtil {
48  
49    /**************** length *********************/
50  
51    /**
52     * Returns number of bytes this cell would have been used if serialized as in {@link KeyValue}
53     * @param cell
54     * @return the length
55     */
56    public static int length(final Cell cell) {
57      return length(cell.getRowLength(), cell.getFamilyLength(), cell.getQualifierLength(),
58          cell.getValueLength(), cell.getTagsLength(), true);
59    }
60  
61    public static int length(short rlen, byte flen, int qlen, int vlen, int tlen, boolean withTags) {
62      if (withTags) {
63        return (int) (KeyValue.getKeyValueDataStructureSize(rlen, flen, qlen, vlen, tlen));
64      }
65      return (int) (KeyValue.getKeyValueDataStructureSize(rlen, flen, qlen, vlen));
66    }
67  
68    /**
69     * Returns number of bytes this cell's key part would have been used if serialized as in
70     * {@link KeyValue}. Key includes rowkey, family, qualifier, timestamp and type.
71     * @param cell
72     * @return the key length
73     */
74    public static int keyLength(final Cell cell) {
75      return keyLength(cell.getRowLength(), cell.getFamilyLength(), cell.getQualifierLength());
76    }
77  
78    private static int keyLength(short rlen, byte flen, int qlen) {
79      return (int) KeyValue.getKeyDataStructureSize(rlen, flen, qlen);
80    }
81  
82    public static int lengthWithMvccVersion(final KeyValue kv, final boolean includeMvccVersion) {
83      int length = kv.getLength();
84      if (includeMvccVersion) {
85        length += WritableUtils.getVIntSize(kv.getSequenceId());
86      }
87      return length;
88    }
89  
90    public static int totalLengthWithMvccVersion(final Iterable<? extends KeyValue> kvs,
91        final boolean includeMvccVersion) {
92      int length = 0;
93      for (KeyValue kv : IterableUtils.nullSafe(kvs)) {
94        length += lengthWithMvccVersion(kv, includeMvccVersion);
95      }
96      return length;
97    }
98  
99  
100   /**************** copy key only *********************/
101 
102   public static KeyValue copyToNewKeyValue(final Cell cell) {
103     byte[] bytes = copyToNewByteArray(cell);
104     KeyValue kvCell = new KeyValue(bytes, 0, bytes.length);
105     kvCell.setSequenceId(cell.getSequenceId());
106     return kvCell;
107   }
108 
109   /**
110    * The position will be set to the beginning of the new ByteBuffer
111    * @param cell
112    * @return the Bytebuffer containing the key part of the cell
113    */
114   public static ByteBuffer copyKeyToNewByteBuffer(final Cell cell) {
115     byte[] bytes = new byte[keyLength(cell)];
116     appendKeyTo(cell, bytes, 0);
117     ByteBuffer buffer = ByteBuffer.wrap(bytes);
118     return buffer;
119   }
120 
121   public static byte[] copyToNewByteArray(final Cell cell) {
122     int v1Length = length(cell);
123     byte[] backingBytes = new byte[v1Length];
124     appendToByteArray(cell, backingBytes, 0);
125     return backingBytes;
126   }
127 
128   public static int appendKeyTo(final Cell cell, final byte[] output,
129       final int offset) {
130     int nextOffset = offset;
131     nextOffset = Bytes.putShort(output, nextOffset, cell.getRowLength());
132     nextOffset = CellUtil.copyRowTo(cell, output, nextOffset);
133     nextOffset = Bytes.putByte(output, nextOffset, cell.getFamilyLength());
134     nextOffset = CellUtil.copyFamilyTo(cell, output, nextOffset);
135     nextOffset = CellUtil.copyQualifierTo(cell, output, nextOffset);
136     nextOffset = Bytes.putLong(output, nextOffset, cell.getTimestamp());
137     nextOffset = Bytes.putByte(output, nextOffset, cell.getTypeByte());
138     return nextOffset;
139   }
140 
141 
142   /**************** copy key and value *********************/
143 
144   public static int appendToByteArray(final Cell cell, final byte[] output, final int offset) {
145     // TODO when cell instance of KV we can bypass all steps and just do backing single array
146     // copy(?)
147     int pos = offset;
148     pos = Bytes.putInt(output, pos, keyLength(cell));
149     pos = Bytes.putInt(output, pos, cell.getValueLength());
150     pos = appendKeyTo(cell, output, pos);
151     pos = CellUtil.copyValueTo(cell, output, pos);
152     if ((cell.getTagsLength() > 0)) {
153       pos = Bytes.putAsShort(output, pos, cell.getTagsLength());
154       pos = CellUtil.copyTagTo(cell, output, pos);
155     }
156     return pos;
157   }
158 
159   /**
160    * The position will be set to the beginning of the new ByteBuffer
161    * @param cell
162    * @return the ByteBuffer containing the cell
163    */
164   public static ByteBuffer copyToNewByteBuffer(final Cell cell) {
165     byte[] bytes = new byte[length(cell)];
166     appendToByteArray(cell, bytes, 0);
167     ByteBuffer buffer = ByteBuffer.wrap(bytes);
168     return buffer;
169   }
170 
171   public static void appendToByteBuffer(final ByteBuffer bb, final KeyValue kv,
172       final boolean includeMvccVersion) {
173     // keep pushing the limit out. assume enough capacity
174     bb.limit(bb.position() + kv.getLength());
175     bb.put(kv.getBuffer(), kv.getOffset(), kv.getLength());
176     if (includeMvccVersion) {
177       int numMvccVersionBytes = WritableUtils.getVIntSize(kv.getSequenceId());
178       ByteBufferUtils.extendLimit(bb, numMvccVersionBytes);
179       ByteBufferUtils.writeVLong(bb, kv.getSequenceId());
180     }
181   }
182 
183 
184   /**************** iterating *******************************/
185 
186   /**
187    * Creates a new KeyValue object positioned in the supplied ByteBuffer and sets the ByteBuffer's
188    * position to the start of the next KeyValue. Does not allocate a new array or copy data.
189    * @param bb
190    * @param includesMvccVersion
191    * @param includesTags
192    */
193   public static KeyValue nextShallowCopy(final ByteBuffer bb, final boolean includesMvccVersion,
194       boolean includesTags) {
195     if (bb.isDirect()) {
196       throw new IllegalArgumentException("only supports heap buffers");
197     }
198     if (bb.remaining() < 1) {
199       return null;
200     }
201     KeyValue keyValue = null;
202     int underlyingArrayOffset = bb.arrayOffset() + bb.position();
203     int keyLength = bb.getInt();
204     int valueLength = bb.getInt();
205     ByteBufferUtils.skip(bb, keyLength + valueLength);
206     int tagsLength = 0;
207     if (includesTags) {
208       // Read short as unsigned, high byte first
209       tagsLength = ((bb.get() & 0xff) << 8) ^ (bb.get() & 0xff);
210       ByteBufferUtils.skip(bb, tagsLength);
211     }
212     int kvLength = (int) KeyValue.getKeyValueDataStructureSize(keyLength, valueLength, tagsLength);
213     keyValue = new KeyValue(bb.array(), underlyingArrayOffset, kvLength);
214     if (includesMvccVersion) {
215       long mvccVersion = ByteBufferUtils.readVLong(bb);
216       keyValue.setSequenceId(mvccVersion);
217     }
218     return keyValue;
219   }
220 
221 
222   /*************** next/previous **********************************/
223 
224   /**
225    * Decrement the timestamp.  For tests (currently wasteful)
226    *
227    * Remember timestamps are sorted reverse chronologically.
228    * @param in
229    * @return previous key
230    */
231   public static KeyValue previousKey(final KeyValue in) {
232     return createFirstOnRow(CellUtil.cloneRow(in), CellUtil.cloneFamily(in),
233       CellUtil.cloneQualifier(in), in.getTimestamp() - 1);
234   }
235 
236 
237   /**
238    * Create a KeyValue for the specified row, family and qualifier that would be
239    * larger than or equal to all other possible KeyValues that have the same
240    * row, family, qualifier. Used for reseeking.
241    *
242    * @param row
243    *          row key
244    * @param roffset
245    *         row offset
246    * @param rlength
247    *         row length
248    * @param family
249    *         family name
250    * @param foffset
251    *         family offset
252    * @param flength
253    *         family length
254    * @param qualifier
255    *        column qualifier
256    * @param qoffset
257    *        qualifier offset
258    * @param qlength
259    *        qualifier length
260    * @return Last possible key on passed row, family, qualifier.
261    */
262   public static KeyValue createLastOnRow(final byte[] row, final int roffset, final int rlength,
263       final byte[] family, final int foffset, final int flength, final byte[] qualifier,
264       final int qoffset, final int qlength) {
265     return new KeyValue(row, roffset, rlength, family, foffset, flength, qualifier, qoffset,
266         qlength, HConstants.OLDEST_TIMESTAMP, Type.Minimum, null, 0, 0);
267   }
268 
269   /**
270    * Create a KeyValue that is smaller than all other possible KeyValues
271    * for the given row. That is any (valid) KeyValue on 'row' would sort
272    * _after_ the result.
273    *
274    * @param row - row key (arbitrary byte array)
275    * @return First possible KeyValue on passed <code>row</code>
276    */
277   public static KeyValue createFirstOnRow(final byte [] row, int roffset, short rlength) {
278     return new KeyValue(row, roffset, rlength,
279         null, 0, 0, null, 0, 0, HConstants.LATEST_TIMESTAMP, Type.Maximum, null, 0, 0);
280   }
281 
282   /**
283    * Creates a KeyValue that is last on the specified row id. That is,
284    * every other possible KeyValue for the given row would compareTo()
285    * less than the result of this call.
286    * @param row row key
287    * @return Last possible KeyValue on passed <code>row</code>
288    */
289   public static KeyValue createLastOnRow(final byte[] row) {
290     return new KeyValue(row, null, null, HConstants.LATEST_TIMESTAMP, Type.Minimum);
291   }
292 
293   /**
294    * Create a KeyValue that is smaller than all other possible KeyValues
295    * for the given row. That is any (valid) KeyValue on 'row' would sort
296    * _after_ the result.
297    *
298    * @param row - row key (arbitrary byte array)
299    * @return First possible KeyValue on passed <code>row</code>
300    */
301   public static KeyValue createFirstOnRow(final byte [] row) {
302     return createFirstOnRow(row, HConstants.LATEST_TIMESTAMP);
303   }
304 
305   /**
306    * Creates a KeyValue that is smaller than all other KeyValues that
307    * are older than the passed timestamp.
308    * @param row - row key (arbitrary byte array)
309    * @param ts - timestamp
310    * @return First possible key on passed <code>row</code> and timestamp.
311    */
312   public static KeyValue createFirstOnRow(final byte [] row,
313       final long ts) {
314     return new KeyValue(row, null, null, ts, Type.Maximum);
315   }
316 
317   /**
318    * Create a KeyValue for the specified row, family and qualifier that would be
319    * smaller than all other possible KeyValues that have the same row,family,qualifier.
320    * Used for seeking.
321    * @param row - row key (arbitrary byte array)
322    * @param family - family name
323    * @param qualifier - column qualifier
324    * @return First possible key on passed <code>row</code>, and column.
325    */
326   public static KeyValue createFirstOnRow(final byte [] row, final byte [] family,
327       final byte [] qualifier) {
328     return new KeyValue(row, family, qualifier, HConstants.LATEST_TIMESTAMP, Type.Maximum);
329   }
330 
331   /**
332    * @param row - row key (arbitrary byte array)
333    * @param f - family name
334    * @param q - column qualifier
335    * @param ts - timestamp
336    * @return First possible key on passed <code>row</code>, column and timestamp
337    */
338   public static KeyValue createFirstOnRow(final byte [] row, final byte [] f,
339       final byte [] q, final long ts) {
340     return new KeyValue(row, f, q, ts, Type.Maximum);
341   }
342 
343   /**
344    * Create a KeyValue for the specified row, family and qualifier that would be
345    * smaller than all other possible KeyValues that have the same row,
346    * family, qualifier.
347    * Used for seeking.
348    * @param row row key
349    * @param roffset row offset
350    * @param rlength row length
351    * @param family family name
352    * @param foffset family offset
353    * @param flength family length
354    * @param qualifier column qualifier
355    * @param qoffset qualifier offset
356    * @param qlength qualifier length
357    * @return First possible key on passed Row, Family, Qualifier.
358    */
359   public static KeyValue createFirstOnRow(final byte [] row,
360       final int roffset, final int rlength, final byte [] family,
361       final int foffset, final int flength, final byte [] qualifier,
362       final int qoffset, final int qlength) {
363     return new KeyValue(row, roffset, rlength, family,
364         foffset, flength, qualifier, qoffset, qlength,
365         HConstants.LATEST_TIMESTAMP, Type.Maximum, null, 0, 0);
366   }
367 
368   /**
369    * Create a KeyValue for the specified row, family and qualifier that would be
370    * smaller than all other possible KeyValues that have the same row,
371    * family, qualifier.
372    * Used for seeking.
373    *
374    * @param buffer the buffer to use for the new <code>KeyValue</code> object
375    * @param row the value key
376    * @param family family name
377    * @param qualifier column qualifier
378    *
379    * @return First possible key on passed Row, Family, Qualifier.
380    *
381    * @throws IllegalArgumentException The resulting <code>KeyValue</code> object would be larger
382    * than the provided buffer or than <code>Integer.MAX_VALUE</code>
383    */
384   public static KeyValue createFirstOnRow(byte [] buffer, final byte [] row,
385       final byte [] family, final byte [] qualifier)
386           throws IllegalArgumentException {
387     return createFirstOnRow(buffer, 0, row, 0, row.length,
388         family, 0, family.length,
389         qualifier, 0, qualifier.length);
390   }
391 
392   /**
393    * Create a KeyValue for the specified row, family and qualifier that would be
394    * smaller than all other possible KeyValues that have the same row,
395    * family, qualifier.
396    * Used for seeking.
397    *
398    * @param buffer the buffer to use for the new <code>KeyValue</code> object
399    * @param boffset buffer offset
400    * @param row the value key
401    * @param roffset row offset
402    * @param rlength row length
403    * @param family family name
404    * @param foffset family offset
405    * @param flength family length
406    * @param qualifier column qualifier
407    * @param qoffset qualifier offset
408    * @param qlength qualifier length
409    *
410    * @return First possible key on passed Row, Family, Qualifier.
411    *
412    * @throws IllegalArgumentException The resulting <code>KeyValue</code> object would be larger
413    * than the provided buffer or than <code>Integer.MAX_VALUE</code>
414    */
415   public static KeyValue createFirstOnRow(byte[] buffer, final int boffset, final byte[] row,
416       final int roffset, final int rlength, final byte[] family, final int foffset,
417       final int flength, final byte[] qualifier, final int qoffset, final int qlength)
418       throws IllegalArgumentException {
419 
420     long lLength = KeyValue.getKeyValueDataStructureSize(rlength, flength, qlength, 0);
421 
422     if (lLength > Integer.MAX_VALUE) {
423       throw new IllegalArgumentException("KeyValue length " + lLength + " > " + Integer.MAX_VALUE);
424     }
425     int iLength = (int) lLength;
426     if (buffer.length - boffset < iLength) {
427       throw new IllegalArgumentException("Buffer size " + (buffer.length - boffset) + " < "
428           + iLength);
429     }
430 
431     int len = KeyValue.writeByteArray(buffer, boffset, row, roffset, rlength, family, foffset,
432         flength, qualifier, qoffset, qlength, HConstants.LATEST_TIMESTAMP, KeyValue.Type.Maximum,
433         null, 0, 0, null);
434     return new KeyValue(buffer, boffset, len);
435   }
436 
437   /*************** misc **********************************/
438   /**
439    * @param cell
440    * @return <code>cell</code> if it is an object of class {@link KeyValue} else we will return a
441    *         new {@link KeyValue} instance made from <code>cell</code> Note: Even if the cell is an
442    *         object of any of the subclass of {@link KeyValue}, we will create a new
443    *         {@link KeyValue} object wrapping same buffer. This API is used only with MR based tools
444    *         which expect the type to be exactly KeyValue. That is the reason for doing this way.
445    * @deprecated without any replacement.
446    */
447   @Deprecated
448   public static KeyValue ensureKeyValue(final Cell cell) {
449     if (cell == null) return null;
450     if (cell instanceof KeyValue) {
451       if (cell.getClass().getName().equals(KeyValue.class.getName())) {
452         return (KeyValue) cell;
453       }
454       // Cell is an Object of any of the sub classes of KeyValue. Make a new KeyValue wrapping the
455       // same byte[]
456       KeyValue kv = (KeyValue) cell;
457       KeyValue newKv = new KeyValue(kv.bytes, kv.offset, kv.length);
458       newKv.setSequenceId(kv.getSequenceId());
459       return newKv;
460     }
461     return copyToNewKeyValue(cell);
462   }
463 
464   @Deprecated
465   public static List<KeyValue> ensureKeyValues(List<Cell> cells) {
466     List<KeyValue> lazyList = Lists.transform(cells, new Function<Cell, KeyValue>() {
467       @Override
468       public KeyValue apply(Cell arg0) {
469         return KeyValueUtil.ensureKeyValue(arg0);
470       }
471     });
472     return new ArrayList<KeyValue>(lazyList);
473   }
474   /**
475    * Write out a KeyValue in the manner in which we used to when KeyValue was a
476    * Writable.
477    *
478    * @param kv
479    * @param out
480    * @return Length written on stream
481    * @throws IOException
482    * @see #create(DataInput) for the inverse function
483    */
484   public static long write(final KeyValue kv, final DataOutput out) throws IOException {
485     // This is how the old Writables write used to serialize KVs. Need to figure
486     // way to make it
487     // work for all implementations.
488     int length = kv.getLength();
489     out.writeInt(length);
490     out.write(kv.getBuffer(), kv.getOffset(), length);
491     return length + Bytes.SIZEOF_INT;
492   }
493 
494   /**
495    * Create a KeyValue reading from the raw InputStream. Named
496    * <code>iscreate</code> so doesn't clash with {@link #create(DataInput)}
497    *
498    * @param in
499    * @param withTags whether the keyvalue should include tags are not
500    * @return Created KeyValue OR if we find a length of zero, we will return
501    *         null which can be useful marking a stream as done.
502    * @throws IOException
503    */
504   public static KeyValue iscreate(final InputStream in, boolean withTags) throws IOException {
505     byte[] intBytes = new byte[Bytes.SIZEOF_INT];
506     int bytesRead = 0;
507     while (bytesRead < intBytes.length) {
508       int n = in.read(intBytes, bytesRead, intBytes.length - bytesRead);
509       if (n < 0) {
510         if (bytesRead == 0) {
511           throw new EOFException();
512         }
513         throw new IOException("Failed read of int, read " + bytesRead + " bytes");
514       }
515       bytesRead += n;
516     }
517     // TODO: perhaps some sanity check is needed here.
518     byte[] bytes = new byte[Bytes.toInt(intBytes)];
519     IOUtils.readFully(in, bytes, 0, bytes.length);
520     if (withTags) {
521       return new KeyValue(bytes, 0, bytes.length);
522     } else {
523       return new NoTagsKeyValue(bytes, 0, bytes.length);
524     }
525   }
526 
527   /**
528    * @param b
529    * @return A KeyValue made of a byte array that holds the key-only part.
530    *         Needed to convert hfile index members to KeyValues.
531    */
532   public static KeyValue createKeyValueFromKey(final byte[] b) {
533     return createKeyValueFromKey(b, 0, b.length);
534   }
535 
536   /**
537    * @param bb
538    * @return A KeyValue made of a byte buffer that holds the key-only part.
539    *         Needed to convert hfile index members to KeyValues.
540    */
541   public static KeyValue createKeyValueFromKey(final ByteBuffer bb) {
542     return createKeyValueFromKey(bb.array(), bb.arrayOffset(), bb.limit());
543   }
544 
545   /**
546    * @param b
547    * @param o
548    * @param l
549    * @return A KeyValue made of a byte array that holds the key-only part.
550    *         Needed to convert hfile index members to KeyValues.
551    */
552   public static KeyValue createKeyValueFromKey(final byte[] b, final int o, final int l) {
553     byte[] newb = new byte[l + KeyValue.ROW_OFFSET];
554     System.arraycopy(b, o, newb, KeyValue.ROW_OFFSET, l);
555     Bytes.putInt(newb, 0, l);
556     Bytes.putInt(newb, Bytes.SIZEOF_INT, 0);
557     return new KeyValue(newb);
558   }
559 
560   /**
561    * @param in
562    *          Where to read bytes from. Creates a byte array to hold the
563    *          KeyValue backing bytes copied from the steam.
564    * @return KeyValue created by deserializing from <code>in</code> OR if we
565    *         find a length of zero, we will return null which can be useful
566    *         marking a stream as done.
567    * @throws IOException
568    */
569   public static KeyValue create(final DataInput in) throws IOException {
570     return create(in.readInt(), in);
571   }
572 
573   /**
574    * Create a KeyValue reading <code>length</code> from <code>in</code>
575    *
576    * @param length
577    * @param in
578    * @return Created KeyValue OR if we find a length of zero, we will return
579    *         null which can be useful marking a stream as done.
580    * @throws IOException
581    */
582   public static KeyValue create(int length, final DataInput in) throws IOException {
583 
584     if (length <= 0) {
585       if (length == 0)
586         return null;
587       throw new IOException("Failed read " + length + " bytes, stream corrupt?");
588     }
589 
590     // This is how the old Writables.readFrom used to deserialize. Didn't even
591     // vint.
592     byte[] bytes = new byte[length];
593     in.readFully(bytes);
594     return new KeyValue(bytes, 0, length);
595   }
596 
597   public static void oswrite(final Cell cell, final OutputStream out, final boolean withTags)
598       throws IOException {
599     if (cell instanceof Streamable) {
600       ((Streamable)cell).write(out, withTags);
601     } else {
602       short rlen = cell.getRowLength();
603       byte flen = cell.getFamilyLength();
604       int qlen = cell.getQualifierLength();
605       int vlen = cell.getValueLength();
606       int tlen = cell.getTagsLength();
607 
608       // write total length
609       ByteBufferUtils.putInt(out, length(rlen, flen, qlen, vlen, tlen, withTags));
610       // write key length
611       ByteBufferUtils.putInt(out, keyLength(rlen, flen, qlen));
612       // write value length
613       ByteBufferUtils.putInt(out, vlen);
614       // Write rowkey - 2 bytes rk length followed by rowkey bytes
615       StreamUtils.writeShort(out, rlen);
616       out.write(cell.getRowArray(), cell.getRowOffset(), rlen);
617       // Write cf - 1 byte of cf length followed by the family bytes
618       out.write(flen);
619       out.write(cell.getFamilyArray(), cell.getFamilyOffset(), flen);
620       // write qualifier
621       out.write(cell.getQualifierArray(), cell.getQualifierOffset(), qlen);
622       // write timestamp
623       StreamUtils.writeLong(out, cell.getTimestamp());
624       // write the type
625       out.write(cell.getTypeByte());
626       // write value
627       out.write(cell.getValueArray(), cell.getValueOffset(), vlen);
628       // write tags if we have to
629       if (withTags && tlen > 0) {
630         // 2 bytes tags length followed by tags bytes
631         // tags length is serialized with 2 bytes only(short way) even if the
632         // type is int. As this
633         // is non -ve numbers, we save the sign bit. See HBASE-11437
634         out.write((byte) (0xff & (tlen >> 8)));
635         out.write((byte) (0xff & tlen));
636         out.write(cell.getTagsArray(), cell.getTagsOffset(), tlen);
637       }
638     }
639   }
640 }