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