View Javadoc

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