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