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