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;
20  
21  import com.google.protobuf.ByteString;
22  import com.google.protobuf.InvalidProtocolBufferException;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.hadoop.classification.InterfaceAudience;
26  import org.apache.hadoop.classification.InterfaceStability;
27  import org.apache.hadoop.hbase.KeyValue.KVComparator;
28  import org.apache.hadoop.hbase.client.Result;
29  import org.apache.hadoop.hbase.exceptions.DeserializationException;
30  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
31  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
32  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionInfo;
33  import org.apache.hadoop.hbase.util.Bytes;
34  import org.apache.hadoop.hbase.util.JenkinsHash;
35  import org.apache.hadoop.hbase.util.MD5Hash;
36  import org.apache.hadoop.hbase.util.Pair;
37  import org.apache.hadoop.hbase.util.PairOfSameType;
38  import org.apache.hadoop.io.DataInputBuffer;
39  
40  import java.io.ByteArrayInputStream;
41  import java.io.DataInput;
42  import java.io.DataInputStream;
43  import java.io.DataOutput;
44  import java.io.EOFException;
45  import java.io.IOException;
46  import java.io.SequenceInputStream;
47  import java.util.ArrayList;
48  import java.util.Arrays;
49  import java.util.List;
50  
51  /**
52   * HRegion information.
53   * Contains HRegion id, start and end keys, a reference to this HRegions' table descriptor, etc.
54   */
55  @InterfaceAudience.Public
56  @InterfaceStability.Evolving
57  public class HRegionInfo implements Comparable<HRegionInfo> {
58    /*
59     * There are two versions associated with HRegionInfo: HRegionInfo.VERSION and
60     * HConstants.META_VERSION. HRegionInfo.VERSION indicates the data structure's versioning
61     * while HConstants.META_VERSION indicates the versioning of the serialized HRIs stored in
62     * the META table.
63     *
64     * Pre-0.92:
65     *   HRI.VERSION == 0 and HConstants.META_VERSION does not exist (is not stored at META table)
66     *   HRegionInfo had an HTableDescriptor reference inside it.
67     *   HRegionInfo is serialized as Writable to META table.
68     * For 0.92.x and 0.94.x:
69     *   HRI.VERSION == 1 and HConstants.META_VERSION == 0
70     *   HRI no longer has HTableDescriptor in it.
71     *   HRI is serialized as Writable to META table.
72     * For 0.96.x:
73     *   HRI.VERSION == 1 and HConstants.META_VERSION == 1
74     *   HRI data structure is the same as 0.92 and 0.94
75     *   HRI is serialized as PB to META table.
76     *
77     * Versioning of HRegionInfo is deprecated. HRegionInfo does protobuf
78     * serialization using RegionInfo class, which has it's own versioning.
79     */
80    @Deprecated
81    public static final byte VERSION = 1;
82    private static final Log LOG = LogFactory.getLog(HRegionInfo.class);
83  
84    /**
85     * The new format for a region name contains its encodedName at the end.
86     * The encoded name also serves as the directory name for the region
87     * in the filesystem.
88     *
89     * New region name format:
90     *    &lt;tablename>,,&lt;startkey>,&lt;regionIdTimestamp>.&lt;encodedName>.
91     * where,
92     *    &lt;encodedName> is a hex version of the MD5 hash of
93     *    &lt;tablename>,&lt;startkey>,&lt;regionIdTimestamp>
94     *
95     * The old region name format:
96     *    &lt;tablename>,&lt;startkey>,&lt;regionIdTimestamp>
97     * For region names in the old format, the encoded name is a 32-bit
98     * JenkinsHash integer value (in its decimal notation, string form).
99     *<p>
100    * **NOTE**
101    *
102    * The first META region, and regions created by an older
103    * version of HBase (0.20 or prior) will continue to use the
104    * old region name format.
105    */
106 
107   /** Separator used to demarcate the encodedName in a region name
108    * in the new format. See description on new format above.
109    */
110   private static final int ENC_SEPARATOR = '.';
111   public  static final int MD5_HEX_LENGTH   = 32;
112 
113   /** A non-capture group so that this can be embedded. */
114   public static final String ENCODED_REGION_NAME_REGEX = "(?:[a-f0-9]+)";
115 
116   /**
117    * Does region name contain its encoded name?
118    * @param regionName region name
119    * @return boolean indicating if this a new format region
120    *         name which contains its encoded name.
121    */
122   private static boolean hasEncodedName(final byte[] regionName) {
123     // check if region name ends in ENC_SEPARATOR
124     if ((regionName.length >= 1)
125         && (regionName[regionName.length - 1] == ENC_SEPARATOR)) {
126       // region name is new format. it contains the encoded name.
127       return true;
128     }
129     return false;
130   }
131 
132   /**
133    * @param regionName
134    * @return the encodedName
135    */
136   public static String encodeRegionName(final byte [] regionName) {
137     String encodedName;
138     if (hasEncodedName(regionName)) {
139       // region is in new format:
140       // <tableName>,<startKey>,<regionIdTimeStamp>/encodedName/
141       encodedName = Bytes.toString(regionName,
142           regionName.length - MD5_HEX_LENGTH - 1,
143           MD5_HEX_LENGTH);
144     } else {
145       // old format region name. First META region also
146       // use this format.EncodedName is the JenkinsHash value.
147       int hashVal = Math.abs(JenkinsHash.getInstance().hash(regionName,
148         regionName.length, 0));
149       encodedName = String.valueOf(hashVal);
150     }
151     return encodedName;
152   }
153 
154   /**
155    * Use logging.
156    * @param encodedRegionName The encoded regionname.
157    * @return <code>.META.</code> if passed </code>1028785192</code> else returns
158    * <code>encodedRegionName</code>
159    */
160   public static String prettyPrint(final String encodedRegionName) {
161     if (encodedRegionName.equals("1028785192")) {
162       return encodedRegionName + "/.META.";
163     }
164     return encodedRegionName;
165   }
166 
167   private byte [] endKey = HConstants.EMPTY_BYTE_ARRAY;
168   // This flag is in the parent of a split while the parent is still referenced
169   // by daughter regions.  We USED to set this flag when we disabled a table
170   // but now table state is kept up in zookeeper as of 0.90.0 HBase.
171   private boolean offLine = false;
172   private long regionId = -1;
173   private transient byte [] regionName = HConstants.EMPTY_BYTE_ARRAY;
174   private String regionNameStr = "";
175   private boolean split = false;
176   private byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
177   private int hashCode = -1;
178   //TODO: Move NO_HASH to HStoreFile which is really the only place it is used.
179   public static final String NO_HASH = null;
180   private volatile String encodedName = NO_HASH;
181   private byte [] encodedNameAsBytes = null;
182 
183   // Current TableName
184   private byte[] tableName = null;
185 
186   /** HRegionInfo for root region */
187   public static final HRegionInfo ROOT_REGIONINFO =
188       new HRegionInfo(0L, Bytes.toBytes("-ROOT-"));
189 
190   /** HRegionInfo for first meta region */
191   public static final HRegionInfo FIRST_META_REGIONINFO =
192       new HRegionInfo(1L, Bytes.toBytes(".META."));
193 
194   private void setHashCode() {
195     int result = Arrays.hashCode(this.regionName);
196     result ^= this.regionId;
197     result ^= Arrays.hashCode(this.startKey);
198     result ^= Arrays.hashCode(this.endKey);
199     result ^= Boolean.valueOf(this.offLine).hashCode();
200     result ^= Arrays.hashCode(this.tableName);
201     this.hashCode = result;
202   }
203 
204 
205   /**
206    * Private constructor used constructing HRegionInfo for the
207    * first meta regions
208    */
209   private HRegionInfo(long regionId, byte[] tableName) {
210     super();
211     this.regionId = regionId;
212     this.tableName = tableName.clone();
213     // Note: First Meta regions names are still in old format
214     this.regionName = createRegionName(tableName, null,
215                                        regionId, false);
216     this.regionNameStr = Bytes.toStringBinary(this.regionName);
217     setHashCode();
218   }
219 
220   /** Default constructor - creates empty object
221    * @deprecated Used by Writables and Writables are going away.
222    */
223   @Deprecated
224   public HRegionInfo() {
225     super();
226   }
227 
228   public HRegionInfo(final byte[] tableName) {
229     this(tableName, null, null);
230   }
231 
232   /**
233    * Construct HRegionInfo with explicit parameters
234    *
235    * @param tableName the table name
236    * @param startKey first key in region
237    * @param endKey end of key range
238    * @throws IllegalArgumentException
239    */
240   public HRegionInfo(final byte[] tableName, final byte[] startKey, final byte[] endKey)
241   throws IllegalArgumentException {
242     this(tableName, startKey, endKey, false);
243   }
244 
245 
246   /**
247    * Construct HRegionInfo with explicit parameters
248    *
249    * @param tableName the table descriptor
250    * @param startKey first key in region
251    * @param endKey end of key range
252    * @param split true if this region has split and we have daughter regions
253    * regions that may or may not hold references to this region.
254    * @throws IllegalArgumentException
255    */
256   public HRegionInfo(final byte[] tableName, final byte[] startKey, final byte[] endKey,
257       final boolean split)
258   throws IllegalArgumentException {
259     this(tableName, startKey, endKey, split, System.currentTimeMillis());
260   }
261 
262 
263   /**
264    * Construct HRegionInfo with explicit parameters
265    *
266    * @param tableName the table descriptor
267    * @param startKey first key in region
268    * @param endKey end of key range
269    * @param split true if this region has split and we have daughter regions
270    * regions that may or may not hold references to this region.
271    * @param regionid Region id to use.
272    * @throws IllegalArgumentException
273    */
274   public HRegionInfo(final byte[] tableName, final byte[] startKey,
275                      final byte[] endKey, final boolean split, final long regionid)
276   throws IllegalArgumentException {
277 
278     super();
279     if (tableName == null) {
280       throw new IllegalArgumentException("tableName cannot be null");
281     }
282     this.tableName = tableName.clone();
283     this.offLine = false;
284     this.regionId = regionid;
285 
286     this.regionName = createRegionName(this.tableName, startKey, regionId, true);
287 
288     this.regionNameStr = Bytes.toStringBinary(this.regionName);
289     this.split = split;
290     this.endKey = endKey == null? HConstants.EMPTY_END_ROW: endKey.clone();
291     this.startKey = startKey == null?
292       HConstants.EMPTY_START_ROW: startKey.clone();
293     this.tableName = tableName.clone();
294     setHashCode();
295   }
296 
297   /**
298    * Costruct a copy of another HRegionInfo
299    *
300    * @param other
301    */
302   public HRegionInfo(HRegionInfo other) {
303     super();
304     this.endKey = other.getEndKey();
305     this.offLine = other.isOffline();
306     this.regionId = other.getRegionId();
307     this.regionName = other.getRegionName();
308     this.regionNameStr = Bytes.toStringBinary(this.regionName);
309     this.split = other.isSplit();
310     this.startKey = other.getStartKey();
311     this.hashCode = other.hashCode();
312     this.encodedName = other.getEncodedName();
313     this.tableName = other.tableName;
314   }
315 
316 
317   /**
318    * Make a region name of passed parameters.
319    * @param tableName
320    * @param startKey Can be null
321    * @param regionid Region id (Usually timestamp from when region was created).
322    * @param newFormat should we create the region name in the new format
323    *                  (such that it contains its encoded name?).
324    * @return Region name made of passed tableName, startKey and id
325    */
326   public static byte [] createRegionName(final byte [] tableName,
327       final byte [] startKey, final long regionid, boolean newFormat) {
328     return createRegionName(tableName, startKey, Long.toString(regionid), newFormat);
329   }
330 
331   /**
332    * Make a region name of passed parameters.
333    * @param tableName
334    * @param startKey Can be null
335    * @param id Region id (Usually timestamp from when region was created).
336    * @param newFormat should we create the region name in the new format
337    *                  (such that it contains its encoded name?).
338    * @return Region name made of passed tableName, startKey and id
339    */
340   public static byte [] createRegionName(final byte [] tableName,
341       final byte [] startKey, final String id, boolean newFormat) {
342     return createRegionName(tableName, startKey, Bytes.toBytes(id), newFormat);
343   }
344 
345   /**
346    * Make a region name of passed parameters.
347    * @param tableName
348    * @param startKey Can be null
349    * @param id Region id (Usually timestamp from when region was created).
350    * @param newFormat should we create the region name in the new format
351    *                  (such that it contains its encoded name?).
352    * @return Region name made of passed tableName, startKey and id
353    */
354   public static byte [] createRegionName(final byte [] tableName,
355       final byte [] startKey, final byte [] id, boolean newFormat) {
356     byte [] b = new byte [tableName.length + 2 + id.length +
357        (startKey == null? 0: startKey.length) +
358        (newFormat ? (MD5_HEX_LENGTH + 2) : 0)];
359 
360     int offset = tableName.length;
361     System.arraycopy(tableName, 0, b, 0, offset);
362     b[offset++] = HConstants.DELIMITER;
363     if (startKey != null && startKey.length > 0) {
364       System.arraycopy(startKey, 0, b, offset, startKey.length);
365       offset += startKey.length;
366     }
367     b[offset++] = HConstants.DELIMITER;
368     System.arraycopy(id, 0, b, offset, id.length);
369     offset += id.length;
370 
371     if (newFormat) {
372       //
373       // Encoded name should be built into the region name.
374       //
375       // Use the region name thus far (namely, <tablename>,<startKey>,<id>)
376       // to compute a MD5 hash to be used as the encoded name, and append
377       // it to the byte buffer.
378       //
379       String md5Hash = MD5Hash.getMD5AsHex(b, 0, offset);
380       byte [] md5HashBytes = Bytes.toBytes(md5Hash);
381 
382       if (md5HashBytes.length != MD5_HEX_LENGTH) {
383         LOG.error("MD5-hash length mismatch: Expected=" + MD5_HEX_LENGTH +
384                   "; Got=" + md5HashBytes.length);
385       }
386 
387       // now append the bytes '.<encodedName>.' to the end
388       b[offset++] = ENC_SEPARATOR;
389       System.arraycopy(md5HashBytes, 0, b, offset, MD5_HEX_LENGTH);
390       offset += MD5_HEX_LENGTH;
391       b[offset++] = ENC_SEPARATOR;
392     }
393 
394     return b;
395   }
396 
397   /**
398    * Gets the table name from the specified region name.
399    * @param regionName
400    * @return Table name.
401    */
402   public static byte [] getTableName(byte [] regionName) {
403     int offset = -1;
404     for (int i = 0; i < regionName.length; i++) {
405       if (regionName[i] == HConstants.DELIMITER) {
406         offset = i;
407         break;
408       }
409     }
410     byte [] tableName = new byte[offset];
411     System.arraycopy(regionName, 0, tableName, 0, offset);
412     return tableName;
413   }
414 
415   /**
416    * Gets the start key from the specified region name.
417    * @param regionName
418    * @return Start key.
419    */
420   public static byte[] getStartKey(final byte[] regionName) throws IOException {
421     return parseRegionName(regionName)[1];
422   }
423 
424   /**
425    * Separate elements of a regionName.
426    * @param regionName
427    * @return Array of byte[] containing tableName, startKey and id
428    * @throws IOException
429    */
430   public static byte [][] parseRegionName(final byte [] regionName)
431   throws IOException {
432     int offset = -1;
433     for (int i = 0; i < regionName.length; i++) {
434       if (regionName[i] == HConstants.DELIMITER) {
435         offset = i;
436         break;
437       }
438     }
439     if(offset == -1) throw new IOException("Invalid regionName format");
440     byte [] tableName = new byte[offset];
441     System.arraycopy(regionName, 0, tableName, 0, offset);
442     offset = -1;
443     for (int i = regionName.length - 1; i > 0; i--) {
444       if(regionName[i] == HConstants.DELIMITER) {
445         offset = i;
446         break;
447       }
448     }
449     if(offset == -1) throw new IOException("Invalid regionName format");
450     byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
451     if(offset != tableName.length + 1) {
452       startKey = new byte[offset - tableName.length - 1];
453       System.arraycopy(regionName, tableName.length + 1, startKey, 0,
454           offset - tableName.length - 1);
455     }
456     byte [] id = new byte[regionName.length - offset - 1];
457     System.arraycopy(regionName, offset + 1, id, 0,
458         regionName.length - offset - 1);
459     byte [][] elements = new byte[3][];
460     elements[0] = tableName;
461     elements[1] = startKey;
462     elements[2] = id;
463     return elements;
464   }
465 
466   /** @return the regionId */
467   public long getRegionId(){
468     return regionId;
469   }
470 
471   /**
472    * @return the regionName as an array of bytes.
473    * @see #getRegionNameAsString()
474    */
475   public byte [] getRegionName(){
476     return regionName;
477   }
478 
479   /**
480    * @return Region name as a String for use in logging, etc.
481    */
482   public String getRegionNameAsString() {
483     if (hasEncodedName(this.regionName)) {
484       // new format region names already have their encoded name.
485       return this.regionNameStr;
486     }
487 
488     // old format. regionNameStr doesn't have the region name.
489     //
490     //
491     return this.regionNameStr + "." + this.getEncodedName();
492   }
493 
494   /** @return the encoded region name */
495   public synchronized String getEncodedName() {
496     if (this.encodedName == NO_HASH) {
497       this.encodedName = encodeRegionName(this.regionName);
498     }
499     return this.encodedName;
500   }
501 
502   public synchronized byte [] getEncodedNameAsBytes() {
503     if (this.encodedNameAsBytes == null) {
504       this.encodedNameAsBytes = Bytes.toBytes(getEncodedName());
505     }
506     return this.encodedNameAsBytes;
507   }
508 
509   /** @return the startKey */
510   public byte [] getStartKey(){
511     return startKey;
512   }
513 
514   /** @return the endKey */
515   public byte [] getEndKey(){
516     return endKey;
517   }
518 
519   /**
520    * Get current table name of the region
521    * @return byte array of table name
522    */
523   public byte[] getTableName() {
524     if (tableName == null || tableName.length == 0) {
525       tableName = getTableName(getRegionName());
526     }
527     return tableName;
528   }
529 
530   /**
531    * Get current table name as string
532    * @return string representation of current table
533    */
534   public String getTableNameAsString() {
535     return Bytes.toString(tableName);
536   }
537 
538   /**
539    * Returns true if the given inclusive range of rows is fully contained
540    * by this region. For example, if the region is foo,a,g and this is
541    * passed ["b","c"] or ["a","c"] it will return true, but if this is passed
542    * ["b","z"] it will return false.
543    * @throws IllegalArgumentException if the range passed is invalid (ie end < start)
544    */
545   public boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey) {
546     if (Bytes.compareTo(rangeStartKey, rangeEndKey) > 0) {
547       throw new IllegalArgumentException(
548       "Invalid range: " + Bytes.toStringBinary(rangeStartKey) +
549       " > " + Bytes.toStringBinary(rangeEndKey));
550     }
551 
552     boolean firstKeyInRange = Bytes.compareTo(rangeStartKey, startKey) >= 0;
553     boolean lastKeyInRange =
554       Bytes.compareTo(rangeEndKey, endKey) < 0 ||
555       Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY);
556     return firstKeyInRange && lastKeyInRange;
557   }
558 
559   /**
560    * Return true if the given row falls in this region.
561    */
562   public boolean containsRow(byte[] row) {
563     return Bytes.compareTo(row, startKey) >= 0 &&
564       (Bytes.compareTo(row, endKey) < 0 ||
565        Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY));
566   }
567 
568   /**
569    * @return true if this region is from .META.
570    */
571   public boolean isMetaTable() {
572     return isMetaRegion();
573   }
574 
575   /** @return true if this region is a meta region */
576   public boolean isMetaRegion() {
577      return Bytes.equals(tableName, HRegionInfo.FIRST_META_REGIONINFO.getTableName());
578   }
579 
580   /**
581    * @return True if has been split and has daughters.
582    */
583   public boolean isSplit() {
584     return this.split;
585   }
586 
587   /**
588    * @param split set split status
589    */
590   public void setSplit(boolean split) {
591     this.split = split;
592   }
593 
594   /**
595    * @return True if this region is offline.
596    */
597   public boolean isOffline() {
598     return this.offLine;
599   }
600 
601   /**
602    * The parent of a region split is offline while split daughters hold
603    * references to the parent. Offlined regions are closed.
604    * @param offLine Set online/offline status.
605    */
606   public void setOffline(boolean offLine) {
607     this.offLine = offLine;
608   }
609 
610 
611   /**
612    * @return True if this is a split parent region.
613    */
614   public boolean isSplitParent() {
615     if (!isSplit()) return false;
616     if (!isOffline()) {
617       LOG.warn("Region is split but NOT offline: " + getRegionNameAsString());
618     }
619     return true;
620   }
621 
622   /**
623    * @see java.lang.Object#toString()
624    */
625   @Override
626   public String toString() {
627     return "{" + HConstants.NAME + " => '" +
628       this.regionNameStr
629       + "', STARTKEY => '" +
630       Bytes.toStringBinary(this.startKey) + "', ENDKEY => '" +
631       Bytes.toStringBinary(this.endKey) +
632       "', ENCODED => " + getEncodedName() + "," +
633       (isOffline()? " OFFLINE => true,": "") +
634       (isSplit()? " SPLIT => true,": "") + "}";
635   }
636 
637   /**
638    * @see java.lang.Object#equals(java.lang.Object)
639    */
640   @Override
641   public boolean equals(Object o) {
642     if (this == o) {
643       return true;
644     }
645     if (o == null) {
646       return false;
647     }
648     if (!(o instanceof HRegionInfo)) {
649       return false;
650     }
651     return this.compareTo((HRegionInfo)o) == 0;
652   }
653 
654   /**
655    * @see java.lang.Object#hashCode()
656    */
657   @Override
658   public int hashCode() {
659     return this.hashCode;
660   }
661 
662   /** @return the object version number
663    * @deprecated HRI is no longer a VersionedWritable */
664   @Deprecated
665   public byte getVersion() {
666     return VERSION;
667   }
668 
669   /**
670    * @deprecated Use protobuf serialization instead.  See {@link #toByteArray()} and
671    * {@link #toDelimitedByteArray()}
672    */
673   @Deprecated
674   public void write(DataOutput out) throws IOException {
675     out.writeByte(getVersion());
676     Bytes.writeByteArray(out, endKey);
677     out.writeBoolean(offLine);
678     out.writeLong(regionId);
679     Bytes.writeByteArray(out, regionName);
680     out.writeBoolean(split);
681     Bytes.writeByteArray(out, startKey);
682     Bytes.writeByteArray(out, tableName);
683     out.writeInt(hashCode);
684   }
685 
686   /**
687    * @deprecated Use protobuf deserialization instead. 
688    * @see #parseFrom(byte[])
689    */
690   @Deprecated
691   public void readFields(DataInput in) throws IOException {
692     // Read the single version byte.  We don't ask the super class do it
693     // because freaks out if its not the current classes' version.  This method
694     // can deserialize version 0 and version 1 of HRI.
695     byte version = in.readByte();
696     if (version == 0) {
697       // This is the old HRI that carried an HTD.  Migrate it.  The below
698       // was copied from the old 0.90 HRI readFields.
699       this.endKey = Bytes.readByteArray(in);
700       this.offLine = in.readBoolean();
701       this.regionId = in.readLong();
702       this.regionName = Bytes.readByteArray(in);
703       this.regionNameStr = Bytes.toStringBinary(this.regionName);
704       this.split = in.readBoolean();
705       this.startKey = Bytes.readByteArray(in);
706       try {
707         HTableDescriptor htd = new HTableDescriptor();
708         htd.readFields(in);
709         this.tableName = htd.getName();
710       } catch(EOFException eofe) {
711          throw new IOException("HTD not found in input buffer", eofe);
712       }
713       this.hashCode = in.readInt();
714     } else if (getVersion() == version) {
715       this.endKey = Bytes.readByteArray(in);
716       this.offLine = in.readBoolean();
717       this.regionId = in.readLong();
718       this.regionName = Bytes.readByteArray(in);
719       this.regionNameStr = Bytes.toStringBinary(this.regionName);
720       this.split = in.readBoolean();
721       this.startKey = Bytes.readByteArray(in);
722       this.tableName = Bytes.readByteArray(in);
723       this.hashCode = in.readInt();
724     } else {
725       throw new IOException("Non-migratable/unknown version=" + getVersion());
726     }
727   }
728 
729   @Deprecated
730   private void readFields(byte[] bytes) throws IOException {
731     if (bytes == null || bytes.length <= 0) {
732       throw new IllegalArgumentException("Can't build a writable with empty " +
733         "bytes array");
734     }
735     DataInputBuffer in = new DataInputBuffer();
736     try {
737       in.reset(bytes, 0, bytes.length);
738       this.readFields(in);
739     } finally {
740       in.close();
741     }
742   }
743 
744   //
745   // Comparable
746   //
747 
748   public int compareTo(HRegionInfo o) {
749     if (o == null) {
750       return 1;
751     }
752 
753     // Are regions of same table?
754     int result = Bytes.compareTo(this.tableName, o.tableName);
755     if (result != 0) {
756       return result;
757     }
758 
759     // Compare start keys.
760     result = Bytes.compareTo(this.startKey, o.startKey);
761     if (result != 0) {
762       return result;
763     }
764 
765     // Compare end keys.
766     result = Bytes.compareTo(this.endKey, o.endKey);
767 
768     if (result != 0) {
769       if (this.getStartKey().length != 0
770               && this.getEndKey().length == 0) {
771           return 1; // this is last region
772       }
773       if (o.getStartKey().length != 0
774               && o.getEndKey().length == 0) {
775           return -1; // o is the last region
776       }
777       return result;
778     }
779 
780     // regionId is usually milli timestamp -- this defines older stamps
781     // to be "smaller" than newer stamps in sort order.
782     if (this.regionId > o.regionId) {
783       return 1;
784     } else if (this.regionId < o.regionId) {
785       return -1;
786     }
787 
788     if (this.offLine == o.offLine)
789       return 0;
790     if (this.offLine == true) return -1;
791 
792     return 1;
793   }
794 
795   /**
796    * @return Comparator to use comparing {@link KeyValue}s.
797    */
798   public KVComparator getComparator() {
799     return isMetaRegion()?
800       KeyValue.META_COMPARATOR: KeyValue.COMPARATOR;
801   }
802 
803   /**
804    * Convert a HRegionInfo to a RegionInfo
805    *
806    * @return the converted RegionInfo
807    */
808   RegionInfo convert() {
809     return convert(this);
810   }
811 
812   /**
813    * Convert a HRegionInfo to a RegionInfo
814    *
815    * @param info the HRegionInfo to convert
816    * @return the converted RegionInfo
817    */
818   public static RegionInfo convert(final HRegionInfo info) {
819     if (info == null) return null;
820     RegionInfo.Builder builder = RegionInfo.newBuilder();
821     builder.setTableName(ByteString.copyFrom(info.getTableName()));
822     builder.setRegionId(info.getRegionId());
823     if (info.getStartKey() != null) {
824       builder.setStartKey(ByteString.copyFrom(info.getStartKey()));
825     }
826     if (info.getEndKey() != null) {
827       builder.setEndKey(ByteString.copyFrom(info.getEndKey()));
828     }
829     builder.setOffline(info.isOffline());
830     builder.setSplit(info.isSplit());
831     return builder.build();
832   }
833 
834   /**
835    * Convert a RegionInfo to a HRegionInfo
836    *
837    * @param proto the RegionInfo to convert
838    * @return the converted HRegionInfo
839    */
840   public static HRegionInfo convert(final RegionInfo proto) {
841     if (proto == null) return null;
842     byte [] tableName = proto.getTableName().toByteArray();
843     if (Bytes.equals(tableName, HConstants.META_TABLE_NAME)) {
844       return FIRST_META_REGIONINFO;
845     }
846     long regionId = proto.getRegionId();
847     byte[] startKey = null;
848     byte[] endKey = null;
849     if (proto.hasStartKey()) {
850       startKey = proto.getStartKey().toByteArray();
851     }
852     if (proto.hasEndKey()) {
853       endKey = proto.getEndKey().toByteArray();
854     }
855     boolean split = false;
856     if (proto.hasSplit()) {
857       split = proto.getSplit();
858     }
859     HRegionInfo hri = new HRegionInfo(tableName, startKey, endKey, split, regionId);
860     if (proto.hasOffline()) {
861       hri.setOffline(proto.getOffline());
862     }
863     return hri;
864   }
865 
866   /**
867    * @return This instance serialized as protobuf w/ a magic pb prefix.
868    * @see #parseFrom(byte[])
869    */
870   public byte [] toByteArray() {
871     byte [] bytes = convert().toByteArray();
872     return ProtobufUtil.prependPBMagic(bytes);
873   }
874 
875   /**
876    * @param bytes
877    * @return A deserialized {@link HRegionInfo} or null if we failed deserialize or passed bytes null
878    * @see #toByteArray()
879    */
880   public static HRegionInfo parseFromOrNull(final byte [] bytes) {
881     if (bytes == null || bytes.length <= 0) return null;
882     try {
883       return parseFrom(bytes);
884     } catch (DeserializationException e) {
885       return null;
886     }
887   }
888 
889   /**
890    * @param bytes A pb RegionInfo serialized with a pb magic prefix.
891    * @return A deserialized {@link HRegionInfo}
892    * @throws DeserializationException
893    * @see #toByteArray()
894    */
895   public static HRegionInfo parseFrom(final byte [] bytes) throws DeserializationException {
896     if (ProtobufUtil.isPBMagicPrefix(bytes)) {
897       int pblen = ProtobufUtil.lengthOfPBMagic();
898       try {
899         HBaseProtos.RegionInfo ri =
900           HBaseProtos.RegionInfo.newBuilder().mergeFrom(bytes, pblen, bytes.length - pblen).build();
901         return convert(ri);
902       } catch (InvalidProtocolBufferException e) {
903         throw new DeserializationException(e);
904       }
905     } else {
906       try {
907         HRegionInfo hri = new HRegionInfo();
908         hri.readFields(bytes);
909         return hri;
910       } catch (IOException e) {
911         throw new DeserializationException(e);
912       }
913     }
914   }
915 
916   /**
917    * Use this instead of {@link #toByteArray()} when writing to a stream and you want to use
918    * the pb mergeDelimitedFrom (w/o the delimiter, pb reads to EOF which may not be what you want).
919    * @return This instance serialized as a delimited protobuf w/ a magic pb prefix.
920    * @throws IOException
921    * @see #toByteArray()
922    */
923   public byte [] toDelimitedByteArray() throws IOException {
924     return ProtobufUtil.toDelimitedByteArray(convert());
925   }
926 
927   /**
928    * Extract a HRegionInfo and ServerName from catalog table {@link Result}.
929    * @param r Result to pull from
930    * @return A pair of the {@link HRegionInfo} and the {@link ServerName}
931    * (or null for server address if no address set in .META.).
932    * @throws IOException
933    */
934   public static Pair<HRegionInfo, ServerName> getHRegionInfoAndServerName(final Result r) {
935     HRegionInfo info =
936       getHRegionInfo(r, HConstants.REGIONINFO_QUALIFIER);
937     ServerName sn = getServerName(r);
938     return new Pair<HRegionInfo, ServerName>(info, sn);
939   }
940 
941   /**
942    * Returns HRegionInfo object from the column
943    * HConstants.CATALOG_FAMILY:HConstants.REGIONINFO_QUALIFIER of the catalog
944    * table Result.
945    * @param data a Result object from the catalog table scan
946    * @return HRegionInfo or null
947    */
948   public static HRegionInfo getHRegionInfo(Result data) {
949     byte [] bytes =
950       data.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
951     if (bytes == null) return null;
952     HRegionInfo info = parseFromOrNull(bytes);
953     if (LOG.isDebugEnabled()) {
954       LOG.debug("Current INFO from scan results = " + info);
955     }
956     return info;
957   }
958 
959   /**
960    * Returns the daughter regions by reading the corresponding columns of the catalog table
961    * Result.
962    * @param data a Result object from the catalog table scan
963    * @return a pair of HRegionInfo or PairOfSameType(null, null) if the region is not a split
964    * parent
965    */
966   public static PairOfSameType<HRegionInfo> getDaughterRegions(Result data) throws IOException {
967     HRegionInfo splitA = getHRegionInfo(data, HConstants.SPLITA_QUALIFIER);
968     HRegionInfo splitB = getHRegionInfo(data, HConstants.SPLITB_QUALIFIER);
969 
970     return new PairOfSameType<HRegionInfo>(splitA, splitB);
971   }
972 
973   /**
974    * Returns the HRegionInfo object from the column {@link HConstants#CATALOG_FAMILY} and
975    * <code>qualifier</code> of the catalog table result.
976    * @param r a Result object from the catalog table scan
977    * @param qualifier Column family qualifier -- either
978    * {@link HConstants#SPLITA_QUALIFIER}, {@link HConstants#SPLITB_QUALIFIER} or
979    * {@link HConstants#REGIONINFO_QUALIFIER}.
980    * @return An HRegionInfo instance or null.
981    * @throws IOException
982    */
983   public static HRegionInfo getHRegionInfo(final Result r, byte [] qualifier) {
984     byte [] bytes = r.getValue(HConstants.CATALOG_FAMILY, qualifier);
985     if (bytes == null || bytes.length <= 0) return null;
986     return parseFromOrNull(bytes);
987   }
988 
989   /**
990    * Returns a {@link ServerName} from catalog table {@link Result}.
991    * @param r Result to pull from
992    * @return A ServerName instance or null if necessary fields not found or empty.
993    */
994   public static ServerName getServerName(final Result r) {
995     byte[] value = r.getValue(HConstants.CATALOG_FAMILY,
996       HConstants.SERVER_QUALIFIER);
997     if (value == null || value.length == 0) return null;
998     String hostAndPort = Bytes.toString(value);
999     value = r.getValue(HConstants.CATALOG_FAMILY,
1000       HConstants.STARTCODE_QUALIFIER);
1001     if (value == null || value.length == 0) return null;
1002     return new ServerName(hostAndPort, Bytes.toLong(value));
1003   }
1004 
1005   /**
1006    * The latest seqnum that the server writing to meta observed when opening the region.
1007    * E.g. the seqNum when the result of {@link #getServerName(Result)} was written.
1008    * @param r Result to pull the seqNum from
1009    * @return SeqNum, or HConstants.NO_SEQNUM if there's no value written.
1010    */
1011   public static long getSeqNumDuringOpen(final Result r) {
1012     byte[] value = r.getValue(HConstants.CATALOG_FAMILY, HConstants.SEQNUM_QUALIFIER);
1013     if (value == null || value.length == 0) return HConstants.NO_SEQNUM;
1014     Long result = Bytes.toLong(value);
1015     if (result == null) return HConstants.NO_SEQNUM;
1016     return result.longValue();
1017   }
1018 
1019   /**
1020    * Parses an HRegionInfo instance from the passed in stream.  Presumes the HRegionInfo was
1021    * serialized to the stream with {@link #toDelimitedByteArray()}
1022    * @param in
1023    * @return An instance of HRegionInfo.
1024    * @throws IOException
1025    */
1026   public static HRegionInfo parseFrom(final DataInputStream in) throws IOException {
1027     // I need to be able to move back in the stream if this is not a pb serialization so I can
1028     // do the Writable decoding instead.
1029     int pblen = ProtobufUtil.lengthOfPBMagic();
1030     byte [] pbuf = new byte[pblen];
1031     if (in.markSupported()) { //read it with mark()
1032       in.mark(pblen);
1033     }
1034     int read = in.read(pbuf); //assumption: if Writable serialization, it should be longer than pblen.
1035     if (read != pblen) throw new IOException("read=" + read + ", wanted=" + pblen);
1036     if (ProtobufUtil.isPBMagicPrefix(pbuf)) {
1037       return convert(HBaseProtos.RegionInfo.parseDelimitedFrom(in));
1038     } else {
1039         // Presume Writables.  Need to reset the stream since it didn't start w/ pb.
1040       if (in.markSupported()) {
1041         in.reset();
1042         HRegionInfo hri = new HRegionInfo();
1043         hri.readFields(in);
1044         return hri;
1045       } else {
1046         //we cannot use BufferedInputStream, it consumes more than we read from the underlying IS
1047         ByteArrayInputStream bais = new ByteArrayInputStream(pbuf);
1048         SequenceInputStream sis = new SequenceInputStream(bais, in); //concatenate input streams
1049         HRegionInfo hri = new HRegionInfo();
1050         hri.readFields(new DataInputStream(sis));
1051         return hri;
1052       }
1053     }
1054   }
1055 
1056   /**
1057    * Serializes given HRegionInfo's as a byte array. Use this instead of {@link #toByteArray()} when
1058    * writing to a stream and you want to use the pb mergeDelimitedFrom (w/o the delimiter, pb reads
1059    * to EOF which may not be what you want). {@link #parseDelimitedFrom(byte[], int, int)} can
1060    * be used to read back the instances.
1061    * @param infos HRegionInfo objects to serialize
1062    * @return This instance serialized as a delimited protobuf w/ a magic pb prefix.
1063    * @throws IOException
1064    * @see #toByteArray()
1065    */
1066   public static byte[] toDelimitedByteArray(HRegionInfo... infos) throws IOException {
1067     byte[][] bytes = new byte[infos.length][];
1068     int size = 0;
1069     for (int i = 0; i < infos.length; i++) {
1070       bytes[i] = infos[i].toDelimitedByteArray();
1071       size += bytes[i].length;
1072     }
1073 
1074     byte[] result = new byte[size];
1075     int offset = 0;
1076     for (byte[] b : bytes) {
1077       System.arraycopy(b, 0, result, offset, b.length);
1078       offset += b.length;
1079     }
1080     return result;
1081   }
1082 
1083   /**
1084    * Parses all the HRegionInfo instances from the passed in stream until EOF. Presumes the
1085    * HRegionInfo's were serialized to the stream with {@link #toDelimitedByteArray()}
1086    * @param bytes serialized bytes
1087    * @param offset the start offset into the byte[] buffer
1088    * @param length how far we should read into the byte[] buffer
1089    * @return All the hregioninfos that are in the byte array. Keeps reading till we hit the end.
1090    */
1091   public static List<HRegionInfo> parseDelimitedFrom(final byte[] bytes, final int offset,
1092       final int length) throws IOException {
1093     if (bytes == null) {
1094       throw new IllegalArgumentException("Can't build an object with empty bytes array");
1095     }
1096     DataInputBuffer in = new DataInputBuffer();
1097     List<HRegionInfo> hris = new ArrayList<HRegionInfo>();
1098     try {
1099       in.reset(bytes, offset, length);
1100       while (in.available() > 0) {
1101         HRegionInfo hri = parseFrom(in);
1102         hris.add(hri);
1103       }
1104     } finally {
1105       in.close();
1106     }
1107     return hris;
1108   }
1109 
1110   /**
1111    * Check whether two regions are adjacent
1112    * @param regionA
1113    * @param regionB
1114    * @return true if two regions are adjacent
1115    */
1116   public static boolean areAdjacent(HRegionInfo regionA, HRegionInfo regionB) {
1117     if (regionA == null || regionB == null) {
1118       throw new IllegalArgumentException(
1119           "Can't check whether adjacent for null region");
1120     }
1121     HRegionInfo a = regionA;
1122     HRegionInfo b = regionB;
1123     if (Bytes.compareTo(a.getStartKey(), b.getStartKey()) > 0) {
1124       a = regionB;
1125       b = regionA;
1126     }
1127     if (Bytes.compareTo(a.getEndKey(), b.getStartKey()) == 0) {
1128       return true;
1129     }
1130     return false;
1131   }
1132 
1133 }