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