View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.io.hfile;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.DataInput;
24  import java.io.DataInputStream;
25  import java.io.DataOutputStream;
26  import java.io.IOException;
27  import java.nio.ByteBuffer;
28  
29  import org.apache.hadoop.hbase.util.ByteStringer;
30  import org.apache.hadoop.hbase.classification.InterfaceAudience;
31  import org.apache.hadoop.fs.FSDataInputStream;
32  import org.apache.hadoop.hbase.KeyValue;
33  import org.apache.hadoop.hbase.KeyValue.KVComparator;
34  import org.apache.hadoop.hbase.io.compress.Compression;
35  import org.apache.hadoop.hbase.protobuf.generated.HFileProtos;
36  import org.apache.hadoop.hbase.util.Bytes;
37  
38  /**
39   * The {@link HFile} has a fixed trailer which contains offsets to other
40   * variable parts of the file. Also includes basic metadata on this file. The
41   * trailer size is fixed within a given {@link HFile} format version only, but
42   * we always store the version number as the last four-byte integer of the file.
43   * The version number itself is split into two portions, a major 
44   * version and a minor version. The last three bytes of a file are the major
45   * version and a single preceding byte is the minor number. The major version
46   * determines which readers/writers to use to read/write a hfile while a minor
47   * version determines smaller changes in hfile format that do not need a new
48   * reader/writer type.
49   */
50  @InterfaceAudience.Private
51  public class FixedFileTrailer {
52    /**
53     * We store the comparator class name as a fixed-length field in the trailer.
54     */
55    private static final int MAX_COMPARATOR_NAME_LENGTH = 128;
56  
57    /**
58     * Offset to the fileinfo data, a small block of vitals. Necessary in v1 but
59     * only potentially useful for pretty-printing in v2.
60     */
61    private long fileInfoOffset;
62  
63    /**
64     * In version 1, the offset to the data block index. Starting from version 2,
65     * the meaning of this field is the offset to the section of the file that
66     * should be loaded at the time the file is being opened: i.e. on open we load
67     * the root index, file info, etc. See http://hbase.apache.org/book.html#_hfile_format_2
68     * in the reference guide.
69     */
70    private long loadOnOpenDataOffset;
71  
72    /** The number of entries in the root data index. */
73    private int dataIndexCount;
74  
75    /** Total uncompressed size of all blocks of the data index */
76    private long uncompressedDataIndexSize;
77  
78    /** The number of entries in the meta index */
79    private int metaIndexCount;
80  
81    /** The total uncompressed size of keys/values stored in the file. */
82    private long totalUncompressedBytes;
83  
84    /**
85     * The number of key/value pairs in the file. This field was int in version 1,
86     * but is now long.
87     */
88    private long entryCount;
89  
90    /** The compression codec used for all blocks. */
91    private Compression.Algorithm compressionCodec = Compression.Algorithm.NONE;
92  
93    /**
94     * The number of levels in the potentially multi-level data index. Used from
95     * version 2 onwards.
96     */
97    private int numDataIndexLevels;
98  
99    /** The offset of the first data block. */
100   private long firstDataBlockOffset;
101 
102   /**
103    * It is guaranteed that no key/value data blocks start after this offset in
104    * the file.
105    */
106   private long lastDataBlockOffset;
107 
108   /** Raw key comparator class name in version 3 */
109   private String comparatorClassName = KeyValue.COMPARATOR.getLegacyKeyComparatorName();
110 
111   /** The encryption key */
112   private byte[] encryptionKey;
113 
114   /** The {@link HFile} format major version. */
115   private final int majorVersion;
116 
117   /** The {@link HFile} format minor version. */
118   private final int minorVersion;
119 
120   FixedFileTrailer(int majorVersion, int minorVersion) {
121     this.majorVersion = majorVersion;
122     this.minorVersion = minorVersion;
123     HFile.checkFormatVersion(majorVersion);
124   }
125 
126   private static int[] computeTrailerSizeByVersion() {
127     int versionToSize[] = new int[HFile.MAX_FORMAT_VERSION + 1];
128     // We support only 2 major versions now. ie. V2, V3
129     versionToSize[2] = 212;
130     for (int version = 3; version <= HFile.MAX_FORMAT_VERSION; version++) {
131       // Max FFT size for V3 and above is taken as 4KB for future enhancements
132       // if any.
133       // Unless the trailer size exceeds 4K this can continue
134       versionToSize[version] = 1024 * 4;
135     }
136     return versionToSize;
137   }
138 
139   private static int getMaxTrailerSize() {
140     int maxSize = 0;
141     for (int version = HFile.MIN_FORMAT_VERSION;
142          version <= HFile.MAX_FORMAT_VERSION;
143          ++version)
144       maxSize = Math.max(getTrailerSize(version), maxSize);
145     return maxSize;
146   }
147 
148   private static final int TRAILER_SIZE[] = computeTrailerSizeByVersion();
149   private static final int MAX_TRAILER_SIZE = getMaxTrailerSize();
150 
151   private static final int NOT_PB_SIZE = BlockType.MAGIC_LENGTH + Bytes.SIZEOF_INT;
152 
153   static int getTrailerSize(int version) {
154     return TRAILER_SIZE[version];
155   }
156 
157   public int getTrailerSize() {
158     return getTrailerSize(majorVersion);
159   }
160 
161   /**
162    * Write the trailer to a data stream. We support writing version 1 for
163    * testing and for determining version 1 trailer size. It is also easy to see
164    * what fields changed in version 2.
165    *
166    * @param outputStream
167    * @throws IOException
168    */
169   void serialize(DataOutputStream outputStream) throws IOException {
170     HFile.checkFormatVersion(majorVersion);
171 
172     ByteArrayOutputStream baos = new ByteArrayOutputStream();
173     DataOutputStream baosDos = new DataOutputStream(baos);
174 
175     BlockType.TRAILER.write(baosDos);
176     serializeAsPB(baosDos);
177 
178     // The last 4 bytes of the file encode the major and minor version universally
179     baosDos.writeInt(materializeVersion(majorVersion, minorVersion));
180 
181     baos.writeTo(outputStream);
182   }
183 
184   /**
185    * Write trailer data as protobuf
186    * @param outputStream
187    * @throws IOException
188    */
189   void serializeAsPB(DataOutputStream output) throws IOException {
190     ByteArrayOutputStream baos = new ByteArrayOutputStream();
191     HFileProtos.FileTrailerProto.Builder builder = HFileProtos.FileTrailerProto.newBuilder()
192       .setFileInfoOffset(fileInfoOffset)
193       .setLoadOnOpenDataOffset(loadOnOpenDataOffset)
194       .setUncompressedDataIndexSize(uncompressedDataIndexSize)
195       .setTotalUncompressedBytes(totalUncompressedBytes)
196       .setDataIndexCount(dataIndexCount)
197       .setMetaIndexCount(metaIndexCount)
198       .setEntryCount(entryCount)
199       .setNumDataIndexLevels(numDataIndexLevels)
200       .setFirstDataBlockOffset(firstDataBlockOffset)
201       .setLastDataBlockOffset(lastDataBlockOffset)
202       // TODO this is a classname encoded into an  HFile's trailer. We are going to need to have 
203       // some compat code here.
204       .setComparatorClassName(comparatorClassName)
205       .setCompressionCodec(compressionCodec.ordinal());
206     if (encryptionKey != null) {
207       builder.setEncryptionKey(ByteStringer.wrap(encryptionKey));
208     }
209     // We need this extra copy unfortunately to determine the final size of the
210     // delimited output, see use of baos.size() below.
211     builder.build().writeDelimitedTo(baos);
212     baos.writeTo(output);
213     // Pad to make up the difference between variable PB encoding length and the
214     // length when encoded as writable under earlier V2 formats. Failure to pad
215     // properly or if the PB encoding is too big would mean the trailer wont be read
216     // in properly by HFile.
217     int padding = getTrailerSize() - NOT_PB_SIZE - baos.size();
218     if (padding < 0) {
219       throw new IOException("Pbuf encoding size exceeded fixed trailer size limit");
220     }
221     for (int i = 0; i < padding; i++) {
222       output.write(0);
223     }
224   }
225 
226   /**
227    * Deserialize the fixed file trailer from the given stream. The version needs
228    * to already be specified. Make sure this is consistent with
229    * {@link #serialize(DataOutputStream)}.
230    *
231    * @param inputStream
232    * @throws IOException
233    */
234   void deserialize(DataInputStream inputStream) throws IOException {
235     HFile.checkFormatVersion(majorVersion);
236 
237     BlockType.TRAILER.readAndCheck(inputStream);
238 
239     if (majorVersion > 2
240         || (majorVersion == 2 && minorVersion >= HFileReaderV2.PBUF_TRAILER_MINOR_VERSION)) {
241       deserializeFromPB(inputStream);
242     } else {
243       deserializeFromWritable(inputStream);
244     }
245 
246     // The last 4 bytes of the file encode the major and minor version universally
247     int version = inputStream.readInt();
248     expectMajorVersion(extractMajorVersion(version));
249     expectMinorVersion(extractMinorVersion(version));
250   }
251 
252   /**
253    * Deserialize the file trailer as protobuf
254    * @param inputStream
255    * @throws IOException
256    */
257   void deserializeFromPB(DataInputStream inputStream) throws IOException {
258     // read PB and skip padding
259     int start = inputStream.available();
260     HFileProtos.FileTrailerProto trailerProto =
261         HFileProtos.FileTrailerProto.PARSER.parseDelimitedFrom(inputStream);
262     int size = start - inputStream.available();
263     inputStream.skip(getTrailerSize() - NOT_PB_SIZE - size);
264 
265     // process the PB
266     if (trailerProto.hasFileInfoOffset()) {
267       fileInfoOffset = trailerProto.getFileInfoOffset();
268     }
269     if (trailerProto.hasLoadOnOpenDataOffset()) {
270       loadOnOpenDataOffset = trailerProto.getLoadOnOpenDataOffset();
271     }
272     if (trailerProto.hasUncompressedDataIndexSize()) {
273       uncompressedDataIndexSize = trailerProto.getUncompressedDataIndexSize();
274     }
275     if (trailerProto.hasTotalUncompressedBytes()) {
276       totalUncompressedBytes = trailerProto.getTotalUncompressedBytes();
277     }
278     if (trailerProto.hasDataIndexCount()) {
279       dataIndexCount = trailerProto.getDataIndexCount();
280     }
281     if (trailerProto.hasMetaIndexCount()) {
282       metaIndexCount = trailerProto.getMetaIndexCount();
283     }
284     if (trailerProto.hasEntryCount()) {
285       entryCount = trailerProto.getEntryCount();
286     }
287     if (trailerProto.hasNumDataIndexLevels()) {
288       numDataIndexLevels = trailerProto.getNumDataIndexLevels();
289     }
290     if (trailerProto.hasFirstDataBlockOffset()) {
291       firstDataBlockOffset = trailerProto.getFirstDataBlockOffset();
292     }
293     if (trailerProto.hasLastDataBlockOffset()) {
294       lastDataBlockOffset = trailerProto.getLastDataBlockOffset();
295     }
296     if (trailerProto.hasComparatorClassName()) {
297       // TODO this is a classname encoded into an  HFile's trailer. We are going to need to have 
298       // some compat code here.
299       setComparatorClass(getComparatorClass(trailerProto.getComparatorClassName()));
300     }
301     if (trailerProto.hasCompressionCodec()) {
302       compressionCodec = Compression.Algorithm.values()[trailerProto.getCompressionCodec()];
303     } else {
304       compressionCodec = Compression.Algorithm.NONE;
305     }
306     if (trailerProto.hasEncryptionKey()) {
307       encryptionKey = trailerProto.getEncryptionKey().toByteArray();
308     }
309   }
310 
311   /**
312    * Deserialize the file trailer as writable data
313    * @param input
314    * @throws IOException
315    */
316   void deserializeFromWritable(DataInput input) throws IOException {
317     fileInfoOffset = input.readLong();
318     loadOnOpenDataOffset = input.readLong();
319     dataIndexCount = input.readInt();
320     uncompressedDataIndexSize = input.readLong();
321     metaIndexCount = input.readInt();
322 
323     totalUncompressedBytes = input.readLong();
324     entryCount = input.readLong();
325     compressionCodec = Compression.Algorithm.values()[input.readInt()];
326     numDataIndexLevels = input.readInt();
327     firstDataBlockOffset = input.readLong();
328     lastDataBlockOffset = input.readLong();
329     // TODO this is a classname encoded into an  HFile's trailer. We are going to need to have 
330     // some compat code here.
331     setComparatorClass(getComparatorClass(Bytes.readStringFixedSize(input,
332         MAX_COMPARATOR_NAME_LENGTH)));
333   }
334   
335   private void append(StringBuilder sb, String s) {
336     if (sb.length() > 0)
337       sb.append(", ");
338     sb.append(s);
339   }
340 
341   @Override
342   public String toString() {
343     StringBuilder sb = new StringBuilder();
344     append(sb, "fileinfoOffset=" + fileInfoOffset);
345     append(sb, "loadOnOpenDataOffset=" + loadOnOpenDataOffset);
346     append(sb, "dataIndexCount=" + dataIndexCount);
347     append(sb, "metaIndexCount=" + metaIndexCount);
348     append(sb, "totalUncomressedBytes=" + totalUncompressedBytes);
349     append(sb, "entryCount=" + entryCount);
350     append(sb, "compressionCodec=" + compressionCodec);
351     append(sb, "uncompressedDataIndexSize=" + uncompressedDataIndexSize);
352     append(sb, "numDataIndexLevels=" + numDataIndexLevels);
353     append(sb, "firstDataBlockOffset=" + firstDataBlockOffset);
354     append(sb, "lastDataBlockOffset=" + lastDataBlockOffset);
355     append(sb, "comparatorClassName=" + comparatorClassName);
356     if (majorVersion >= 3) {
357       append(sb, "encryptionKey=" + (encryptionKey != null ? "PRESENT" : "NONE"));
358     }
359     append(sb, "majorVersion=" + majorVersion);
360     append(sb, "minorVersion=" + minorVersion);
361 
362     return sb.toString();
363   }
364 
365   /**
366    * Reads a file trailer from the given file.
367    *
368    * @param istream the input stream with the ability to seek. Does not have to
369    *          be buffered, as only one read operation is made.
370    * @param fileSize the file size. Can be obtained using
371    *          {@link org.apache.hadoop.fs.FileSystem#getFileStatus(
372    *          org.apache.hadoop.fs.Path)}.
373    * @return the fixed file trailer read
374    * @throws IOException if failed to read from the underlying stream, or the
375    *           trailer is corrupted, or the version of the trailer is
376    *           unsupported
377    */
378   public static FixedFileTrailer readFromStream(FSDataInputStream istream,
379       long fileSize) throws IOException {
380     int bufferSize = MAX_TRAILER_SIZE;
381     long seekPoint = fileSize - bufferSize;
382     if (seekPoint < 0) {
383       // It is hard to imagine such a small HFile.
384       seekPoint = 0;
385       bufferSize = (int) fileSize;
386     }
387 
388     HFileUtil.seekOnMultipleSources(istream, seekPoint);
389 
390     ByteBuffer buf = ByteBuffer.allocate(bufferSize);
391     istream.readFully(buf.array(), buf.arrayOffset(),
392         buf.arrayOffset() + buf.limit());
393 
394     // Read the version from the last int of the file.
395     buf.position(buf.limit() - Bytes.SIZEOF_INT);
396     int version = buf.getInt();
397 
398     // Extract the major and minor versions.
399     int majorVersion = extractMajorVersion(version);
400     int minorVersion = extractMinorVersion(version);
401 
402     HFile.checkFormatVersion(majorVersion); // throws IAE if invalid
403 
404     int trailerSize = getTrailerSize(majorVersion);
405 
406     FixedFileTrailer fft = new FixedFileTrailer(majorVersion, minorVersion);
407     fft.deserialize(new DataInputStream(new ByteArrayInputStream(buf.array(),
408         buf.arrayOffset() + bufferSize - trailerSize, trailerSize)));
409     return fft;
410   }
411 
412   public void expectMajorVersion(int expected) {
413     if (majorVersion != expected) {
414       throw new IllegalArgumentException("Invalid HFile major version: "
415           + majorVersion 
416           + " (expected: " + expected + ")");
417     }
418   }
419 
420   public void expectMinorVersion(int expected) {
421     if (minorVersion != expected) {
422       throw new IllegalArgumentException("Invalid HFile minor version: "
423           + minorVersion + " (expected: " + expected + ")");
424     }
425   }
426 
427   public void expectAtLeastMajorVersion(int lowerBound) {
428     if (majorVersion < lowerBound) {
429       throw new IllegalArgumentException("Invalid HFile major version: "
430           + majorVersion
431           + " (expected: " + lowerBound + " or higher).");
432     }
433   }
434 
435   public long getFileInfoOffset() {
436     return fileInfoOffset;
437   }
438 
439   public void setFileInfoOffset(long fileInfoOffset) {
440     this.fileInfoOffset = fileInfoOffset;
441   }
442 
443   public long getLoadOnOpenDataOffset() {
444     return loadOnOpenDataOffset;
445   }
446 
447   public void setLoadOnOpenOffset(long loadOnOpenDataOffset) {
448     this.loadOnOpenDataOffset = loadOnOpenDataOffset;
449   }
450 
451   public int getDataIndexCount() {
452     return dataIndexCount;
453   }
454 
455   public void setDataIndexCount(int dataIndexCount) {
456     this.dataIndexCount = dataIndexCount;
457   }
458 
459   public int getMetaIndexCount() {
460     return metaIndexCount;
461   }
462 
463   public void setMetaIndexCount(int metaIndexCount) {
464     this.metaIndexCount = metaIndexCount;
465   }
466 
467   public long getTotalUncompressedBytes() {
468     return totalUncompressedBytes;
469   }
470 
471   public void setTotalUncompressedBytes(long totalUncompressedBytes) {
472     this.totalUncompressedBytes = totalUncompressedBytes;
473   }
474 
475   public long getEntryCount() {
476     return entryCount;
477   }
478 
479   public void setEntryCount(long newEntryCount) {
480     entryCount = newEntryCount;
481   }
482 
483   public Compression.Algorithm getCompressionCodec() {
484     return compressionCodec;
485   }
486 
487   public void setCompressionCodec(Compression.Algorithm compressionCodec) {
488     this.compressionCodec = compressionCodec;
489   }
490 
491   public int getNumDataIndexLevels() {
492     expectAtLeastMajorVersion(2);
493     return numDataIndexLevels;
494   }
495 
496   public void setNumDataIndexLevels(int numDataIndexLevels) {
497     expectAtLeastMajorVersion(2);
498     this.numDataIndexLevels = numDataIndexLevels;
499   }
500 
501   public long getLastDataBlockOffset() {
502     expectAtLeastMajorVersion(2);
503     return lastDataBlockOffset;
504   }
505 
506   public void setLastDataBlockOffset(long lastDataBlockOffset) {
507     expectAtLeastMajorVersion(2);
508     this.lastDataBlockOffset = lastDataBlockOffset;
509   }
510 
511   public long getFirstDataBlockOffset() {
512     expectAtLeastMajorVersion(2);
513     return firstDataBlockOffset;
514   }
515 
516   public void setFirstDataBlockOffset(long firstDataBlockOffset) {
517     expectAtLeastMajorVersion(2);
518     this.firstDataBlockOffset = firstDataBlockOffset;
519   }
520 
521   public String getComparatorClassName() {
522     return comparatorClassName;
523   }
524 
525   /**
526    * Returns the major version of this HFile format
527    */
528   public int getMajorVersion() {
529     return majorVersion;
530   }
531 
532   /**
533    * Returns the minor version of this HFile format
534    */
535   public int getMinorVersion() {
536     return minorVersion;
537   }
538 
539   public void setComparatorClass(Class<? extends KVComparator> klass) {
540     // Is the comparator instantiable?
541     try {
542       KVComparator comp = klass.newInstance();
543 
544       // HFile V2 legacy comparator class names.
545       if (KeyValue.COMPARATOR.getClass().equals(klass)) {
546         comparatorClassName = KeyValue.COMPARATOR.getLegacyKeyComparatorName();
547       } else if (KeyValue.META_COMPARATOR.getClass().equals(klass)) {
548         comparatorClassName = KeyValue.META_COMPARATOR.getLegacyKeyComparatorName();
549       } else if (KeyValue.RAW_COMPARATOR.getClass().equals(klass)) {
550         comparatorClassName = KeyValue.RAW_COMPARATOR.getLegacyKeyComparatorName();
551       } else {
552         // if the name wasn't one of the legacy names, maybe its a legit new kind of comparator.
553         comparatorClassName = klass.getName();
554       }
555 
556     } catch (Exception e) {
557       throw new RuntimeException("Comparator class " + klass.getName() +
558         " is not instantiable", e);
559     }
560 
561   }
562 
563   @SuppressWarnings("unchecked")
564   private static Class<? extends KVComparator> getComparatorClass(
565       String comparatorClassName) throws IOException {
566     try {
567       // HFile V2 legacy comparator class names.
568       if (comparatorClassName.equals(KeyValue.COMPARATOR.getLegacyKeyComparatorName())) {
569         comparatorClassName = KeyValue.COMPARATOR.getClass().getName();
570       } else if (comparatorClassName.equals(KeyValue.META_COMPARATOR.getLegacyKeyComparatorName())) {
571         comparatorClassName = KeyValue.META_COMPARATOR.getClass().getName();
572       } else if (comparatorClassName.equals(KeyValue.RAW_COMPARATOR.getLegacyKeyComparatorName())) {
573         comparatorClassName = KeyValue.RAW_COMPARATOR.getClass().getName();
574       } else if (comparatorClassName.equals("org.apache.hadoop.hbase.CellComparatorImpl")) {
575         // 2.0 based comparators found in class name. Convert it to corresponding Comparators in 1.x
576         // branch. Refer to HBASE-19052
577         comparatorClassName = KeyValue.COMPARATOR.getClass().getName();
578       } else if ((comparatorClassName
579           .equals("org.apache.hadoop.hbase.CellComparatorImpl$MetaCellComparator"))) {
580         // Refer to HBASE-19052. Fallback to 1.x comparators
581         comparatorClassName = KeyValue.META_COMPARATOR.getClass().getName();
582       }
583 
584       // if the name wasn't one of the legacy names, maybe its a legit new kind of comparator.
585       return (Class<? extends KVComparator>)
586           Class.forName(comparatorClassName);
587     } catch (ClassNotFoundException ex) {
588       throw new IOException(ex);
589     }
590   }
591 
592   public static KVComparator createComparator(
593       String comparatorClassName) throws IOException {
594     try {
595       return getComparatorClass(comparatorClassName).newInstance();
596     } catch (InstantiationException e) {
597       throw new IOException("Comparator class " + comparatorClassName +
598         " is not instantiable", e);
599     } catch (IllegalAccessException e) {
600       throw new IOException("Comparator class " + comparatorClassName +
601         " is not instantiable", e);
602     }
603   }
604 
605   KVComparator createComparator() throws IOException {
606     expectAtLeastMajorVersion(2);
607     return createComparator(comparatorClassName);
608   }
609 
610   public long getUncompressedDataIndexSize() {
611     return uncompressedDataIndexSize;
612   }
613 
614   public void setUncompressedDataIndexSize(
615       long uncompressedDataIndexSize) {
616     expectAtLeastMajorVersion(2);
617     this.uncompressedDataIndexSize = uncompressedDataIndexSize;
618   }
619 
620   public byte[] getEncryptionKey() {
621     expectAtLeastMajorVersion(3);
622     return encryptionKey;
623   }
624 
625   public void setEncryptionKey(byte[] keyBytes) {
626     this.encryptionKey = keyBytes;
627   }
628 
629   /**
630    * Extracts the major version for a 4-byte serialized version data.
631    * The major version is the 3 least significant bytes
632    */
633   private static int extractMajorVersion(int serializedVersion) {
634     return (serializedVersion & 0x00ffffff);
635   }
636 
637   /**
638    * Extracts the minor version for a 4-byte serialized version data.
639    * The major version are the 3 the most significant bytes
640    */
641   private static int extractMinorVersion(int serializedVersion) {
642     return (serializedVersion >>> 24);
643   }
644 
645   /**
646    * Create a 4 byte serialized version number by combining the
647    * minor and major version numbers.
648    */
649   static int materializeVersion(int majorVersion, int minorVersion) {
650     return ((majorVersion & 0x00ffffff) | (minorVersion << 24));
651   }
652 }