/* * 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.Iterator; import java.util.NoSuchElementException; import java.util.TreeSet; import org.apache.geode.internal.InternalDataSerializer; import org.apache.geode.internal.cache.versions.RVVException.ReceivedVersionsIterator; /** * This subclass of RVVException is the original class that uses TreeSets to hold received versions. * It is now only used if the exception represents a large gap. * * * */ public class RVVExceptionT extends RVVException { TreeSet<Long> received; RVVExceptionT(long previousVersion, long nextVersion) { super(previousVersion, nextVersion); } @Override public void add(long receivedVersion) { if (receivedVersion == this.previousVersion + 1) { this.previousVersion = receivedVersion; if (this.received != null) { consumeReceivedVersions(); } } else if (receivedVersion == this.nextVersion - 1) { this.nextVersion = receivedVersion; if (this.received != null) { consumeReceivedVersions(); } } else if (this.previousVersion < receivedVersion && receivedVersion < this.nextVersion) { addReceived(receivedVersion); } } @Override protected void addReceived(long rv) { if (this.received == null) { this.received = new TreeSet<Long>(); } this.received.add(rv); } /** * checks to see if any of the received versions can be merged into the start/end version numbers */ private void consumeReceivedVersions() { // Iterate in forward order for (Iterator<Long> it = this.received.iterator(); it.hasNext();) { long v = it.next(); if (v <= this.previousVersion + 1) { // if the received version is less than the previous + 1, remove it. it.remove(); if (v == this.previousVersion + 1) { // if the received version is equal to previous +1, also update the previous this.previousVersion = v; } } else { // Once we reach received entries greater than the previous, stop. break; } } // Iterate in reverse order for (Iterator<Long> it = this.received.descendingIterator(); it.hasNext();) { long v = it.next(); if (v >= this.nextVersion - 1) { // if the received version is greater than the next - 1, remove it. it.remove(); if (v == this.nextVersion - 1) { // if the received version is equal the next - 1, also update next. this.nextVersion = v; } } else { break; } } } @Override public RVVException clone() { RVVExceptionT clone = new RVVExceptionT(previousVersion, nextVersion); if (this.received != null) { clone.received = new TreeSet<Long>(this.received); } return clone; } public void writeReceived(DataOutput out) throws IOException { int size = this.received == null ? 0 : this.received.size(); InternalDataSerializer.writeUnsignedVL(size, 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 last = this.previousVersion; if (this.received != null) { for (Long version : this.received) { long delta = version.longValue() - last; InternalDataSerializer.writeUnsignedVL(delta, out); last = version.longValue(); } } long delta = this.nextVersion - last; InternalDataSerializer.writeUnsignedVL(delta, out); } @Override public String toString() { if (this.received != null) { return "e(n=" + this.nextVersion + " p=" + +this.previousVersion + (this.received.size() == 0 ? "" : "; rs=" + this.received) + ")"; } return "et(n=" + this.nextVersion + " p=" + this.previousVersion + "; rs=[])"; } // @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. <br> */ @Override public boolean sameAs(RVVException ex) { if (!super.sameAs(ex)) { return false; } RVVExceptionT other = (RVVExceptionT) ex; if (this.received == null) { if (other.received != null && !other.received.isEmpty()) { return false; } } else if (other.received == null || !this.received.equals(other.received)) { return false; } return true; } protected boolean sameAs(RVVExceptionB ex) { if (!super.sameAs(ex)) { return false; } for (ReceivedVersionsIterator it = receivedVersionsIterator(); it.hasNext();) { if (!ex.contains(it.next())) { return false; } } for (ReceivedVersionsIterator it = ex.receivedVersionsIterator(); it.hasNext();) { if (!contains(it.next())) { 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.contains(version))); } /** 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() { ReceivedVersionsIteratorT result = new ReceivedVersionsIteratorT(); result.initForForwardIteration(); return result; } public long getHighestReceivedVersion() { if (received == null || received.isEmpty()) { return previousVersion; } else { return received.last(); } } @Override public boolean shouldChangeForm() { // If the received set size * 512 as big as the gap in versions, switch // to using bitset instead because that will use less memory. // A bit set using 1 bit for each *possible* entry // A treeset uses approximately 64 bytes for each *actual* entry return this.received != null && this.received.size() * 512 > this.nextVersion - this.previousVersion; } @Override public RVVException changeForm() { // Convert the exception to a bitset exception RVVExceptionB ex = new RVVExceptionB(previousVersion, nextVersion); for (ReceivedVersionsIterator it = this.receivedVersionsIterator(); it.hasNext();) { long next = it.next(); ex.add(next); } return ex; } /** it's a shame that BitSet has no iterator */ protected class ReceivedVersionsIteratorT extends ReceivedVersionsIterator { boolean noIterator; Iterator<Long> treeSetIterator; void initForForwardIteration() { if (received == null) { this.noIterator = true; } else { this.treeSetIterator = received.iterator(); } } boolean hasNext() { return !noIterator && this.treeSetIterator.hasNext(); } long next() { if (!noIterator) { return this.treeSetIterator.next().longValue(); } throw new NoSuchElementException("no more elements"); } void remove() { if (!noIterator) { this.treeSetIterator.remove(); } } } }