/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.wasp;
import com.alibaba.wasp.protobuf.ProtobufUtil;
import com.alibaba.wasp.protobuf.generated.WaspProtos.EntityGroupInfoProtos;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.JenkinsHash;
import org.apache.hadoop.hbase.util.MD5Hash;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.PairOfSameType;
import org.apache.hadoop.io.DataInputBuffer;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* EntityGroup information. Contains EntityGroup id, start and end keys, a
* reference to this EntityGroup' table descriptor, etc.
*/
public class EntityGroupInfo implements Comparable<EntityGroupInfo> {
private static final Log LOG = LogFactory.getLog(EntityGroupInfo.class);
/**
* Separator used to demarcate the encodedName in a entityGroup name in the
* new format. See description on new format above.
*/
private static final int ENC_SEPARATOR = '.';
public static final int MD5_HEX_LENGTH = 32;
/**
* Does entityGroup name contain its encoded name?
*
* @param entityGroupName
* entityGroup name
* @return boolean indicating if this a new format entityGroup name which
* contains its encoded name.
*/
private static boolean hasEncodedName(final byte[] entityGroupName) {
// check if entityGroup name ends in ENC_SEPARATOR
if ((entityGroupName.length >= 1)
&& (entityGroupName[entityGroupName.length - 1] == ENC_SEPARATOR)) {
// entityGroup name is new format. it contains the encoded name.
return true;
}
return false;
}
/**
* @param entityGroupName
* @return the encodedName
*/
public static String encodeEntityGroupName(final byte[] entityGroupName) {
String encodedName;
if (hasEncodedName(entityGroupName)) {
// entityGroup is in new format:
// <tableName>,<startKey>,<entityGroupIdTimeStamp>/encodedName/
encodedName = Bytes.toString(entityGroupName, entityGroupName.length
- MD5_HEX_LENGTH - 1, MD5_HEX_LENGTH);
} else {
// old format entityGroup name. first META entityGroup also
// use this format.EncodedName is the JenkinsHash value.
int hashVal = Math.abs(JenkinsHash.getInstance().hash(entityGroupName,
entityGroupName.length, 0));
encodedName = String.valueOf(hashVal);
}
return encodedName;
}
private boolean offLine = false;
private byte[] endKey = FConstants.EMPTY_BYTE_ARRAY;
private long entityGroupId = -1;
private transient byte[] entityGroupName = FConstants.EMPTY_BYTE_ARRAY;
private String entityGroupNameStr = "";
private boolean split = false;
private byte[] startKey = FConstants.EMPTY_BYTE_ARRAY;
private int hashCode = -1;
public static final String NO_HASH = null;
private volatile String encodedName = NO_HASH;
private byte[] encodedNameAsBytes = null;
// Current TableName
private byte[] tableName = null;
private void setHashCode() {
int result = Arrays.hashCode(this.entityGroupName);
result ^= this.entityGroupId;
result ^= Arrays.hashCode(this.startKey);
result ^= Arrays.hashCode(this.endKey);
result ^= Boolean.valueOf(this.offLine).hashCode();
result ^= Arrays.hashCode(this.tableName);
this.hashCode = result;
}
/**
*
* @param tableName
*/
public EntityGroupInfo(String tableName) {
this(Bytes.toBytes(tableName), null, null);
}
/**
*
* @param tableName
*/
public EntityGroupInfo(final byte[] tableName) {
this(tableName, null, null);
}
/**
*
* @param tableName
* @param startKey
* @param endKey
* @throws IllegalArgumentException
*/
public EntityGroupInfo(String tableName, final byte[] startKey,
final byte[] endKey) throws IllegalArgumentException {
this(Bytes.toBytes(tableName), startKey, endKey, false);
}
/**
* Construct EntityGroupInfo with explicit parameters
*
* @param tableName
* the table name
* @param startKey
* first key in entityGroup
* @param endKey
* end of key range
* @throws IllegalArgumentException
*/
public EntityGroupInfo(final byte[] tableName, final byte[] startKey,
final byte[] endKey) throws IllegalArgumentException {
this(tableName, startKey, endKey, false);
}
/**
* Construct EntityGroupInfo with explicit parameters
*
* @param tableName
* the table descriptor
* @param startKey
* first key in entityGroup
* @param endKey
* end of key range
* @param split
* true if this entityGroup has split and we have daughter
* entityGroups entityGroups that may or may not hold references to
* this entityGroup.
* @throws IllegalArgumentException
*/
public EntityGroupInfo(final byte[] tableName, final byte[] startKey,
final byte[] endKey, final boolean split) throws IllegalArgumentException {
this(tableName, startKey, endKey, split, System.currentTimeMillis());
}
/**
* Construct EntityGroupInfo with explicit parameters
*
* @param tableName
* the table descriptor
* @param startKey
* first key in entityGroup
* @param endKey
* end of key range
* @param split
* true if this entityGroup has split and we have daughter
* entityGroups entityGroups that may or may not hold references to
* this entityGroup.
* @param entityGroupId
* EntityGroup id to use.
* @throws IllegalArgumentException
*/
public EntityGroupInfo(final byte[] tableName, final byte[] startKey,
final byte[] endKey, final boolean split, final long entityGroupId)
throws IllegalArgumentException {
super();
if (tableName == null) {
throw new IllegalArgumentException("tableName cannot be null");
}
this.tableName = tableName.clone();
this.offLine = false;
this.entityGroupId = entityGroupId;
this.entityGroupName = createEntityGroupName(this.tableName, startKey,
entityGroupId, true);
this.entityGroupNameStr = Bytes.toStringBinary(this.entityGroupName);
this.split = split;
this.endKey = endKey == null ? FConstants.EMPTY_END_ROW : endKey.clone();
this.startKey = startKey == null ? FConstants.EMPTY_START_ROW : startKey
.clone();
this.tableName = tableName.clone();
setHashCode();
}
/**
* Construct a copy of another EntityGroupInfo
*
* @param other
*/
public EntityGroupInfo(EntityGroupInfo other) {
super();
this.endKey = other.getEndKey();
this.offLine = other.isOffline();
this.entityGroupId = other.getEntityGroupId();
this.entityGroupName = other.getEntityGroupName();
this.entityGroupNameStr = Bytes.toStringBinary(this.entityGroupName);
this.split = other.isSplit();
this.startKey = other.getStartKey();
this.hashCode = other.hashCode();
this.encodedName = other.getEncodedName();
this.tableName = other.tableName;
}
/**
* Make a entityGroup name of passed parameters.
*
* @param tableName
* @param startKey
* Can be null
* @param entityGroupId
* EntityGroup id (Usually timestamp from when EntityGroup was
* created).
* @param newFormat
* should we create the EntityGroup name in the new format (such that
* it contains its encoded name?).
* @return EntityGroup name made of passed tableName, startKey and id
*/
public static byte[] createEntityGroupName(final byte[] tableName,
final byte[] startKey, final long entityGroupId, boolean newFormat) {
return createEntityGroupName(tableName, startKey,
Long.toString(entityGroupId), newFormat);
}
/**
* Make a entityGroup name of passed parameters.
*
* @param tableName
* @param startKey
* Can be null
* @param id
* EntityGroup id (Usually timestamp from when entityGroup was
* created).
* @param newFormat
* should we create the entityGroup name in the new format (such that
* it contains its encoded name?).
* @return EntityGroup name made of passed tableName, startKey and id
*/
public static byte[] createEntityGroupName(final byte[] tableName,
final byte[] startKey, final String id, boolean newFormat) {
return createEntityGroupName(tableName, startKey, Bytes.toBytes(id),
newFormat);
}
/**
* Make a entityGroup name of passed parameters.
*
* @param tableName
* @param startKey
* Can be null
* @param id
* RntityGroup id (Usually timestamp from when entityGroup was
* created).
* @param newFormat
* should we create the entityGroup name in the new format (such that
* it contains its encoded name?).
* @return EntityGroup name made of passed tableName, startKey and id
*/
public static byte[] createEntityGroupName(final byte[] tableName,
final byte[] startKey, final byte[] id, boolean newFormat) {
byte[] b = new byte[tableName.length + 2 + id.length
+ (startKey == null ? 0 : startKey.length)
+ (newFormat ? (MD5_HEX_LENGTH + 2) : 0)];
int offset = tableName.length;
System.arraycopy(tableName, 0, b, 0, offset);
b[offset++] = FConstants.DELIMITER;
if (startKey != null && startKey.length > 0) {
System.arraycopy(startKey, 0, b, offset, startKey.length);
offset += startKey.length;
}
b[offset++] = FConstants.DELIMITER;
System.arraycopy(id, 0, b, offset, id.length);
offset += id.length;
if (newFormat) {
//
// Encoded name should be built into the entityGroup name.
//
// Use the entityGroup name thus far (namely, <tablename>,<startKey>,<id>)
// to compute a MD5 hash to be used as the encoded name, and append
// it to the byte buffer.
//
String md5Hash = MD5Hash.getMD5AsHex(b, 0, offset);
byte[] md5HashBytes = Bytes.toBytes(md5Hash);
if (md5HashBytes.length != MD5_HEX_LENGTH) {
LOG.error("MD5-hash length mismatch: Expected=" + MD5_HEX_LENGTH
+ "; Got=" + md5HashBytes.length);
}
// now append the bytes '.<encodedName>.' to the end
b[offset++] = ENC_SEPARATOR;
System.arraycopy(md5HashBytes, 0, b, offset, MD5_HEX_LENGTH);
offset += MD5_HEX_LENGTH;
b[offset++] = ENC_SEPARATOR;
}
return b;
}
/**
* Gets the table name from the specified entityGroup name.
*
* @param entityGroupName
* @return Table name.
*/
public static byte[] getTableName(byte[] entityGroupName) {
int offset = -1;
for (int i = 0; i < entityGroupName.length; i++) {
if (entityGroupName[i] == FConstants.DELIMITER) {
offset = i;
break;
}
}
byte[] tableName = new byte[offset];
System.arraycopy(entityGroupName, 0, tableName, 0, offset);
return tableName;
}
/**
* Separate elements of a entityGroupName.
*
* @param entityGroupName
* @return Array of byte[] containing tableName, startKey and id
* @throws java.io.IOException
*/
public static byte[][] parseEntityGroupName(final byte[] entityGroupName)
throws IOException {
int offset = -1;
for (int i = 0; i < entityGroupName.length; i++) {
if (entityGroupName[i] == FConstants.DELIMITER) {
offset = i;
break;
}
}
if (offset == -1)
throw new IOException("Invalid entityGroupName format");
byte[] tableName = new byte[offset];
System.arraycopy(entityGroupName, 0, tableName, 0, offset);
offset = -1;
for (int i = entityGroupName.length - 1; i > 0; i--) {
if (entityGroupName[i] == FConstants.DELIMITER) {
offset = i;
break;
}
}
if (offset == -1)
throw new IOException("Invalid entityGroupName format");
byte[] startKey = FConstants.EMPTY_BYTE_ARRAY;
if (offset != tableName.length + 1) {
startKey = new byte[offset - tableName.length - 1];
System.arraycopy(entityGroupName, tableName.length + 1, startKey, 0,
offset - tableName.length - 1);
}
byte[] id = new byte[entityGroupName.length - offset - 1];
System.arraycopy(entityGroupName, offset + 1, id, 0, entityGroupName.length
- offset - 1);
byte[][] elements = new byte[3][];
elements[0] = tableName;
elements[1] = startKey;
elements[2] = id;
return elements;
}
/** @return the entityGroupId */
public long getEntityGroupId() {
return entityGroupId;
}
/**
* @return the entityGroupName as an array of bytes.
* @see #getEntityGroupNameAsString()
*/
public byte[] getEntityGroupName() {
return entityGroupName;
}
/**
* @return EntityGroup name as a String for use in logging, etc.
*/
public String getEntityGroupNameAsString() {
if (hasEncodedName(this.entityGroupName)) {
// new format entityGroup names already have their encoded name.
return this.entityGroupNameStr;
}
// old format. entityGroupNameStr doesn't have the entityGroup name.
//
//
return this.entityGroupNameStr + "." + this.getEncodedName();
}
/** @return the encoded entityGroup name */
public synchronized String getEncodedName() {
if (this.encodedName == NO_HASH) {
this.encodedName = encodeEntityGroupName(this.entityGroupName);
}
return this.encodedName;
}
public synchronized byte[] getEncodedNameAsBytes() {
if (this.encodedNameAsBytes == null) {
this.encodedNameAsBytes = Bytes.toBytes(getEncodedName());
}
return this.encodedNameAsBytes;
}
/** @return the startKey */
public byte[] getStartKey() {
return startKey;
}
/** @return the endKey */
public byte[] getEndKey() {
return endKey;
}
/**
* Get current table name of the entityGroup
*
* @return byte array of table name
*/
public byte[] getTableName() {
if (tableName == null || tableName.length == 0) {
tableName = getTableName(getEntityGroupName());
}
return tableName;
}
/**
* Get current table name as string
*
* @return string representation of current table
*/
public String getTableNameAsString() {
return Bytes.toString(tableName);
}
/**
* Returns true if the given inclusive range of rows is fully contained by
* this entityGroup. For example, if the entityGroup is foo,a,g and this is
* passed ["b","c"] or ["a","c"] it will return true, but if this is passed
* ["b","z"] it will return false.
*
* @throws IllegalArgumentException
* if the range passed is invalid (ie end < start)
*/
public boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey) {
if (Bytes.compareTo(rangeStartKey, rangeEndKey) > 0) {
throw new IllegalArgumentException("Invalid range: "
+ Bytes.toStringBinary(rangeStartKey) + " > "
+ Bytes.toStringBinary(rangeEndKey));
}
boolean firstKeyInRange = Bytes.compareTo(rangeStartKey, startKey) >= 0;
boolean lastKeyInRange = Bytes.compareTo(rangeEndKey, endKey) < 0
|| Bytes.equals(endKey, FConstants.EMPTY_BYTE_ARRAY);
return firstKeyInRange && lastKeyInRange;
}
/**
* Return true if the given row falls in this entityGroup.
*/
public boolean containsRow(byte[] row) {
return Bytes.compareTo(row, startKey) >= 0
&& (Bytes.compareTo(row, endKey) < 0 || Bytes.equals(endKey,
FConstants.EMPTY_BYTE_ARRAY));
}
/**
* @return True if has been split and has daughters.
*/
public boolean isSplit() {
return this.split;
}
/**
* @param split
* set split status
*/
public void setSplit(boolean split) {
this.split = split;
}
/**
* @return True if this entityGroup is offLine.
*/
public boolean isOffline() {
return this.offLine;
}
/**
* The parent of a entityGroup split is offLine while split daughters hold
* references to the parent. OffLined entityGroups are closed.
*
* @param offLine
* Set online/offLine status.
*/
public void setOffline(boolean offLine) {
this.offLine = offLine;
}
/**
* @return True if this is a split parent entityGroup.
*/
public boolean isSplitParent() {
if (!isSplit())
return false;
if (!isOffline()) {
LOG.warn("EntityGroup is split but NOT offline: "
+ getEntityGroupNameAsString());
}
return true;
}
/**
* @see Object#toString()
*/
@Override
public String toString() {
return "{" + FConstants.NAME + " => '" + this.entityGroupNameStr
+ "', STARTKEY => '" + Bytes.toStringBinary(this.startKey)
+ "', ENDKEY => '" + Bytes.toStringBinary(this.endKey)
+ "', ENCODED => " + getEncodedName() + ","
+ (isOffline() ? " OFFLINE => true," : "")
+ (isSplit() ? " SPLIT => true," : "") + "}";
}
/**
* @see Object#equals(Object)
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
if (!(o instanceof EntityGroupInfo)) {
return false;
}
return this.compareTo((EntityGroupInfo) o) == 0;
}
/**
* @see Object#hashCode()
*/
@Override
public int hashCode() {
return this.hashCode;
}
//
// Comparable
//
public int compareTo(EntityGroupInfo o) {
if (o == null) {
return 1;
}
// Are entityGroups of same table?
int result = Bytes.compareTo(this.tableName, o.tableName);
if (result != 0) {
return result;
}
// Compare start keys.
result = Bytes.compareTo(this.startKey, o.startKey);
if (result != 0) {
return result;
}
// Compare end keys.
result = Bytes.compareTo(this.endKey, o.endKey);
if (result != 0) {
if (this.getStartKey().length != 0 && this.getEndKey().length == 0) {
return 1; // this is last entityGroup
}
if (o.getStartKey().length != 0 && o.getEndKey().length == 0) {
return -1; // o is the last entityGroup
}
return result;
}
// entityGroupId is usually milli timestamp -- this defines older stamps
// to be "smaller" than newer stamps in sort order.
if (this.entityGroupId > o.entityGroupId) {
return 1;
} else if (this.entityGroupId < o.entityGroupId) {
return -1;
}
if (this.offLine == o.offLine)
return 0;
if (this.offLine == true)
return -1;
return 1;
}
/**
* Convert a EntityGroupInfo to a EntityGroupInfoProtos
*
* @return the converted EntityGroupInfoProtos
*/
public EntityGroupInfoProtos convert() {
return convert(this);
}
/**
* Convert a EntityGroupInfo to a EntityGroupInfoProtos
*
* @param info
* the EntityGroupInfo to convert
* @return the converted EntityGroupInfoProtos
*/
public static EntityGroupInfoProtos convert(final EntityGroupInfo info) {
if (info == null)
return null;
EntityGroupInfoProtos.Builder builder = EntityGroupInfoProtos.newBuilder();
builder.setTableName(ByteString.copyFrom(info.getTableName()));
builder.setEntityGroupId(info.getEntityGroupId());
if (info.getStartKey() != null) {
builder.setStartKey(ByteString.copyFrom(info.getStartKey()));
}
if (info.getEndKey() != null) {
builder.setEndKey(ByteString.copyFrom(info.getEndKey()));
}
builder.setOffline(info.isOffline());
builder.setSplit(info.isSplit());
return builder.build();
}
/**
* Convert a EntityGroupInfoProtos to a EntityGroupInfo
*
* @param proto
* the EntityGroupInfoProtos to convert
* @return the converted EntityGroupInfo
*/
public static EntityGroupInfo convert(final EntityGroupInfoProtos proto) {
if (proto == null)
return null;
byte[] tableName = proto.getTableName().toByteArray();
long entityGroupId = proto.getEntityGroupId();
byte[] startKey = null;
byte[] endKey = null;
if (proto.hasStartKey()) {
startKey = proto.getStartKey().toByteArray();
}
if (proto.hasEndKey()) {
endKey = proto.getEndKey().toByteArray();
}
boolean split = false;
if (proto.hasSplit()) {
split = proto.getSplit();
}
EntityGroupInfo egi = new EntityGroupInfo(tableName, startKey, endKey,
split, entityGroupId);
if (proto.hasOffline()) {
egi.setOffline(proto.getOffline());
}
return egi;
}
/**
* @return This instance serialized as protobuf w/ a magic pb prefix.
* @see #parseFrom(byte[]);
*/
public byte[] toByte() {
return convert().toByteArray();
}
/**
* @param bytes
* @return A deserialized {@link EntityGroupInfo} or null if we failed
* deserialize or passed bytes null
* @see {@link #toByteArray()}
*/
public static EntityGroupInfo parseFromOrNull(final byte[] bytes) {
if (bytes == null || bytes.length <= 0)
return null;
try {
return parseFrom(bytes);
} catch (DeserializationException e) {
return null;
}
}
/**
* @param bytes
* A pb EntityGroupInfo serialized with a pb magic prefix.
* @return A deserialized {@link EntityGroupInfo}
* @throws DeserializationException
* @see {@link #toByteArray()}
*/
public static EntityGroupInfo parseFrom(final byte[] bytes)
throws DeserializationException {
try {
EntityGroupInfoProtos egi = EntityGroupInfoProtos.newBuilder()
.mergeFrom(bytes, 0, bytes.length).build();
return convert(egi);
} catch (InvalidProtocolBufferException e) {
throw new DeserializationException(e);
}
}
/**
* Use this instead of {@link #toByteArray()} when writing to a stream and you
* want to use the pb mergeDelimitedFrom (w/o the delimiter, pb reads to EOF
* which may not be what you want).
*
* @return This instance serialized as a delimited protobuf w/ a magic pb
* prefix.
* @throws java.io.IOException
* @see {@link #toByteArray()}
*/
public byte[] toDelimitedByteArray() throws IOException {
return ProtobufUtil.toDelimitedByteArray(convert());
}
/**
* Extract a EntityGroupInfo and ServerName from catalog table {@link org.apache.hadoop.hbase.client.Result}.
*
* @param r
* Result to pull from
* @return A pair of the {@link EntityGroupInfo} and the {@link ServerName}
* (or null for server address if no address set in .FMETA.).
* @throws java.io.IOException
*/
public static Pair<EntityGroupInfo, ServerName> getEntityGroupInfoAndServerName(
final Result r) {
EntityGroupInfo info = getEntityGroupInfo(r, FConstants.EGINFO);
ServerName sn = ServerName.getServerName(r);
return new Pair<EntityGroupInfo, ServerName>(info, sn);
}
/**
* Returns EntityGroupInfo object from the column
* {@link FConstants#CATALOG_FAMILY};
* {@link FConstants#ENTITYGROUPINFO_QUALIFIER} of the catalog table Result.
*
* @param data
* a Result object from the catalog table scan
* @return EntityGroupInfo or null
*/
public static EntityGroupInfo getEntityGroupInfo(Result data) {
byte[] bytes = data.getValue(FConstants.CATALOG_FAMILY, FConstants.EGINFO);
if (bytes == null)
return null;
EntityGroupInfo info = parseFromOrNull(bytes);
if (LOG.isDebugEnabled()) {
LOG.debug("Current INFO from scan results = " + info);
}
return info;
}
/**
* Returns the daughter entityGroups by reading the corresponding columns of
* the catalog table Result.
*
* @param data
* a Result object from the catalog table scan
* @return a pair of EntityGroupInfo or PairOfSameType(null, null) if the
* entityGroup is not a split parent
*/
public static PairOfSameType<EntityGroupInfo> getDaughterEntityGroups(
Result data) throws IOException {
EntityGroupInfo splitA = getEntityGroupInfo(data,
FConstants.SPLITA_QUALIFIER);
EntityGroupInfo splitB = getEntityGroupInfo(data,
FConstants.SPLITB_QUALIFIER);
return new PairOfSameType<EntityGroupInfo>(splitA, splitB);
}
/**
* Returns the EntityGroupInfo object from the column
* {@link FConstants#CATALOG_FAMILY} and <code>qualifier</code> of the catalog
* table result.
*
* @param r
* a Result object from the catalog table scan
* @param qualifier
* Column family qualifier -- either
* {@link FConstants#SPLITA_QUALIFIER},
* {@link FConstants#SPLITB_QUALIFIER} or
* {@link FConstants#ENTITYGROUPINFO_QUALIFIER}.
* @return An EntityGroupInfo instance or null.
* @throws java.io.IOException
*/
public static EntityGroupInfo getEntityGroupInfo(final Result r,
byte[] qualifier) {
byte[] bytes = r.getValue(FConstants.CATALOG_FAMILY, qualifier);
if (bytes == null || bytes.length <= 0)
return null;
return parseFromOrNull(bytes);
}
/**
* Returns a {@link ServerName} from catalog table {@link org.apache.hadoop.hbase.client.Result}.
*
* @param r
* Result to pull from
* @return A ServerName instance or null if necessary fields not found or
* empty.
*/
public static ServerName getServerName(final Result r) {
return ServerName.getServerName(r);
}
/**
* Parses an EntityGroupInfo instance from the passed in stream. Presumes the
* EntityGroupInfo was serialized to the stream with
* {@link #toDelimitedByteArray()}
*
* @param in
* @return An instance of EntityGroupInfo.
* @throws java.io.IOException
*/
public static EntityGroupInfo parseFrom(final DataInputStream in)
throws IOException {
return convert(EntityGroupInfoProtos.parseDelimitedFrom(in));
}
/**
* Serializes given EntityGroupInfo's as a byte array. Use this instead of
* {@link #toByteArray()} when writing to a stream and you want to use the pb
* mergeDelimitedFrom (w/o the delimiter, pb reads to EOF which may not be
* what you want). {@link #parseDelimitedFrom(byte[], int, int)} can be used
* to read back the instances.
*
* @param infos
* EntityGroupInfo objects to serialize
* @return This instance serialized as a delimited protobuf w/ a magic pb
* prefix.
* @throws java.io.IOException
* @see {@link #toByteArray()}
*/
public static byte[] toDelimitedByteArray(EntityGroupInfo... infos)
throws IOException {
byte[][] bytes = new byte[infos.length][];
int size = 0;
for (int i = 0; i < infos.length; i++) {
bytes[i] = infos[i].toDelimitedByteArray();
size += bytes[i].length;
}
byte[] result = new byte[size];
int offset = 0;
for (byte[] b : bytes) {
System.arraycopy(b, 0, result, offset, b.length);
offset += b.length;
}
return result;
}
/**
* Parses all the EntityGroupInfo instances from the passed in stream until
* EOF. Presumes the EntityGroupInfo's were serialized to the stream with
* {@link #toDelimitedByteArray()}
*
* @param bytes
* serialized bytes
* @param offset
* the start offset into the byte[] buffer
* @param length
* how far we should read into the byte[] buffer
* @return All the entityGroupInfos that are in the byte array. Keeps reading
* till we hit the end.
*/
public static List<EntityGroupInfo> parseDelimitedFrom(final byte[] bytes,
final int offset, final int length) throws IOException {
if (bytes == null) {
throw new IllegalArgumentException(
"Can't build an object with empty bytes array");
}
DataInputBuffer in = new DataInputBuffer();
List<EntityGroupInfo> egis = new ArrayList<EntityGroupInfo>();
try {
in.reset(bytes, offset, length);
while (in.available() > 0) {
EntityGroupInfo egi = parseFrom(in);
egis.add(egi);
}
} finally {
in.close();
}
return egis;
}
}