/* * Copyright 2008-2009 LinkedIn, Inc * Copyright 2013 Big Switch Networks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.sdnplatform.sync.internal.version; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.sdnplatform.sync.IVersion; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.Lists; /** * A vector of the number of writes mastered by each node. The vector is stored * sparely, since, in general, writes will be mastered by only one node. This * means implicitly all the versions are at zero, but we only actually store * those greater than zero. */ public class VectorClock implements IVersion, Serializable, Cloneable { private static final long serialVersionUID = 7663945747147638702L; private static final int MAX_NUMBER_OF_VERSIONS = Short.MAX_VALUE; /* A sorted list of live versions ordered from least to greatest */ private final List<ClockEntry> versions; /* * The time of the last update on the server on which the update was * performed */ private final long timestamp; /** * Construct an empty VectorClock */ public VectorClock() { this(new ArrayList<ClockEntry>(0), System.currentTimeMillis()); } public VectorClock(long timestamp) { this(new ArrayList<ClockEntry>(0), timestamp); } /** * Create a VectorClock with the given version and timestamp * * @param versions The version to prepopulate * @param timestamp The timestamp to prepopulate */ @JsonCreator public VectorClock(@JsonProperty("entries") List<ClockEntry> versions, @JsonProperty("timestamp") long timestamp) { this.versions = versions; this.timestamp = timestamp; } /** * Get new vector clock based on this clock but incremented on index nodeId * * @param nodeId The id of the node to increment * @return A vector clock equal on each element execept that indexed by * nodeId */ public VectorClock incremented(int nodeId, long time) { if(nodeId < 0 || nodeId > Short.MAX_VALUE) throw new IllegalArgumentException(nodeId + " is outside the acceptable range of node ids."); // stop on the index greater or equal to the node List<ClockEntry> newversions = Lists.newArrayList(versions); boolean found = false; int index = 0; for(; index < newversions.size(); index++) { if(newversions.get(index).getNodeId() == nodeId) { found = true; break; } else if(newversions.get(index).getNodeId() > nodeId) { found = false; break; } } if(found) { newversions.set(index, newversions.get(index).incremented()); } else if(index < newversions.size() - 1) { newversions.add(index, new ClockEntry((short) nodeId, 1)); } else { // we don't already have a version for this, so add it if(newversions.size() > MAX_NUMBER_OF_VERSIONS) throw new IllegalStateException("Vector clock is full!"); newversions.add(index, new ClockEntry((short) nodeId, 1)); } return new VectorClock(newversions, time); } @Override public VectorClock clone() { return new VectorClock(Lists.newArrayList(versions), this.timestamp); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (timestamp ^ (timestamp >>> 32)); result = prime * result + ((versions == null) ? 0 : versions.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; VectorClock other = (VectorClock) obj; if (timestamp != other.timestamp) return false; if (versions == null) { if (other.versions != null) return false; } else if (!versions.equals(other.versions)) return false; return true; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("version("); if(this.versions.size() > 0) { for(int i = 0; i < this.versions.size() - 1; i++) { builder.append(this.versions.get(i)); builder.append(", "); } builder.append(this.versions.get(this.versions.size() - 1)); } builder.append(")"); builder.append(" ts:" + timestamp); return builder.toString(); } @JsonIgnore public long getMaxVersion() { long max = -1; for(ClockEntry entry: versions) max = Math.max(entry.getVersion(), max); return max; } public VectorClock merge(VectorClock clock) { VectorClock newClock = new VectorClock(); int i = 0; int j = 0; while(i < this.versions.size() && j < clock.versions.size()) { ClockEntry v1 = this.versions.get(i); ClockEntry v2 = clock.versions.get(j); if(v1.getNodeId() == v2.getNodeId()) { newClock.versions.add(new ClockEntry(v1.getNodeId(), Math.max(v1.getVersion(), v2.getVersion()))); i++; j++; } else if(v1.getNodeId() < v2.getNodeId()) { newClock.versions.add(v1.clone()); i++; } else { newClock.versions.add(v2.clone()); j++; } } // Okay now there may be leftovers on one or the other list remaining for(int k = i; k < this.versions.size(); k++) newClock.versions.add(this.versions.get(k).clone()); for(int k = j; k < clock.versions.size(); k++) newClock.versions.add(clock.versions.get(k).clone()); return newClock; } @Override public Occurred compare(IVersion v) { if(!(v instanceof VectorClock)) throw new IllegalArgumentException("Cannot compare Versions of different types."); return compare(this, (VectorClock) v); } /** * Is this Reflexive, AntiSymetic, and Transitive? Compare two VectorClocks, * the outcomes will be one of the following: -- Clock 1 is BEFORE clock 2 * if there exists an i such that c1(i) <= c(2) and there does not exist a j * such that c1(j) > c2(j). -- Clock 1 is CONCURRENT to clock 2 if there * exists an i, j such that c1(i) < c2(i) and c1(j) > c2(j) -- Clock 1 is * AFTER clock 2 otherwise * * @param v1 The first VectorClock * @param v2 The second VectorClock */ public static Occurred compare(VectorClock v1, VectorClock v2) { if(v1 == null || v2 == null) throw new IllegalArgumentException("Can't compare null vector clocks!"); // We do two checks: v1 <= v2 and v2 <= v1 if both are true then boolean v1Bigger = false; boolean v2Bigger = false; int p1 = 0; int p2 = 0; while(p1 < v1.versions.size() && p2 < v2.versions.size()) { ClockEntry ver1 = v1.versions.get(p1); ClockEntry ver2 = v2.versions.get(p2); if(ver1.getNodeId() == ver2.getNodeId()) { if(ver1.getVersion() > ver2.getVersion()) v1Bigger = true; else if(ver2.getVersion() > ver1.getVersion()) v2Bigger = true; p1++; p2++; } else if(ver1.getNodeId() > ver2.getNodeId()) { // since ver1 is bigger that means it is missing a version that // ver2 has v2Bigger = true; p2++; } else { // this means ver2 is bigger which means it is missing a version // ver1 has v1Bigger = true; p1++; } } /* Okay, now check for left overs */ if(p1 < v1.versions.size()) v1Bigger = true; else if(p2 < v2.versions.size()) v2Bigger = true; /* This is the case where they are equal, return BEFORE arbitrarily */ if(!v1Bigger && !v2Bigger) return Occurred.BEFORE; /* This is the case where v1 is a successor clock to v2 */ else if(v1Bigger && !v2Bigger) return Occurred.AFTER; /* This is the case where v2 is a successor clock to v1 */ else if(!v1Bigger && v2Bigger) return Occurred.BEFORE; /* This is the case where both clocks are parallel to one another */ else return Occurred.CONCURRENTLY; } public long getTimestamp() { return this.timestamp; } public List<ClockEntry> getEntries() { return Collections.unmodifiableList(this.versions); } }