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