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