All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zmq.io.Metadata Maven / Gradle / Ivy

The newest version!
package zmq.io;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import zmq.Msg;
import zmq.ZError;
import zmq.ZMQ;
import zmq.util.Wire;

/**
 * The metadata holder class.
 * 

The class is thread safe, as it uses a {@link ConcurrentHashMap} for the backend

*

Null value are transformed to empty string.

*/ public class Metadata { /** * Call backs during parsing process */ public interface ParseListener { /** * Called when a property has been parsed. * @param name the name of the property. * @param value the value of the property. * @param valueAsString the value in a string representation. * @return 0 to continue the parsing process, any other value to interrupt it. */ int parsed(String name, byte[] value, String valueAsString); } public static final String IDENTITY = "Identity"; public static final String SOCKET_TYPE = "Socket-Type"; public static final String USER_ID = "User-Id"; public static final String PEER_ADDRESS = "Peer-Address"; // Dictionary holding metadata. private final Map dictionary = new ConcurrentHashMap<>(); public Metadata() { super(); } public Metadata(Properties dictionary) { // Ensure a conversion of each entry to a String. // No need to check for nul values as the Properties class ignore null and don't stores them dictionary.forEach((key, value) -> this.dictionary.put(key.toString(), value.toString())); } public Metadata(Map dictionary) { dictionary.forEach((key, value) -> this.dictionary.put(key, Optional.ofNullable(value).orElse(""))); } /** * Returns a {@link Set} view of the keys contained in this metadata. * The set is backed by the metadata, so changes to the map are * reflected in the set, and vice-versa. If the metadata is modified * while an iteration over the set is in progress (except through * the iterator's own {@code remove} operation), the results of * the iteration are undefined. The set supports element removal, * which removes the corresponding mapping from the metadata, via the * {@code Iterator.remove}, {@code Set.remove}, * {@code removeAll}, {@code retainAll}, and {@code clear} * operations. It does not support the {@code add} or {@code addAll} * operations. * * @return a set view of the keys contained in this metadata */ public Set keySet() { return dictionary.keySet(); } /** * Returns a {@link Set} view of the properties contained in this metadata. * The set is backed by the metadata, so changes to the metadata are * reflected in the set, and vice-versa. If the metadata is modified * while an iteration over the set is in progress (except through * the iterator's own {@code remove} operation, or through the * {@code setValue} operation on a metadata property returned by the * iterator) the results of the iteration are undefined. The set * supports element removal, which removes the corresponding * mapping from the metadata, via the {@code Iterator.remove}, * {@code Set.remove}, {@code removeAll}, {@code retainAll} and * {@code clear} operations. It does not support the * {@code add} or {@code addAll} operations. * * @return a set view of the properties contained in this metadata */ public Set> entrySet() { return dictionary.entrySet(); } /** * Returns a {@link Collection} view of the values contained in this metadata. * The collection is backed by the metadata, so changes to the map are * reflected in the collection, and vice-versa. If the metadata is * modified while an iteration over the collection is in progress * (except through the iterator's own {@code remove} operation), * the results of the iteration are undefined. The collection * supports element removal, which removes the corresponding * property from the metadata, via the {@code Iterator.remove}, * {@code Collection.remove}, {@code removeAll}, * {@code retainAll} and {@code clear} operations. It does not * support the {@code add} or {@code addAll} operations. * * @return a collection view of the values contained in this map */ public Collection values() { return dictionary.values(); } /** * Removes the property for a name from this metada if it is present. * *

If this map permits null values, then a return value of * {@code null} does not necessarily indicate that the map * contained no mapping for the key; it's also possible that the map * explicitly mapped the key to {@code null}. * *

The map will not contain a mapping for the specified key once the * call returns. * * @param key key whose mapping is to be removed from the map */ public void remove(String key) { dictionary.remove(key); } /** * Returns the value for this property, * or {@code null} if it does not exist. * * @param property the property name * @return the value of the property, or {@code null} if it does not exist. */ public String get(String property) { return dictionary.get(property); } /** * Define the value for this property. If the value is null, an empty string * is stored instead. * * @param property the property name * @param value value to be associated with the specified property * @deprecated Use {@link #put(String, String)} instead */ @Deprecated public void set(String property, String value) { put(property, value); } /** * Define the value for this property. If the value is null, an empty string * is stored instead. * * @param property the property name * @param value value to be associated with the specified property */ public void put(String property, String value) { if (value != null) { dictionary.put(property, value); } else { dictionary.put(property, ""); } } @Override public int hashCode() { return dictionary.hashCode(); } @Override public boolean equals(Object other) { if (this == other) { return true; } if (other == null) { return false; } if (!(other instanceof Metadata)) { return false; } Metadata that = (Metadata) other; return this.dictionary.equals(that.dictionary); } public void set(Metadata zapProperties) { dictionary.putAll(zapProperties.dictionary); } /** * Returns {@code true} if this map contains no key-value mappings. * * @return {@code true} if this map contains no key-value mappings */ public boolean isEmpty() { return dictionary.isEmpty(); } /** * Returns {@code true} if this metada contains the property requested * * @param property property the name of the property to be tested. * @return {@code true} if this metada contains the property */ public boolean containsKey(String property) { return dictionary.containsKey(property); } /** * Removes all the properties. * The map will be empty after this call returns. */ public void clear() { dictionary.clear(); } /** * Returns the number of properties. If it contains more properties * than {@code Integer.MAX_VALUE} elements, returns {@code Integer.MAX_VALUE}. * * @return the number of properties */ public int size() { return dictionary.size(); } @Override public String toString() { return "Metadata=" + dictionary; } /** * Return the content of the metadata as a new byte array, using the specifications of the ZMTP protocol *

     * property = name value
     * name = OCTET 1*255name-char
     * name-char = ALPHA | DIGIT | "-" | "_" | "." | "+"
     * value = 4OCTET *OCTET       ; Size in network byte order
     * 
* @return a new byte array * @throws IllegalStateException if the content can't be serialized */ public byte[] bytes() { try (ByteArrayOutputStream stream = new ByteArrayOutputStream(bytesSize())) { write(stream); return stream.toByteArray(); } catch (IOException e) { throw new IllegalStateException("Unable to write content as bytes", e); } } /** * Return an approximate size of the serialization of the metadata, it will probably be higher if there is a lot * of non ASCII value in it. * @return the size estimation */ private int bytesSize() { int size = 0; for (Entry entry : dictionary.entrySet()) { size += 1; size += entry.getKey().length(); size += 4; size += entry.getValue().length(); } return size; } /** * Serialize metadata to an output stream, using the specifications of the ZMTP protocol *
     * property = name value
     * name = OCTET 1*255name-char
     * name-char = ALPHA | DIGIT | "-" | "_" | "." | "+"
     * value = 4OCTET *OCTET       ; Size in network byte order
     * 
* @param stream * @throws IOException if an I/O error occurs. * @throws IllegalStateException if one of the properties name size is bigger than 255 */ public void write(OutputStream stream) throws IOException { for (Entry entry : dictionary.entrySet()) { byte[] keyBytes = entry.getKey().getBytes(ZMQ.CHARSET); if (keyBytes.length > 255) { throw new IllegalStateException("Trying to serialize an oversize attribute name"); } // write the length as a byte stream.write(keyBytes.length); stream.write(keyBytes); byte[] valueBytes = entry.getValue().getBytes(ZMQ.CHARSET); stream.write(Wire.putUInt32(valueBytes.length)); stream.write(valueBytes); } } /** * Deserialize metadata from a {@link Msg}, using the specifications of the ZMTP protocol *
     * property = name value
     * name = OCTET 1*255name-char
     * name-char = ALPHA | DIGIT | "-" | "_" | "." | "+"
     * value = 4OCTET *OCTET       ; Size in network byte order
     * 
* @param msg * @param offset * @param listener an optional {@link ParseListener}, can be null. * @return 0 if successful. Otherwise, it returns {@code zmq.ZError.EPROTO} or the error value from the {@link ParseListener}. */ public int read(Msg msg, int offset, ParseListener listener) { return read(msg.buf(), offset, listener); } /** * Deserialize metadata from a {@link ByteBuffer}, using the specifications of the ZMTP protocol *
     * property = name value
     * name = OCTET 1*255name-char
     * name-char = ALPHA | DIGIT | "-" | "_" | "." | "+"
     * value = 4OCTET *OCTET       ; Size in network byte order
     * 
* @param msg * @param offset * @param listener an optional {@link ParseListener}, can be null. * @return 0 if successful. Otherwise, it returns {@code zmq.ZError.EPROTO} or the error value from the {@link ParseListener}. */ public int read(ByteBuffer msg, int offset, ParseListener listener) { ByteBuffer data = msg.duplicate(); data.position(offset); int bytesLeft = data.remaining(); int index = offset; while (bytesLeft > 1) { final int nameLength = Byte.toUnsignedInt(data.get(index)); if (nameLength == 0) { break; } index++; bytesLeft -= 1; if (bytesLeft < nameLength) { break; } final String name = new String(bytes(data, index, nameLength), ZMQ.CHARSET); index += nameLength; bytesLeft -= nameLength; if (bytesLeft < 4) { break; } final int valueLength = Wire.getUInt32(data, index); index += 4; bytesLeft -= 4; if (bytesLeft < valueLength || valueLength < 0) { break; } final byte[] value = bytes(data, index, valueLength); final String valueAsString = new String(value, ZMQ.CHARSET); index += valueLength; bytesLeft -= valueLength; if (listener != null) { int rc = listener.parsed(name, value, valueAsString); if (rc != 0) { return rc; } } set(name, valueAsString); } if (bytesLeft > 0) { return ZError.EPROTO; } return 0; } private byte[] bytes(final ByteBuffer buf, final int position, final int length) { final byte[] bytes = new byte[length]; final int current = buf.position(); buf.position(position); buf.get(bytes, 0, length); buf.position(current); return bytes; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy