View Javadoc

1   /**
2    * Copyright 2007 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase;
21  
22  import java.io.DataInput;
23  import java.io.DataOutput;
24  import java.io.EOFException;
25  import java.io.IOException;
26  import java.util.Arrays;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.fs.FileSystem;
32  import org.apache.hadoop.fs.Path;
33  import org.apache.hadoop.hbase.KeyValue.KVComparator;
34  import org.apache.hadoop.hbase.migration.HRegionInfo090x;
35  import org.apache.hadoop.hbase.util.Bytes;
36  import org.apache.hadoop.hbase.util.FSTableDescriptors;
37  import org.apache.hadoop.hbase.util.JenkinsHash;
38  import org.apache.hadoop.hbase.util.MD5Hash;
39  import org.apache.hadoop.io.VersionedWritable;
40  import org.apache.hadoop.io.WritableComparable;
41  
42  /**
43   * HRegion information.
44   * Contains HRegion id, start and end keys, a reference to this
45   * HRegions' table descriptor, etc.
46   */
47  public class HRegionInfo extends VersionedWritable
48  implements WritableComparable<HRegionInfo> {
49    // VERSION == 0 when HRegionInfo had an HTableDescriptor inside it.
50    public static final byte VERSION_PRE_092 = 0;
51    public static final byte VERSION = 1;
52    private static final Log LOG = LogFactory.getLog(HRegionInfo.class);
53  
54    /**
55     * The new format for a region name contains its encodedName at the end.
56     * The encoded name also serves as the directory name for the region
57     * in the filesystem.
58     *
59     * New region name format:
60     *    &lt;tablename>,,&lt;startkey>,&lt;regionIdTimestamp>.&lt;encodedName>.
61     * where,
62     *    &lt;encodedName> is a hex version of the MD5 hash of
63     *    &lt;tablename>,&lt;startkey>,&lt;regionIdTimestamp>
64     * 
65     * The old region name format:
66     *    &lt;tablename>,&lt;startkey>,&lt;regionIdTimestamp>
67     * For region names in the old format, the encoded name is a 32-bit
68     * JenkinsHash integer value (in its decimal notation, string form). 
69     *<p>
70     * **NOTE**
71     *
72     * ROOT, the first META region, and regions created by an older
73     * version of HBase (0.20 or prior) will continue to use the
74     * old region name format.
75     */
76  
77    /** Separator used to demarcate the encodedName in a region name
78     * in the new format. See description on new format above. 
79     */ 
80    private static final int ENC_SEPARATOR = '.';
81    public  static final int MD5_HEX_LENGTH   = 32;
82  
83    /** A non-capture group so that this can be embedded. */
84    public static final String ENCODED_REGION_NAME_REGEX = "(?:[a-f0-9]+)";
85  
86    /**
87     * Does region name contain its encoded name?
88     * @param regionName region name
89     * @return boolean indicating if this a new format region
90     *         name which contains its encoded name.
91     */
92    private static boolean hasEncodedName(final byte[] regionName) {
93      // check if region name ends in ENC_SEPARATOR
94      if ((regionName.length >= 1)
95          && (regionName[regionName.length - 1] == ENC_SEPARATOR)) {
96        // region name is new format. it contains the encoded name.
97        return true; 
98      }
99      return false;
100   }
101   
102   /**
103    * @param regionName
104    * @return the encodedName
105    */
106   public static String encodeRegionName(final byte [] regionName) {
107     String encodedName;
108     if (hasEncodedName(regionName)) {
109       // region is in new format:
110       // <tableName>,<startKey>,<regionIdTimeStamp>/encodedName/
111       encodedName = Bytes.toString(regionName,
112           regionName.length - MD5_HEX_LENGTH - 1,
113           MD5_HEX_LENGTH);
114     } else {
115       // old format region name. ROOT and first META region also 
116       // use this format.EncodedName is the JenkinsHash value.
117       int hashVal = Math.abs(JenkinsHash.getInstance().hash(regionName,
118         regionName.length, 0));
119       encodedName = String.valueOf(hashVal);
120     }
121     return encodedName;
122   }
123 
124   /**
125    * Use logging.
126    * @param encodedRegionName The encoded regionname.
127    * @return <code>-ROOT-</code> if passed <code>70236052</code> or
128    * <code>.META.</code> if passed </code>1028785192</code> else returns
129    * <code>encodedRegionName</code>
130    */
131   public static String prettyPrint(final String encodedRegionName) {
132     if (encodedRegionName.equals("70236052")) {
133       return encodedRegionName + "/-ROOT-";
134     } else if (encodedRegionName.equals("1028785192")) {
135       return encodedRegionName + "/.META.";
136     }
137     return encodedRegionName;
138   }
139 
140   /** delimiter used between portions of a region name */
141   public static final int DELIMITER = ',';
142 
143   /** HRegionInfo for root region */
144   public static final HRegionInfo ROOT_REGIONINFO =
145     new HRegionInfo(0L, Bytes.toBytes("-ROOT-"));
146 
147   /** HRegionInfo for first meta region */
148   public static final HRegionInfo FIRST_META_REGIONINFO =
149     new HRegionInfo(1L, Bytes.toBytes(".META."));
150 
151   private byte [] endKey = HConstants.EMPTY_BYTE_ARRAY;
152   // This flag is in the parent of a split while the parent is still referenced
153   // by daughter regions.  We USED to set this flag when we disabled a table
154   // but now table state is kept up in zookeeper as of 0.90.0 HBase.
155   private boolean offLine = false;
156   private long regionId = -1;
157   private transient byte [] regionName = HConstants.EMPTY_BYTE_ARRAY;
158   private String regionNameStr = "";
159   private boolean split = false;
160   private byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
161   private int hashCode = -1;
162   //TODO: Move NO_HASH to HStoreFile which is really the only place it is used.
163   public static final String NO_HASH = null;
164   private volatile String encodedName = NO_HASH;
165   private byte [] encodedNameAsBytes = null;
166 
167   // Current TableName
168   private byte[] tableName = null;
169 
170   private void setHashCode() {
171     int result = Arrays.hashCode(this.regionName);
172     result ^= this.regionId;
173     result ^= Arrays.hashCode(this.startKey);
174     result ^= Arrays.hashCode(this.endKey);
175     result ^= Boolean.valueOf(this.offLine).hashCode();
176     result ^= Arrays.hashCode(this.tableName);
177     this.hashCode = result;
178   }
179 
180 
181   /**
182    * Private constructor used constructing HRegionInfo for the catalog root and
183    * first meta regions
184    */
185   private HRegionInfo(long regionId, byte[] tableName) {
186     super();
187     this.regionId = regionId;
188     this.tableName = tableName.clone();
189     // Note: Root & First Meta regions names are still in old format
190     this.regionName = createRegionName(tableName, null,
191                                        regionId, false);
192     this.regionNameStr = Bytes.toStringBinary(this.regionName);
193     setHashCode();
194   }
195 
196   /** Default constructor - creates empty object */
197   public HRegionInfo() {
198     super();
199   }
200 
201   /**
202    * Used only for migration
203    * @param other HRegionInfoForMigration
204    */
205   public HRegionInfo(HRegionInfo090x other) {
206     super();
207     this.endKey = other.getEndKey();
208     this.offLine = other.isOffline();
209     this.regionId = other.getRegionId();
210     this.regionName = other.getRegionName();
211     this.regionNameStr = Bytes.toStringBinary(this.regionName);
212     this.split = other.isSplit();
213     this.startKey = other.getStartKey();
214     this.hashCode = other.hashCode();
215     this.encodedName = other.getEncodedName();
216     this.tableName = other.getTableDesc().getName();
217   }
218 
219   public HRegionInfo(final byte[] tableName) {
220     this(tableName, null, null);
221   }
222 
223   /**
224    * Construct HRegionInfo with explicit parameters
225    *
226    * @param tableName the table name
227    * @param startKey first key in region
228    * @param endKey end of key range
229    * @throws IllegalArgumentException
230    */
231   public HRegionInfo(final byte[] tableName, final byte[] startKey,
232                      final byte[] endKey)
233   throws IllegalArgumentException {
234     this(tableName, startKey, endKey, false);
235   }
236 
237 
238   /**
239    * Construct HRegionInfo with explicit parameters
240    *
241    * @param tableName the table descriptor
242    * @param startKey first key in region
243    * @param endKey end of key range
244    * @param split true if this region has split and we have daughter regions
245    * regions that may or may not hold references to this region.
246    * @throws IllegalArgumentException
247    */
248   public HRegionInfo(final byte[] tableName, final byte[] startKey,
249                      final byte[] endKey, final boolean split)
250   throws IllegalArgumentException {
251     this(tableName, startKey, endKey, split, System.currentTimeMillis());
252   }
253 
254 
255   /**
256    * Construct HRegionInfo with explicit parameters
257    *
258    * @param tableName the table descriptor
259    * @param startKey first key in region
260    * @param endKey end of key range
261    * @param split true if this region has split and we have daughter regions
262    * regions that may or may not hold references to this region.
263    * @param regionid Region id to use.
264    * @throws IllegalArgumentException
265    */
266   public HRegionInfo(final byte[] tableName, final byte[] startKey,
267                      final byte[] endKey, final boolean split, final long regionid)
268   throws IllegalArgumentException {
269 
270     super();
271     if (tableName == null) {
272       throw new IllegalArgumentException("tableName cannot be null");
273     }
274     this.tableName = tableName.clone();
275     this.offLine = false;
276     this.regionId = regionid;
277 
278     this.regionName = createRegionName(this.tableName, startKey, regionId, true);
279 
280     this.regionNameStr = Bytes.toStringBinary(this.regionName);
281     this.split = split;
282     this.endKey = endKey == null? HConstants.EMPTY_END_ROW: endKey.clone();
283     this.startKey = startKey == null?
284       HConstants.EMPTY_START_ROW: startKey.clone();
285     this.tableName = tableName.clone();
286     setHashCode();
287   }
288 
289   /**
290    * Costruct a copy of another HRegionInfo
291    *
292    * @param other
293    */
294   public HRegionInfo(HRegionInfo other) {
295     super();
296     this.endKey = other.getEndKey();
297     this.offLine = other.isOffline();
298     this.regionId = other.getRegionId();
299     this.regionName = other.getRegionName();
300     this.regionNameStr = Bytes.toStringBinary(this.regionName);
301     this.split = other.isSplit();
302     this.startKey = other.getStartKey();
303     this.hashCode = other.hashCode();
304     this.encodedName = other.getEncodedName();
305     this.tableName = other.tableName;
306   }
307 
308 
309   /**
310    * Make a region name of passed parameters.
311    * @param tableName
312    * @param startKey Can be null
313    * @param regionid Region id (Usually timestamp from when region was created).
314    * @param newFormat should we create the region name in the new format
315    *                  (such that it contains its encoded name?).
316    * @return Region name made of passed tableName, startKey and id
317    */
318   public static byte [] createRegionName(final byte [] tableName,
319       final byte [] startKey, final long regionid, boolean newFormat) {
320     return createRegionName(tableName, startKey, Long.toString(regionid), newFormat);
321   }
322 
323   /**
324    * Make a region name of passed parameters.
325    * @param tableName
326    * @param startKey Can be null
327    * @param id Region id (Usually timestamp from when region was created).
328    * @param newFormat should we create the region name in the new format
329    *                  (such that it contains its encoded name?).
330    * @return Region name made of passed tableName, startKey and id
331    */
332   public static byte [] createRegionName(final byte [] tableName,
333       final byte [] startKey, final String id, boolean newFormat) {
334     return createRegionName(tableName, startKey, Bytes.toBytes(id), newFormat);
335   }
336 
337   /**
338    * Make a region name of passed parameters.
339    * @param tableName
340    * @param startKey Can be null
341    * @param id Region id (Usually timestamp from when region was created).
342    * @param newFormat should we create the region name in the new format
343    *                  (such that it contains its encoded name?).
344    * @return Region name made of passed tableName, startKey and id
345    */
346   public static byte [] createRegionName(final byte [] tableName,
347       final byte [] startKey, final byte [] id, boolean newFormat) {
348     byte [] b = new byte [tableName.length + 2 + id.length +
349        (startKey == null? 0: startKey.length) +
350        (newFormat ? (MD5_HEX_LENGTH + 2) : 0)];
351 
352     int offset = tableName.length;
353     System.arraycopy(tableName, 0, b, 0, offset);
354     b[offset++] = DELIMITER;
355     if (startKey != null && startKey.length > 0) {
356       System.arraycopy(startKey, 0, b, offset, startKey.length);
357       offset += startKey.length;
358     }
359     b[offset++] = DELIMITER;
360     System.arraycopy(id, 0, b, offset, id.length);
361     offset += id.length;
362 
363     if (newFormat) {
364       //
365       // Encoded name should be built into the region name.
366       //
367       // Use the region name thus far (namely, <tablename>,<startKey>,<id>)
368       // to compute a MD5 hash to be used as the encoded name, and append
369       // it to the byte buffer.
370       //
371       String md5Hash = MD5Hash.getMD5AsHex(b, 0, offset);
372       byte [] md5HashBytes = Bytes.toBytes(md5Hash);
373 
374       if (md5HashBytes.length != MD5_HEX_LENGTH) {
375         LOG.error("MD5-hash length mismatch: Expected=" + MD5_HEX_LENGTH +
376                   "; Got=" + md5HashBytes.length); 
377       }
378 
379       // now append the bytes '.<encodedName>.' to the end
380       b[offset++] = ENC_SEPARATOR;
381       System.arraycopy(md5HashBytes, 0, b, offset, MD5_HEX_LENGTH);
382       offset += MD5_HEX_LENGTH;
383       b[offset++] = ENC_SEPARATOR;
384     }
385     
386     return b;
387   }
388 
389   /**
390    * Gets the table name from the specified region name.
391    * @param regionName
392    * @return Table name.
393    */
394   public static byte [] getTableName(byte [] regionName) {
395     int offset = -1;
396     for (int i = 0; i < regionName.length; i++) {
397       if (regionName[i] == DELIMITER) {
398         offset = i;
399         break;
400       }
401     }
402     byte [] tableName = new byte[offset];
403     System.arraycopy(regionName, 0, tableName, 0, offset);
404     return tableName;
405   }
406 
407   /**
408    * Gets the start key from the specified region name.
409    * @param regionName
410    * @return Start key.
411    */
412   public static byte[] getStartKey(final byte[] regionName) throws IOException {
413     return parseRegionName(regionName)[1];
414   }
415 
416   /**
417    * Separate elements of a regionName.
418    * @param regionName
419    * @return Array of byte[] containing tableName, startKey and id
420    * @throws IOException
421    */
422   public static byte [][] parseRegionName(final byte [] regionName)
423   throws IOException {
424     int offset = -1;
425     for (int i = 0; i < regionName.length; i++) {
426       if (regionName[i] == DELIMITER) {
427         offset = i;
428         break;
429       }
430     }
431     if(offset == -1) throw new IOException("Invalid regionName format");
432     byte [] tableName = new byte[offset];
433     System.arraycopy(regionName, 0, tableName, 0, offset);
434     offset = -1;
435     for (int i = regionName.length - 1; i > 0; i--) {
436       if(regionName[i] == DELIMITER) {
437         offset = i;
438         break;
439       }
440     }
441     if(offset == -1) throw new IOException("Invalid regionName format");
442     byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
443     if(offset != tableName.length + 1) {
444       startKey = new byte[offset - tableName.length - 1];
445       System.arraycopy(regionName, tableName.length + 1, startKey, 0,
446           offset - tableName.length - 1);
447     }
448     byte [] id = new byte[regionName.length - offset - 1];
449     System.arraycopy(regionName, offset + 1, id, 0,
450         regionName.length - offset - 1);
451     byte [][] elements = new byte[3][];
452     elements[0] = tableName;
453     elements[1] = startKey;
454     elements[2] = id;
455     return elements;
456   }
457 
458   /** @return the regionId */
459   public long getRegionId(){
460     return regionId;
461   }
462 
463   /**
464    * @return the regionName as an array of bytes.
465    * @see #getRegionNameAsString()
466    */
467   public byte [] getRegionName(){
468     return regionName;
469   }
470 
471   /**
472    * @return Region name as a String for use in logging, etc.
473    */
474   public String getRegionNameAsString() {
475     if (hasEncodedName(this.regionName)) {
476       // new format region names already have their encoded name.
477       return this.regionNameStr;
478     }
479 
480     // old format. regionNameStr doesn't have the region name.
481     //
482     //
483     return this.regionNameStr + "." + this.getEncodedName();
484   }
485 
486   /** @return the encoded region name */
487   public synchronized String getEncodedName() {
488     if (this.encodedName == NO_HASH) {
489       this.encodedName = encodeRegionName(this.regionName);
490     }
491     return this.encodedName;
492   }
493 
494   public synchronized byte [] getEncodedNameAsBytes() {
495     if (this.encodedNameAsBytes == null) {
496       this.encodedNameAsBytes = Bytes.toBytes(getEncodedName());
497     }
498     return this.encodedNameAsBytes;
499   }
500 
501   /** @return the startKey */
502   public byte [] getStartKey(){
503     return startKey;
504   }
505   
506   /** @return the endKey */
507   public byte [] getEndKey(){
508     return endKey;
509   }
510 
511   /**
512    * Get current table name of the region
513    * @return byte array of table name
514    */
515   public byte[] getTableName() {
516     if (tableName == null || tableName.length == 0) {
517       tableName = getTableName(getRegionName());
518     }
519     return tableName;
520   }
521 
522   /**
523    * Get current table name as string
524    * @return string representation of current table
525    */
526   public String getTableNameAsString() {
527     return Bytes.toString(tableName);
528   }
529 
530   /**
531    * Returns true if the given inclusive range of rows is fully contained
532    * by this region. For example, if the region is foo,a,g and this is
533    * passed ["b","c"] or ["a","c"] it will return true, but if this is passed
534    * ["b","z"] it will return false.
535    * @throws IllegalArgumentException if the range passed is invalid (ie end < start)
536    */
537   public boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey) {
538     if (Bytes.compareTo(rangeStartKey, rangeEndKey) > 0) {
539       throw new IllegalArgumentException(
540       "Invalid range: " + Bytes.toStringBinary(rangeStartKey) +
541       " > " + Bytes.toStringBinary(rangeEndKey));
542     }
543 
544     boolean firstKeyInRange = Bytes.compareTo(rangeStartKey, startKey) >= 0;
545     boolean lastKeyInRange =
546       Bytes.compareTo(rangeEndKey, endKey) < 0 ||
547       Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY);
548     return firstKeyInRange && lastKeyInRange;
549   }
550   
551   /**
552    * Return true if the given row falls in this region.
553    */
554   public boolean containsRow(byte[] row) {
555     return Bytes.compareTo(row, startKey) >= 0 &&
556       (Bytes.compareTo(row, endKey) < 0 ||
557        Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY));
558   }
559 
560   /**
561    * @return the tableDesc
562    * @deprecated Do not use; expensive call
563    *         use HRegionInfo.getTableNameAsString() in place of
564    *         HRegionInfo.getTableDesc().getNameAsString()
565    */
566    @Deprecated
567   public HTableDescriptor getTableDesc() {
568     Configuration c = HBaseConfiguration.create();
569     c.set("fs.defaultFS", c.get(HConstants.HBASE_DIR));
570     c.set("fs.default.name", c.get(HConstants.HBASE_DIR));
571     FileSystem fs;
572     try {
573       fs = FileSystem.get(c);
574     } catch (IOException e) {
575       throw new RuntimeException(e);
576     }
577     FSTableDescriptors fstd =
578       new FSTableDescriptors(fs, new Path(c.get(HConstants.HBASE_DIR)));
579     try {
580       return fstd.get(this.tableName);
581     } catch (IOException e) {
582       throw new RuntimeException(e);
583     }
584   }
585 
586   /**
587    * @param newDesc new table descriptor to use
588    * @deprecated Do not use; expensive call
589    */
590   @Deprecated
591   public void setTableDesc(HTableDescriptor newDesc) {
592     Configuration c = HBaseConfiguration.create();
593     FileSystem fs;
594     try {
595       fs = FileSystem.get(c);
596     } catch (IOException e) {
597       throw new RuntimeException(e);
598     }
599     FSTableDescriptors fstd =
600       new FSTableDescriptors(fs, new Path(c.get(HConstants.HBASE_DIR)));
601     try {
602       fstd.add(newDesc);
603     } catch (IOException e) {
604       throw new RuntimeException(e);
605     }
606   }
607 
608   /** @return true if this is the root region */
609   public boolean isRootRegion() {
610     return Bytes.equals(tableName, HRegionInfo.ROOT_REGIONINFO.getTableName());
611   }
612 
613   /** @return true if this region is from a table that is a meta table,
614    * either <code>.META.</code> or <code>-ROOT-</code>
615    */
616   public boolean isMetaTable() {
617     return isRootRegion() || isMetaRegion();
618   }
619 
620   /** @return true if this region is a meta region */
621   public boolean isMetaRegion() {
622      return Bytes.equals(tableName, HRegionInfo.FIRST_META_REGIONINFO.getTableName());
623   }
624 
625   /**
626    * @return True if has been split and has daughters.
627    */
628   public boolean isSplit() {
629     return this.split;
630   }
631 
632   /**
633    * @param split set split status
634    */
635   public void setSplit(boolean split) {
636     this.split = split;
637   }
638 
639   /**
640    * @return True if this region is offline.
641    */
642   public boolean isOffline() {
643     return this.offLine;
644   }
645 
646   /**
647    * The parent of a region split is offline while split daughters hold
648    * references to the parent. Offlined regions are closed.
649    * @param offLine Set online/offline status.
650    */
651   public void setOffline(boolean offLine) {
652     this.offLine = offLine;
653   }
654 
655 
656   /**
657    * @return True if this is a split parent region.
658    */
659   public boolean isSplitParent() {
660     if (!isSplit()) return false;
661     if (!isOffline()) {
662       LOG.warn("Region is split but NOT offline: " + getRegionNameAsString());
663     }
664     return true;
665   }
666 
667   /**
668    * @see java.lang.Object#toString()
669    */
670   @Override
671   public String toString() {
672     return "{" + HConstants.NAME + " => '" +
673       this.regionNameStr
674       + "', STARTKEY => '" +
675       Bytes.toStringBinary(this.startKey) + "', ENDKEY => '" +
676       Bytes.toStringBinary(this.endKey) +
677       "', ENCODED => " + getEncodedName() + "," +
678       (isOffline()? " OFFLINE => true,": "") +
679       (isSplit()? " SPLIT => true,": "") + "}";
680   }
681 
682   /**
683    * @see java.lang.Object#equals(java.lang.Object)
684    */
685   @Override
686   public boolean equals(Object o) {
687     if (this == o) {
688       return true;
689     }
690     if (o == null) {
691       return false;
692     }
693     if (!(o instanceof HRegionInfo)) {
694       return false;
695     }
696     return this.compareTo((HRegionInfo)o) == 0;
697   }
698 
699   /**
700    * @see java.lang.Object#hashCode()
701    */
702   @Override
703   public int hashCode() {
704     return this.hashCode;
705   }
706 
707   /** @return the object version number */
708   @Override
709   public byte getVersion() {
710     return VERSION;
711   }
712 
713   //
714   // Writable
715   //
716 
717   @Override
718   public void write(DataOutput out) throws IOException {
719     super.write(out);
720     Bytes.writeByteArray(out, endKey);
721     out.writeBoolean(offLine);
722     out.writeLong(regionId);
723     Bytes.writeByteArray(out, regionName);
724     out.writeBoolean(split);
725     Bytes.writeByteArray(out, startKey);
726     Bytes.writeByteArray(out, tableName);
727     out.writeInt(hashCode);
728   }
729 
730   @Override
731   public void readFields(DataInput in) throws IOException {
732     // Read the single version byte.  We don't ask the super class do it
733     // because freaks out if its not the current classes' version.  This method
734     // can deserialize version 0 and version 1 of HRI.
735     byte version = in.readByte();
736     if (version == 0) {
737       // This is the old HRI that carried an HTD.  Migrate it.  The below
738       // was copied from the old 0.90 HRI readFields.
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       try {
747         HTableDescriptor htd = new HTableDescriptor();
748         htd.readFields(in);
749         this.tableName = htd.getName();
750       } catch(EOFException eofe) {
751          throw new IOException("HTD not found in input buffer", eofe);
752       }
753       this.hashCode = in.readInt();
754     } else if (getVersion() == version) {
755       this.endKey = Bytes.readByteArray(in);
756       this.offLine = in.readBoolean();
757       this.regionId = in.readLong();
758       this.regionName = Bytes.readByteArray(in);
759       this.regionNameStr = Bytes.toStringBinary(this.regionName);
760       this.split = in.readBoolean();
761       this.startKey = Bytes.readByteArray(in);
762       this.tableName = Bytes.readByteArray(in);
763       this.hashCode = in.readInt();
764     } else {
765       throw new IOException("Non-migratable/unknown version=" + getVersion());
766     }
767   }
768 
769   //
770   // Comparable
771   //
772 
773   public int compareTo(HRegionInfo o) {
774     if (o == null) {
775       return 1;
776     }
777 
778     // Are regions of same table?
779     int result = Bytes.compareTo(this.tableName, o.tableName);
780     if (result != 0) {
781       return result;
782     }
783 
784     // Compare start keys.
785     result = Bytes.compareTo(this.startKey, o.startKey);
786     if (result != 0) {
787       return result;
788     }
789 
790     // Compare end keys.
791     result = Bytes.compareTo(this.endKey, o.endKey);
792 
793     if (result != 0) {
794       if (this.getStartKey().length != 0
795               && this.getEndKey().length == 0) {
796           return 1; // this is last region
797       }
798       if (o.getStartKey().length != 0
799               && o.getEndKey().length == 0) {
800           return -1; // o is the last region
801       }
802       return result;
803     }
804 
805     // regionId is usually milli timestamp -- this defines older stamps
806     // to be "smaller" than newer stamps in sort order.
807     if (this.regionId > o.regionId) {
808       return 1;
809     } else if (this.regionId < o.regionId) {
810       return -1;
811     }
812 
813     if (this.offLine == o.offLine)
814       return 0;
815     if (this.offLine == true) return -1;
816         
817     return 1;
818   }
819 
820   /**
821    * @return Comparator to use comparing {@link KeyValue}s.
822    */
823   public KVComparator getComparator() {
824     return isRootRegion()? KeyValue.ROOT_COMPARATOR: isMetaRegion()?
825       KeyValue.META_COMPARATOR: KeyValue.COMPARATOR;
826   }
827 }