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