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 java.io.DataInputStream;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.List;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.hbase.KeyValue.KVComparator;
31  import org.apache.hadoop.hbase.classification.InterfaceAudience;
32  import org.apache.hadoop.hbase.classification.InterfaceStability;
33  import org.apache.hadoop.hbase.client.RegionReplicaUtil;
34  import org.apache.hadoop.hbase.client.Result;
35  import org.apache.hadoop.hbase.exceptions.DeserializationException;
36  import org.apache.hadoop.hbase.master.RegionState;
37  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
38  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
39  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionInfo;
40  import org.apache.hadoop.hbase.util.ByteStringer;
41  import org.apache.hadoop.hbase.util.Bytes;
42  import org.apache.hadoop.hbase.util.JenkinsHash;
43  import org.apache.hadoop.hbase.util.MD5Hash;
44  import org.apache.hadoop.hbase.util.Pair;
45  import org.apache.hadoop.hbase.util.PairOfSameType;
46  import org.apache.hadoop.io.DataInputBuffer;
47  
48  import com.google.protobuf.InvalidProtocolBufferException;
49  
50  /**
51   * Information about a region. A region is a range of keys in the whole keyspace of a table, an
52   * identifier (a timestamp) for differentiating between subset ranges (after region split)
53   * and a replicaId for differentiating the instance for the same range and some status information
54   * about the region.
55   *
56   * The region has a unique name which consists of the following fields:
57   * <li> tableName   : The name of the table </li>
58   * <li> startKey    : The startKey for the region. </li>
59   * <li> regionId    : A timestamp when the region is created. </li>
60   * <li> replicaId   : An id starting from 0 to differentiate replicas of the same region range
61   * but hosted in separated servers. The same region range can be hosted in multiple locations.</li>
62   * <li> encodedName : An MD5 encoded string for the region name.</li>
63   *
64   * <br> Other than the fields in the region name, region info contains:
65   * <li> endKey      : the endKey for the region (exclusive) </li>
66   * <li> split       : Whether the region is split </li>
67   * <li> offline     : Whether the region is offline </li>
68   *
69   * In 0.98 or before, a list of table's regions would fully cover the total keyspace, and at any
70   * point in time, a row key always belongs to a single region, which is hosted in a single server.
71   * In 0.99+, a region can have multiple instances (called replicas), and thus a range (or row) can
72   * correspond to multiple HRegionInfo's. These HRI's share the same fields however except the
73   * replicaId field. If the replicaId is not set, it defaults to 0, which is compatible with the
74   * previous behavior of a range corresponding to 1 region.
75   */
76  @InterfaceAudience.Public
77  @InterfaceStability.Evolving
78  public class HRegionInfo implements Comparable<HRegionInfo> {
79  
80    private static final Log LOG = LogFactory.getLog(HRegionInfo.class);
81  
82    /**
83     * The new format for a region name contains its encodedName at the end.
84     * The encoded name also serves as the directory name for the region
85     * in the filesystem.
86     *
87     * New region name format:
88     *    &lt;tablename>,,&lt;startkey>,&lt;regionIdTimestamp>.&lt;encodedName>.
89     * where,
90     *    &lt;encodedName> is a hex version of the MD5 hash of
91     *    &lt;tablename>,&lt;startkey>,&lt;regionIdTimestamp>
92     *
93     * The old region name format:
94     *    &lt;tablename>,&lt;startkey>,&lt;regionIdTimestamp>
95     * For region names in the old format, the encoded name is a 32-bit
96     * JenkinsHash integer value (in its decimal notation, string form).
97     *<p>
98     * **NOTE**
99     *
100    * The first hbase:meta region, and regions created by an older
101    * version of HBase (0.20 or prior) will continue to use the
102    * old region name format.
103    */
104 
105   /** Separator used to demarcate the encodedName in a region name
106    * in the new format. See description on new format above.
107    */
108   private static final int ENC_SEPARATOR = '.';
109   public  static final int MD5_HEX_LENGTH   = 32;
110 
111   /** A non-capture group so that this can be embedded. */
112   public static final String ENCODED_REGION_NAME_REGEX = "(?:[a-f0-9]+)";
113 
114   // to keep appended int's sorted in string format. Only allows 2 bytes to be
115   // sorted for replicaId
116   public static final String REPLICA_ID_FORMAT = "%04X";
117 
118   public static final byte REPLICA_ID_DELIMITER = (byte)'_';
119 
120   private static final int MAX_REPLICA_ID = 0xFFFF;
121   public static final int DEFAULT_REPLICA_ID = 0;
122   /**
123    * Does region name contain its encoded name?
124    * @param regionName region name
125    * @return boolean indicating if this a new format region
126    *         name which contains its encoded name.
127    */
128   private static boolean hasEncodedName(final byte[] regionName) {
129     // check if region name ends in ENC_SEPARATOR
130     if ((regionName.length >= 1)
131         && (regionName[regionName.length - 1] == ENC_SEPARATOR)) {
132       // region name is new format. it contains the encoded name.
133       return true;
134     }
135     return false;
136   }
137 
138   /**
139    * @param regionName
140    * @return the encodedName
141    */
142   public static String encodeRegionName(final byte [] regionName) {
143     String encodedName;
144     if (hasEncodedName(regionName)) {
145       // region is in new format:
146       // <tableName>,<startKey>,<regionIdTimeStamp>/encodedName/
147       encodedName = Bytes.toString(regionName,
148           regionName.length - MD5_HEX_LENGTH - 1,
149           MD5_HEX_LENGTH);
150     } else {
151       // old format region name. First hbase:meta region also
152       // use this format.EncodedName is the JenkinsHash value.
153       int hashVal = Math.abs(JenkinsHash.getInstance().hash(regionName,
154         regionName.length, 0));
155       encodedName = String.valueOf(hashVal);
156     }
157     return encodedName;
158   }
159 
160   /**
161    * @return Return a short, printable name for this region (usually encoded name) for us logging.
162    */
163   public String getShortNameToLog() {
164     return prettyPrint(this.getEncodedName());
165   }
166 
167   /**
168    * Use logging.
169    * @param encodedRegionName The encoded regionname.
170    * @return <code>hbase:meta</code> if passed <code>1028785192</code> else returns
171    * <code>encodedRegionName</code>
172    */
173   public static String prettyPrint(final String encodedRegionName) {
174     if (encodedRegionName.equals("1028785192")) {
175       return encodedRegionName + "/hbase:meta";
176     }
177     return encodedRegionName;
178   }
179 
180   private byte [] endKey = HConstants.EMPTY_BYTE_ARRAY;
181   // This flag is in the parent of a split while the parent is still referenced
182   // by daughter regions.  We USED to set this flag when we disabled a table
183   // but now table state is kept up in zookeeper as of 0.90.0 HBase.
184   private boolean offLine = false;
185   private long regionId = -1;
186   private transient byte [] regionName = HConstants.EMPTY_BYTE_ARRAY;
187   private boolean split = false;
188   private byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
189   private int hashCode = -1;
190   //TODO: Move NO_HASH to HStoreFile which is really the only place it is used.
191   public static final String NO_HASH = null;
192   private String encodedName = null;
193   private byte [] encodedNameAsBytes = null;
194   private int replicaId = DEFAULT_REPLICA_ID;
195 
196   // Current TableName
197   private TableName tableName = null;
198   final static String DISPLAY_KEYS_KEY = "hbase.display.keys";
199   public final static byte[] HIDDEN_END_KEY = Bytes.toBytes("hidden-end-key");
200   public final static byte[] HIDDEN_START_KEY = Bytes.toBytes("hidden-start-key");
201 
202   /** HRegionInfo for first meta region */
203   public static final HRegionInfo FIRST_META_REGIONINFO =
204       new HRegionInfo(1L, TableName.META_TABLE_NAME);
205 
206   private void setHashCode() {
207     int result = Arrays.hashCode(this.regionName);
208     result ^= this.regionId;
209     result ^= Arrays.hashCode(this.startKey);
210     result ^= Arrays.hashCode(this.endKey);
211     result ^= Boolean.valueOf(this.offLine).hashCode();
212     result ^= Arrays.hashCode(this.tableName.getName());
213     result ^= this.replicaId;
214     this.hashCode = result;
215   }
216 
217 
218   /**
219    * Private constructor used constructing HRegionInfo for the
220    * first meta regions
221    */
222   private HRegionInfo(long regionId, TableName tableName) {
223     this(regionId, tableName, DEFAULT_REPLICA_ID);
224   }
225 
226   public HRegionInfo(long regionId, TableName tableName, int replicaId) {
227     super();
228     this.regionId = regionId;
229     this.tableName = tableName;
230     this.replicaId = replicaId;
231     // Note: First Meta region replicas names are in old format
232     this.regionName = createRegionName(tableName, null, regionId, replicaId, false);
233     setHashCode();
234   }
235 
236   /** Default constructor - creates empty object
237    * @deprecated Used by Writables and Writables are going away.
238    */
239   @Deprecated
240   public HRegionInfo() {
241     super();
242   }
243 
244   public HRegionInfo(final TableName tableName) {
245     this(tableName, null, null);
246   }
247 
248   /**
249    * Construct HRegionInfo with explicit parameters
250    *
251    * @param tableName the table name
252    * @param startKey first key in region
253    * @param endKey end of key range
254    * @throws IllegalArgumentException
255    */
256   public HRegionInfo(final TableName tableName, final byte[] startKey, final byte[] endKey)
257   throws IllegalArgumentException {
258     this(tableName, startKey, endKey, false);
259   }
260 
261   /**
262    * Construct HRegionInfo with explicit parameters
263    *
264    * @param tableName the table descriptor
265    * @param startKey first key in region
266    * @param endKey end of key range
267    * @param split true if this region has split and we have daughter regions
268    * regions that may or may not hold references to this region.
269    * @throws IllegalArgumentException
270    */
271   public HRegionInfo(final TableName tableName, final byte[] startKey, final byte[] endKey,
272       final boolean split)
273   throws IllegalArgumentException {
274     this(tableName, startKey, endKey, split, System.currentTimeMillis());
275   }
276 
277   /**
278    * Construct HRegionInfo with explicit parameters
279    *
280    * @param tableName the table descriptor
281    * @param startKey first key in region
282    * @param endKey end of key range
283    * @param split true if this region has split and we have daughter regions
284    * regions that may or may not hold references to this region.
285    * @param regionid Region id to use.
286    * @throws IllegalArgumentException
287    */
288   public HRegionInfo(final TableName tableName, final byte[] startKey,
289                      final byte[] endKey, final boolean split, final long regionid)
290   throws IllegalArgumentException {
291     this(tableName, startKey, endKey, split, regionid, DEFAULT_REPLICA_ID);
292   }
293 
294   /**
295    * Construct HRegionInfo with explicit parameters
296    *
297    * @param tableName the table descriptor
298    * @param startKey first key in region
299    * @param endKey end of key range
300    * @param split true if this region has split and we have daughter regions
301    * regions that may or may not hold references to this region.
302    * @param regionid Region id to use.
303    * @param replicaId the replicaId to use
304    * @throws IllegalArgumentException
305    */
306   public HRegionInfo(final TableName tableName, final byte[] startKey,
307                      final byte[] endKey, final boolean split, final long regionid,
308                      final int replicaId)
309     throws IllegalArgumentException {
310     super();
311     if (tableName == null) {
312       throw new IllegalArgumentException("TableName cannot be null");
313     }
314     this.tableName = tableName;
315     this.offLine = false;
316     this.regionId = regionid;
317     this.replicaId = replicaId;
318     if (this.replicaId > MAX_REPLICA_ID) {
319       throw new IllegalArgumentException("ReplicaId cannot be greater than" + MAX_REPLICA_ID);
320     }
321 
322     this.regionName = createRegionName(this.tableName, startKey, regionId, replicaId, true);
323 
324     this.split = split;
325     this.endKey = endKey == null? HConstants.EMPTY_END_ROW: endKey.clone();
326     this.startKey = startKey == null?
327       HConstants.EMPTY_START_ROW: startKey.clone();
328     this.tableName = tableName;
329     setHashCode();
330   }
331 
332   /**
333    * Costruct a copy of another HRegionInfo
334    *
335    * @param other
336    */
337   public HRegionInfo(HRegionInfo other) {
338     super();
339     this.endKey = other.getEndKey();
340     this.offLine = other.isOffline();
341     this.regionId = other.getRegionId();
342     this.regionName = other.getRegionName();
343     this.split = other.isSplit();
344     this.startKey = other.getStartKey();
345     this.hashCode = other.hashCode();
346     this.encodedName = other.getEncodedName();
347     this.tableName = other.tableName;
348     this.replicaId = other.replicaId;
349   }
350 
351   public HRegionInfo(HRegionInfo other, int replicaId) {
352     this(other);
353     this.replicaId = replicaId;
354     this.setHashCode();
355   }
356 
357   /**
358    * Make a region name of passed parameters.
359    * @param tableName
360    * @param startKey Can be null
361    * @param regionid Region id (Usually timestamp from when region was created).
362    * @param newFormat should we create the region name in the new format
363    *                  (such that it contains its encoded name?).
364    * @return Region name made of passed tableName, startKey and id
365    */
366   public static byte [] createRegionName(final TableName tableName,
367       final byte [] startKey, final long regionid, boolean newFormat) {
368     return createRegionName(tableName, startKey, Long.toString(regionid), newFormat);
369   }
370 
371   /**
372    * Make a region name of passed parameters.
373    * @param tableName
374    * @param startKey Can be null
375    * @param id Region id (Usually timestamp from when region was created).
376    * @param newFormat should we create the region name in the new format
377    *                  (such that it contains its encoded name?).
378    * @return Region name made of passed tableName, startKey and id
379    */
380   public static byte [] createRegionName(final TableName tableName,
381       final byte [] startKey, final String id, boolean newFormat) {
382     return createRegionName(tableName, startKey, Bytes.toBytes(id), newFormat);
383   }
384 
385   /**
386    * Make a region name of passed parameters.
387    * @param tableName
388    * @param startKey Can be null
389    * @param regionid Region id (Usually timestamp from when region was created).
390    * @param replicaId
391    * @param newFormat should we create the region name in the new format
392    *                  (such that it contains its encoded name?).
393    * @return Region name made of passed tableName, startKey, id and replicaId
394    */
395   public static byte [] createRegionName(final TableName tableName,
396       final byte [] startKey, final long regionid, int replicaId, boolean newFormat) {
397     return createRegionName(tableName, startKey, Bytes.toBytes(Long.toString(regionid)),
398         replicaId, newFormat);
399   }
400 
401   /**
402    * Make a region name of passed parameters.
403    * @param tableName
404    * @param startKey Can be null
405    * @param id Region id (Usually timestamp from when region was created).
406    * @param newFormat should we create the region name in the new format
407    *                  (such that it contains its encoded name?).
408    * @return Region name made of passed tableName, startKey and id
409    */
410   public static byte [] createRegionName(final TableName tableName,
411       final byte [] startKey, final byte [] id, boolean newFormat) {
412     return createRegionName(tableName, startKey, id, DEFAULT_REPLICA_ID, newFormat);
413   }
414   /**
415    * Make a region name of passed parameters.
416    * @param tableName
417    * @param startKey Can be null
418    * @param id Region id (Usually timestamp from when region was created).
419    * @param replicaId
420    * @param newFormat should we create the region name in the new format
421    * @return Region name made of passed tableName, startKey, id and replicaId
422    */
423   public static byte [] createRegionName(final TableName tableName,
424       final byte [] startKey, final byte [] id, final int replicaId, boolean newFormat) {
425     int len = tableName.getName().length + 2 + id.length +
426         (startKey == null? 0: startKey.length);
427     if (newFormat) {
428       len += MD5_HEX_LENGTH + 2;
429     }
430     byte[] replicaIdBytes = null;
431     // Special casing: replicaId is only appended if replicaId is greater than
432     // 0. This is because all regions in meta would have to be migrated to the new
433     // name otherwise
434     if (replicaId > 0) {
435       // use string representation for replica id
436       replicaIdBytes = Bytes.toBytes(String.format(REPLICA_ID_FORMAT, replicaId));
437       len += 1 + replicaIdBytes.length;
438     }
439 
440     byte [] b = new byte [len];
441 
442     int offset = tableName.getName().length;
443     System.arraycopy(tableName.getName(), 0, b, 0, offset);
444     b[offset++] = HConstants.DELIMITER;
445     if (startKey != null && startKey.length > 0) {
446       System.arraycopy(startKey, 0, b, offset, startKey.length);
447       offset += startKey.length;
448     }
449     b[offset++] = HConstants.DELIMITER;
450     System.arraycopy(id, 0, b, offset, id.length);
451     offset += id.length;
452 
453     if (replicaIdBytes != null) {
454       b[offset++] = REPLICA_ID_DELIMITER;
455       System.arraycopy(replicaIdBytes, 0, b, offset, replicaIdBytes.length);
456       offset += replicaIdBytes.length;
457     }
458 
459     if (newFormat) {
460       //
461       // Encoded name should be built into the region name.
462       //
463       // Use the region name thus far (namely, <tablename>,<startKey>,<id>_<replicaId>)
464       // to compute a MD5 hash to be used as the encoded name, and append
465       // it to the byte buffer.
466       //
467       String md5Hash = MD5Hash.getMD5AsHex(b, 0, offset);
468       byte [] md5HashBytes = Bytes.toBytes(md5Hash);
469 
470       if (md5HashBytes.length != MD5_HEX_LENGTH) {
471         LOG.error("MD5-hash length mismatch: Expected=" + MD5_HEX_LENGTH +
472                   "; Got=" + md5HashBytes.length);
473       }
474 
475       // now append the bytes '.<encodedName>.' to the end
476       b[offset++] = ENC_SEPARATOR;
477       System.arraycopy(md5HashBytes, 0, b, offset, MD5_HEX_LENGTH);
478       offset += MD5_HEX_LENGTH;
479       b[offset++] = ENC_SEPARATOR;
480     }
481 
482     return b;
483   }
484 
485   /**
486    * Gets the table name from the specified region name.
487    * @param regionName
488    * @return Table name.
489    * @deprecated Since 0.96.0; use #getTable(byte[])
490    */
491   @Deprecated
492   public static byte [] getTableName(byte[] regionName) {
493     int offset = -1;
494     for (int i = 0; i < regionName.length; i++) {
495       if (regionName[i] == HConstants.DELIMITER) {
496         offset = i;
497         break;
498       }
499     }
500     byte[] buff  = new byte[offset];
501     System.arraycopy(regionName, 0, buff, 0, offset);
502     return buff;
503   }
504 
505 
506   /**
507    * Gets the table name from the specified region name.
508    * Like {@link #getTableName(byte[])} only returns a {@link TableName} rather than a byte array.
509    * @param regionName
510    * @return Table name
511    * @see #getTableName(byte[])
512    */
513   public static TableName getTable(final byte [] regionName) {
514     return TableName.valueOf(getTableName(regionName));
515   }
516 
517   /**
518    * Gets the start key from the specified region name.
519    * @param regionName
520    * @return Start key.
521    */
522   public static byte[] getStartKey(final byte[] regionName) throws IOException {
523     return parseRegionName(regionName)[1];
524   }
525 
526   /**
527    * Separate elements of a regionName.
528    * @param regionName
529    * @return Array of byte[] containing tableName, startKey and id
530    * @throws IOException
531    */
532   public static byte [][] parseRegionName(final byte [] regionName)
533   throws IOException {
534     // Region name is of the format:
535     // tablename,startkey,regionIdTimestamp[_replicaId][.encodedName.]
536     // startkey can contain the delimiter (',') so we parse from the start and end
537 
538     // parse from start
539     int offset = -1;
540     for (int i = 0; i < regionName.length; i++) {
541       if (regionName[i] == HConstants.DELIMITER) {
542         offset = i;
543         break;
544       }
545     }
546     if (offset == -1) {
547       throw new IOException("Invalid regionName format: " + Bytes.toStringBinary(regionName));
548     }
549     byte[] tableName = new byte[offset];
550     System.arraycopy(regionName, 0, tableName, 0, offset);
551     offset = -1;
552 
553     int endOffset = regionName.length;
554     // check whether regionName contains encodedName
555     if (regionName.length > MD5_HEX_LENGTH + 2
556         && regionName[regionName.length-1] == ENC_SEPARATOR
557         && regionName[regionName.length-MD5_HEX_LENGTH-2] == ENC_SEPARATOR) {
558       endOffset = endOffset - MD5_HEX_LENGTH - 2;
559     }
560 
561     // parse from end
562     byte[] replicaId = null;
563     int idEndOffset = endOffset;
564     for (int i = endOffset - 1; i > 0; i--) {
565       if (regionName[i] == REPLICA_ID_DELIMITER) { //replicaId may or may not be present
566         replicaId = new byte[endOffset - i - 1];
567         System.arraycopy(regionName, i + 1, replicaId, 0,
568           endOffset - i - 1);
569         idEndOffset = i;
570         // do not break, continue to search for id
571       }
572       if (regionName[i] == HConstants.DELIMITER) {
573         offset = i;
574         break;
575       }
576     }
577     if (offset == -1) {
578       throw new IOException("Invalid regionName format: " + Bytes.toStringBinary(regionName));
579     }
580     byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
581     if(offset != tableName.length + 1) {
582       startKey = new byte[offset - tableName.length - 1];
583       System.arraycopy(regionName, tableName.length + 1, startKey, 0,
584           offset - tableName.length - 1);
585     }
586     byte [] id = new byte[idEndOffset - offset - 1];
587     System.arraycopy(regionName, offset + 1, id, 0,
588       idEndOffset - offset - 1);
589     byte [][] elements = new byte[replicaId == null ? 3 : 4][];
590     elements[0] = tableName;
591     elements[1] = startKey;
592     elements[2] = id;
593     if (replicaId != null) {
594       elements[3] = replicaId;
595     }
596 
597     return elements;
598   }
599 
600   /** @return the regionId */
601   public long getRegionId(){
602     return regionId;
603   }
604 
605   /**
606    * @return the regionName as an array of bytes.
607    * @see #getRegionNameAsString()
608    */
609   public byte [] getRegionName(){
610     return regionName;
611   }
612 
613   /**
614    * @return Region name as a String for use in logging, etc.
615    */
616   public String getRegionNameAsString() {
617     if (hasEncodedName(this.regionName)) {
618       // new format region names already have their encoded name.
619       return Bytes.toStringBinary(this.regionName);
620     }
621 
622     // old format. regionNameStr doesn't have the region name.
623     //
624     //
625     return Bytes.toStringBinary(this.regionName) + "." + this.getEncodedName();
626   }
627 
628   /** @return the encoded region name */
629   public synchronized String getEncodedName() {
630     if (this.encodedName == null) {
631       this.encodedName = encodeRegionName(this.regionName);
632     }
633     return this.encodedName;
634   }
635 
636   public synchronized byte [] getEncodedNameAsBytes() {
637     if (this.encodedNameAsBytes == null) {
638       this.encodedNameAsBytes = Bytes.toBytes(getEncodedName());
639     }
640     return this.encodedNameAsBytes;
641   }
642 
643   /** @return the startKey */
644   public byte [] getStartKey(){
645     return startKey;
646   }
647 
648   /** @return the endKey */
649   public byte [] getEndKey(){
650     return endKey;
651   }
652 
653   /**
654    * Get current table name of the region
655    * @return byte array of table name
656    * @deprecated Since 0.96.0; use #getTable()
657    */
658   @Deprecated
659   public byte [] getTableName() {
660     return getTable().toBytes();
661   }
662 
663   /**
664    * Get current table name of the region
665    * @return TableName
666    * @see #getTableName()
667    */
668   public TableName getTable() {
669     // This method name should be getTableName but there was already a method getTableName
670     // that returned a byte array.  It is unfortunate given everwhere else, getTableName returns
671     // a TableName instance.
672     if (tableName == null || tableName.getName().length == 0) {
673       tableName = getTable(getRegionName());
674     }
675     return this.tableName;
676   }
677 
678   /**
679    * Returns true if the given inclusive range of rows is fully contained
680    * by this region. For example, if the region is foo,a,g and this is
681    * passed ["b","c"] or ["a","c"] it will return true, but if this is passed
682    * ["b","z"] it will return false.
683    * @throws IllegalArgumentException if the range passed is invalid (ie end < start)
684    */
685   public boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey) {
686     if (Bytes.compareTo(rangeStartKey, rangeEndKey) > 0) {
687       throw new IllegalArgumentException(
688       "Invalid range: " + Bytes.toStringBinary(rangeStartKey) +
689       " > " + Bytes.toStringBinary(rangeEndKey));
690     }
691 
692     boolean firstKeyInRange = Bytes.compareTo(rangeStartKey, startKey) >= 0;
693     boolean lastKeyInRange =
694       Bytes.compareTo(rangeEndKey, endKey) < 0 ||
695       Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY);
696     return firstKeyInRange && lastKeyInRange;
697   }
698 
699   /**
700    * Return true if the given row falls in this region.
701    */
702   public boolean containsRow(byte[] row) {
703     return Bytes.compareTo(row, startKey) >= 0 &&
704       (Bytes.compareTo(row, endKey) < 0 ||
705        Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY));
706   }
707 
708   /**
709    * @return true if this region is from hbase:meta
710    */
711   public boolean isMetaTable() {
712     return isMetaRegion();
713   }
714 
715   /** @return true if this region is a meta region */
716   public boolean isMetaRegion() {
717      return tableName.equals(HRegionInfo.FIRST_META_REGIONINFO.getTable());
718   }
719 
720   /**
721    * @return true if this region is from a system table
722    */
723   public boolean isSystemTable() {
724     return tableName.isSystemTable();
725   }
726 
727   /**
728    * @return True if has been split and has daughters.
729    */
730   public boolean isSplit() {
731     return this.split;
732   }
733 
734   /**
735    * @param split set split status
736    */
737   public void setSplit(boolean split) {
738     this.split = split;
739   }
740 
741   /**
742    * @return True if this region is offline.
743    */
744   public boolean isOffline() {
745     return this.offLine;
746   }
747 
748   /**
749    * The parent of a region split is offline while split daughters hold
750    * references to the parent. Offlined regions are closed.
751    * @param offLine Set online/offline status.
752    */
753   public void setOffline(boolean offLine) {
754     this.offLine = offLine;
755   }
756 
757   /**
758    * @return True if this is a split parent region.
759    */
760   public boolean isSplitParent() {
761     if (!isSplit()) return false;
762     if (!isOffline()) {
763       LOG.warn("Region is split but NOT offline: " + getRegionNameAsString());
764     }
765     return true;
766   }
767 
768   /**
769    * Returns the region replica id
770    * @return returns region replica id
771    */
772   public int getReplicaId() {
773     return replicaId;
774   }
775 
776   /**
777    * @see java.lang.Object#toString()
778    */
779   @Override
780   public String toString() {
781     return "{ENCODED => " + getEncodedName() + ", " +
782       HConstants.NAME + " => '" + Bytes.toStringBinary(this.regionName)
783       + "', STARTKEY => '" +
784       Bytes.toStringBinary(this.startKey) + "', ENDKEY => '" +
785       Bytes.toStringBinary(this.endKey) + "'" +
786       (isOffline()? ", OFFLINE => true": "") +
787       (isSplit()? ", SPLIT => true": "") +
788       ((replicaId > 0)? ", REPLICA_ID => " + replicaId : "") + "}";
789   }
790 
791   /**
792    * @see java.lang.Object#equals(java.lang.Object)
793    */
794   @Override
795   public boolean equals(Object o) {
796     if (this == o) {
797       return true;
798     }
799     if (o == null) {
800       return false;
801     }
802     if (!(o instanceof HRegionInfo)) {
803       return false;
804     }
805     return this.compareTo((HRegionInfo)o) == 0;
806   }
807 
808   /**
809    * @see java.lang.Object#hashCode()
810    */
811   @Override
812   public int hashCode() {
813     return this.hashCode;
814   }
815 
816   //
817   // Comparable
818   //
819 
820   @Override
821   public int compareTo(HRegionInfo o) {
822     if (o == null) {
823       return 1;
824     }
825 
826     // Are regions of same table?
827     int result = this.tableName.compareTo(o.tableName);
828     if (result != 0) {
829       return result;
830     }
831 
832     // Compare start keys.
833     result = Bytes.compareTo(this.startKey, o.startKey);
834     if (result != 0) {
835       return result;
836     }
837 
838     // Compare end keys.
839     result = Bytes.compareTo(this.endKey, o.endKey);
840 
841     if (result != 0) {
842       if (this.getStartKey().length != 0
843               && this.getEndKey().length == 0) {
844           return 1; // this is last region
845       }
846       if (o.getStartKey().length != 0
847               && o.getEndKey().length == 0) {
848           return -1; // o is the last region
849       }
850       return result;
851     }
852 
853     // regionId is usually milli timestamp -- this defines older stamps
854     // to be "smaller" than newer stamps in sort order.
855     if (this.regionId > o.regionId) {
856       return 1;
857     } else if (this.regionId < o.regionId) {
858       return -1;
859     }
860 
861     int replicaDiff = this.getReplicaId() - o.getReplicaId();
862     if (replicaDiff != 0) return replicaDiff;
863 
864     if (this.offLine == o.offLine)
865       return 0;
866     if (this.offLine == true) return -1;
867 
868     return 1;
869   }
870 
871   /**
872    * @return Comparator to use comparing {@link KeyValue}s.
873    */
874   public KVComparator getComparator() {
875     return isMetaRegion()?
876       KeyValue.META_COMPARATOR: KeyValue.COMPARATOR;
877   }
878 
879   /**
880    * Convert a HRegionInfo to a RegionInfo
881    *
882    * @return the converted RegionInfo
883    */
884   RegionInfo convert() {
885     return convert(this);
886   }
887 
888   /**
889    * Convert a HRegionInfo to a RegionInfo
890    *
891    * @param info the HRegionInfo to convert
892    * @return the converted RegionInfo
893    */
894   public static RegionInfo convert(final HRegionInfo info) {
895     if (info == null) return null;
896     RegionInfo.Builder builder = RegionInfo.newBuilder();
897     builder.setTableName(ProtobufUtil.toProtoTableName(info.getTable()));
898     builder.setRegionId(info.getRegionId());
899     if (info.getStartKey() != null) {
900       builder.setStartKey(ByteStringer.wrap(info.getStartKey()));
901     }
902     if (info.getEndKey() != null) {
903       builder.setEndKey(ByteStringer.wrap(info.getEndKey()));
904     }
905     builder.setOffline(info.isOffline());
906     builder.setSplit(info.isSplit());
907     builder.setReplicaId(info.getReplicaId());
908     return builder.build();
909   }
910 
911   /**
912    * Convert a RegionInfo to a HRegionInfo
913    *
914    * @param proto the RegionInfo to convert
915    * @return the converted HRegionInfho
916    */
917   public static HRegionInfo convert(final RegionInfo proto) {
918     if (proto == null) return null;
919     TableName tableName =
920         ProtobufUtil.toTableName(proto.getTableName());
921     if (tableName.equals(TableName.META_TABLE_NAME)) {
922       return RegionReplicaUtil.getRegionInfoForReplica(FIRST_META_REGIONINFO,
923           proto.getReplicaId());
924     }
925     long regionId = proto.getRegionId();
926     int replicaId = proto.hasReplicaId() ? proto.getReplicaId() : DEFAULT_REPLICA_ID;
927     byte[] startKey = null;
928     byte[] endKey = null;
929     if (proto.hasStartKey()) {
930       startKey = proto.getStartKey().toByteArray();
931     }
932     if (proto.hasEndKey()) {
933       endKey = proto.getEndKey().toByteArray();
934     }
935     boolean split = false;
936     if (proto.hasSplit()) {
937       split = proto.getSplit();
938     }
939     HRegionInfo hri = new HRegionInfo(
940         tableName,
941         startKey,
942         endKey, split, regionId, replicaId);
943     if (proto.hasOffline()) {
944       hri.setOffline(proto.getOffline());
945     }
946     return hri;
947   }
948 
949   /**
950    * @return This instance serialized as protobuf w/ a magic pb prefix.
951    * @see #parseFrom(byte[])
952    */
953   public byte [] toByteArray() {
954     byte [] bytes = convert().toByteArray();
955     return ProtobufUtil.prependPBMagic(bytes);
956   }
957 
958   /**
959    * @return A deserialized {@link HRegionInfo}
960    * or null if we failed deserialize or passed bytes null
961    * @see #toByteArray()
962    */
963   public static HRegionInfo parseFromOrNull(final byte [] bytes) {
964     if (bytes == null) return null;
965     return parseFromOrNull(bytes, 0, bytes.length);
966   }
967 
968   /**
969    * @return A deserialized {@link HRegionInfo} or null
970    *  if we failed deserialize or passed bytes null
971    * @see #toByteArray()
972    */
973   public static HRegionInfo parseFromOrNull(final byte [] bytes, int offset, int len) {
974     if (bytes == null || len <= 0) return null;
975     try {
976       return parseFrom(bytes, offset, len);
977     } catch (DeserializationException e) {
978       return null;
979     }
980   }
981 
982   /**
983    * @param bytes A pb RegionInfo serialized with a pb magic prefix.
984    * @return A deserialized {@link HRegionInfo}
985    * @throws DeserializationException
986    * @see #toByteArray()
987    */
988   public static HRegionInfo parseFrom(final byte [] bytes) throws DeserializationException {
989     if (bytes == null) return null;
990     return parseFrom(bytes, 0, bytes.length);
991   }
992 
993   /**
994    * @param bytes A pb RegionInfo serialized with a pb magic prefix.
995    * @param offset starting point in the byte array
996    * @param len length to read on the byte array
997    * @return A deserialized {@link HRegionInfo}
998    * @throws DeserializationException
999    * @see #toByteArray()
1000    */
1001   public static HRegionInfo parseFrom(final byte [] bytes, int offset, int len)
1002       throws DeserializationException {
1003     if (ProtobufUtil.isPBMagicPrefix(bytes, offset, len)) {
1004       int pblen = ProtobufUtil.lengthOfPBMagic();
1005       try {
1006         HBaseProtos.RegionInfo ri =
1007             HBaseProtos.RegionInfo.newBuilder().
1008                 mergeFrom(bytes, pblen + offset, len - pblen).build();
1009         return convert(ri);
1010       } catch (InvalidProtocolBufferException e) {
1011         throw new DeserializationException(e);
1012       }
1013     } else {
1014       throw new DeserializationException("PB encoded HRegionInfo expected");
1015     }
1016   }
1017 
1018   /**
1019    * Use this instead of {@link #toByteArray()} when writing to a stream and you want to use
1020    * the pb mergeDelimitedFrom (w/o the delimiter, pb reads to EOF which may not be what you want).
1021    * @return This instance serialized as a delimited protobuf w/ a magic pb prefix.
1022    * @throws IOException
1023    * @see #toByteArray()
1024    */
1025   public byte [] toDelimitedByteArray() throws IOException {
1026     return ProtobufUtil.toDelimitedByteArray(convert());
1027   }
1028 
1029   /**
1030    * Get the descriptive name as {@link RegionState} does it but with hidden
1031    * startkey optionally
1032    * @param state
1033    * @param conf
1034    * @return descriptive string
1035    */
1036   public static String getDescriptiveNameFromRegionStateForDisplay(RegionState state,
1037       Configuration conf) {
1038     if (conf.getBoolean(DISPLAY_KEYS_KEY, true)) return state.toDescriptiveString();
1039     String descriptiveStringFromState = state.toDescriptiveString();
1040     int idx = descriptiveStringFromState.lastIndexOf(" state=");
1041     String regionName = getRegionNameAsStringForDisplay(state.getRegion(), conf);
1042     return regionName + descriptiveStringFromState.substring(idx);
1043   }
1044 
1045   /**
1046    * Get the end key for display. Optionally hide the real end key. 
1047    * @param hri
1048    * @param conf
1049    * @return the endkey
1050    */
1051   public static byte[] getEndKeyForDisplay(HRegionInfo hri, Configuration conf) {
1052     boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true);
1053     if (displayKey) return hri.getEndKey();
1054     return HIDDEN_END_KEY;
1055   }
1056 
1057   /**
1058    * Get the start key for display. Optionally hide the real start key. 
1059    * @param hri
1060    * @param conf
1061    * @return the startkey
1062    */
1063   public static byte[] getStartKeyForDisplay(HRegionInfo hri, Configuration conf) {
1064     boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true);
1065     if (displayKey) return hri.getStartKey();
1066     return HIDDEN_START_KEY;
1067   }
1068 
1069   /**
1070    * Get the region name for display. Optionally hide the start key.
1071    * @param hri
1072    * @param conf
1073    * @return region name as String
1074    */
1075   public static String getRegionNameAsStringForDisplay(HRegionInfo hri, Configuration conf) {
1076     return Bytes.toStringBinary(getRegionNameForDisplay(hri, conf));
1077   }
1078 
1079   /**
1080    * Get the region name for display. Optionally hide the start key.
1081    * @param hri
1082    * @param conf
1083    * @return region name bytes
1084    */
1085   public static byte[] getRegionNameForDisplay(HRegionInfo hri, Configuration conf) {
1086     boolean displayKey = conf.getBoolean(DISPLAY_KEYS_KEY, true);
1087     if (displayKey || hri.getTable().equals(TableName.META_TABLE_NAME)) {
1088       return hri.getRegionName();
1089     } else {
1090       // create a modified regionname with the startkey replaced but preserving
1091       // the other parts including the encodedname.
1092       try {
1093         byte[][]regionNameParts = parseRegionName(hri.getRegionName());
1094         regionNameParts[1] = HIDDEN_START_KEY; //replace the real startkey
1095         int len = 0;
1096         // get the total length
1097         for (byte[] b : regionNameParts) {
1098           len += b.length;
1099         }
1100         byte[] encodedRegionName =
1101             Bytes.toBytes(encodeRegionName(hri.getRegionName()));
1102         len += encodedRegionName.length;
1103         //allocate some extra bytes for the delimiters and the last '.'
1104         byte[] modifiedName = new byte[len + regionNameParts.length + 1];
1105         int lengthSoFar = 0;
1106         int loopCount = 0;
1107         for (byte[] b : regionNameParts) {
1108           System.arraycopy(b, 0, modifiedName, lengthSoFar, b.length);
1109           lengthSoFar += b.length;
1110           if (loopCount++ == 2) modifiedName[lengthSoFar++] = REPLICA_ID_DELIMITER;
1111           else  modifiedName[lengthSoFar++] = HConstants.DELIMITER;
1112         }
1113         // replace the last comma with '.'
1114         modifiedName[lengthSoFar - 1] = ENC_SEPARATOR;
1115         System.arraycopy(encodedRegionName, 0, modifiedName, lengthSoFar,
1116             encodedRegionName.length);
1117         lengthSoFar += encodedRegionName.length; 
1118         modifiedName[lengthSoFar] = ENC_SEPARATOR;
1119         return modifiedName;
1120       } catch (IOException e) {
1121         //LOG.warn("Encountered exception " + e);
1122         throw new RuntimeException(e);
1123       }
1124     }
1125   }
1126 
1127   /**
1128    * Extract a HRegionInfo and ServerName from catalog table {@link Result}.
1129    * @param r Result to pull from
1130    * @return A pair of the {@link HRegionInfo} and the {@link ServerName}
1131    * (or null for server address if no address set in hbase:meta).
1132    * @throws IOException
1133    * @deprecated use MetaTableAccessor methods for interacting with meta layouts
1134    */
1135   @Deprecated
1136   public static Pair<HRegionInfo, ServerName> getHRegionInfoAndServerName(final Result r) {
1137     HRegionInfo info =
1138       getHRegionInfo(r, HConstants.REGIONINFO_QUALIFIER);
1139     ServerName sn = getServerName(r);
1140     return new Pair<HRegionInfo, ServerName>(info, sn);
1141   }
1142 
1143   /**
1144    * Returns HRegionInfo object from the column
1145    * HConstants.CATALOG_FAMILY:HConstants.REGIONINFO_QUALIFIER of the catalog
1146    * table Result.
1147    * @param data a Result object from the catalog table scan
1148    * @return HRegionInfo or null
1149    * @deprecated use MetaTableAccessor methods for interacting with meta layouts
1150    */
1151   @Deprecated
1152   public static HRegionInfo getHRegionInfo(Result data) {
1153     return getHRegionInfo(data, HConstants.REGIONINFO_QUALIFIER);
1154   }
1155 
1156   /**
1157    * Returns the daughter regions by reading the corresponding columns of the catalog table
1158    * Result.
1159    * @param data a Result object from the catalog table scan
1160    * @return a pair of HRegionInfo or PairOfSameType(null, null) if the region is not a split
1161    * parent
1162    * @deprecated use MetaTableAccessor methods for interacting with meta layouts
1163    */
1164   @Deprecated
1165   public static PairOfSameType<HRegionInfo> getDaughterRegions(Result data) throws IOException {
1166     HRegionInfo splitA = getHRegionInfo(data, HConstants.SPLITA_QUALIFIER);
1167     HRegionInfo splitB = getHRegionInfo(data, HConstants.SPLITB_QUALIFIER);
1168 
1169     return new PairOfSameType<HRegionInfo>(splitA, splitB);
1170   }
1171 
1172   /**
1173    * Returns the merge regions by reading the corresponding columns of the catalog table
1174    * Result.
1175    * @param data a Result object from the catalog table scan
1176    * @return a pair of HRegionInfo or PairOfSameType(null, null) if the region is not a split
1177    * parent
1178    * @deprecated use MetaTableAccessor methods for interacting with meta layouts
1179    */
1180   @Deprecated
1181   public static PairOfSameType<HRegionInfo> getMergeRegions(Result data) throws IOException {
1182     HRegionInfo mergeA = getHRegionInfo(data, HConstants.MERGEA_QUALIFIER);
1183     HRegionInfo mergeB = getHRegionInfo(data, HConstants.MERGEB_QUALIFIER);
1184 
1185     return new PairOfSameType<HRegionInfo>(mergeA, mergeB);
1186   }
1187 
1188   /**
1189    * Returns the HRegionInfo object from the column {@link HConstants#CATALOG_FAMILY} and
1190    * <code>qualifier</code> of the catalog table result.
1191    * @param r a Result object from the catalog table scan
1192    * @param qualifier Column family qualifier -- either
1193    * {@link HConstants#SPLITA_QUALIFIER}, {@link HConstants#SPLITB_QUALIFIER} or
1194    * {@link HConstants#REGIONINFO_QUALIFIER}.
1195    * @return An HRegionInfo instance or null.
1196    * @deprecated use MetaTableAccessor methods for interacting with meta layouts
1197    */
1198   @Deprecated
1199   public static HRegionInfo getHRegionInfo(final Result r, byte [] qualifier) {
1200     Cell cell = r.getColumnLatestCell(
1201         HConstants.CATALOG_FAMILY, qualifier);
1202     if (cell == null) return null;
1203     return parseFromOrNull(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
1204   }
1205 
1206   /**
1207    * @deprecated use MetaTableAccessor methods for interacting with meta layouts
1208    */
1209   @Deprecated
1210   public static ServerName getServerName(final Result r) {
1211     Cell cell = r.getColumnLatestCell(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER);
1212     if (cell == null || cell.getValueLength() == 0) return null;
1213     String hostAndPort = Bytes.toString(
1214         cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
1215     cell = r.getColumnLatestCell(HConstants.CATALOG_FAMILY,
1216       HConstants.STARTCODE_QUALIFIER);
1217     if (cell == null || cell.getValueLength() == 0) return null;
1218     try {
1219       return ServerName.valueOf(hostAndPort,
1220           Bytes.toLong(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()));
1221     } catch (IllegalArgumentException e) {
1222       LOG.error("Ignoring invalid region for server " + hostAndPort + "; cell=" + cell, e);
1223       return null;
1224     }
1225   }
1226 
1227   /**
1228    * The latest seqnum that the server writing to meta observed when opening the region.
1229    * E.g. the seqNum when the result of {@link #getServerName(Result)} was written.
1230    * @param r Result to pull the seqNum from
1231    * @return SeqNum, or HConstants.NO_SEQNUM if there's no value written.
1232    * @deprecated use MetaTableAccessor methods for interacting with meta layouts
1233    */
1234   @Deprecated
1235   public static long getSeqNumDuringOpen(final Result r) {
1236     Cell cell = r.getColumnLatestCell(HConstants.CATALOG_FAMILY, HConstants.SEQNUM_QUALIFIER);
1237     if (cell == null || cell.getValueLength() == 0) return HConstants.NO_SEQNUM;
1238     return Bytes.toLong(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
1239   }
1240 
1241   /**
1242    * Parses an HRegionInfo instance from the passed in stream.  Presumes the HRegionInfo was
1243    * serialized to the stream with {@link #toDelimitedByteArray()}
1244    * @param in
1245    * @return An instance of HRegionInfo.
1246    * @throws IOException
1247    */
1248   public static HRegionInfo parseFrom(final DataInputStream in) throws IOException {
1249     // I need to be able to move back in the stream if this is not a pb serialization so I can
1250     // do the Writable decoding instead.
1251     int pblen = ProtobufUtil.lengthOfPBMagic();
1252     byte [] pbuf = new byte[pblen];
1253     if (in.markSupported()) { //read it with mark()
1254       in.mark(pblen);
1255     }
1256     int read = in.read(pbuf); //assumption: it should be longer than pblen.
1257     if (read != pblen) throw new IOException("read=" + read + ", wanted=" + pblen);
1258     if (ProtobufUtil.isPBMagicPrefix(pbuf)) {
1259       return convert(HBaseProtos.RegionInfo.parseDelimitedFrom(in));
1260     } else {
1261       throw new IOException("PB encoded HRegionInfo expected");
1262     }
1263   }
1264 
1265   /**
1266    * Serializes given HRegionInfo's as a byte array. Use this instead of {@link #toByteArray()} when
1267    * writing to a stream and you want to use the pb mergeDelimitedFrom (w/o the delimiter, pb reads
1268    * to EOF which may not be what you want). {@link #parseDelimitedFrom(byte[], int, int)} can
1269    * be used to read back the instances.
1270    * @param infos HRegionInfo objects to serialize
1271    * @return This instance serialized as a delimited protobuf w/ a magic pb prefix.
1272    * @throws IOException
1273    * @see #toByteArray()
1274    */
1275   public static byte[] toDelimitedByteArray(HRegionInfo... infos) throws IOException {
1276     byte[][] bytes = new byte[infos.length][];
1277     int size = 0;
1278     for (int i = 0; i < infos.length; i++) {
1279       bytes[i] = infos[i].toDelimitedByteArray();
1280       size += bytes[i].length;
1281     }
1282 
1283     byte[] result = new byte[size];
1284     int offset = 0;
1285     for (byte[] b : bytes) {
1286       System.arraycopy(b, 0, result, offset, b.length);
1287       offset += b.length;
1288     }
1289     return result;
1290   }
1291 
1292   /**
1293    * Parses all the HRegionInfo instances from the passed in stream until EOF. Presumes the
1294    * HRegionInfo's were serialized to the stream with {@link #toDelimitedByteArray()}
1295    * @param bytes serialized bytes
1296    * @param offset the start offset into the byte[] buffer
1297    * @param length how far we should read into the byte[] buffer
1298    * @return All the hregioninfos that are in the byte array. Keeps reading till we hit the end.
1299    */
1300   public static List<HRegionInfo> parseDelimitedFrom(final byte[] bytes, final int offset,
1301       final int length) throws IOException {
1302     if (bytes == null) {
1303       throw new IllegalArgumentException("Can't build an object with empty bytes array");
1304     }
1305     DataInputBuffer in = new DataInputBuffer();
1306     List<HRegionInfo> hris = new ArrayList<HRegionInfo>();
1307     try {
1308       in.reset(bytes, offset, length);
1309       while (in.available() > 0) {
1310         HRegionInfo hri = parseFrom(in);
1311         hris.add(hri);
1312       }
1313     } finally {
1314       in.close();
1315     }
1316     return hris;
1317   }
1318 
1319   /**
1320    * Check whether two regions are adjacent
1321    * @param regionA
1322    * @param regionB
1323    * @return true if two regions are adjacent
1324    */
1325   public static boolean areAdjacent(HRegionInfo regionA, HRegionInfo regionB) {
1326     if (regionA == null || regionB == null) {
1327       throw new IllegalArgumentException(
1328           "Can't check whether adjacent for null region");
1329     }
1330     HRegionInfo a = regionA;
1331     HRegionInfo b = regionB;
1332     if (Bytes.compareTo(a.getStartKey(), b.getStartKey()) > 0) {
1333       a = regionB;
1334       b = regionA;
1335     }
1336     if (Bytes.compareTo(a.getEndKey(), b.getStartKey()) == 0) {
1337       return true;
1338     }
1339     return false;
1340   }
1341 }