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