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