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 */ 223 boolean isOffline(); 224 225 /** 226 * @return True if this is a split parent region. 227 */ 228 boolean isSplitParent(); 229 230 /** 231 * @return true if this region is a meta region. 232 */ 233 boolean isMetaRegion(); 234 235 /** 236 * @return true if the given inclusive range of rows is fully contained 237 * by this region. For example, if the region is foo,a,g and this is 238 * passed ["b","c"] or ["a","c"] it will return true, but if this is passed 239 * ["b","z"] it will return false. 240 * @throws IllegalArgumentException if the range passed is invalid (ie. end < start) 241 */ 242 boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey); 243 244 /** 245 * @return true if the given row falls in this region. 246 */ 247 boolean containsRow(byte[] row); 248 249 /** 250 * Does region name contain its encoded name? 251 * @param regionName region name 252 * @return boolean indicating if this a new format region 253 * name which contains its encoded name. 254 */ 255 @InterfaceAudience.Private 256 static boolean hasEncodedName(final byte[] regionName) { 257 // check if region name ends in ENC_SEPARATOR 258 return (regionName.length >= 1) && 259 (regionName[regionName.length - 1] == RegionInfo.ENC_SEPARATOR); 260 } 261 262 /** 263 * @return the encodedName 264 */ 265 @InterfaceAudience.Private 266 static String encodeRegionName(final byte [] regionName) { 267 String encodedName; 268 if (hasEncodedName(regionName)) { 269 // region is in new format: 270 // <tableName>,<startKey>,<regionIdTimeStamp>/encodedName/ 271 encodedName = Bytes.toString(regionName, 272 regionName.length - MD5_HEX_LENGTH - 1, 273 MD5_HEX_LENGTH); 274 } else { 275 // old format region name. First hbase:meta region also 276 // use this format.EncodedName is the JenkinsHash value. 277 HashKey<byte[]> key = new ByteArrayHashKey(regionName, 0, regionName.length); 278 int hashVal = Math.abs(JenkinsHash.getInstance().hash(key, 0)); 279 encodedName = String.valueOf(hashVal); 280 } 281 return encodedName; 282 } 283 284 @InterfaceAudience.Private 285 static String getRegionNameAsString(byte[] regionName) { 286 return getRegionNameAsString(null, regionName); 287 } 288 289 @InterfaceAudience.Private 290 static String getRegionNameAsString(@CheckForNull RegionInfo ri, byte[] regionName) { 291 if (RegionInfo.hasEncodedName(regionName)) { 292 // new format region names already have their encoded name. 293 return Bytes.toStringBinary(regionName); 294 } 295 296 // old format. regionNameStr doesn't have the region name. 297 if (ri == null) { 298 return Bytes.toStringBinary(regionName) + "." + RegionInfo.encodeRegionName(regionName); 299 } else { 300 return Bytes.toStringBinary(regionName) + "." + ri.getEncodedName(); 301 } 302 } 303 304 /** 305 * @return Return a String of short, printable names for <code>hris</code> 306 * (usually encoded name) for us logging. 307 */ 308 static String getShortNameToLog(RegionInfo...hris) { 309 return getShortNameToLog(Arrays.asList(hris)); 310 } 311 312 /** 313 * @return Return a String of short, printable names for <code>hris</code> (usually encoded name) 314 * for us logging. 315 */ 316 static String getShortNameToLog(final List<RegionInfo> ris) { 317 return ris.stream().map(RegionInfo::getEncodedName).collect(Collectors.toList()).toString(); 318 } 319 320 /** 321 * Gets the table name from the specified region name. 322 * @param regionName to extract the table name from 323 * @return Table name 324 */ 325 @InterfaceAudience.Private 326 // This method should never be used. Its awful doing parse from bytes. 327 // It is fallback in case we can't get the tablename any other way. Could try removing it. 328 // Keeping it Audience Private so can remove at later date. 329 static TableName getTable(final byte [] regionName) { 330 int offset = -1; 331 for (int i = 0; i < regionName.length; i++) { 332 if (regionName[i] == HConstants.DELIMITER) { 333 offset = i; 334 break; 335 } 336 } 337 if (offset <= 0) { 338 throw new IllegalArgumentException("offset=" + offset); 339 } 340 byte[] buff = new byte[offset]; 341 System.arraycopy(regionName, 0, buff, 0, offset); 342 return TableName.valueOf(buff); 343 } 344 345 /** 346 * Gets the start key from the specified region name. 347 * @return Start key. 348 */ 349 static byte[] getStartKey(final byte[] regionName) throws IOException { 350 return parseRegionName(regionName)[1]; 351 } 352 353 /** 354 * Figure if the passed bytes represent an encoded region name or not. 355 * @param regionName A Region name either encoded or not. 356 * @return True if <code>regionName</code> represents an encoded name. 357 */ 358 @InterfaceAudience.Private // For use by internals only. 359 public static boolean isEncodedRegionName(byte[] regionName) { 360 // If not parseable as region name, presume encoded. TODO: add stringency; e.g. if hex. 361 return parseRegionNameOrReturnNull(regionName) == null && regionName.length <= MD5_HEX_LENGTH; 362 } 363 364 /** 365 * @return A deserialized {@link RegionInfo} 366 * or null if we failed deserialize or passed bytes null 367 */ 368 @InterfaceAudience.Private 369 static RegionInfo parseFromOrNull(final byte [] bytes) { 370 if (bytes == null) return null; 371 return parseFromOrNull(bytes, 0, bytes.length); 372 } 373 374 /** 375 * @return A deserialized {@link RegionInfo} or null 376 * if we failed deserialize or passed bytes null 377 */ 378 @InterfaceAudience.Private 379 static RegionInfo parseFromOrNull(final byte [] bytes, int offset, int len) { 380 if (bytes == null || len <= 0) return null; 381 try { 382 return parseFrom(bytes, offset, len); 383 } catch (DeserializationException e) { 384 return null; 385 } 386 } 387 388 /** 389 * @param bytes A pb RegionInfo serialized with a pb magic prefix. 390 * @return A deserialized {@link RegionInfo} 391 */ 392 @InterfaceAudience.Private 393 static RegionInfo parseFrom(final byte [] bytes) throws DeserializationException { 394 if (bytes == null) return null; 395 return parseFrom(bytes, 0, bytes.length); 396 } 397 398 /** 399 * @param bytes A pb RegionInfo serialized with a pb magic prefix. 400 * @param offset starting point in the byte array 401 * @param len length to read on the byte array 402 * @return A deserialized {@link RegionInfo} 403 */ 404 @InterfaceAudience.Private 405 static RegionInfo parseFrom(final byte [] bytes, int offset, int len) 406 throws DeserializationException { 407 if (ProtobufUtil.isPBMagicPrefix(bytes, offset, len)) { 408 int pblen = ProtobufUtil.lengthOfPBMagic(); 409 try { 410 HBaseProtos.RegionInfo.Builder builder = HBaseProtos.RegionInfo.newBuilder(); 411 ProtobufUtil.mergeFrom(builder, bytes, pblen + offset, len - pblen); 412 HBaseProtos.RegionInfo ri = builder.build(); 413 return ProtobufUtil.toRegionInfo(ri); 414 } catch (IOException e) { 415 throw new DeserializationException(e); 416 } 417 } else { 418 throw new DeserializationException("PB encoded RegionInfo expected"); 419 } 420 } 421 422 static boolean isMD5Hash(String encodedRegionName) { 423 if (encodedRegionName.length() != MD5_HEX_LENGTH) { 424 return false; 425 } 426 427 for (int i = 0; i < encodedRegionName.length(); i++) { 428 char c = encodedRegionName.charAt(i); 429 if (!((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'))) { 430 return false; 431 } 432 } 433 return true; 434 } 435 436 /** 437 * Check whether two regions are adjacent; i.e. lies just before or just 438 * after in a table. 439 * @return true if two regions are adjacent 440 */ 441 static boolean areAdjacent(RegionInfo regionA, RegionInfo regionB) { 442 if (regionA == null || regionB == null) { 443 throw new IllegalArgumentException( 444 "Can't check whether adjacent for null region"); 445 } 446 if (!regionA.getTable().equals(regionB.getTable())) { 447 return false; 448 } 449 RegionInfo a = regionA; 450 RegionInfo b = regionB; 451 if (Bytes.compareTo(a.getStartKey(), b.getStartKey()) > 0) { 452 a = regionB; 453 b = regionA; 454 } 455 return Bytes.equals(a.getEndKey(), b.getStartKey()); 456 } 457 458 /** 459 * @return This instance serialized as protobuf w/ a magic pb prefix. 460 * @see #parseFrom(byte[]) 461 */ 462 static byte [] toByteArray(RegionInfo ri) { 463 byte [] bytes = ProtobufUtil.toRegionInfo(ri).toByteArray(); 464 return ProtobufUtil.prependPBMagic(bytes); 465 } 466 467 /** 468 * Use logging. 469 * @param encodedRegionName The encoded regionname. 470 * @return <code>hbase:meta</code> if passed <code>1028785192</code> else returns 471 * <code>encodedRegionName</code> 472 */ 473 static String prettyPrint(final String encodedRegionName) { 474 if (encodedRegionName.equals("1028785192")) { 475 return encodedRegionName + "/hbase:meta"; 476 } 477 return encodedRegionName; 478 } 479 480 /** 481 * Make a region name of passed parameters. 482 * @param startKey Can be null 483 * @param regionid Region id (Usually timestamp from when region was created). 484 * @param newFormat should we create the region name in the new format 485 * (such that it contains its encoded name?). 486 * @return Region name made of passed tableName, startKey and id 487 */ 488 static byte [] createRegionName(final TableName tableName, final byte[] startKey, 489 final long regionid, boolean newFormat) { 490 return createRegionName(tableName, startKey, Long.toString(regionid), 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 498 * (such that it contains its encoded name?). 499 * @return Region name made of passed tableName, startKey and id 500 */ 501 static byte [] createRegionName(final TableName tableName, 502 final byte[] startKey, final String id, boolean newFormat) { 503 return createRegionName(tableName, startKey, Bytes.toBytes(id), newFormat); 504 } 505 506 /** 507 * Make a region name of passed parameters. 508 * @param startKey Can be null 509 * @param regionid Region id (Usually timestamp from when region was created). 510 * @param newFormat should we create the region name in the new format 511 * (such that it contains its encoded name?). 512 * @return Region name made of passed tableName, startKey, id and replicaId 513 */ 514 static byte [] createRegionName(final TableName tableName, 515 final byte[] startKey, final long regionid, int replicaId, boolean newFormat) { 516 return createRegionName(tableName, startKey, Bytes.toBytes(Long.toString(regionid)), 517 replicaId, newFormat); 518 } 519 520 /** 521 * Make a region name of passed parameters. 522 * @param startKey Can be null 523 * @param id Region id (Usually timestamp from when region was created). 524 * @param newFormat should we create the region name in the new format 525 * (such that it contains its encoded name?). 526 * @return Region name made of passed tableName, startKey and id 527 */ 528 static byte [] createRegionName(final TableName tableName, 529 final byte[] startKey, final byte[] id, boolean newFormat) { 530 return createRegionName(tableName, startKey, id, DEFAULT_REPLICA_ID, newFormat); 531 } 532 533 /** 534 * Make a region name of passed parameters. 535 * @param startKey Can be null 536 * @param id Region id (Usually timestamp from when region was created). 537 * @param newFormat should we create the region name in the new format 538 * @return Region name made of passed tableName, startKey, id and replicaId 539 */ 540 static byte [] createRegionName(final TableName tableName, 541 final byte[] startKey, final byte[] id, final int replicaId, boolean newFormat) { 542 int len = tableName.getName().length + 2 + id.length + (startKey == null? 0: startKey.length); 543 if (newFormat) { 544 len += MD5_HEX_LENGTH + 2; 545 } 546 byte[] replicaIdBytes = null; 547 // Special casing: replicaId is only appended if replicaId is greater than 548 // 0. This is because all regions in meta would have to be migrated to the new 549 // name otherwise 550 if (replicaId > 0) { 551 // use string representation for replica id 552 replicaIdBytes = Bytes.toBytes(String.format(REPLICA_ID_FORMAT, replicaId)); 553 len += 1 + replicaIdBytes.length; 554 } 555 556 byte [] b = new byte [len]; 557 558 int offset = tableName.getName().length; 559 System.arraycopy(tableName.getName(), 0, b, 0, offset); 560 b[offset++] = HConstants.DELIMITER; 561 if (startKey != null && startKey.length > 0) { 562 System.arraycopy(startKey, 0, b, offset, startKey.length); 563 offset += startKey.length; 564 } 565 b[offset++] = HConstants.DELIMITER; 566 System.arraycopy(id, 0, b, offset, id.length); 567 offset += id.length; 568 569 if (replicaIdBytes != null) { 570 b[offset++] = REPLICA_ID_DELIMITER; 571 System.arraycopy(replicaIdBytes, 0, b, offset, replicaIdBytes.length); 572 offset += replicaIdBytes.length; 573 } 574 575 if (newFormat) { 576 // 577 // Encoded name should be built into the region name. 578 // 579 // Use the region name thus far (namely, <tablename>,<startKey>,<id>_<replicaId>) 580 // to compute a MD5 hash to be used as the encoded name, and append 581 // it to the byte buffer. 582 // 583 String md5Hash = MD5Hash.getMD5AsHex(b, 0, offset); 584 byte [] md5HashBytes = Bytes.toBytes(md5Hash); 585 586 if (md5HashBytes.length != MD5_HEX_LENGTH) { 587 System.out.println("MD5-hash length mismatch: Expected=" + MD5_HEX_LENGTH + 588 "; Got=" + md5HashBytes.length); 589 } 590 591 // now append the bytes '.<encodedName>.' to the end 592 b[offset++] = ENC_SEPARATOR; 593 System.arraycopy(md5HashBytes, 0, b, offset, MD5_HEX_LENGTH); 594 offset += MD5_HEX_LENGTH; 595 b[offset] = ENC_SEPARATOR; 596 } 597 598 return b; 599 } 600 601 /** 602 * Creates a RegionInfo object for MOB data. 603 * 604 * @param tableName the name of the table 605 * @return the MOB {@link RegionInfo}. 606 */ 607 static RegionInfo createMobRegionInfo(TableName tableName) { 608 // Skipping reference to RegionInfoBuilder in this class. 609 return new MutableRegionInfo(tableName, Bytes.toBytes(".mob"), 610 HConstants.EMPTY_END_ROW, false, 0, DEFAULT_REPLICA_ID, false); 611 } 612 613 /** 614 * Separate elements of a regionName. 615 * @return Array of byte[] containing tableName, startKey and id OR null if 616 * not parseable as a region name. 617 * @throws IOException if not parseable as regionName. 618 */ 619 static byte [][] parseRegionName(final byte[] regionName) throws IOException { 620 byte [][] result = parseRegionNameOrReturnNull(regionName); 621 if (result == null) { 622 throw new IOException(INVALID_REGION_NAME_FORMAT_MESSAGE + ": " + Bytes.toStringBinary(regionName)); 623 } 624 return result; 625 } 626 627 /** 628 * Separate elements of a regionName. 629 * Region name is of the format: 630 * <code>tablename,startkey,regionIdTimestamp[_replicaId][.encodedName.]</code>. 631 * Startkey can contain the delimiter (',') so we parse from the start and then parse from 632 * the end. 633 * @return Array of byte[] containing tableName, startKey and id OR null if not parseable 634 * as a region name. 635 */ 636 static byte [][] parseRegionNameOrReturnNull(final byte[] regionName) { 637 int offset = -1; 638 for (int i = 0; i < regionName.length; i++) { 639 if (regionName[i] == HConstants.DELIMITER) { 640 offset = i; 641 break; 642 } 643 } 644 if (offset == -1) { 645 return null; 646 } 647 byte[] tableName = new byte[offset]; 648 System.arraycopy(regionName, 0, tableName, 0, offset); 649 offset = -1; 650 651 int endOffset = regionName.length; 652 // check whether regionName contains encodedName 653 if (regionName.length > MD5_HEX_LENGTH + 2 && 654 regionName[regionName.length-1] == ENC_SEPARATOR && 655 regionName[regionName.length-MD5_HEX_LENGTH-2] == ENC_SEPARATOR) { 656 endOffset = endOffset - MD5_HEX_LENGTH - 2; 657 } 658 659 // parse from end 660 byte[] replicaId = null; 661 int idEndOffset = endOffset; 662 for (int i = endOffset - 1; i > 0; i--) { 663 if (regionName[i] == REPLICA_ID_DELIMITER) { //replicaId may or may not be present 664 replicaId = new byte[endOffset - i - 1]; 665 System.arraycopy(regionName, i + 1, replicaId, 0, 666 endOffset - i - 1); 667 idEndOffset = i; 668 // do not break, continue to search for id 669 } 670 if (regionName[i] == HConstants.DELIMITER) { 671 offset = i; 672 break; 673 } 674 } 675 if (offset == -1) { 676 return null; 677 } 678 byte [] startKey = HConstants.EMPTY_BYTE_ARRAY; 679 if(offset != tableName.length + 1) { 680 startKey = new byte[offset - tableName.length - 1]; 681 System.arraycopy(regionName, tableName.length + 1, startKey, 0, 682 offset - tableName.length - 1); 683 } 684 byte [] id = new byte[idEndOffset - offset - 1]; 685 System.arraycopy(regionName, offset + 1, id, 0, 686 idEndOffset - offset - 1); 687 byte [][] elements = new byte[replicaId == null ? 3 : 4][]; 688 elements[0] = tableName; 689 elements[1] = startKey; 690 elements[2] = id; 691 if (replicaId != null) { 692 elements[3] = replicaId; 693 } 694 return elements; 695 } 696 697 /** 698 * Serializes given RegionInfo's as a byte array. Use this instead of 699 * {@link RegionInfo#toByteArray(RegionInfo)} when 700 * writing to a stream and you want to use the pb mergeDelimitedFrom (w/o the delimiter, pb reads 701 * to EOF which may not be what you want). {@link #parseDelimitedFrom(byte[], int, int)} can 702 * be used to read back the instances. 703 * @param infos RegionInfo objects to serialize 704 * @return This instance serialized as a delimited protobuf w/ a magic pb prefix. 705 */ 706 static byte[] toDelimitedByteArray(RegionInfo... infos) throws IOException { 707 byte[][] bytes = new byte[infos.length][]; 708 int size = 0; 709 for (int i = 0; i < infos.length; i++) { 710 bytes[i] = toDelimitedByteArray(infos[i]); 711 size += bytes[i].length; 712 } 713 714 byte[] result = new byte[size]; 715 int offset = 0; 716 for (byte[] b : bytes) { 717 System.arraycopy(b, 0, result, offset, b.length); 718 offset += b.length; 719 } 720 return result; 721 } 722 723 /** 724 * Use this instead of {@link RegionInfo#toByteArray(RegionInfo)} when writing to a stream and you want to use 725 * the pb mergeDelimitedFrom (w/o the delimiter, pb reads to EOF which may not be what you want). 726 * @return This instance serialized as a delimied protobuf w/ a magic pb prefix. 727 */ 728 static byte [] toDelimitedByteArray(RegionInfo ri) throws IOException { 729 return ProtobufUtil.toDelimitedByteArray(ProtobufUtil.toRegionInfo(ri)); 730 } 731 732 /** 733 * Parses an RegionInfo instance from the passed in stream. 734 * Presumes the RegionInfo was serialized to the stream with 735 * {@link #toDelimitedByteArray(RegionInfo)}. 736 * @return An instance of RegionInfo. 737 */ 738 static RegionInfo parseFrom(final DataInputStream in) throws IOException { 739 // I need to be able to move back in the stream if this is not a pb 740 // serialization so I can do the Writable decoding instead. 741 int pblen = ProtobufUtil.lengthOfPBMagic(); 742 byte [] pbuf = new byte[pblen]; 743 if (in.markSupported()) { //read it with mark() 744 in.mark(pblen); 745 } 746 747 //assumption: if Writable serialization, it should be longer than pblen. 748 IOUtils.readFully(in, pbuf, 0, pblen); 749 if (ProtobufUtil.isPBMagicPrefix(pbuf)) { 750 return ProtobufUtil.toRegionInfo(HBaseProtos.RegionInfo.parseDelimitedFrom(in)); 751 } else { 752 throw new IOException("PB encoded RegionInfo expected"); 753 } 754 } 755 756 /** 757 * Parses all the RegionInfo instances from the passed in stream until EOF. Presumes the 758 * RegionInfo's were serialized to the stream with oDelimitedByteArray() 759 * @param bytes serialized bytes 760 * @param offset the start offset into the byte[] buffer 761 * @param length how far we should read into the byte[] buffer 762 * @return All the RegionInfos that are in the byte array. Keeps reading till we hit the end. 763 */ 764 static List<RegionInfo> parseDelimitedFrom(final byte[] bytes, final int offset, 765 final int length) throws IOException { 766 if (bytes == null) { 767 throw new IllegalArgumentException("Can't build an object with empty bytes array"); 768 } 769 List<RegionInfo> ris = new ArrayList<>(); 770 try (DataInputBuffer in = new DataInputBuffer()) { 771 in.reset(bytes, offset, length); 772 while (in.available() > 0) { 773 RegionInfo ri = parseFrom(in); 774 ris.add(ri); 775 } 776 } 777 return ris; 778 } 779 780 /** 781 * @return True if this is first Region in Table 782 */ 783 default boolean isFirst() { 784 return Bytes.equals(getStartKey(), HConstants.EMPTY_START_ROW); 785 } 786 787 /** 788 * @return True if this is last Region in Table 789 */ 790 default boolean isLast() { 791 return Bytes.equals(getEndKey(), HConstants.EMPTY_END_ROW); 792 } 793 794 /** 795 * @return True if region is next, adjacent but 'after' this one. 796 * @see #isAdjacent(RegionInfo) 797 * @see #areAdjacent(RegionInfo, RegionInfo) 798 */ 799 default boolean isNext(RegionInfo after) { 800 return getTable().equals(after.getTable()) && Bytes.equals(getEndKey(), after.getStartKey()); 801 } 802 803 /** 804 * @return True if region is adjacent, either just before or just after this one. 805 * @see #isNext(RegionInfo) 806 */ 807 default boolean isAdjacent(RegionInfo other) { 808 return getTable().equals(other.getTable()) && areAdjacent(this, other); 809 } 810 811 /** 812 * @return True if RegionInfo is degenerate... if startKey > endKey. 813 */ 814 default boolean isDegenerate() { 815 return !isLast() && Bytes.compareTo(getStartKey(), getEndKey()) > 0; 816 } 817 818 /** 819 * @return True if an overlap in region range. 820 * @see #isDegenerate() 821 */ 822 default boolean isOverlap(RegionInfo other) { 823 if (other == null) { 824 return false; 825 } 826 if (!getTable().equals(other.getTable())) { 827 return false; 828 } 829 int startKeyCompare = Bytes.compareTo(getStartKey(), other.getStartKey()); 830 if (startKeyCompare == 0) { 831 return true; 832 } 833 if (startKeyCompare < 0) { 834 if (isLast()) { 835 return true; 836 } 837 return Bytes.compareTo(getEndKey(), other.getStartKey()) > 0; 838 } 839 if (other.isLast()) { 840 return true; 841 } 842 return Bytes.compareTo(getStartKey(), other.getEndKey()) < 0; 843 } 844 845 default int compareTo(RegionInfo other) { 846 return RegionInfo.COMPARATOR.compare(this, other); 847 } 848}