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