io.github.bucket4j.BucketState64BitsInteger Maven / Gradle / Ivy
The newest version!
/*-
* ========================LICENSE_START=================================
* Bucket4j
* %%
* Copyright (C) 2015 - 2020 Vladimir Bukhtoyarov
* %%
* 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.
* =========================LICENSE_END==================================
*/
package io.github.bucket4j;
import io.github.bucket4j.distributed.serialization.DeserializationAdapter;
import io.github.bucket4j.distributed.serialization.Scope;
import io.github.bucket4j.distributed.serialization.SerializationAdapter;
import io.github.bucket4j.distributed.serialization.SerializationHandle;
import io.github.bucket4j.distributed.versioning.Version;
import io.github.bucket4j.distributed.versioning.Versions;
import io.github.bucket4j.util.ComparableByContent;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static io.github.bucket4j.distributed.versioning.Versions.v_7_0_0;
public class BucketState64BitsInteger implements BucketState, ComparableByContent {
private static final int BANDWIDTH_SIZE = 3;
long[] stateData;
private BucketConfiguration configuration;
public static final SerializationHandle SERIALIZATION_HANDLE = new SerializationHandle<>() {
@Override
public BucketState64BitsInteger deserialize(DeserializationAdapter adapter, S input) throws IOException {
int formatNumber = adapter.readInt(input);
Versions.check(formatNumber, v_7_0_0, v_7_0_0);
long[] data = adapter.readLongArray(input);
return new BucketState64BitsInteger(data);
}
@Override
public void serialize(SerializationAdapter adapter, O output, BucketState64BitsInteger state, Version backwardCompatibilityVersion, Scope scope) throws IOException {
adapter.writeInt(output, v_7_0_0.getNumber());
adapter.writeLongArray(output, state.stateData);
}
@Override
public int getTypeId() {
return 3;
}
@Override
public Class getSerializedType() {
return BucketState64BitsInteger.class;
}
@Override
public BucketState64BitsInteger fromJsonCompatibleSnapshot(Map snapshot) throws IOException {
int formatNumber = readIntValue(snapshot, "version");
Versions.check(formatNumber, v_7_0_0, v_7_0_0);
long[] stateDate = readLongArray(snapshot, "stateData");
Map configurationSnapshot = (Map) snapshot.get("configuration");
BucketConfiguration configuration = BucketConfiguration.SERIALIZATION_HANDLE.fromJsonCompatibleSnapshot(configurationSnapshot);
BucketState64BitsInteger state = new BucketState64BitsInteger(stateDate);
state.setConfiguration(configuration);
return state;
}
@Override
public Map toJsonCompatibleSnapshot(BucketState64BitsInteger state, Version backwardCompatibilityVersion, Scope scope) throws IOException {
Map result = new HashMap<>();
result.put("version", v_7_0_0.getNumber());
result.put("stateData", state.stateData);
result.put("configuration", BucketConfiguration.SERIALIZATION_HANDLE.toJsonCompatibleSnapshot(state.configuration, backwardCompatibilityVersion, scope));
return result;
}
@Override
public String getTypeName() {
return "BucketState64BitsInteger";
}
};
private BucketState64BitsInteger(BucketState64BitsInteger otherState) {
this.stateData = otherState.stateData.clone();
this.configuration = otherState.configuration;
}
private BucketState64BitsInteger(long[] stateData) {
this.stateData = stateData;
}
public BucketState64BitsInteger(BucketConfiguration configuration, long currentTimeNanos) {
this.configuration = configuration;
Bandwidth[] bandwidths = configuration.getBandwidths();
this.stateData = new long[bandwidths.length * 3];
for(int i = 0; i < bandwidths.length; i++) {
setCurrentSize(i, calculateInitialTokens(bandwidths[i], currentTimeNanos));
setLastRefillTimeNanos(i, calculateLastRefillTimeNanos(bandwidths[i], currentTimeNanos));
}
}
@Override
public BucketState copy() {
return new BucketState64BitsInteger(this);
}
@Override
public BucketConfiguration getConfiguration() {
return configuration;
}
@Override
public void setConfiguration(BucketConfiguration configuration) {
this.configuration = configuration;
}
@Override
public BucketState replaceConfiguration(BucketConfiguration newConfiguration,
TokensInheritanceStrategy tokensInheritanceStrategy, long currentTimeNanos) {
BucketConfiguration previousConfiguration = this.configuration;
if (tokensInheritanceStrategy == TokensInheritanceStrategy.RESET) {
return new BucketState64BitsInteger(newConfiguration, currentTimeNanos);
}
boolean nullIdComparisonCanBeApplied = countOfBandwidthsWithNullIdentifiers(previousConfiguration) < 2
&& countOfBandwidthsWithNullIdentifiers(newConfiguration) < 2;
Bandwidth[] previousBandwidths = previousConfiguration.getBandwidths();
Bandwidth[] newBandwidths = newConfiguration.getBandwidths();
BucketState64BitsInteger newState = new BucketState64BitsInteger(new long[newBandwidths.length * 3]);
newState.setConfiguration(newConfiguration);
for (int newBandwidthIndex = 0; newBandwidthIndex < newBandwidths.length; newBandwidthIndex++) {
Bandwidth newBandwidth = newBandwidths[newBandwidthIndex];
Bandwidth previousBandwidth = null;
int previousBandwidthIndex = -1;
if (newBandwidth.getId() != null || nullIdComparisonCanBeApplied) {
for (int j = 0; j < previousBandwidths.length; j++) {
if (Objects.equals(newBandwidth.getId(), previousBandwidths[j].getId()) ) {
previousBandwidth = previousBandwidths[j];
previousBandwidthIndex = j;
break;
}
}
}
if (previousBandwidth == null) {
newState.setCurrentSize(newBandwidthIndex, calculateInitialTokens(newBandwidth, currentTimeNanos));
newState.setLastRefillTimeNanos(newBandwidthIndex, calculateLastRefillTimeNanos(newBandwidth, currentTimeNanos));
continue;
}
switch (tokensInheritanceStrategy) {
case AS_IS:
replaceBandwidthAsIs(newState, newBandwidthIndex, newBandwidth, previousBandwidthIndex, previousBandwidth, currentTimeNanos);
break;
case PROPORTIONALLY:
replaceBandwidthProportional(newState, newBandwidthIndex, newBandwidth, previousBandwidthIndex, previousBandwidth, currentTimeNanos);
break;
case ADDITIVE:
replaceBandwidthAdditive(newState, newBandwidthIndex, newBandwidth, previousBandwidthIndex, previousBandwidth, currentTimeNanos);
break;
default: throw new IllegalStateException("Should never reach there");
}
}
return newState;
}
private void replaceBandwidthAsIs(BucketState64BitsInteger newState, int newBandwidthIndex, Bandwidth newBandwidth,
int previousBandwidthIndex, Bandwidth previousBandwidth, long currentTimeNanos) {
long lastRefillTimeNanos = getLastRefillTimeNanos(previousBandwidthIndex);
newState.setLastRefillTimeNanos(newBandwidthIndex, lastRefillTimeNanos);
long currentSize = getCurrentSize(previousBandwidthIndex);
if (currentSize >= newBandwidth.capacity) {
newState.setCurrentSize(newBandwidthIndex, newBandwidth.capacity);
return;
}
if (newBandwidth.isGready() && previousBandwidth.isGready()) {
newState.setCurrentSize(newBandwidthIndex, currentSize);
long roundingError = getRoundingError(previousBandwidthIndex);
double roundingScale = (double) newBandwidth.refillPeriodNanos / (double) previousBandwidth.refillPeriodNanos;
long newRoundingError = (long) roundingScale * roundingError;
if (newRoundingError >= newBandwidth.refillPeriodNanos) {
newRoundingError = newBandwidth.refillPeriodNanos - 1;
}
newState.setRoundingError(newBandwidthIndex, newRoundingError);
return;
}
newState.setCurrentSize(newBandwidthIndex, currentSize);
}
private void replaceBandwidthProportional(BucketState64BitsInteger newState, int newBandwidthIndex, Bandwidth newBandwidth, int previousBandwidthIndex, Bandwidth previousBandwidth, long currentTimeNanos) {
newState.setLastRefillTimeNanos(newBandwidthIndex, getLastRefillTimeNanos(previousBandwidthIndex));
long currentSize = getCurrentSize(previousBandwidthIndex);
if (currentSize >= previousBandwidth.capacity) {
// can come here if forceAddTokens has been used
newState.setCurrentSize(newBandwidthIndex, newBandwidth.capacity);
return;
}
long roundingError = getRoundingError(previousBandwidthIndex);
double realRoundedError = (double) roundingError / (double) previousBandwidth.refillPeriodNanos;
double scale = (double) newBandwidth.capacity / (double) previousBandwidth.capacity;
double realNewSize = ((double) currentSize + realRoundedError) * scale;
long newSize = (long) realNewSize;
if (newSize >= newBandwidth.capacity) {
newState.setCurrentSize(newBandwidthIndex, newBandwidth.capacity);
return;
}
if (newSize == Long.MIN_VALUE) {
newState.setCurrentSize(newBandwidthIndex, Long.MIN_VALUE);
return;
}
double restOfDivision = realNewSize - newSize;
if (restOfDivision > 1.0d || restOfDivision < - 1.0d) {
restOfDivision = realNewSize % 1;
}
if (restOfDivision == 0.0d) {
newState.setCurrentSize(newBandwidthIndex, newSize);
return;
}
if (realNewSize < 0) {
newSize--;
restOfDivision = restOfDivision + 1;
}
newState.setCurrentSize(newBandwidthIndex, newSize);
if (newBandwidth.isGready()) {
long newRoundingError = (long) (restOfDivision * newBandwidth.refillPeriodNanos);
newState.setRoundingError(newBandwidthIndex, newRoundingError);
}
}
private void replaceBandwidthAdditive(BucketState64BitsInteger newState, int newBandwidthIndex, Bandwidth newBandwidth,
int previousBandwidthIndex, Bandwidth previousBandwidth, long currentTimeNanos) {
if (newBandwidth.capacity <= previousBandwidth.capacity) {
replaceBandwidthAsIs(newState, newBandwidthIndex, newBandwidth, previousBandwidthIndex, previousBandwidth, currentTimeNanos);
return;
}
long lastRefillTimeNanos = getLastRefillTimeNanos(previousBandwidthIndex);
newState.setLastRefillTimeNanos(newBandwidthIndex, lastRefillTimeNanos);
long currentSize = getCurrentSize(previousBandwidthIndex);
if (currentSize >= previousBandwidth.capacity) {
newState.setCurrentSize(newBandwidthIndex, newBandwidth.capacity);
return;
}
long newSize = currentSize + (newBandwidth.capacity - previousBandwidth.capacity);
newState.setCurrentSize(newBandwidthIndex, newSize);
if (newSize < newBandwidth.capacity && newBandwidth.isGready() && previousBandwidth.isGready()) {
long roundingError = getRoundingError(previousBandwidthIndex);
double roundingScale = (double) newBandwidth.refillPeriodNanos / (double) previousBandwidth.refillPeriodNanos;
long newRoundingError = (long) roundingScale * roundingError;
if (newRoundingError >= newBandwidth.refillPeriodNanos) {
newRoundingError = newBandwidth.refillPeriodNanos - 1;
}
newState.setRoundingError(newBandwidthIndex, newRoundingError);
}
}
private int countOfBandwidthsWithNullIdentifiers(BucketConfiguration configuration) {
Bandwidth[] bandwidths = configuration.getBandwidths();
int count = 0;
for (int i = 0; i < bandwidths.length; i++) {
if (bandwidths[i].getId() == null) {
count++;
}
}
return count;
}
@Override
public void copyStateFrom(BucketState sourceState) {
BucketState64BitsInteger sourceState64BitsInteger = (BucketState64BitsInteger) sourceState;
if (sourceState64BitsInteger.configuration == configuration) {
System.arraycopy(sourceState64BitsInteger.stateData, 0, stateData, 0, stateData.length);
} else {
this.configuration = sourceState64BitsInteger.configuration;
this.stateData = sourceState64BitsInteger.stateData.clone();
}
}
@Override
public long getAvailableTokens() {
long availableTokens = getCurrentSize(0);
for (int i = 1; i < configuration.getBandwidths().length; i++) {
availableTokens = Math.min(availableTokens, getCurrentSize(i));
}
return availableTokens;
}
@Override
public void consume(long toConsume) {
for (int i = 0; i < configuration.getBandwidths().length; i++) {
consume(i, toConsume);
}
}
@Override
public long calculateDelayNanosAfterWillBePossibleToConsume(long tokensToConsume, long currentTimeNanos, boolean checkTokensToConsumeShouldBeLessThenCapacity) {
Bandwidth[] bandwidths = configuration.getBandwidths();
long delayAfterWillBePossibleToConsume = calculateDelayNanosAfterWillBePossibleToConsume(0, bandwidths[0], tokensToConsume, currentTimeNanos, checkTokensToConsumeShouldBeLessThenCapacity);
for (int i = 1; i < bandwidths.length; i++) {
Bandwidth bandwidth = bandwidths[i];
long delay = calculateDelayNanosAfterWillBePossibleToConsume(i, bandwidth, tokensToConsume, currentTimeNanos, checkTokensToConsumeShouldBeLessThenCapacity);
delayAfterWillBePossibleToConsume = Math.max(delayAfterWillBePossibleToConsume, delay);
}
return delayAfterWillBePossibleToConsume;
}
@Override
public void refillAllBandwidth(long currentTimeNanos) {
Bandwidth[] bandwidths = configuration.getBandwidths();
for (int i = 0; i < bandwidths.length; i++) {
refill(i, bandwidths[i], currentTimeNanos);
}
}
@Override
public void addTokens(long tokensToAdd) {
Bandwidth[] bandwidths = configuration.getBandwidths();
for (int i = 0; i < bandwidths.length; i++) {
addTokens(i, bandwidths[i], tokensToAdd);
}
}
@Override
public void reset() {
Bandwidth[] bandwidths = configuration.getBandwidths();
for (int i = 0; i < bandwidths.length; i++) {
resetBandwidth(i, bandwidths[i].capacity);
}
}
@Override
public void forceAddTokens(long tokensToAdd) {
Bandwidth[] bandwidths = configuration.getBandwidths();
for (int i = 0; i < bandwidths.length; i++) {
forceAddTokens(i, bandwidths[i], tokensToAdd);
}
}
private long calculateLastRefillTimeNanos(Bandwidth bandwidth, long currentTimeNanos) {
if (!bandwidth.isIntervallyAligned()) {
return currentTimeNanos;
}
return bandwidth.timeOfFirstRefillMillis * 1_000_000 - bandwidth.refillPeriodNanos;
}
private long calculateInitialTokens(Bandwidth bandwidth, long currentTimeNanos) {
if (!bandwidth.useAdaptiveInitialTokens) {
return bandwidth.initialTokens;
}
long timeOfFirstRefillNanos = bandwidth.timeOfFirstRefillMillis * 1_000_000;
if (currentTimeNanos >= timeOfFirstRefillNanos) {
return bandwidth.initialTokens;
}
long guaranteedBase = Math.max(0, bandwidth.capacity - bandwidth.refillTokens);
long nanosBeforeFirstRefill = timeOfFirstRefillNanos - currentTimeNanos;
if (multiplyExactOrReturnMaxValue(nanosBeforeFirstRefill, bandwidth.refillTokens) != Long.MAX_VALUE) {
return Math.min(bandwidth.capacity, guaranteedBase + nanosBeforeFirstRefill * bandwidth.refillTokens / bandwidth.refillPeriodNanos);
} else {
// arithmetic overflow happens.
// there is no sense to stay in integer arithmetic when having deal with so big numbers
return Math.min(bandwidth.capacity, guaranteedBase + (long)((double)nanosBeforeFirstRefill * (double) bandwidth.refillTokens / (double) bandwidth.refillPeriodNanos));
}
}
@Override
public long calculateFullRefillingTime(long currentTimeNanos) {
Bandwidth[] bandwidths = configuration.getBandwidths();
long maxTimeToFullRefillNanos = calculateFullRefillingTime(0, bandwidths[0], currentTimeNanos);
for (int i = 1; i < bandwidths.length; i++) {
maxTimeToFullRefillNanos = Math.max(maxTimeToFullRefillNanos, calculateFullRefillingTime(i, bandwidths[i], currentTimeNanos));
}
return maxTimeToFullRefillNanos;
}
private long calculateFullRefillingTime(int bandwidthIndex, Bandwidth bandwidth, long currentTimeNanos) {
long availableTokens = getCurrentSize(bandwidthIndex);
if (availableTokens >= bandwidth.capacity) {
return 0L;
}
long deficit = bandwidth.capacity - availableTokens;
if (bandwidth.isRefillIntervally()) {
return calculateDelayNanosAfterWillBePossibleToConsumeForIntervalBandwidth(bandwidthIndex, bandwidth, deficit, currentTimeNanos);
} else {
return calculateDelayNanosAfterWillBePossibleToConsumeForGreedyBandwidth(bandwidthIndex, bandwidth, deficit);
}
}
private void addTokens(int bandwidthIndex, Bandwidth bandwidth, long tokensToAdd) {
long currentSize = getCurrentSize(bandwidthIndex);
long newSize = currentSize + tokensToAdd;
if (newSize >= bandwidth.getCapacity()) {
resetBandwidth(bandwidthIndex, bandwidth.getCapacity());
} else if (newSize < currentSize) {
// arithmetic overflow happens. This mean that bucket reached Long.MAX_VALUE tokens.
// just reset bandwidth state
resetBandwidth(bandwidthIndex, bandwidth.getCapacity());
} else {
setCurrentSize(bandwidthIndex, newSize);
}
}
private void forceAddTokens(int bandwidthIndex, Bandwidth bandwidth, long tokensToAdd) {
long currentSize = getCurrentSize(bandwidthIndex);
long newSize = currentSize + tokensToAdd;
if (newSize < currentSize) {
// arithmetic overflow happens. This mean that bucket reached Long.MAX_VALUE tokens.
// just set MAX_VALUE tokens
setCurrentSize(bandwidthIndex, Long.MAX_VALUE);
setRoundingError(bandwidthIndex, 0);
} else {
setCurrentSize(bandwidthIndex, newSize);
}
}
private void refill(int bandwidthIndex, Bandwidth bandwidth, long currentTimeNanos) {
long previousRefillNanos = getLastRefillTimeNanos(bandwidthIndex);
if (currentTimeNanos <= previousRefillNanos) {
return;
}
if (bandwidth.isRefillIntervally()) {
long incompleteIntervalCorrection = (currentTimeNanos - previousRefillNanos) % bandwidth.getRefillPeriodNanos();
currentTimeNanos -= incompleteIntervalCorrection;
}
if (currentTimeNanos <= previousRefillNanos) {
return;
} else {
setLastRefillTimeNanos(bandwidthIndex, currentTimeNanos);
}
final long capacity = bandwidth.getCapacity();
final long refillPeriodNanos = bandwidth.getRefillPeriodNanos();
final long refillTokens = bandwidth.getRefillTokens();
final long currentSize = getCurrentSize(bandwidthIndex);
if (currentSize >= capacity) {
// can come here if forceAddTokens has been used
return;
}
long durationSinceLastRefillNanos = currentTimeNanos - previousRefillNanos;
long newSize = currentSize;
if (durationSinceLastRefillNanos > refillPeriodNanos) {
long elapsedPeriods = durationSinceLastRefillNanos / refillPeriodNanos;
long calculatedRefill = elapsedPeriods * refillTokens;
newSize += calculatedRefill;
if (newSize > capacity) {
resetBandwidth(bandwidthIndex, capacity);
return;
}
if (newSize < currentSize) {
// arithmetic overflow happens. This mean that tokens reached Long.MAX_VALUE tokens.
// just reset bandwidth state
resetBandwidth(bandwidthIndex, capacity);
return;
}
durationSinceLastRefillNanos %= refillPeriodNanos;
}
long roundingError = getRoundingError(bandwidthIndex);
long dividedWithoutError = multiplyExactOrReturnMaxValue(refillTokens, durationSinceLastRefillNanos);
long divided = dividedWithoutError + roundingError;
if (divided < 0 || dividedWithoutError == Long.MAX_VALUE) {
// arithmetic overflow happens.
// there is no sense to stay in integer arithmetic when having deal with so big numbers
long calculatedRefill = (long) ((double) durationSinceLastRefillNanos / (double) refillPeriodNanos * (double) refillTokens);
newSize += calculatedRefill;
roundingError = 0;
} else {
long calculatedRefill = divided / refillPeriodNanos;
if (calculatedRefill == 0) {
roundingError = divided;
} else {
newSize += calculatedRefill;
roundingError = divided % refillPeriodNanos;
}
}
if (newSize >= capacity) {
resetBandwidth(bandwidthIndex, capacity);
return;
}
if (newSize < currentSize) {
// arithmetic overflow happens. This mean that bucket reached Long.MAX_VALUE tokens.
// just reset bandwidth state
resetBandwidth(bandwidthIndex, capacity);
return;
}
setCurrentSize(bandwidthIndex, newSize);
setRoundingError(bandwidthIndex, roundingError);
}
private void resetBandwidth(int bandwidthIndex, long capacity) {
setCurrentSize(bandwidthIndex, capacity);
setRoundingError(bandwidthIndex, 0);
}
private long calculateDelayNanosAfterWillBePossibleToConsume(int bandwidthIndex, Bandwidth bandwidth, long tokens, long currentTimeNanos, boolean checkTokensToConsumeShouldBeLessThenCapacity) {
if (checkTokensToConsumeShouldBeLessThenCapacity && tokens > bandwidth.capacity) {
return Long.MAX_VALUE;
}
long currentSize = getCurrentSize(bandwidthIndex);
if (tokens <= currentSize) {
return 0;
}
long deficit = tokens - currentSize;
if (deficit <= 0) {
// math overflow happen
return Long.MAX_VALUE;
}
if (bandwidth.isRefillIntervally()) {
return calculateDelayNanosAfterWillBePossibleToConsumeForIntervalBandwidth(bandwidthIndex, bandwidth, deficit, currentTimeNanos);
} else {
return calculateDelayNanosAfterWillBePossibleToConsumeForGreedyBandwidth(bandwidthIndex, bandwidth, deficit);
}
}
private long calculateDelayNanosAfterWillBePossibleToConsumeForGreedyBandwidth(int bandwidthIndex, Bandwidth bandwidth, long deficit) {
long refillPeriodNanos = bandwidth.getRefillPeriodNanos();
long refillPeriodTokens = bandwidth.getRefillTokens();
long divided = multiplyExactOrReturnMaxValue(refillPeriodNanos, deficit);
if (divided == Long.MAX_VALUE) {
// math overflow happen.
// there is no sense to stay in integer arithmetic when having deal with so big numbers
return (long)((double) deficit / (double)refillPeriodTokens * (double)refillPeriodNanos);
} else {
long correctionForPartiallyRefilledToken = getRoundingError(bandwidthIndex);
divided -= correctionForPartiallyRefilledToken;
return divided / refillPeriodTokens;
}
}
private long calculateDelayNanosAfterWillBePossibleToConsumeForIntervalBandwidth(int bandwidthIndex, Bandwidth bandwidth, long deficit, long currentTimeNanos) {
long refillPeriodNanos = bandwidth.getRefillPeriodNanos();
long refillTokens = bandwidth.getRefillTokens();
long previousRefillNanos = getLastRefillTimeNanos(bandwidthIndex);
long timeOfNextRefillNanos = previousRefillNanos + refillPeriodNanos;
long waitForNextRefillNanos = timeOfNextRefillNanos - currentTimeNanos;
if (deficit <= refillTokens) {
return waitForNextRefillNanos;
}
deficit -= refillTokens;
if (deficit < refillTokens) {
return waitForNextRefillNanos + refillPeriodNanos;
}
long deficitPeriods = deficit / refillTokens + (deficit % refillTokens == 0L? 0 : 1);
long deficitNanos = multiplyExactOrReturnMaxValue(deficitPeriods, refillPeriodNanos);
if (deficitNanos == Long.MAX_VALUE) {
// math overflow happen
return Long.MAX_VALUE;
}
deficitNanos += waitForNextRefillNanos;
if (deficitNanos < 0) {
// math overflow happen
return Long.MAX_VALUE;
}
return deficitNanos;
}
private long getLastRefillTimeNanos(int bandwidth) {
return stateData[bandwidth * BANDWIDTH_SIZE];
}
private void setLastRefillTimeNanos(int bandwidth, long nanos) {
stateData[bandwidth * BANDWIDTH_SIZE] = nanos;
}
@Override
public long getCurrentSize(int bandwidth) {
return stateData[bandwidth * BANDWIDTH_SIZE + 1];
}
@Override
public long getRoundingError(int bandwidth) {
return stateData[bandwidth * BANDWIDTH_SIZE + 2];
}
@Override
public MathType getMathType() {
return MathType.INTEGER_64_BITS;
}
private void setCurrentSize(int bandwidth, long currentSize) {
stateData[bandwidth * BANDWIDTH_SIZE + 1] = currentSize;
}
private void consume(int bandwidth, long tokens) {
stateData[bandwidth * BANDWIDTH_SIZE + 1] -= tokens;
}
private void setRoundingError(int bandwidth, long roundingError) {
stateData[bandwidth * BANDWIDTH_SIZE + 2] = roundingError;
}
@Override
public String toString() {
return "BucketState{" +
"bandwidthStates=" + Arrays.toString(stateData) +
'}';
}
// just a copy of JDK method Math#multiplyExact,
// but instead of throwing exception it returns Long.MAX_VALUE in case of overflow
private static long multiplyExactOrReturnMaxValue(long x, long y) {
long r = x * y;
long ax = Math.abs(x);
long ay = Math.abs(y);
if (((ax | ay) >>> 31 != 0)) {
// Some bits greater than 2^31 that might cause overflow
// Check the result using the divide operator
// and check for the special case of Long.MIN_VALUE * -1
if (((y != 0) && (r / y != x)) || (x == Long.MIN_VALUE && y == -1)) {
return Long.MAX_VALUE;
}
}
return r;
}
@Override
public boolean equalsByContent(BucketState64BitsInteger other) {
return Arrays.equals(stateData, other.stateData);
}
}