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