/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2009 Sun Microsystems, Inc. * Portions Copyright 2013 ForgeRock AS. */ package org.opends.server.replication.common; import java.util.Date; import org.opends.server.types.ByteSequence; import org.opends.server.types.ByteSequenceReader; import org.opends.server.types.ByteString; import org.opends.server.types.ByteStringBuilder; /** * Class used to represent Change Numbers. */ public class ChangeNumber implements java.io.Serializable, java.lang.Comparable<ChangeNumber> { /** * The number of bytes used by the byte string representation of a change * number. * * @see #valueOf(ByteSequence) * @see #toByteString() * @see #toByteString(ByteStringBuilder) */ public static final int BYTE_ENCODING_LENGTH = 14; /** * The number of characters used by the string representation of a change * number. * * @see #valueOf(String) * @see #toString() */ public static final int STRING_ENCODING_LENGTH = 28; private static final long serialVersionUID = -8802722277749190740L; private final long timeStamp; private final int seqnum; private final int serverId; /** * Parses the provided {@link #toString()} representation of a change number. * * @param s * The string to be parsed. * @return The parsed change number. * @see #toString() */ public static ChangeNumber valueOf(String s) { return new ChangeNumber(s); } /** * Decodes the provided {@link #toByteString()} representation of a change * number. * * @param bs * The byte sequence to be parsed. * @return The decoded change number. * @see #toByteString() */ public static ChangeNumber valueOf(ByteSequence bs) { ByteSequenceReader reader = bs.asReader(); long timeStamp = reader.getLong(); int serverId = reader.getShort() & 0xffff; int seqnum = reader.getInt(); return new ChangeNumber(timeStamp, seqnum, serverId); } /** * Create a new ChangeNumber from a String. * * @param str the string from which to create a ChangeNumber */ public ChangeNumber(String str) { String temp = str.substring(0, 16); timeStamp = Long.parseLong(temp, 16); temp = str.substring(16, 20); serverId = Integer.parseInt(temp, 16); temp = str.substring(20, 28); seqnum = Integer.parseInt(temp, 16); } /** * Create a new ChangeNumber. * * @param time time for the ChangeNumber * @param seq sequence number * @param serverId2 identity of server */ public ChangeNumber(long time, int seq, int serverId2) { serverId = serverId2; timeStamp = time; seqnum = seq; } /** * Getter for the time. * @return the time */ public long getTime() { return timeStamp; } /** * Get the timestamp associated to this ChangeNumber in seconds. * @return timestamp associated to this ChangeNumber in seconds */ public long getTimeSec() { return timeStamp/1000; } /** * Getter for the sequence number. * @return the sequence number */ public int getSeqnum() { return seqnum; } /** * Getter for the server ID. * @return the server ID */ public int getServerId() { return serverId; } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (obj instanceof ChangeNumber) { ChangeNumber cn = (ChangeNumber) obj; return this.seqnum == cn.seqnum && this.serverId == cn.serverId && this.timeStamp == cn.timeStamp; } else return false; } /** * {@inheritDoc} */ @Override public int hashCode() { return this.seqnum + this.serverId + Long.valueOf(timeStamp).hashCode(); } /** * Encodes this change number as a byte string. * <p> * NOTE: this representation must not be modified otherwise interop with * earlier protocol versions will be broken. * * @return The encoded representation of this change number. * @see #valueOf(ByteSequence) */ public ByteString toByteString() { return toByteString(new ByteStringBuilder(BYTE_ENCODING_LENGTH)) .toByteString(); } /** * Encodes this change number into the provided byte string builder. * <p> * NOTE: this representation must not be modified otherwise interop with * earlier protocol versions will be broken. * * @param builder * The byte string builder. * @return The byte string builder containing the encoded change number. * @see #valueOf(ByteSequence) */ public ByteStringBuilder toByteString(ByteStringBuilder builder) { return builder.append(timeStamp).append((short) (serverId & 0xffff)) .append(seqnum); } /** * Convert the ChangeNumber to a printable String. * <p> * NOTE: this representation must not be modified otherwise interop with * earlier protocol versions will be broken. * * @return the string */ public String toString() { return String.format("%016x%04x%08x", timeStamp, serverId, seqnum); } /** * Convert the ChangeNumber to a printable String with a user friendly * format. * * @return the string */ public String toStringUI() { Date date = new Date(timeStamp); return String.format( "%016x%04x%08x (sid=%d,tsd=%s,ts=%d,seqnum=%d)", timeStamp, serverId, seqnum, serverId, date.toString(), timeStamp, seqnum); } /** * Compares 2 ChangeNumber. * @param CN1 the first ChangeNumber to compare * @param CN2 the second ChangeNumber to compare * @return value 0 if changeNumber matches, negative if first * changeNumber is smaller, positive otherwise */ public static int compare(ChangeNumber CN1, ChangeNumber CN2) { if (CN1 == null) { if (CN2 == null) return 0; else return -1; } else if (CN2 == null) return 1; else if (CN1.timeStamp < CN2.timeStamp) return -1; else if (CN2.timeStamp < CN1.timeStamp) return 1; else { // timestamps are equals compare seqnums if (CN1.seqnum < CN2.seqnum) return -1; else if (CN2.seqnum < CN1.seqnum) return 1; else { // timestamp and seqnum are equals compare serverIds if (CN1.serverId < CN2.serverId) return -1; else if (CN2.serverId < CN1.serverId) return 1; // if we get here ChangeNumber are equals return 0; } } } /** * Computes the difference in number of changes between 2 * change numbers. First one is expected to be newer than second one. If this * is not the case, 0 will be returned. * @param op1 the first ChangeNumber * @param op2 the second ChangeNumber * @return the difference */ public static int diffSeqNum(ChangeNumber op1, ChangeNumber op2) { if (op1 == null) { return 0; } if (op2 == null) { return op1.getSeqnum(); } if (op2.newerOrEquals(op1)) { return 0; } int seqnum1 = op1.getSeqnum(); long time1 = op1.getTime(); int seqnum2 = op2.getSeqnum(); long time2 = op2.getTime(); if (time2 <= time1) { if (seqnum2 <= seqnum1) { return seqnum1 - seqnum2; } else { return Integer.MAX_VALUE - (seqnum2 - seqnum1) + 1; } } else { return 0; } } /** * check if the current Object is strictly older than ChangeNumber * given in parameter. * @param CN the Changenumber to compare with * @return true if strictly older, false if younger or same */ public Boolean older(ChangeNumber CN) { return compare(this, CN) < 0; } /** * check if the current Object is older than ChangeNumber * given in parameter. * @param CN the Changenumber to compare with * @return true if older or equal, false if younger */ public Boolean olderOrEqual(ChangeNumber CN) { return compare(this, CN) <= 0; } /** * Check if the current Object is newer than ChangeNumber. * @param CN the Changenumber to compare with * @return true if newer */ public boolean newerOrEquals(ChangeNumber CN) { return compare(this, CN) >= 0; } /** * Check if the current Object is strictly newer than ChangeNumber. * @param CN the Changenumber to compare with * @return true if strictly newer */ public boolean newer(ChangeNumber CN) { return compare(this, CN) > 0; } /** * Compares this object with the specified object for order. * @param cn the ChangeNumber to compare with. * @return a negative integer, zero, or a positive integer as this object * is less than, equal to, or greater than the specified object. */ public int compareTo(ChangeNumber cn) { return compare(this, cn); } }