/* * This file is part of Gradoop. * * Gradoop is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Gradoop is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Gradoop. If not, see <http://www.gnu.org/licenses/>. */ package org.gradoop.common.model.impl.id; import org.apache.flink.core.memory.DataInputView; import org.apache.flink.core.memory.DataOutputView; import org.apache.flink.core.memory.MemorySegment; import org.apache.flink.types.CopyableValue; import org.apache.flink.types.NormalizableKey; import org.bson.types.ObjectId; import org.gradoop.common.model.api.entities.EPGMIdentifiable; import edu.umd.cs.findbugs.annotations.SuppressWarnings; import java.io.IOException; /** * Primary key for an EPGM element. * * This implementation uses a BSON {@link ObjectId} to guarantee uniqueness. Performance critical * methods, e.g. {@link GradoopId#equals(Object)} and {@link GradoopId#hashCode()} contain code * copied from {@link ObjectId} to avoid unnecessary object instantiations. * * @see EPGMIdentifiable */ public class GradoopId implements NormalizableKey<GradoopId>, CopyableValue<GradoopId> { /** * Represents a null id. */ public static final GradoopId NULL_VALUE = new GradoopId(new ObjectId(0, 0, (short) 0, 0)); /** * Highest possible Gradoop Id. */ public static final GradoopId MAX_VALUE = new GradoopId( new ObjectId(Integer.MAX_VALUE, 16777215, Short.MAX_VALUE, 16777215)); /** * Lowest possible Gradoop Id. */ public static final GradoopId MIN_VALUE = new GradoopId( new ObjectId(Integer.MIN_VALUE, 0, Short.MIN_VALUE, 0)); /** * Number of bytes to represent an id internally. */ public static final int ID_SIZE = 12; /** * Required for {@link GradoopId#toString()} */ private static final char[] HEX_CHARS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; /** * Internal byte representation */ private byte[] bytes; /** * Required default constructor for instantiation by serialization logic. */ public GradoopId() { bytes = new byte[ID_SIZE]; } /** * Create GradoopId from existing ObjectId. * * @param objectId ObjectId */ GradoopId(ObjectId objectId) { this.bytes = objectId.toByteArray(); } /** * Creates a GradoopId from a given byte representation * * @param bytes the GradoopId represented by the byte array */ private GradoopId(byte[] bytes) { this.bytes = bytes; } /** * Returns a new GradoopId * * @return new GradoopId */ public static GradoopId get() { return new GradoopId(new ObjectId()); } /** * Returns the Gradoop ID represented by a specified hexadecimal string. * * Note: Implementation taken from {@link ObjectId#ObjectId(String)} to avoid object * instantiation. * * @param string hexadecimal GradoopId representation * @return GradoopId */ public static GradoopId fromString(String string) { if (!ObjectId.isValid(string)) { throw new IllegalArgumentException( "invalid hexadecimal representation of a GradoopId: [" + string + "]"); } byte[] b = new byte[12]; for (int i = 0; i < b.length; i++) { b[i] = (byte) Integer.parseInt(string.substring(i * 2, i * 2 + 2), 16); } return new GradoopId(b); } /** * Returns the Gradoop ID represented by a byte array * * @param bytes byte representation * @return Gradoop ID */ public static GradoopId fromByteArray(byte[] bytes) { return new GradoopId(bytes); } /** * Returns byte representation of a GradoopId * * @return Byte representation */ @SuppressWarnings(value = "EI_EXPOSE_REP", justification = "never mutated") public byte[] toByteArray() { return bytes; } /** * Checks if the specified object is equal to the current id. * * @param o the object to be compared * @return true, iff the specified id is equal to this id */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } return equals(bytes, ((GradoopId) o).bytes, 0, 0); } /** * Returns the hash code of this GradoopId. * * Note: Implementation is taken from {@link ObjectId#hashCode()} to avoid object instantiation. * * @return hash code */ @Override public int hashCode() { int result = getTimeStamp(); result = 31 * result + getMachineIdentifier(); result = 31 * result + (int) getProcessIdentifier(); result = 31 * result + getCounter(); return result; } /** * Performs a byte-wise comparison of this and the specified GradoopId. * * @param o the object to be compared. * @return a negative integer, zero, or a positive integer as this object * is less than, equal to, or greater than the specified object. */ @Override public int compareTo(GradoopId o) { return compare(this.bytes, o.bytes); } /** * Returns hex string representation of a GradoopId. * * Note: Implementation is taken from {@link ObjectId#toString()} to avoid object instantiation. * * @return GradoopId string representation. */ @Override public String toString() { char[] chars = new char[24]; int i = 0; for (byte b : bytes) { chars[i++] = HEX_CHARS[b >> 4 & 0xF]; chars[i++] = HEX_CHARS[b & 0xF]; } return new String(chars); } //------------------------------------------------------------------------------------------------ // methods inherited from NormalizableKey //------------------------------------------------------------------------------------------------ @Override public int getMaxNormalizedKeyLen() { return ID_SIZE; } @Override public void copyNormalizedKey(MemorySegment target, int offset, int len) { target.put(offset, bytes, 0, len); } @Override public void write(DataOutputView out) throws IOException { out.write(bytes); } @Override public void read(DataInputView in) throws IOException { in.readFully(bytes); } //------------------------------------------------------------------------------------------------ // methods inherited from CopyableValue //------------------------------------------------------------------------------------------------ @Override public int getBinaryLength() { return ID_SIZE; } @Override public void copyTo(GradoopId target) { target.bytes = this.bytes; } @Override public GradoopId copy() { return new GradoopId(this.bytes); } @Override public void copy(DataInputView source, DataOutputView target) throws IOException { target.write(source, ID_SIZE); } //------------------------------------------------------------------------------------------------ // private little helpers //------------------------------------------------------------------------------------------------ /** * Returns the timestamp component of the id. * * @return the timestamp */ private int getTimeStamp() { return makeInt(bytes[0], bytes[1], bytes[2], bytes[3]); } /** * Returns the machine identifier component of the id. * * @return the machine identifier */ private int getMachineIdentifier() { return makeInt((byte) 0, bytes[4], bytes[5], bytes[6]); } /** * Returns the process identifier component of the id. * * @return the process identifier */ private short getProcessIdentifier() { return (short) makeInt((byte) 0, (byte) 0, bytes[7], bytes[8]); } /** * Returns the counter component of the id. * * @return the counter */ private int getCounter() { return makeInt((byte) 0, bytes[9], bytes[10], bytes[11]); } //------------------------------------------------------------------------------------------------ // static helper functions //------------------------------------------------------------------------------------------------ /** * Compares the given GradoopIds and returns the smaller one. It both are equal, the first * argument is returned. * * @param first first GradoopId * @param second second GradoopId * @return smaller GradoopId or first if equal */ public static GradoopId min(GradoopId first, GradoopId second) { int comparison = first.compareTo(second); return comparison == 0 ? first : (comparison == -1 ? first : second); } /** * Checks if the Gradoop ids stored at the specified positions are equal. * * Note: The order in which the id components are compared is taken from * {@link ObjectId#equals(Object)}. However, we compare the values of the byte arrays directly. * * @param first first gradoop id * @param second second gradoop id * @param firstPos start index in the first byte array * @param secondPos start index in the second byte array * * @return true, iff first is equal to second */ static boolean equals(byte[] first, byte[] second, int firstPos, int secondPos) { // compare counter (byte 9 to 11) if (!equalsInRange(first, second, firstPos + 9, secondPos + 9, 3)) { return false; } // compare machine identifier (byte 4 to 6) if (!equalsInRange(first, second, firstPos + 4, secondPos + 4, 2)) { return false; } // compare process identifier (byte 7 to 8) if (!equalsInRange(first, second, firstPos + 7, secondPos + 7, 1)) { return false; } // compare timestamp (byte 0 to 3) if (!equalsInRange(first, second, firstPos, secondPos,3)) { return false; } return true; } /** * Compares the specified GradoopIds based on their byte representation * (to avoid object instantiation). * * Note: Implementation is taken from {@link ObjectId#compareTo(Object)} to avoid object * instantiation. * * @param first first GradoopId * @param second second GradoopId * @return a negative integer, zero, or a positive integer as this object is less than, equal to, * or greater than the specified object. */ private static int compare(byte[] first, byte[] second) { return compare(first, second, 0, 0, GradoopId.ID_SIZE); } /** * Compares multiple GradoopIds represented a byte arrays at the specified ranges. * * @param first first byte representation of multiple gradoop ids * @param second second byte representation of multiple gradoop ids * @param firstPos start index in the first array * @param secondPos start index in the second array * @param length length of the range * * @return a negative integer, zero, or a positive integer as this object is less than, equal to, * or greater than the specified object. */ private static int compare(byte[] first, byte[] second, int firstPos, int secondPos, int length) { for (int i = 0; i < length; i++) { if (first[firstPos + i] != second[secondPos + i]) { return ((first[firstPos + i] & 0xff) < (second[secondPos + i] & 0xff)) ? -1 : 1; } } return 0; } /** * Checks if the given byte arrays contain equal elements in specified given range. * * @param first first array * @param second second array * @param firstPos start index in the first array * @param secondPos start index in the second array * @param length number of bytes to compare for equality * * @return true, iff both arrays have equal values in the specified range */ private static boolean equalsInRange(byte[] first, byte[] second, int firstPos, int secondPos, int length) { int upperBound = firstPos + length; while (firstPos < upperBound) { if (first[firstPos] != second[secondPos]) { return false; } ++firstPos; ++secondPos; } return true; } /** * Returns a primitive int represented by the given 4 bytes. * * @param b3 byte 3 * @param b2 byte 2 * @param b1 byte 1 * @param b0 byte 0 * * @return int value */ private static int makeInt(final byte b3, final byte b2, final byte b1, final byte b0) { return (((b3) << 24) | ((b2 & 0xff) << 16) | ((b1 & 0xff) << 8) | ((b0 & 0xff))); } }