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