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 &lt; 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}