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