/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.apache.geode.internal.cache.versions;
import java.io.DataOutput;
import java.io.IOException;
import java.util.BitSet;
import java.util.NoSuchElementException;
import org.apache.geode.internal.InternalDataSerializer;
public class RVVExceptionB extends RVVException {
/**
* received represents individual received versions that fall within this exception. position 0
* corresponds to version receivedBaseVersion.
*/
BitSet received;
private long receivedBaseVersion;
public RVVExceptionB(long previousVersion, long nextVersion) {
super(previousVersion, nextVersion);
}
/**
* add a received version
*/
public void add(long receivedVersion) {
// String me = this.toString();
// long oldv = this.nextVersion;
if (receivedVersion == this.previousVersion + 1) {
this.previousVersion = receivedVersion;
if (this.received != null) {
addReceived(receivedVersion);
consumeReceivedVersions();
}
} else if (receivedVersion == this.nextVersion - 1) {
this.nextVersion = receivedVersion;
if (this.received != null) {
addReceived(receivedVersion);
consumeReceivedVersions();
}
} else if (this.previousVersion < receivedVersion && receivedVersion < this.nextVersion) {
addReceived(receivedVersion);
}
// if (this.nextVersion == 29 && oldv != 29) {
// System.out.println("before=" + me + "\nafter=" + this + "\nadded "+receivedVersion);
// }
}
protected void addReceived(long rv) {
if (this.received == null) {
this.receivedBaseVersion = this.previousVersion + 1;
if (this.nextVersion > this.previousVersion) { // next version not known during
// deserialization
long size = this.nextVersion - this.previousVersion;
this.received = new BitSet((int) size);
} else {
this.received = new BitSet();
}
}
// Assert.assertTrue(this.receivedBaseVersion > 0, "should not have a base version of zero. rv="
// + rv + " ex=" + this);
// Assert.assertTrue(rv >= this.receivedBaseVersion,
// "attempt to record a version not in this exception. version=" + rv + " exception=" + this);
this.received.set((int) (rv - this.receivedBaseVersion));
}
/**
* checks to see if any of the received versions can be merged into the start/end version numbers
*/
private void consumeReceivedVersions() {
int idx = (int) (this.previousVersion - this.receivedBaseVersion + 1);
while (this.previousVersion < this.nextVersion && this.received.get(idx)) {
idx++;
this.previousVersion++;
}
if (this.previousVersion < this.nextVersion) {
idx = (int) (this.nextVersion - this.receivedBaseVersion) - 1;
while (this.previousVersion < this.nextVersion && this.received.get(idx)) {
idx--;
this.nextVersion--;
}
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(RVVException o) {
long thisVal = this.previousVersion;
long anotherVal = o.previousVersion;
return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1));
}
@Override
public RVVExceptionB clone() {
RVVExceptionB clone = new RVVExceptionB(previousVersion, nextVersion);
if (this.received != null) {
clone.received = (BitSet) this.received.clone();
clone.receivedBaseVersion = this.receivedBaseVersion;
}
return clone;
}
public void toData(DataOutput out) throws IOException {
InternalDataSerializer.writeUnsignedVL(this.previousVersion, out);
writeReceived(out);
}
protected void writeReceived(DataOutput out) throws IOException {
int size = 0;
long[] deltas = null;
long last = this.previousVersion;
// TODO - it would be better just to serialize the longs[] in the BitSet
// as is, rather than go through this delta encoding.
for (ReceivedVersionsIterator it = receivedVersionsIterator(); it.hasNext();) {
Long version = it.next();
long delta = version.longValue() - last;
if (deltas == null) {
deltas = new long[this.received.length()];
}
deltas[size++] = delta;
last = version.longValue();
}
InternalDataSerializer.writeUnsignedVL(size, out);
for (int i = 0; i < size; i++) {
InternalDataSerializer.writeUnsignedVL(deltas[i], out);
}
// Write each version in the exception as a delta from the previous version
// this will likely be smaller than the absolute value, so it will
// be more likely to fit into a byte or a short.
long delta = this.nextVersion - last;
InternalDataSerializer.writeUnsignedVL(delta, out);
}
@Override
public String toString() {
if (this.received != null) {
StringBuilder sb = new StringBuilder();
sb.append("e(n=").append(this.nextVersion).append("; p=").append(this.previousVersion);
if (this.receivedBaseVersion != this.previousVersion + 1) {
sb.append("; b=").append(this.receivedBaseVersion);
}
int lastBit = (int) (this.nextVersion - this.receivedBaseVersion);
sb.append("; rb=[");
int i = this.received.nextSetBit((int) (this.previousVersion - this.receivedBaseVersion + 1));
if (i >= 0) {
sb.append(i);
for (i = this.received.nextSetBit(i + 1); (0 < i) && (i < lastBit); i =
this.received.nextSetBit(i + 1)) {
sb.append(',').append(i);
}
}
sb.append(']');
return sb.toString();
}
return "e(n=" + this.nextVersion + " p=" + this.previousVersion + "; rb=[])";
}
// @Override
// public int hashCode() {
// final int prime = 31;
// int result = 1;
// result = prime * result + (int) (nextVersion ^ (nextVersion >>> 32));
// result = prime * result
// + (int) (previousVersion ^ (previousVersion >>> 32));
// result = prime * result + ((this.received == null) ? 0 : this.received.hashCode());
// return result;
// }
/**
* For test purposes only. This isn't quite accurate, because I think two RVVs that have
* effectively same exceptions may represent the exceptions differently. This method is testing
* for an exact match of exception format.
*/
@Override
public boolean sameAs(RVVException ex) {
if (ex instanceof RVVExceptionT) {
return ((RVVExceptionT) ex).sameAs(this);
}
if (!super.sameAs(ex)) {
return false;
}
RVVExceptionB other = (RVVExceptionB) ex;
if (this.received == null) {
if (other.received != null && !other.received.isEmpty()) {
return false;
}
} else if (!this.received.equals(other.received))
return false;
return true;
}
/** has the given version been recorded as having been received? */
public boolean contains(long version) {
if (version <= this.previousVersion) {
return false;
}
return (this.received != null && this.received.get((int) (version - this.receivedBaseVersion)));
}
/** return false if any revisions have been recorded in the range of this exception */
public boolean isEmpty() {
return (this.received == null) || (this.received.isEmpty());
}
public ReceivedVersionsIterator receivedVersionsIterator() {
ReceivedVersionsIteratorB result = new ReceivedVersionsIteratorB();
result.initForForwardIteration();
return result;
}
@Override
public long getHighestReceivedVersion() {
if (isEmpty()) {
return this.previousVersion;
} else {
// Note, the "length" of the bitset is the highest set bit + 1,
// see the javadocs. That's why this works to return the highest
// received version
return receivedBaseVersion + received.length() - 1;
}
}
/** it's a shame that BitSet has no iterator */
protected class ReceivedVersionsIteratorB extends ReceivedVersionsIterator {
int index;
int nextIndex;
void initForForwardIteration() {
this.index = -1;
if (received == null) {
this.nextIndex = -1;
} else {
this.nextIndex = received.nextSetBit((int) (previousVersion - receivedBaseVersion + 1));
if (this.nextIndex + receivedBaseVersion >= nextVersion) {
this.nextIndex = -1;
}
}
}
boolean hasNext() {
return this.nextIndex >= 0;
}
long next() {
this.index = this.nextIndex;
if (this.index < 0) {
throw new NoSuchElementException("no more elements available");
}
advance();
return this.index + receivedBaseVersion;
}
void remove() {
if (this.index < 0) {
throw new NoSuchElementException("no more elements available");
}
received.clear(this.index);
}
private void advance() {
this.nextIndex = received.nextSetBit(this.index + 1);
if ((this.nextIndex + receivedBaseVersion) >= nextVersion) {
this.nextIndex = -1;
}
}
}
}