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