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