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