View Javadoc

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