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