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