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 < 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}