blob: 7a03e052a316d124dec76677d4b4b169d728c9f3 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.client;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.exceptions.DeserializationException;
import org.apache.hadoop.hbase.util.ByteArrayHashKey;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.HashKey;
import org.apache.hadoop.hbase.util.JenkinsHash;
import org.apache.hadoop.hbase.util.MD5Hash;
import org.apache.hadoop.io.DataInputBuffer;
import org.apache.hadoop.util.StringUtils;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
/**
* Information about a region. A region is a range of keys in the whole keyspace
* of a table, an identifier (a timestamp) for differentiating between subset
* ranges (after region split) and a replicaId for differentiating the instance
* for the same range and some status information about the region.
*
* The region has a unique name which consists of the following fields:
* <ul>
* <li> tableName : The name of the table </li>
* <li> startKey : The startKey for the region. </li>
* <li> regionId : A timestamp when the region is created. </li>
* <li> replicaId : An id starting from 0 to differentiate replicas of the
* same region range but hosted in separated servers. The same region range can
* be hosted in multiple locations.</li>
* <li> encodedName : An MD5 encoded string for the region name.</li>
* </ul>
*
* <br> Other than the fields in the region name, region info contains:
* <ul>
* <li> endKey : the endKey for the region (exclusive) </li>
* <li> split : Whether the region is split </li>
* <li> offline : Whether the region is offline </li>
* </ul>
*
*/
@InterfaceAudience.Public
public interface RegionInfo {
RegionInfo UNDEFINED = RegionInfoBuilder.newBuilder(TableName.valueOf("__UNDEFINED__")).build();
/**
* Separator used to demarcate the encodedName in a region name
* in the new format. See description on new format above.
*/
@InterfaceAudience.Private
int ENC_SEPARATOR = '.';
@InterfaceAudience.Private
int MD5_HEX_LENGTH = 32;
@InterfaceAudience.Private
int DEFAULT_REPLICA_ID = 0;
/**
* to keep appended int's sorted in string format. Only allows 2 bytes
* to be sorted for replicaId.
*/
@InterfaceAudience.Private
String REPLICA_ID_FORMAT = "%04X";
@InterfaceAudience.Private
byte REPLICA_ID_DELIMITER = (byte)'_';
@InterfaceAudience.Private
String INVALID_REGION_NAME_FORMAT_MESSAGE = "Invalid regionName format";
@InterfaceAudience.Private
Comparator<RegionInfo> COMPARATOR
= (RegionInfo lhs, RegionInfo rhs) -> {
if (rhs == null) {
return 1;
}
// Are regions of same table?
int result = lhs.getTable().compareTo(rhs.getTable());
if (result != 0) {
return result;
}
// Compare start keys.
result = Bytes.compareTo(lhs.getStartKey(), rhs.getStartKey());
if (result != 0) {
return result;
}
// Compare end keys.
result = Bytes.compareTo(lhs.getEndKey(), rhs.getEndKey());
if (result != 0) {
if (lhs.getStartKey().length != 0
&& lhs.getEndKey().length == 0) {
return 1; // this is last region
}
if (rhs.getStartKey().length != 0
&& rhs.getEndKey().length == 0) {
return -1; // o is the last region
}
return result;
}
// regionId is usually milli timestamp -- this defines older stamps
// to be "smaller" than newer stamps in sort order.
if (lhs.getRegionId() > rhs.getRegionId()) {
return 1;
} else if (lhs.getRegionId() < rhs.getRegionId()) {
return -1;
}
int replicaDiff = lhs.getReplicaId() - rhs.getReplicaId();
if (replicaDiff != 0) {
return replicaDiff;
}
if (lhs.isOffline() == rhs.isOffline()) {
return 0;
}
if (lhs.isOffline()) {
return -1;
}
return 1;
};
/**
* @return Return a short, printable name for this region
* (usually encoded name) for us logging.
*/
String getShortNameToLog();
/**
* @return the regionId.
*/
long getRegionId();
/**
* @return the regionName as an array of bytes.
* @see #getRegionNameAsString()
*/
byte [] getRegionName();
/**
* @return Region name as a String for use in logging, etc.
*/
String getRegionNameAsString();
/**
* @return the encoded region name.
*/
String getEncodedName();
/**
* @return the encoded region name as an array of bytes.
*/
byte [] getEncodedNameAsBytes();
/**
* @return the startKey.
*/
byte [] getStartKey();
/**
* @return the endKey.
*/
byte [] getEndKey();
/**
* @return current table name of the region
*/
TableName getTable();
/**
* @return returns region replica id
*/
int getReplicaId();
/**
* @return True if has been split and has daughters.
*/
boolean isSplit();
/**
* @return True if this region is offline.
*/
boolean isOffline();
/**
* @return True if this is a split parent region.
*/
boolean isSplitParent();
/**
* @return true if this region is a meta region.
*/
boolean isMetaRegion();
/**
* @return true if the given inclusive range of rows is fully contained
* by this region. For example, if the region is foo,a,g and this is
* passed ["b","c"] or ["a","c"] it will return true, but if this is passed
* ["b","z"] it will return false.
* @throws IllegalArgumentException if the range passed is invalid (ie. end &lt; start)
*/
boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey);
/**
* @return true if the given row falls in this region.
*/
boolean containsRow(byte[] row);
/**
* Does region name contain its encoded name?
* @param regionName region name
* @return boolean indicating if this a new format region
* name which contains its encoded name.
*/
@InterfaceAudience.Private
static boolean hasEncodedName(final byte[] regionName) {
// check if region name ends in ENC_SEPARATOR
return (regionName.length >= 1) &&
(regionName[regionName.length - 1] == RegionInfo.ENC_SEPARATOR);
}
/**
* @return the encodedName
*/
@InterfaceAudience.Private
static String encodeRegionName(final byte [] regionName) {
String encodedName;
if (hasEncodedName(regionName)) {
// region is in new format:
// <tableName>,<startKey>,<regionIdTimeStamp>/encodedName/
encodedName = Bytes.toString(regionName,
regionName.length - MD5_HEX_LENGTH - 1,
MD5_HEX_LENGTH);
} else {
// old format region name. First hbase:meta region also
// use this format.EncodedName is the JenkinsHash value.
HashKey<byte[]> key = new ByteArrayHashKey(regionName, 0, regionName.length);
int hashVal = Math.abs(JenkinsHash.getInstance().hash(key, 0));
encodedName = String.valueOf(hashVal);
}
return encodedName;
}
@InterfaceAudience.Private
static String getRegionNameAsString(byte[] regionName) {
return getRegionNameAsString(null, regionName);
}
@InterfaceAudience.Private
static String getRegionNameAsString(@CheckForNull RegionInfo ri, byte[] regionName) {
if (RegionInfo.hasEncodedName(regionName)) {
// new format region names already have their encoded name.
return Bytes.toStringBinary(regionName);
}
// old format. regionNameStr doesn't have the region name.
if (ri == null) {
return Bytes.toStringBinary(regionName) + "." + RegionInfo.encodeRegionName(regionName);
} else {
return Bytes.toStringBinary(regionName) + "." + ri.getEncodedName();
}
}
/**
* @return Return a String of short, printable names for <code>hris</code>
* (usually encoded name) for us logging.
*/
static String getShortNameToLog(RegionInfo...hris) {
return getShortNameToLog(Arrays.asList(hris));
}
/**
* @return Return a String of short, printable names for <code>hris</code>
* (usually encoded name) for us logging.
*/
static String getShortNameToLog(final List<RegionInfo> ris) {
return ris.stream().map(ri -> ri.getShortNameToLog()).
collect(Collectors.toList()).toString();
}
/**
* Gets the table name from the specified region name.
* @param regionName to extract the table name from
* @return Table name
*/
@InterfaceAudience.Private
// This method should never be used. Its awful doing parse from bytes.
// It is fallback in case we can't get the tablename any other way. Could try removing it.
// Keeping it Audience Private so can remove at later date.
static TableName getTable(final byte [] regionName) {
int offset = -1;
for (int i = 0; i < regionName.length; i++) {
if (regionName[i] == HConstants.DELIMITER) {
offset = i;
break;
}
}
if (offset <= 0) {
throw new IllegalArgumentException("offset=" + offset);
}
byte[] buff = new byte[offset];
System.arraycopy(regionName, 0, buff, 0, offset);
return TableName.valueOf(buff);
}
/**
* Gets the start key from the specified region name.
* @return Start key.
*/
static byte[] getStartKey(final byte[] regionName) throws IOException {
return parseRegionName(regionName)[1];
}
@InterfaceAudience.Private
static boolean isEncodedRegionName(byte[] regionName) throws IOException {
try {
parseRegionName(regionName);
return false;
} catch (IOException e) {
if (StringUtils.stringifyException(e)
.contains(INVALID_REGION_NAME_FORMAT_MESSAGE)) {
return true;
}
throw e;
}
}
/**
* @return A deserialized {@link RegionInfo}
* or null if we failed deserialize or passed bytes null
*/
@InterfaceAudience.Private
static RegionInfo parseFromOrNull(final byte [] bytes) {
if (bytes == null) return null;
return parseFromOrNull(bytes, 0, bytes.length);
}
/**
* @return A deserialized {@link RegionInfo} or null
* if we failed deserialize or passed bytes null
*/
@InterfaceAudience.Private
static RegionInfo parseFromOrNull(final byte [] bytes, int offset, int len) {
if (bytes == null || len <= 0) return null;
try {
return parseFrom(bytes, offset, len);
} catch (DeserializationException e) {
return null;
}
}
/**
* @param bytes A pb RegionInfo serialized with a pb magic prefix.
* @return A deserialized {@link RegionInfo}
*/
@InterfaceAudience.Private
static RegionInfo parseFrom(final byte [] bytes) throws DeserializationException {
if (bytes == null) return null;
return parseFrom(bytes, 0, bytes.length);
}
/**
* @param bytes A pb RegionInfo serialized with a pb magic prefix.
* @param offset starting point in the byte array
* @param len length to read on the byte array
* @return A deserialized {@link RegionInfo}
*/
@InterfaceAudience.Private
static RegionInfo parseFrom(final byte [] bytes, int offset, int len)
throws DeserializationException {
if (ProtobufUtil.isPBMagicPrefix(bytes, offset, len)) {
int pblen = ProtobufUtil.lengthOfPBMagic();
try {
HBaseProtos.RegionInfo.Builder builder = HBaseProtos.RegionInfo.newBuilder();
ProtobufUtil.mergeFrom(builder, bytes, pblen + offset, len - pblen);
HBaseProtos.RegionInfo ri = builder.build();
return ProtobufUtil.toRegionInfo(ri);
} catch (IOException e) {
throw new DeserializationException(e);
}
} else {
throw new DeserializationException("PB encoded RegionInfo expected");
}
}
/**
* Check whether two regions are adjacent; i.e. lies just before or just
* after in a table.
* @return true if two regions are adjacent
*/
static boolean areAdjacent(RegionInfo regionA, RegionInfo regionB) {
if (regionA == null || regionB == null) {
throw new IllegalArgumentException(
"Can't check whether adjacent for null region");
}
if (!regionA.getTable().equals(regionB.getTable())) {
return false;
}
RegionInfo a = regionA;
RegionInfo b = regionB;
if (Bytes.compareTo(a.getStartKey(), b.getStartKey()) > 0) {
a = regionB;
b = regionA;
}
return Bytes.equals(a.getEndKey(), b.getStartKey());
}
/**
* @return This instance serialized as protobuf w/ a magic pb prefix.
* @see #parseFrom(byte[])
*/
static byte [] toByteArray(RegionInfo ri) {
byte [] bytes = ProtobufUtil.toRegionInfo(ri).toByteArray();
return ProtobufUtil.prependPBMagic(bytes);
}
/**
* Use logging.
* @param encodedRegionName The encoded regionname.
* @return <code>hbase:meta</code> if passed <code>1028785192</code> else returns
* <code>encodedRegionName</code>
*/
static String prettyPrint(final String encodedRegionName) {
if (encodedRegionName.equals("1028785192")) {
return encodedRegionName + "/hbase:meta";
}
return encodedRegionName;
}
/**
* Make a region name of passed parameters.
* @param startKey Can be null
* @param regionid Region id (Usually timestamp from when region was created).
* @param newFormat should we create the region name in the new format
* (such that it contains its encoded name?).
* @return Region name made of passed tableName, startKey and id
*/
static byte [] createRegionName(final TableName tableName, final byte[] startKey,
final long regionid, boolean newFormat) {
return createRegionName(tableName, startKey, Long.toString(regionid), newFormat);
}
/**
* Make a region name of passed parameters.
* @param startKey Can be null
* @param id Region id (Usually timestamp from when region was created).
* @param newFormat should we create the region name in the new format
* (such that it contains its encoded name?).
* @return Region name made of passed tableName, startKey and id
*/
static byte [] createRegionName(final TableName tableName,
final byte[] startKey, final String id, boolean newFormat) {
return createRegionName(tableName, startKey, Bytes.toBytes(id), newFormat);
}
/**
* Make a region name of passed parameters.
* @param startKey Can be null
* @param regionid Region id (Usually timestamp from when region was created).
* @param newFormat should we create the region name in the new format
* (such that it contains its encoded name?).
* @return Region name made of passed tableName, startKey, id and replicaId
*/
static byte [] createRegionName(final TableName tableName,
final byte[] startKey, final long regionid, int replicaId, boolean newFormat) {
return createRegionName(tableName, startKey, Bytes.toBytes(Long.toString(regionid)),
replicaId, newFormat);
}
/**
* Make a region name of passed parameters.
* @param startKey Can be null
* @param id Region id (Usually timestamp from when region was created).
* @param newFormat should we create the region name in the new format
* (such that it contains its encoded name?).
* @return Region name made of passed tableName, startKey and id
*/
static byte [] createRegionName(final TableName tableName,
final byte[] startKey, final byte[] id, boolean newFormat) {
return createRegionName(tableName, startKey, id, DEFAULT_REPLICA_ID, newFormat);
}
/**
* Make a region name of passed parameters.
* @param startKey Can be null
* @param id Region id (Usually timestamp from when region was created).
* @param newFormat should we create the region name in the new format
* @return Region name made of passed tableName, startKey, id and replicaId
*/
static byte [] createRegionName(final TableName tableName,
final byte[] startKey, final byte[] id, final int replicaId, boolean newFormat) {
int len = tableName.getName().length + 2 + id.length + (startKey == null? 0: startKey.length);
if (newFormat) {
len += MD5_HEX_LENGTH + 2;
}
byte[] replicaIdBytes = null;
// Special casing: replicaId is only appended if replicaId is greater than
// 0. This is because all regions in meta would have to be migrated to the new
// name otherwise
if (replicaId > 0) {
// use string representation for replica id
replicaIdBytes = Bytes.toBytes(String.format(REPLICA_ID_FORMAT, replicaId));
len += 1 + replicaIdBytes.length;
}
byte [] b = new byte [len];
int offset = tableName.getName().length;
System.arraycopy(tableName.getName(), 0, b, 0, offset);
b[offset++] = HConstants.DELIMITER;
if (startKey != null && startKey.length > 0) {
System.arraycopy(startKey, 0, b, offset, startKey.length);
offset += startKey.length;
}
b[offset++] = HConstants.DELIMITER;
System.arraycopy(id, 0, b, offset, id.length);
offset += id.length;
if (replicaIdBytes != null) {
b[offset++] = REPLICA_ID_DELIMITER;
System.arraycopy(replicaIdBytes, 0, b, offset, replicaIdBytes.length);
offset += replicaIdBytes.length;
}
if (newFormat) {
//
// Encoded name should be built into the region name.
//
// Use the region name thus far (namely, <tablename>,<startKey>,<id>_<replicaId>)
// to compute a MD5 hash to be used as the encoded name, and append
// it to the byte buffer.
//
String md5Hash = MD5Hash.getMD5AsHex(b, 0, offset);
byte [] md5HashBytes = Bytes.toBytes(md5Hash);
if (md5HashBytes.length != MD5_HEX_LENGTH) {
System.out.println("MD5-hash length mismatch: Expected=" + MD5_HEX_LENGTH +
"; Got=" + md5HashBytes.length);
}
// now append the bytes '.<encodedName>.' to the end
b[offset++] = ENC_SEPARATOR;
System.arraycopy(md5HashBytes, 0, b, offset, MD5_HEX_LENGTH);
offset += MD5_HEX_LENGTH;
b[offset] = ENC_SEPARATOR;
}
return b;
}
/**
* Creates a RegionInfo object for MOB data.
*
* @param tableName the name of the table
* @return the MOB {@link RegionInfo}.
*/
static RegionInfo createMobRegionInfo(TableName tableName) {
return RegionInfoBuilder.newBuilder(tableName)
.setStartKey(Bytes.toBytes(".mob")).setRegionId(0).build();
}
/**
* Separate elements of a regionName.
* @return Array of byte[] containing tableName, startKey and id
*/
static byte [][] parseRegionName(final byte[] regionName)
throws IOException {
// Region name is of the format:
// tablename,startkey,regionIdTimestamp[_replicaId][.encodedName.]
// startkey can contain the delimiter (',') so we parse from the start and end
// parse from start
int offset = -1;
for (int i = 0; i < regionName.length; i++) {
if (regionName[i] == HConstants.DELIMITER) {
offset = i;
break;
}
}
if (offset == -1) {
throw new IOException(INVALID_REGION_NAME_FORMAT_MESSAGE
+ ": " + Bytes.toStringBinary(regionName));
}
byte[] tableName = new byte[offset];
System.arraycopy(regionName, 0, tableName, 0, offset);
offset = -1;
int endOffset = regionName.length;
// check whether regionName contains encodedName
if (regionName.length > MD5_HEX_LENGTH + 2
&& regionName[regionName.length-1] == ENC_SEPARATOR
&& regionName[regionName.length-MD5_HEX_LENGTH-2] == ENC_SEPARATOR) {
endOffset = endOffset - MD5_HEX_LENGTH - 2;
}
// parse from end
byte[] replicaId = null;
int idEndOffset = endOffset;
for (int i = endOffset - 1; i > 0; i--) {
if (regionName[i] == REPLICA_ID_DELIMITER) { //replicaId may or may not be present
replicaId = new byte[endOffset - i - 1];
System.arraycopy(regionName, i + 1, replicaId, 0,
endOffset - i - 1);
idEndOffset = i;
// do not break, continue to search for id
}
if (regionName[i] == HConstants.DELIMITER) {
offset = i;
break;
}
}
if (offset == -1) {
throw new IOException(INVALID_REGION_NAME_FORMAT_MESSAGE
+ ": " + Bytes.toStringBinary(regionName));
}
byte [] startKey = HConstants.EMPTY_BYTE_ARRAY;
if(offset != tableName.length + 1) {
startKey = new byte[offset - tableName.length - 1];
System.arraycopy(regionName, tableName.length + 1, startKey, 0,
offset - tableName.length - 1);
}
byte [] id = new byte[idEndOffset - offset - 1];
System.arraycopy(regionName, offset + 1, id, 0,
idEndOffset - offset - 1);
byte [][] elements = new byte[replicaId == null ? 3 : 4][];
elements[0] = tableName;
elements[1] = startKey;
elements[2] = id;
if (replicaId != null) {
elements[3] = replicaId;
}
return elements;
}
/**
* Serializes given RegionInfo's as a byte array. Use this instead of
* {@link RegionInfo#toByteArray(RegionInfo)} when
* writing to a stream and you want to use the pb mergeDelimitedFrom (w/o the delimiter, pb reads
* to EOF which may not be what you want). {@link #parseDelimitedFrom(byte[], int, int)} can
* be used to read back the instances.
* @param infos RegionInfo objects to serialize
* @return This instance serialized as a delimited protobuf w/ a magic pb prefix.
*/
static byte[] toDelimitedByteArray(RegionInfo... infos) throws IOException {
byte[][] bytes = new byte[infos.length][];
int size = 0;
for (int i = 0; i < infos.length; i++) {
bytes[i] = toDelimitedByteArray(infos[i]);
size += bytes[i].length;
}
byte[] result = new byte[size];
int offset = 0;
for (byte[] b : bytes) {
System.arraycopy(b, 0, result, offset, b.length);
offset += b.length;
}
return result;
}
/**
* Use this instead of {@link RegionInfo#toByteArray(RegionInfo)} when writing to a stream and you want to use
* the pb mergeDelimitedFrom (w/o the delimiter, pb reads to EOF which may not be what you want).
* @return This instance serialized as a delimied protobuf w/ a magic pb prefix.
*/
static byte [] toDelimitedByteArray(RegionInfo ri) throws IOException {
return ProtobufUtil.toDelimitedByteArray(ProtobufUtil.toRegionInfo(ri));
}
/**
* Parses an RegionInfo instance from the passed in stream.
* Presumes the RegionInfo was serialized to the stream with
* {@link #toDelimitedByteArray(RegionInfo)}.
* @return An instance of RegionInfo.
*/
static RegionInfo parseFrom(final DataInputStream in) throws IOException {
// I need to be able to move back in the stream if this is not a pb
// serialization so I can do the Writable decoding instead.
int pblen = ProtobufUtil.lengthOfPBMagic();
byte [] pbuf = new byte[pblen];
if (in.markSupported()) { //read it with mark()
in.mark(pblen);
}
//assumption: if Writable serialization, it should be longer than pblen.
int read = in.read(pbuf);
if (read != pblen) throw new IOException("read=" + read + ", wanted=" + pblen);
if (ProtobufUtil.isPBMagicPrefix(pbuf)) {
return ProtobufUtil.toRegionInfo(HBaseProtos.RegionInfo.parseDelimitedFrom(in));
} else {
throw new IOException("PB encoded RegionInfo expected");
}
}
/**
* Parses all the RegionInfo instances from the passed in stream until EOF. Presumes the
* RegionInfo's were serialized to the stream with oDelimitedByteArray()
* @param bytes serialized bytes
* @param offset the start offset into the byte[] buffer
* @param length how far we should read into the byte[] buffer
* @return All the RegionInfos that are in the byte array. Keeps reading till we hit the end.
*/
static List<RegionInfo> parseDelimitedFrom(final byte[] bytes, final int offset,
final int length) throws IOException {
if (bytes == null) {
throw new IllegalArgumentException("Can't build an object with empty bytes array");
}
List<RegionInfo> ris = new ArrayList<>();
try (DataInputBuffer in = new DataInputBuffer()) {
in.reset(bytes, offset, length);
while (in.available() > 0) {
RegionInfo ri = parseFrom(in);
ris.add(ri);
}
}
return ris;
}
/**
* @return True if this is first Region in Table
*/
default boolean isFirst() {
return Bytes.equals(getStartKey(), HConstants.EMPTY_START_ROW);
}
/**
* @return True if this is last Region in Table
*/
default boolean isLast() {
return Bytes.equals(getEndKey(), HConstants.EMPTY_START_ROW);
}
/**
* @return True if region is next, adjacent but 'after' this one.
* @see #isAdjacent(RegionInfo)
* @see #areAdjacent(RegionInfo, RegionInfo)
*/
default boolean isNext(RegionInfo after) {
return getTable().equals(after.getTable()) && Bytes.equals(getEndKey(), after.getStartKey());
}
/**
* @return True if region is adjacent, either just before or just after this one.
* @see #isNext(RegionInfo)
*/
default boolean isAdjacent(RegionInfo other) {
return getTable().equals(other.getTable()) && areAdjacent(this, other);
}
/**
* @return True if RegionInfo is degenerate... if startKey > endKey.
*/
default boolean isDegenerate() {
return !isLast() && Bytes.compareTo(getStartKey(), getEndKey()) > 0;
}
/**
* @return True if an overlap in region range.
* @see #isDegenerate()
*/
default boolean isOverlap(RegionInfo other) {
if (other == null) {
return false;
}
if (!getTable().equals(other.getTable())) {
return false;
}
int startKeyCompare = Bytes.compareTo(getStartKey(), other.getStartKey());
if (startKeyCompare == 0) {
return true;
}
if (startKeyCompare < 0) {
if (isLast()) {
return true;
}
return Bytes.compareTo(getEndKey(), other.getStartKey()) > 0;
}
if (other.isLast()) {
return true;
}
return Bytes.compareTo(getStartKey(), other.getEndKey()) < 0;
}
}