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