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