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     istream.seek(seekPoint);
389     ByteBuffer buf = ByteBuffer.allocate(bufferSize);
390     istream.readFully(buf.array(), buf.arrayOffset(),
391         buf.arrayOffset() + buf.limit());
392 
393     // Read the version from the last int of the file.
394     buf.position(buf.limit() - Bytes.SIZEOF_INT);
395     int version = buf.getInt();
396 
397     // Extract the major and minor versions.
398     int majorVersion = extractMajorVersion(version);
399     int minorVersion = extractMinorVersion(version);
400 
401     HFile.checkFormatVersion(majorVersion); // throws IAE if invalid
402 
403     int trailerSize = getTrailerSize(majorVersion);
404 
405     FixedFileTrailer fft = new FixedFileTrailer(majorVersion, minorVersion);
406     fft.deserialize(new DataInputStream(new ByteArrayInputStream(buf.array(),
407         buf.arrayOffset() + bufferSize - trailerSize, trailerSize)));
408     return fft;
409   }
410 
411   public void expectMajorVersion(int expected) {
412     if (majorVersion != expected) {
413       throw new IllegalArgumentException("Invalid HFile major version: "
414           + majorVersion 
415           + " (expected: " + expected + ")");
416     }
417   }
418 
419   public void expectMinorVersion(int expected) {
420     if (minorVersion != expected) {
421       throw new IllegalArgumentException("Invalid HFile minor version: "
422           + minorVersion + " (expected: " + expected + ")");
423     }
424   }
425 
426   public void expectAtLeastMajorVersion(int lowerBound) {
427     if (majorVersion < lowerBound) {
428       throw new IllegalArgumentException("Invalid HFile major version: "
429           + majorVersion
430           + " (expected: " + lowerBound + " or higher).");
431     }
432   }
433 
434   public long getFileInfoOffset() {
435     return fileInfoOffset;
436   }
437 
438   public void setFileInfoOffset(long fileInfoOffset) {
439     this.fileInfoOffset = fileInfoOffset;
440   }
441 
442   public long getLoadOnOpenDataOffset() {
443     return loadOnOpenDataOffset;
444   }
445 
446   public void setLoadOnOpenOffset(long loadOnOpenDataOffset) {
447     this.loadOnOpenDataOffset = loadOnOpenDataOffset;
448   }
449 
450   public int getDataIndexCount() {
451     return dataIndexCount;
452   }
453 
454   public void setDataIndexCount(int dataIndexCount) {
455     this.dataIndexCount = dataIndexCount;
456   }
457 
458   public int getMetaIndexCount() {
459     return metaIndexCount;
460   }
461 
462   public void setMetaIndexCount(int metaIndexCount) {
463     this.metaIndexCount = metaIndexCount;
464   }
465 
466   public long getTotalUncompressedBytes() {
467     return totalUncompressedBytes;
468   }
469 
470   public void setTotalUncompressedBytes(long totalUncompressedBytes) {
471     this.totalUncompressedBytes = totalUncompressedBytes;
472   }
473 
474   public long getEntryCount() {
475     return entryCount;
476   }
477 
478   public void setEntryCount(long newEntryCount) {
479     entryCount = newEntryCount;
480   }
481 
482   public Compression.Algorithm getCompressionCodec() {
483     return compressionCodec;
484   }
485 
486   public void setCompressionCodec(Compression.Algorithm compressionCodec) {
487     this.compressionCodec = compressionCodec;
488   }
489 
490   public int getNumDataIndexLevels() {
491     expectAtLeastMajorVersion(2);
492     return numDataIndexLevels;
493   }
494 
495   public void setNumDataIndexLevels(int numDataIndexLevels) {
496     expectAtLeastMajorVersion(2);
497     this.numDataIndexLevels = numDataIndexLevels;
498   }
499 
500   public long getLastDataBlockOffset() {
501     expectAtLeastMajorVersion(2);
502     return lastDataBlockOffset;
503   }
504 
505   public void setLastDataBlockOffset(long lastDataBlockOffset) {
506     expectAtLeastMajorVersion(2);
507     this.lastDataBlockOffset = lastDataBlockOffset;
508   }
509 
510   public long getFirstDataBlockOffset() {
511     expectAtLeastMajorVersion(2);
512     return firstDataBlockOffset;
513   }
514 
515   public void setFirstDataBlockOffset(long firstDataBlockOffset) {
516     expectAtLeastMajorVersion(2);
517     this.firstDataBlockOffset = firstDataBlockOffset;
518   }
519 
520   public String getComparatorClassName() {
521     return comparatorClassName;
522   }
523 
524   /**
525    * Returns the major version of this HFile format
526    */
527   public int getMajorVersion() {
528     return majorVersion;
529   }
530 
531   /**
532    * Returns the minor version of this HFile format
533    */
534   public int getMinorVersion() {
535     return minorVersion;
536   }
537 
538   public void setComparatorClass(Class<? extends KVComparator> klass) {
539     // Is the comparator instantiable?
540     try {
541       KVComparator comp = klass.newInstance();
542 
543       // HFile V2 legacy comparator class names.
544       if (KeyValue.COMPARATOR.getClass().equals(klass)) {
545         comparatorClassName = KeyValue.COMPARATOR.getLegacyKeyComparatorName();
546       } else if (KeyValue.META_COMPARATOR.getClass().equals(klass)) {
547         comparatorClassName = KeyValue.META_COMPARATOR.getLegacyKeyComparatorName();
548       } else if (KeyValue.RAW_COMPARATOR.getClass().equals(klass)) {
549         comparatorClassName = KeyValue.RAW_COMPARATOR.getLegacyKeyComparatorName();
550       } else {
551         // if the name wasn't one of the legacy names, maybe its a legit new kind of comparator.
552         comparatorClassName = klass.getName();
553       }
554 
555     } catch (Exception e) {
556       throw new RuntimeException("Comparator class " + klass.getName() +
557         " is not instantiable", e);
558     }
559 
560   }
561 
562   @SuppressWarnings("unchecked")
563   private static Class<? extends KVComparator> getComparatorClass(
564       String comparatorClassName) throws IOException {
565     try {
566       // HFile V2 legacy comparator class names.
567       if (comparatorClassName.equals(KeyValue.COMPARATOR.getLegacyKeyComparatorName())) {
568         comparatorClassName = KeyValue.COMPARATOR.getClass().getName();
569       } else if (comparatorClassName.equals(KeyValue.META_COMPARATOR.getLegacyKeyComparatorName())) {
570         comparatorClassName = KeyValue.META_COMPARATOR.getClass().getName();
571       } else if (comparatorClassName.equals(KeyValue.RAW_COMPARATOR.getLegacyKeyComparatorName())) {
572         comparatorClassName = KeyValue.RAW_COMPARATOR.getClass().getName();
573       } else if (comparatorClassName.equals("org.apache.hadoop.hbase.CellComparator")) {
574         // 2.0 based comparators found in class name. Convert it to corresponding Comparators in 1.x
575         // branch. Refer to HBASE-16189
576         comparatorClassName = KeyValue.COMPARATOR.getClass().getName();
577       } else if ((comparatorClassName
578           .equals("org.apache.hadoop.hbase.CellComparator$MetaCellComparator"))) {
579         // Refer to HBASE-16189. Fallback to 1.x comparators
580         comparatorClassName = KeyValue.META_COMPARATOR.getClass().getName();
581       }
582 
583       // if the name wasn't one of the legacy names, maybe its a legit new kind of comparator.
584       return (Class<? extends KVComparator>)
585           Class.forName(comparatorClassName);
586     } catch (ClassNotFoundException ex) {
587       throw new IOException(ex);
588     }
589   }
590 
591   public static KVComparator createComparator(
592       String comparatorClassName) throws IOException {
593     try {
594       return getComparatorClass(comparatorClassName).newInstance();
595     } catch (InstantiationException e) {
596       throw new IOException("Comparator class " + comparatorClassName +
597         " is not instantiable", e);
598     } catch (IllegalAccessException e) {
599       throw new IOException("Comparator class " + comparatorClassName +
600         " is not instantiable", e);
601     }
602   }
603 
604   KVComparator createComparator() throws IOException {
605     expectAtLeastMajorVersion(2);
606     return createComparator(comparatorClassName);
607   }
608 
609   public long getUncompressedDataIndexSize() {
610     return uncompressedDataIndexSize;
611   }
612 
613   public void setUncompressedDataIndexSize(
614       long uncompressedDataIndexSize) {
615     expectAtLeastMajorVersion(2);
616     this.uncompressedDataIndexSize = uncompressedDataIndexSize;
617   }
618 
619   public byte[] getEncryptionKey() {
620     expectAtLeastMajorVersion(3);
621     return encryptionKey;
622   }
623 
624   public void setEncryptionKey(byte[] keyBytes) {
625     this.encryptionKey = keyBytes;
626   }
627 
628   /**
629    * Extracts the major version for a 4-byte serialized version data.
630    * The major version is the 3 least significant bytes
631    */
632   private static int extractMajorVersion(int serializedVersion) {
633     return (serializedVersion & 0x00ffffff);
634   }
635 
636   /**
637    * Extracts the minor version for a 4-byte serialized version data.
638    * The major version are the 3 the most significant bytes
639    */
640   private static int extractMinorVersion(int serializedVersion) {
641     return (serializedVersion >>> 24);
642   }
643 
644   /**
645    * Create a 4 byte serialized version number by combining the
646    * minor and major version numbers.
647    */
648   static int materializeVersion(int majorVersion, int minorVersion) {
649     return ((majorVersion & 0x00ffffff) | (minorVersion << 24));
650   }
651 }