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