io.hekate.messaging.MessageMetaData Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hekate-core Show documentation
Show all versions of hekate-core Show documentation
Java library for cluster communications and computing.
/*
* Copyright 2020 The Hekate Project
*
* The Hekate Project 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 io.hekate.messaging;
import io.hekate.codec.DataReader;
import io.hekate.codec.DataWriter;
import io.hekate.core.internal.util.ArgAssert;
import java.io.IOException;
import java.util.Arrays;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Message meta-data.
*/
public class MessageMetaData {
/**
* Codec for {@link MessageMetaData} values.
*
* @param Value type.
*/
public interface MetaDataCodec {
/** Codec for values of {@code byte[]} type. */
MetaDataCodec BYTES = new MetaDataCodec() {
@Override
public byte[] encode(byte[] value) {
return value;
}
@Override
public byte[] decode(byte[] bytes) {
return bytes;
}
};
/** Codec for values of {@code UTF-8}-encoded strings. */
MetaDataCodec TEXT = new MetaDataCodec() {
@Override
public byte[] encode(String value) {
return value.getBytes(UTF_8);
}
@Override
public String decode(byte[] bytes) {
return new String(bytes, UTF_8);
}
};
/**
* Encodes value.
*
* @param value Value.
*
* @return Bytes.
*/
byte[] encode(T value);
/**
* Decodes value.
*
* @param bytes Bytes.
*
* @return Value.
*/
T decode(byte[] bytes);
}
/**
* Key of a {@link MessageMetaData}'s attribute.
*
* @param Value type.
*/
public static final class Key {
/** See {@link #name()}. */
private final String name;
/** Cached of {@link #name}'s bytes. */
private final byte[] nameBytes;
/** Value codec. */
private final MetaDataCodec codec;
private Key(String name, MetaDataCodec codec) {
this.name = name;
this.codec = codec;
this.nameBytes = name.getBytes(UTF_8);
}
/**
* Constructs a new key.
*
*
* For performance reasons it is highly recommended to cache and reuse such instances via globally accessible static fields (i.e.
* store them as constants).
*
*
* @param name Key name (see {@link #name()}).
* @param codec Codec to encode/decode values of this key.
* @param Value type.
*
* @return New key.
*/
public static Key of(String name, MetaDataCodec codec) {
ArgAssert.notNull(name, "Name");
ArgAssert.notNull(codec, "Codec");
return new Key<>(name, codec);
}
/**
* Returns the name of this key.
*
* @return Name of this key.
*/
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
}
/** Initial capacity of key/value pairs. */
private static final int INIT_CAPACITY = 4;
/** Keys and values (even elements are keys, odd elements are values). */
private byte[][] keyAndValue;
/** Amount of key/value pairs that are stored in {@link #keyAndValue}. */
private int size;
/**
* Constructs a new instance.
*/
public MessageMetaData() {
// No-op.
}
private MessageMetaData(int size, byte[][] keyAndValue) {
this.size = size;
this.keyAndValue = keyAndValue;
}
/**
* Reads a {@link MessageMetaData} from the specified reader.
*
* @param in Reader.
*
* @return An instance if {@link MessageMetaData} that was decoded from the reader.
*
* @throws IOException if failed to read data.
*/
public static MessageMetaData readFrom(DataReader in) throws IOException {
int size = in.readVarInt();
if (size > 0) {
byte[][] keyAndValue = new byte[size][];
for (int i = 0; i < size; i++) {
byte[] bytes = new byte[in.readVarInt()];
in.readFully(bytes);
keyAndValue[i] = bytes;
}
return new MessageMetaData(size, keyAndValue);
} else {
return new MessageMetaData();
}
}
/**
* Writes the content of this instance into the specified writer.
*
* @param out Writer.
*
* @throws IOException if failed to write data.
* @see MessageMetaData#readFrom(DataReader)
*/
public void writeTo(DataWriter out) throws IOException {
out.writeVarInt(size);
for (int i = 0; i < size; i++) {
byte[] bytes = keyAndValue[i];
out.writeVarInt(bytes.length);
out.write(bytes);
}
}
/**
* Returns the value of the specified key. Returns {@code null} if such key doesn't exist.
*
* @param key Key.
* @param Value type.
*
* @return Value for the specified key or {@code null}.
*/
public T get(Key key) {
ArgAssert.notNull(key, "Key");
if (keyAndValue != null) {
for (int i = 0; i < size; i += 2) {
if (Arrays.equals(keyAndValue[i], key.nameBytes)) {
return key.codec.decode(keyAndValue[i + 1]);
}
}
}
return null;
}
/**
* Sets the key/value pair. If key already exists then its value will be overwritten.
*
* @param key Key.
* @param value Value.
* @param Value type.
*/
public void set(Key key, T value) {
ArgAssert.notNull(key, "Key");
ArgAssert.notNull(value, "Value");
if (!replace(key, value)) {
ensureCapacity();
// Add new entry.
keyAndValue[size++] = key.nameBytes;
keyAndValue[size++] = key.codec.encode(value);
}
}
/**
* Returns the amount of key/value pairs of this instance.
*
* @return Amount of key/value pairs of this instance.
*/
public int size() {
return size / 2;
}
/**
* Returns {@code true} if this instance doesn't hold any key/value pairs.
*
* @return {@code true} if this instance doesn't hold any key/value pairs.
*/
public boolean isEmpty() {
return size == 0;
}
private boolean replace(Key key, T value) {
if (keyAndValue != null) {
for (int i = 0; i < size; i += 2) {
byte[] otherKeyBytes = keyAndValue[i];
if (Arrays.equals(otherKeyBytes, key.nameBytes)) {
keyAndValue[i + 1] = key.codec.encode(value);
return true;
}
}
}
return false;
}
private void ensureCapacity() {
if (keyAndValue == null) {
keyAndValue = new byte[INIT_CAPACITY * 2][];
} else if (size == keyAndValue.length) {
byte[][] newKeyAndValue = new byte[size * 2][];
System.arraycopy(keyAndValue, 0, newKeyAndValue, 0, size);
this.keyAndValue = newKeyAndValue;
}
}
@Override
public String toString() {
return MessageMetaData.class.getSimpleName() + "[size=" + size() + ']';
}
}