package org.scale7.cassandra.pelops.types; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; import org.scale7.cassandra.pelops.Bytes; import org.scale7.cassandra.pelops.Bytes.BufferHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * CompositeType utility class * * @author Ali Serghini */ public class CompositeType { private static final byte COMPONENT_END = (byte) 0; private static final Logger LOGGER = LoggerFactory.getLogger(CompositeType.class); //Utility class private CompositeType() { } /** * A Builder class that creates a CompositeType. */ public static class Builder { private List<ByteBuffer> parts = null; private Builder(int count) { parts = new ArrayList<ByteBuffer>(count); } /** * Creates a new builder * * @param partsCount - CompositeType parts count. Should be 2 or more. * @return CompositeType builder */ public static Builder newBuilder(int partsCount) { if (partsCount < 1) throw new IllegalArgumentException("Invalid parts count. Should be 2 or more."); return new Builder(partsCount); } /** * Creates a new builder. Assumes that there will be 2 elements in the composite type * * @return CompositeType builder */ public static Builder newBuilder() { return new Builder(2); } public Builder addByteBuffer(ByteBuffer value) { parts.add(value); return this; } public Builder addBytes(Bytes value) { return addByteBuffer(value.getBytes()); } public Builder addBoolean(boolean value) { return addByteBuffer(BufferHelper.fromBoolean(value)); } public Builder addBoolean(Boolean value) { return addByteBuffer(BufferHelper.fromBoolean(value)); } public Builder addByte(byte value) { return addByteBuffer(BufferHelper.fromByte(value)); } public Builder addByte(Byte value) { return addByteBuffer(BufferHelper.fromByte(value)); } public Builder addByteArray(byte[] value) { return addByteBuffer(BufferHelper.fromByteArray(value)); } public Builder addChar(char value) { return addByteBuffer(BufferHelper.fromChar(value)); } public Builder addChar(Character value) { return addByteBuffer(BufferHelper.fromChar(value)); } public Builder addDouble(double value) { return addByteBuffer(BufferHelper.fromDouble(value)); } public Builder addDouble(Double value) { return addByteBuffer(BufferHelper.fromDouble(value)); } public Builder addFloat(float value) { return addByteBuffer(BufferHelper.fromFloat(value)); } public Builder addFloat(Float value) { return addByteBuffer(BufferHelper.fromFloat(value)); } public Builder addInt(int value) { return addByteBuffer(BufferHelper.fromInt(value)); } public Builder addInt(Integer value) { return addByteBuffer(BufferHelper.fromInt(value)); } public Builder addLong(long value) { return addByteBuffer(BufferHelper.fromLong(value)); } public Builder addLong(Long value) { return addByteBuffer(BufferHelper.fromLong(value)); } public Builder addShort(short value) { return addByteBuffer(BufferHelper.fromShort(value)); } public Builder addShort(Short value) { return addByteBuffer(BufferHelper.fromShort(value)); } public Builder addUTF8(String str) { return addByteBuffer(BufferHelper.fromUTF8(str)); } public Builder addUuid(UUID value) { return addByteBuffer(BufferHelper.fromUuid(value)); } public Builder addUuid(String value) { return addByteBuffer(BufferHelper.fromUuid(value)); } public Builder addUuid(long msb, long lsb) { return addByteBuffer(BufferHelper.fromUuid(msb, lsb)); } public Builder addTimeUuid(com.eaio.uuid.UUID value) { return addByteBuffer(BufferHelper.fromUuid(value.getTime(), value.getClockSeqAndNode())); } /** * Reset the builder */ public void clear() { parts.clear(); } /** * Build the CompositeType using the added parts. * * @return CompositeType as Bytes */ public Bytes build() { if (parts == null || parts.isEmpty()) return null; final ByteArrayOutputStream bos = new ByteArrayOutputStream(); for (ByteBuffer part : parts) { if (!part.hasArray()) throw new IllegalStateException("Connot build CompositeType. Invalid composite byte part encountered"); bos.write((byte) ((part.array().length >> (7 + 1)) & 0xFF)); bos.write((byte) (part.array().length & 0xFF)); for (byte partByte : part.array()) { bos.write(partByte & 0xFF); } bos.write(COMPONENT_END); } final Bytes bytes = Bytes.fromByteArray(bos.toByteArray()); try { bos.close(); } catch (IOException ex) { LOGGER.error("Failed to close the compostite type output stream", ex); } return bytes; } } /** * Parses the CompositeType * * @param compositeKey - composite key as Bytes * @return list of the composite key elements */ public static List<byte[]> parse(Bytes compositeKey) { if (compositeKey == null) return null; return parse(compositeKey.toByteArray()); } /** * Parses the CompositeType * * @param compositeKey - composite key as byte array * @return list of the composite key elements */ public static List<byte[]> parse(byte[] compositeKey) { if (compositeKey == null) return null; //Validate the array length if (compositeKey.length < 2) throw new IllegalArgumentException("Invalid Composite type structure"); final List<byte[]> list = new ArrayList<byte[]>(3);//Default to 3 int ndx = 0; int componentStartNdx = 0; int componentEndNdx = 0; short componentLength = 0; while (compositeKey.length > (componentStartNdx = (ndx + 2))) { // Value length is a 2 bytes short componentLength = ByteBuffer.wrap(Arrays.copyOfRange(compositeKey, ndx, componentStartNdx)).getShort(); componentEndNdx = componentStartNdx + componentLength; // Check if the component legth is valid if (compositeKey.length < componentEndNdx + 1) throw new IllegalStateException("Invalid Composite type structure"); // If the value is not properly terminated throw an exception if (compositeKey[componentEndNdx] != COMPONENT_END) throw new IllegalStateException("Invalid Composite type structure: Not properly terminated, should be 0 byte terminated, found " + compositeKey[componentEndNdx]); // Get the value list.add(Arrays.copyOfRange(compositeKey, componentStartNdx, componentEndNdx)); // Update the value of the index ndx = componentEndNdx + 1; } return list; } }