View Javadoc

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