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

io.permazen.core.ObjId Maven / Gradle / Ivy


/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.core;

import com.google.common.base.Preconditions;

import io.permazen.kv.KeyRange;
import io.permazen.util.ByteReader;
import io.permazen.util.ByteUtil;
import io.permazen.util.ByteWriter;
import io.permazen.util.UnsignedIntEncoder;

import java.io.Serializable;
import java.security.SecureRandom;
import java.util.regex.Pattern;

import javax.annotation.concurrent.Immutable;

/**
 * Object IDs. Instances identify individual {@link Database} objects.
 */
@Immutable
public class ObjId implements Comparable, Serializable {

    /**
     * The number of bytes in the binary encoding of an {@link ObjId}.
     */
    public static final int NUM_BYTES = 8;

    /**
     * Regular expression that matches the string encoding of an {@link ObjId}.
     */
    public static final Pattern PATTERN = Pattern.compile("\\p{XDigit}{" + (NUM_BYTES * 2) + "}");

    private static final long serialVersionUID = 1598203254073015116L;

    private static final ThreadLocal RANDOM = new ThreadLocal() {
        @Override
        protected SecureRandom initialValue() {
            return new SecureRandom();
        }
    };

    private final long value;

// Constructors

    /**
     * Create a new, random instance with the given storage ID.
     *
     * @param storageId storage ID, must be greater than zero
     * @throws IllegalArgumentException if {@code storageId} is zero or negative
     */
    public ObjId(int storageId) {
        this(ObjId.buildRandom(storageId));
    }

    /**
     * Constructor that parses a string previously returned by {@link #toString}.
     *
     * @param string string encoding of an instance
     * @throws IllegalArgumentException if {@code string} is invalid
     */
    public ObjId(String string) {
        this(ObjId.parseString(string));
    }

    /**
     * Constructor that reads an encoded instance from the given {@link ByteReader}.
     *
     * @param reader input for binary encoding of an instance
     * @throws IllegalArgumentException if {@code reader} contains invalid data
     */
    public ObjId(ByteReader reader) {
        Preconditions.checkArgument(reader != null, "null reader");
        this.value = ByteUtil.readLong(reader);
        this.validateStorageId();
    }

    /**
     * Constructor using a long value previously returned by {@link #asLong}.
     *
     * @param value long encoding of an instance
     * @throws IllegalArgumentException if {@code value} is invalid
     */
    public ObjId(long value) {
        this.value = value;
        this.validateStorageId();
    }

    private void validateStorageId() {
        final int storageId;
        try {
            storageId = this.getStorageId();
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("invalid object ID", e);
        }
        Preconditions.checkArgument(storageId > 0, "invalid object ID containing non-positive storage ID");
    }

// Methods

    /**
     * Get the storage ID associated with this instance. This is the storage ID of the associated object type.
     *
     * @return object type storage ID
     */
    public int getStorageId() {
        return UnsignedIntEncoder.read(new ByteReader(this.getBytes()));
    }

    /**
     * Get the binary encoding of this instance.
     *
     * @return binary encoding
     */
    public byte[] getBytes() {
        final ByteWriter writer = new ByteWriter(NUM_BYTES);
        this.writeTo(writer);
        return writer.getBytes();
    }

    /**
     * Get this instance encoded as a {@code long} value.
     *
     * @return long encoding
     */
    public long asLong() {
        return this.value;
    }

    /**
     * Write the binary encoding of this instance to the given output.
     *
     * @param writer destination
     */
    public void writeTo(ByteWriter writer) {
        ByteUtil.writeLong(writer, this.value);
    }

    /**
     * Get the smallest (i.e., first) instance having the given storage ID.
     *
     * @param storageId storage ID, must be greater than zero
     * @return smallest instance with storage ID {@code storageId} (inclusive)
     * @throws IllegalArgumentException if {@code storageId} is zero or negative
     */
    public static ObjId getMin(int storageId) {
        return ObjId.getFill(storageId, 0x00);
    }

    /**
     * Get the largest (i.e., last) instance having the given storage ID.
     *
     * @param storageId storage ID, must be greater than zero
     * @return largest instance with storage ID {@code storageId} (inclusive)
     * @throws IllegalArgumentException if {@code storageId} is zero or negative
     */
    public static ObjId getMax(int storageId) {
        return ObjId.getFill(storageId, 0xff);
    }

    /**
     * Get the {@link KeyRange} containing all object IDs with the given storage ID.
     *
     * @param storageId storage ID, must be greater than zero
     * @return {@link KeyRange} containing all object IDs having the specified type
     * @throws IllegalArgumentException if {@code storageId} is zero or negative
     */
    public static KeyRange getKeyRange(int storageId) {
        Preconditions.checkArgument(storageId > 0, "invalid non-positive storage ID");
        final ByteWriter writer = new ByteWriter(NUM_BYTES);
        UnsignedIntEncoder.write(writer, storageId);
        return KeyRange.forPrefix(writer.getBytes());
    }

    private static ObjId getFill(int storageId, int value) {
        Preconditions.checkArgument(storageId > 0, "invalid non-positive storage ID");
        final ByteWriter writer = new ByteWriter(NUM_BYTES);
        UnsignedIntEncoder.write(writer, storageId);
        for (int remain = NUM_BYTES - writer.getLength(); remain > 0; remain--)
            writer.writeByte(value);
        return new ObjId(new ByteReader(writer));
    }

// Object

    /**
     * Encode this instance as a string.
     */
    @Override
    public String toString() {
        final ByteWriter writer = new ByteWriter(NUM_BYTES);
        this.writeTo(writer);
        final byte[] buf = writer.getBytes();
        final char[] result = new char[NUM_BYTES * 2];
        int off = 0;
        for (byte b : buf) {
            result[off++] = Character.forDigit((b >> 4) & 0x0f, 16);
            result[off++] = Character.forDigit(b & 0x0f, 16);
        }
        return new String(result);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (obj == null || obj.getClass() != this.getClass())
            return false;
        final ObjId that = (ObjId)obj;
        return this.value == that.value;
    }

    /**
     * Returns the hash code value for this instance.
     *
     * 

* The hash code of an {@link ObjId} is defined as the hash code of its {@link #asLong} value, * which is {@linkplain Long#hashCode defined} as the exclusive-or of the upper and lower 32 bits. */ @Override public int hashCode() { return Long.hashCode(this.value); } // Comparable @Override public int compareTo(ObjId that) { return ByteUtil.compare(this.getBytes(), that.getBytes()); } // Internal methods private static ByteReader buildRandom(int storageId) { if (storageId <= 0) throw new IllegalArgumentException("invalid storage ID " + storageId); final ByteWriter writer = new ByteWriter(NUM_BYTES); UnsignedIntEncoder.write(writer, storageId); final byte[] randomPart = new byte[NUM_BYTES - writer.getLength()]; ObjId.RANDOM.get().nextBytes(randomPart); writer.write(randomPart); return new ByteReader(writer); } private static ByteReader parseString(String string) { if (string == null) throw new IllegalArgumentException("null string"); if (string.length() != NUM_BYTES * 2) throw new IllegalArgumentException("invalid object ID `" + string + "'"); final byte[] buf = new byte[NUM_BYTES]; int off = 0; for (int i = 0; i < buf.length; i++) { final int digit1 = Character.digit(string.charAt(off++), 16); final int digit2 = Character.digit(string.charAt(off++), 16); if (digit1 == -1 || digit2 == -1) throw new IllegalArgumentException("invalid object ID `" + string + "'"); buf[i] = (byte)((digit1 << 4) | digit2); } return new ByteReader(buf); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy