001/*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.client;
020
021import edu.umd.cs.findbugs.annotations.CheckForNull;
022
023import java.io.DataInputStream;
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Comparator;
028import java.util.List;
029import java.util.stream.Collectors;
030
031import org.apache.hadoop.hbase.HConstants;
032import org.apache.hadoop.hbase.TableName;
033import org.apache.hadoop.hbase.exceptions.DeserializationException;
034import org.apache.hadoop.hbase.util.ByteArrayHashKey;
035import org.apache.hadoop.hbase.util.Bytes;
036import org.apache.hadoop.hbase.util.HashKey;
037import org.apache.hadoop.hbase.util.JenkinsHash;
038import org.apache.hadoop.hbase.util.MD5Hash;
039import org.apache.hadoop.io.DataInputBuffer;
040import org.apache.hadoop.util.StringUtils;
041import org.apache.yetus.audience.InterfaceAudience;
042
043import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
044import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
045
046/**
047 * Information about a region. A region is a range of keys in the whole keyspace
048 * of a table, an identifier (a timestamp) for differentiating between subset
049 * ranges (after region split) and a replicaId for differentiating the instance
050 * for the same range and some status information about the region.
051 *
052 * The region has a unique name which consists of the following fields:
053 * <ul>
054 * <li> tableName   : The name of the table </li>
055 * <li> startKey    : The startKey for the region. </li>
056 * <li> regionId    : A timestamp when the region is created. </li>
057 * <li> replicaId   : An id starting from 0 to differentiate replicas of the
058 * same region range but hosted in separated servers. The same region range can
059 * be hosted in multiple locations.</li>
060 * <li> encodedName : An MD5 encoded string for the region name.</li>
061 * </ul>
062 *
063 * <br> Other than the fields in the region name, region info contains:
064 * <ul>
065 * <li> endKey      : the endKey for the region (exclusive) </li>
066 * <li> split       : Whether the region is split </li>
067 * <li> offline     : Whether the region is offline </li>
068 * </ul>
069 *
070 */
071@InterfaceAudience.Public
072public interface RegionInfo {
073  RegionInfo UNDEFINED = RegionInfoBuilder.newBuilder(TableName.valueOf("__UNDEFINED__")).build();
074  /**
075   * Separator used to demarcate the encodedName in a region name
076   * in the new format. See description on new format above.
077   */
078  @InterfaceAudience.Private
079  int ENC_SEPARATOR = '.';
080
081  @InterfaceAudience.Private
082  int MD5_HEX_LENGTH = 32;
083
084  @InterfaceAudience.Private
085  int DEFAULT_REPLICA_ID = 0;
086
087  /**
088   * to keep appended int's sorted in string format. Only allows 2 bytes
089   * to be sorted for replicaId.
090   */
091  @InterfaceAudience.Private
092  String REPLICA_ID_FORMAT = "%04X";
093
094  @InterfaceAudience.Private
095  byte REPLICA_ID_DELIMITER = (byte)'_';
096
097  @InterfaceAudience.Private
098  String INVALID_REGION_NAME_FORMAT_MESSAGE = "Invalid regionName format";
099
100  @InterfaceAudience.Private
101  Comparator<RegionInfo> COMPARATOR
102    = (RegionInfo lhs, RegionInfo rhs) -> {
103      if (rhs == null) {
104        return 1;
105      }
106
107      // Are regions of same table?
108      int result = lhs.getTable().compareTo(rhs.getTable());
109      if (result != 0) {
110        return result;
111      }
112
113      // Compare start keys.
114      result = Bytes.compareTo(lhs.getStartKey(), rhs.getStartKey());
115      if (result != 0) {
116        return result;
117      }
118
119      // Compare end keys.
120      result = Bytes.compareTo(lhs.getEndKey(), rhs.getEndKey());
121
122      if (result != 0) {
123        if (lhs.getStartKey().length != 0
124                && lhs.getEndKey().length == 0) {
125            return 1; // this is last region
126        }
127        if (rhs.getStartKey().length != 0
128                && rhs.getEndKey().length == 0) {
129            return -1; // o is the last region
130        }
131        return result;
132      }
133
134      // regionId is usually milli timestamp -- this defines older stamps
135      // to be "smaller" than newer stamps in sort order.
136      if (lhs.getRegionId() > rhs.getRegionId()) {
137        return 1;
138      } else if (lhs.getRegionId() < rhs.getRegionId()) {
139        return -1;
140      }
141
142      int replicaDiff = lhs.getReplicaId() - rhs.getReplicaId();
143      if (replicaDiff != 0) {
144        return replicaDiff;
145      }
146
147      if (lhs.isOffline() == rhs.isOffline()) {
148        return 0;
149      }
150      if (lhs.isOffline()) {
151        return -1;
152      }
153
154      return 1;
155  };
156
157
158  /**
159   * @return Return a short, printable name for this region
160   * (usually encoded name) for us logging.
161   */
162  String getShortNameToLog();
163
164  /**
165   * @return the regionId.
166   */
167  long getRegionId();
168
169  /**
170   * @return the regionName as an array of bytes.
171   * @see #getRegionNameAsString()
172   */
173  byte [] getRegionName();
174
175  /**
176   * @return Region name as a String for use in logging, etc.
177   */
178  String getRegionNameAsString();
179
180  /**
181   * @return the encoded region name.
182   */
183  String getEncodedName();
184
185  /**
186   * @return the encoded region name as an array of bytes.
187   */
188  byte [] getEncodedNameAsBytes();
189
190  /**
191   * @return the startKey.
192   */
193  byte [] getStartKey();
194
195  /**
196   * @return the endKey.
197   */
198  byte [] getEndKey();
199
200  /**
201   * @return current table name of the region
202   */
203  TableName getTable();
204
205  /**
206   * @return returns region replica id
207   */
208  int getReplicaId();
209
210  /**
211   * @return True if has been split and has daughters.
212   */
213  boolean isSplit();
214
215  /**
216   * @return True if this region is offline.
217   */
218  boolean isOffline();
219
220  /**
221   * @return True if this is a split parent region.
222   */
223  boolean isSplitParent();
224
225  /**
226   * @return true if this region is a meta region.
227   */
228  boolean isMetaRegion();
229
230  /**
231   * @return true if the given inclusive range of rows is fully contained
232   * by this region. For example, if the region is foo,a,g and this is
233   * passed ["b","c"] or ["a","c"] it will return true, but if this is passed
234   * ["b","z"] it will return false.
235   * @throws IllegalArgumentException if the range passed is invalid (ie. end &lt; start)
236   */
237  boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey);
238
239  /**
240   * @return true if the given row falls in this region.
241   */
242  boolean containsRow(byte[] row);
243
244  /**
245   * Does region name contain its encoded name?
246   * @param regionName region name
247   * @return boolean indicating if this a new format region
248   *         name which contains its encoded name.
249   */
250  @InterfaceAudience.Private
251  static boolean hasEncodedName(final byte[] regionName) {
252    // check if region name ends in ENC_SEPARATOR
253    return (regionName.length >= 1) &&
254      (regionName[regionName.length - 1] == RegionInfo.ENC_SEPARATOR);
255  }
256
257  /**
258   * @return the encodedName
259   */
260  @InterfaceAudience.Private
261  static String encodeRegionName(final byte [] regionName) {
262    String encodedName;
263    if (hasEncodedName(regionName)) {
264      // region is in new format:
265      // <tableName>,<startKey>,<regionIdTimeStamp>/encodedName/
266      encodedName = Bytes.toString(regionName,
267      regionName.length - MD5_HEX_LENGTH - 1,
268      MD5_HEX_LENGTH);
269    } else {
270      // old format region name. First hbase:meta region also
271      // use this format.EncodedName is the JenkinsHash value.
272      HashKey<byte[]> key = new ByteArrayHashKey(regionName, 0, regionName.length);
273      int hashVal = Math.abs(JenkinsHash.getInstance().hash(key, 0));
274      encodedName = String.valueOf(hashVal);
275    }
276    return encodedName;
277  }
278
279  @InterfaceAudience.Private
280  static String getRegionNameAsString(byte[] regionName) {
281    return getRegionNameAsString(null, regionName);
282  }
283
284  @InterfaceAudience.Private
285  static String getRegionNameAsString(@CheckForNull RegionInfo ri, byte[] regionName) {
286    if (RegionInfo.hasEncodedName(regionName)) {
287      // new format region names already have their encoded name.
288      return Bytes.toStringBinary(regionName);
289    }
290
291    // old format. regionNameStr doesn't have the region name.
292    if (ri == null) {
293      return Bytes.toStringBinary(regionName) + "." + RegionInfo.encodeRegionName(regionName);
294    } else {
295      return Bytes.toStringBinary(regionName) + "." + ri.getEncodedName();
296    }
297  }
298
299  /**
300   * @return Return a String of short, printable names for <code>hris</code>
301   * (usually encoded name) for us logging.
302   */
303  static String getShortNameToLog(RegionInfo...hris) {
304    return getShortNameToLog(Arrays.asList(hris));
305  }
306
307  /**
308   * @return Return a String of short, printable names for <code>hris</code>
309   * (usually encoded name) for us logging.
310   */
311  static String getShortNameToLog(final List<RegionInfo> ris) {
312    return ris.stream().map(ri -> ri.getShortNameToLog()).
313    collect(Collectors.toList()).toString();
314  }
315
316  /**
317   * Gets the table name from the specified region name.
318   * @param regionName to extract the table name from
319   * @return Table name
320   */
321  @InterfaceAudience.Private
322  // This method should never be used. Its awful doing parse from bytes.
323  // It is fallback in case we can't get the tablename any other way. Could try removing it.
324  // Keeping it Audience Private so can remove at later date.
325  static TableName getTable(final byte [] regionName) {
326    int offset = -1;
327    for (int i = 0; i < regionName.length; i++) {
328      if (regionName[i] == HConstants.DELIMITER) {
329        offset = i;
330        break;
331      }
332    }
333    if (offset <= 0) {
334      throw new IllegalArgumentException("offset=" + offset);
335    }
336    byte[] buff  = new byte[offset];
337    System.arraycopy(regionName, 0, buff, 0, offset);
338    return TableName.valueOf(buff);
339  }
340
341  /**
342   * Gets the start key from the specified region name.
343   * @return Start key.
344   */
345  static byte[] getStartKey(final byte[] regionName) throws IOException {
346    return parseRegionName(regionName)[1];
347  }
348
349  @InterfaceAudience.Private
350  static boolean isEncodedRegionName(byte[] regionName) throws IOException {
351    try {
352      parseRegionName(regionName);
353      return false;
354    } catch (IOException e) {
355      if (StringUtils.stringifyException(e)
356      .contains(INVALID_REGION_NAME_FORMAT_MESSAGE)) {
357        return true;
358      }
359      throw e;
360    }
361  }
362
363  /**
364   * @return A deserialized {@link RegionInfo}
365   * or null if we failed deserialize or passed bytes null
366   */
367  @InterfaceAudience.Private
368  static RegionInfo parseFromOrNull(final byte [] bytes) {
369    if (bytes == null) return null;
370    return parseFromOrNull(bytes, 0, bytes.length);
371  }
372
373  /**
374   * @return A deserialized {@link RegionInfo} or null
375   *  if we failed deserialize or passed bytes null
376   */
377  @InterfaceAudience.Private
378  static RegionInfo parseFromOrNull(final byte [] bytes, int offset, int len) {
379    if (bytes == null || len <= 0) return null;
380    try {
381      return parseFrom(bytes, offset, len);
382    } catch (DeserializationException e) {
383      return null;
384    }
385  }
386
387  /**
388   * @param bytes A pb RegionInfo serialized with a pb magic prefix.
389   * @return A deserialized {@link RegionInfo}
390   */
391  @InterfaceAudience.Private
392  static RegionInfo parseFrom(final byte [] bytes) throws DeserializationException {
393    if (bytes == null) return null;
394    return parseFrom(bytes, 0, bytes.length);
395  }
396
397  /**
398   * @param bytes A pb RegionInfo serialized with a pb magic prefix.
399   * @param offset starting point in the byte array
400   * @param len length to read on the byte array
401   * @return A deserialized {@link RegionInfo}
402   */
403  @InterfaceAudience.Private
404  static RegionInfo parseFrom(final byte [] bytes, int offset, int len)
405  throws DeserializationException {
406    if (ProtobufUtil.isPBMagicPrefix(bytes, offset, len)) {
407      int pblen = ProtobufUtil.lengthOfPBMagic();
408      try {
409        HBaseProtos.RegionInfo.Builder builder = HBaseProtos.RegionInfo.newBuilder();
410        ProtobufUtil.mergeFrom(builder, bytes, pblen + offset, len - pblen);
411        HBaseProtos.RegionInfo ri = builder.build();
412        return ProtobufUtil.toRegionInfo(ri);
413      } catch (IOException e) {
414        throw new DeserializationException(e);
415      }
416    } else {
417      throw new DeserializationException("PB encoded RegionInfo expected");
418    }
419  }
420
421  /**
422   * Check whether two regions are adjacent; i.e. lies just before or just
423   * after in a table.
424   * @return true if two regions are adjacent
425   */
426  static boolean areAdjacent(RegionInfo regionA, RegionInfo regionB) {
427    if (regionA == null || regionB == null) {
428      throw new IllegalArgumentException(
429      "Can't check whether adjacent for null region");
430    }
431    if (!regionA.getTable().equals(regionB.getTable())) {
432      return false;
433    }
434    RegionInfo a = regionA;
435    RegionInfo b = regionB;
436    if (Bytes.compareTo(a.getStartKey(), b.getStartKey()) > 0) {
437      a = regionB;
438      b = regionA;
439    }
440    return Bytes.equals(a.getEndKey(), b.getStartKey());
441  }
442
443  /**
444   * @return This instance serialized as protobuf w/ a magic pb prefix.
445   * @see #parseFrom(byte[])
446   */
447  static byte [] toByteArray(RegionInfo ri) {
448    byte [] bytes = ProtobufUtil.toRegionInfo(ri).toByteArray();
449    return ProtobufUtil.prependPBMagic(bytes);
450  }
451
452  /**
453   * Use logging.
454   * @param encodedRegionName The encoded regionname.
455   * @return <code>hbase:meta</code> if passed <code>1028785192</code> else returns
456   * <code>encodedRegionName</code>
457   */
458  static String prettyPrint(final String encodedRegionName) {
459    if (encodedRegionName.equals("1028785192")) {
460      return encodedRegionName + "/hbase:meta";
461    }
462    return encodedRegionName;
463  }
464
465  /**
466   * Make a region name of passed parameters.
467   * @param startKey Can be null
468   * @param regionid Region id (Usually timestamp from when region was created).
469   * @param newFormat should we create the region name in the new format
470   *                  (such that it contains its encoded name?).
471   * @return Region name made of passed tableName, startKey and id
472   */
473  static byte [] createRegionName(final TableName tableName, final byte[] startKey,
474                                  final long regionid, boolean newFormat) {
475    return createRegionName(tableName, startKey, Long.toString(regionid), newFormat);
476  }
477
478  /**
479   * Make a region name of passed parameters.
480   * @param startKey Can be null
481   * @param id Region id (Usually timestamp from when region was created).
482   * @param newFormat should we create the region name in the new format
483   *                  (such that it contains its encoded name?).
484   * @return Region name made of passed tableName, startKey and id
485   */
486  static byte [] createRegionName(final TableName tableName,
487                                  final byte[] startKey, final String id, boolean newFormat) {
488    return createRegionName(tableName, startKey, Bytes.toBytes(id), newFormat);
489  }
490
491  /**
492   * Make a region name of passed parameters.
493   * @param startKey Can be null
494   * @param regionid Region id (Usually timestamp from when region was created).
495   * @param newFormat should we create the region name in the new format
496   *                  (such that it contains its encoded name?).
497   * @return Region name made of passed tableName, startKey, id and replicaId
498   */
499  static byte [] createRegionName(final TableName tableName,
500      final byte[] startKey, final long regionid, int replicaId, boolean newFormat) {
501    return createRegionName(tableName, startKey, Bytes.toBytes(Long.toString(regionid)),
502      replicaId, newFormat);
503  }
504
505  /**
506   * Make a region name of passed parameters.
507   * @param startKey Can be null
508   * @param id Region id (Usually timestamp from when region was created).
509   * @param newFormat should we create the region name in the new format
510   *                  (such that it contains its encoded name?).
511   * @return Region name made of passed tableName, startKey and id
512   */
513  static byte [] createRegionName(final TableName tableName,
514      final byte[] startKey, final byte[] id, boolean newFormat) {
515    return createRegionName(tableName, startKey, id, DEFAULT_REPLICA_ID, newFormat);
516  }
517
518  /**
519   * Make a region name of passed parameters.
520   * @param startKey Can be null
521   * @param id Region id (Usually timestamp from when region was created).
522   * @param newFormat should we create the region name in the new format
523   * @return Region name made of passed tableName, startKey, id and replicaId
524   */
525  static byte [] createRegionName(final TableName tableName,
526      final byte[] startKey, final byte[] id, final int replicaId, boolean newFormat) {
527    int len = tableName.getName().length + 2 + id.length + (startKey == null? 0: startKey.length);
528    if (newFormat) {
529      len += MD5_HEX_LENGTH + 2;
530    }
531    byte[] replicaIdBytes = null;
532    // Special casing: replicaId is only appended if replicaId is greater than
533    // 0. This is because all regions in meta would have to be migrated to the new
534    // name otherwise
535    if (replicaId > 0) {
536      // use string representation for replica id
537      replicaIdBytes = Bytes.toBytes(String.format(REPLICA_ID_FORMAT, replicaId));
538      len += 1 + replicaIdBytes.length;
539    }
540
541    byte [] b = new byte [len];
542
543    int offset = tableName.getName().length;
544    System.arraycopy(tableName.getName(), 0, b, 0, offset);
545    b[offset++] = HConstants.DELIMITER;
546    if (startKey != null && startKey.length > 0) {
547      System.arraycopy(startKey, 0, b, offset, startKey.length);
548      offset += startKey.length;
549    }
550    b[offset++] = HConstants.DELIMITER;
551    System.arraycopy(id, 0, b, offset, id.length);
552    offset += id.length;
553
554    if (replicaIdBytes != null) {
555      b[offset++] = REPLICA_ID_DELIMITER;
556      System.arraycopy(replicaIdBytes, 0, b, offset, replicaIdBytes.length);
557      offset += replicaIdBytes.length;
558    }
559
560    if (newFormat) {
561      //
562      // Encoded name should be built into the region name.
563      //
564      // Use the region name thus far (namely, <tablename>,<startKey>,<id>_<replicaId>)
565      // to compute a MD5 hash to be used as the encoded name, and append
566      // it to the byte buffer.
567      //
568      String md5Hash = MD5Hash.getMD5AsHex(b, 0, offset);
569      byte [] md5HashBytes = Bytes.toBytes(md5Hash);
570
571      if (md5HashBytes.length != MD5_HEX_LENGTH) {
572        System.out.println("MD5-hash length mismatch: Expected=" + MD5_HEX_LENGTH +
573        "; Got=" + md5HashBytes.length);
574      }
575
576      // now append the bytes '.<encodedName>.' to the end
577      b[offset++] = ENC_SEPARATOR;
578      System.arraycopy(md5HashBytes, 0, b, offset, MD5_HEX_LENGTH);
579      offset += MD5_HEX_LENGTH;
580      b[offset] = ENC_SEPARATOR;
581    }
582
583    return b;
584  }
585
586  /**
587   * Creates a RegionInfo object for MOB data.
588   *
589   * @param tableName the name of the table
590   * @return the MOB {@link RegionInfo}.
591   */
592  static RegionInfo createMobRegionInfo(TableName tableName) {
593    return RegionInfoBuilder.newBuilder(tableName)
594        .setStartKey(Bytes.toBytes(".mob")).setRegionId(0).build();
595  }
596
597  /**
598   * Separate elements of a regionName.
599   * @return Array of byte[] containing tableName, startKey and id
600   */
601  static byte [][] parseRegionName(final byte[] regionName)
602  throws IOException {
603    // Region name is of the format:
604    // tablename,startkey,regionIdTimestamp[_replicaId][.encodedName.]
605    // startkey can contain the delimiter (',') so we parse from the start and end
606
607    // parse from start
608    int offset = -1;
609    for (int i = 0; i < regionName.length; i++) {
610      if (regionName[i] == HConstants.DELIMITER) {
611        offset = i;
612        break;
613      }
614    }
615    if (offset == -1) {
616      throw new IOException(INVALID_REGION_NAME_FORMAT_MESSAGE
617      + ": " + Bytes.toStringBinary(regionName));
618    }
619    byte[] tableName = new byte[offset];
620    System.arraycopy(regionName, 0, tableName, 0, offset);
621    offset = -1;
622
623    int endOffset = regionName.length;
624    // check whether regionName contains encodedName
625    if (regionName.length > MD5_HEX_LENGTH + 2
626    && regionName[regionName.length-1] == ENC_SEPARATOR
627    && regionName[regionName.length-MD5_HEX_LENGTH-2] == ENC_SEPARATOR) {
628      endOffset = endOffset - MD5_HEX_LENGTH - 2;
629    }
630
631    // parse from end
632    byte[] replicaId = null;
633    int idEndOffset = endOffset;
634    for (int i = endOffset - 1; i > 0; i--) {
635      if (regionName[i] == REPLICA_ID_DELIMITER) { //replicaId may or may not be present
636        replicaId = new byte[endOffset - i - 1];
637        System.arraycopy(regionName, i + 1, replicaId, 0,
638        endOffset - i - 1);
639        idEndOffset = i;
640        // do not break, continue to search for id
641      }
642      if (regionName[i] == HConstants.DELIMITER) {
643        offset = i;
644        break;
645      }
646    }
647    if (offset == -1) {
648      throw new IOException(INVALID_REGION_NAME_FORMAT_MESSAGE
649      + ": " + Bytes.toStringBinary(regionName));
650    }
651    byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
652    if(offset != tableName.length + 1) {
653      startKey = new byte[offset - tableName.length - 1];
654      System.arraycopy(regionName, tableName.length + 1, startKey, 0,
655      offset - tableName.length - 1);
656    }
657    byte [] id = new byte[idEndOffset - offset - 1];
658    System.arraycopy(regionName, offset + 1, id, 0,
659    idEndOffset - offset - 1);
660    byte [][] elements = new byte[replicaId == null ? 3 : 4][];
661    elements[0] = tableName;
662    elements[1] = startKey;
663    elements[2] = id;
664    if (replicaId != null) {
665      elements[3] = replicaId;
666    }
667    return elements;
668  }
669
670  /**
671   * Serializes given RegionInfo's as a byte array. Use this instead of
672   * {@link RegionInfo#toByteArray(RegionInfo)} when
673   * writing to a stream and you want to use the pb mergeDelimitedFrom (w/o the delimiter, pb reads
674   * to EOF which may not be what you want). {@link #parseDelimitedFrom(byte[], int, int)} can
675   * be used to read back the instances.
676   * @param infos RegionInfo objects to serialize
677   * @return This instance serialized as a delimited protobuf w/ a magic pb prefix.
678   */
679  static byte[] toDelimitedByteArray(RegionInfo... infos) throws IOException {
680    byte[][] bytes = new byte[infos.length][];
681    int size = 0;
682    for (int i = 0; i < infos.length; i++) {
683      bytes[i] = toDelimitedByteArray(infos[i]);
684      size += bytes[i].length;
685    }
686
687    byte[] result = new byte[size];
688    int offset = 0;
689    for (byte[] b : bytes) {
690      System.arraycopy(b, 0, result, offset, b.length);
691      offset += b.length;
692    }
693    return result;
694  }
695
696  /**
697   * Use this instead of {@link RegionInfo#toByteArray(RegionInfo)} when writing to a stream and you want to use
698   * the pb mergeDelimitedFrom (w/o the delimiter, pb reads to EOF which may not be what you want).
699   * @return This instance serialized as a delimied protobuf w/ a magic pb prefix.
700   */
701  static byte [] toDelimitedByteArray(RegionInfo ri) throws IOException {
702    return ProtobufUtil.toDelimitedByteArray(ProtobufUtil.toRegionInfo(ri));
703  }
704
705  /**
706   * Parses an RegionInfo instance from the passed in stream.
707   * Presumes the RegionInfo was serialized to the stream with
708   * {@link #toDelimitedByteArray(RegionInfo)}.
709   * @return An instance of RegionInfo.
710   */
711  static RegionInfo parseFrom(final DataInputStream in) throws IOException {
712    // I need to be able to move back in the stream if this is not a pb
713    // serialization so I can do the Writable decoding instead.
714    int pblen = ProtobufUtil.lengthOfPBMagic();
715    byte [] pbuf = new byte[pblen];
716    if (in.markSupported()) { //read it with mark()
717      in.mark(pblen);
718    }
719
720    //assumption: if Writable serialization, it should be longer than pblen.
721    int read = in.read(pbuf);
722    if (read != pblen) throw new IOException("read=" + read + ", wanted=" + pblen);
723    if (ProtobufUtil.isPBMagicPrefix(pbuf)) {
724      return ProtobufUtil.toRegionInfo(HBaseProtos.RegionInfo.parseDelimitedFrom(in));
725    } else {
726      throw new IOException("PB encoded RegionInfo expected");
727    }
728  }
729
730  /**
731   * Parses all the RegionInfo instances from the passed in stream until EOF. Presumes the
732   * RegionInfo's were serialized to the stream with oDelimitedByteArray()
733   * @param bytes serialized bytes
734   * @param offset the start offset into the byte[] buffer
735   * @param length how far we should read into the byte[] buffer
736   * @return All the RegionInfos that are in the byte array. Keeps reading till we hit the end.
737   */
738  static List<RegionInfo> parseDelimitedFrom(final byte[] bytes, final int offset,
739                                             final int length) throws IOException {
740    if (bytes == null) {
741      throw new IllegalArgumentException("Can't build an object with empty bytes array");
742    }
743    List<RegionInfo> ris = new ArrayList<>();
744    try (DataInputBuffer in = new DataInputBuffer()) {
745      in.reset(bytes, offset, length);
746      while (in.available() > 0) {
747        RegionInfo ri = parseFrom(in);
748        ris.add(ri);
749      }
750    }
751    return ris;
752  }
753
754  /**
755   * @return True if this is first Region in Table
756   */
757  default boolean isFirst() {
758    return Bytes.equals(getStartKey(), HConstants.EMPTY_START_ROW);
759  }
760
761  /**
762   * @return True if this is last Region in Table
763   */
764  default boolean isLast() {
765    return Bytes.equals(getEndKey(), HConstants.EMPTY_START_ROW);
766  }
767
768  /**
769   * @return True if region is next, adjacent but 'after' this one.
770   * @see #isAdjacent(RegionInfo)
771   * @see #areAdjacent(RegionInfo, RegionInfo)
772   */
773  default boolean isNext(RegionInfo after) {
774    return getTable().equals(after.getTable()) && Bytes.equals(getEndKey(), after.getStartKey());
775  }
776
777  /**
778   * @return True if region is adjacent, either just before or just after this one.
779   * @see #isNext(RegionInfo)
780   */
781  default boolean isAdjacent(RegionInfo other) {
782    return getTable().equals(other.getTable()) && areAdjacent(this, other);
783  }
784
785  /**
786   * @return True if RegionInfo is degenerate... if startKey > endKey.
787   */
788  default boolean isDegenerate() {
789    return !isLast() && Bytes.compareTo(getStartKey(), getEndKey()) > 0;
790  }
791
792  /**
793   * @return True if an overlap in region range.
794   * @see #isDegenerate()
795   */
796  default boolean isOverlap(RegionInfo other) {
797    if (!getTable().equals(other.getTable())) {
798      return false;
799    }
800    int startKeyCompare = Bytes.compareTo(getStartKey(), other.getStartKey());
801    if (startKeyCompare == 0) {
802      return true;
803    }
804    if (startKeyCompare < 0) {
805      if (isLast()) {
806        return true;
807      }
808      return Bytes.compareTo(getEndKey(), other.getStartKey()) > 0;
809    }
810    if (other.isLast()) {
811      return true;
812    }
813    return Bytes.compareTo(getStartKey(), other.getEndKey()) < 0;
814  }
815}