All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.bucket4j.BandwidthBuilder Maven / Gradle / Ivy

The newest version!
/*-
 * ========================LICENSE_START=================================
 * Bucket4j
 * %%
 * Copyright (C) 2015 - 2023 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 java.time.Duration;
import java.time.Instant;

import io.github.bucket4j.local.LocalBucketBuilder;

import static io.github.bucket4j.Bandwidth.UNDEFINED_ID;
import static io.github.bucket4j.Bandwidth.UNSPECIFIED_TIME_OF_FIRST_REFILL;

/**
 * Provides API for bandwidth builder
 *
 */
public class BandwidthBuilder {

    /**
     * Capacity configuration building stage
     */
    public interface BandwidthBuilderCapacityStage {

        /**
         * Specifying capacity in terms of Token-Bucket
         *
         * @param tokens capacity in terms of Token-Bucket
         *
         * @return next stage that responsible for configuration of refilling speed
         */
        BandwidthBuilderRefillStage capacity(long tokens);

    }

    /**
     * Stage is responsible for configuration of refilling speed
     */
    public interface BandwidthBuilderRefillStage {
        /**
         * Configures refill that does refill of tokens in greedy manner,
         * it will try to add the tokens to bucket as soon as possible.
         * For example refill "10 tokens per 1 second" will add 1 token per each 100 millisecond,
         * in other words refill will not wait 1 second to regenerate a bunch of 10 tokens.
         *
         * 

* The three refills bellow do refill of tokens with same speed: *

         *      limit -> refillGreedy(600, Duration.ofMinutes(1))
         *      limit -> refillGreedy(10, Duration.ofSeconds(1))
         *      limit -> refillGreedy(1, Duration.ofMillis(100))
         * 
* *

* If greediness is undesired then you can specify the fixed interval refill via {@link #refillIntervally(long, Duration)} * * @param tokens amount of tokens * @param period the period within {@code tokens} will be fully regenerated * * @return the final build stage */ BandwidthBuilderBuildStage refillGreedy(long tokens, Duration period); /** * Configures refill that does refill of tokens in intervally manner. * "Intervally" in opposite to "greedy" will wait until whole {@code period} will be elapsed before regenerate {@code tokens} * * @param tokens amount of tokens * @param period the period within {@code tokens} will be fully regenerated * * @return the final build stage */ BandwidthBuilderBuildStage refillIntervally(long tokens, Duration period); /** * Configures refill that does refill of tokens in intervally manner. * "Intervally" in opposite to "greedy" will wait until whole {@code period} will be elapsed before regenerate {@code tokens}. *
* In additional to {@link #refillIntervally(long, Duration)} this method allows to specify the time when first refill should happen via {@code timeOfFirstRefill}. * This option can be used to configure clear interval boundary i.e. start of second, minute, hour, day. *

         * {@code
         *         // imagine that wall clock is 16:20, the first refill will happen at 17:00
         *         // first refill will happen in the beginning of next hour
         *         Instant firstRefillTime = ZonedDateTime.now()
         *                 .truncatedTo(ChronoUnit.HOURS)
         *                 .plus(1, ChronoUnit.HOURS)
         *                 .toInstant();
         *
         *        Bandwidth.builder(limit ->
         *             limit.capacity(600)
         *             .refillIntervallyAligned(400, Duration.ofHours(1), firstRefillTime)
         *        )
         *  }
* * * @param tokens amount of tokens * @param period the period within {@code tokens} will be fully regenerated * @param timeOfFirstRefill the time of first refill, typically it should be a moment in the future * * @return the final build stage */ BandwidthBuilderBuildStage refillIntervallyAligned(long tokens, Duration period, Instant timeOfFirstRefill); /** * Configures refill that does refill of tokens in intervally manner. * "Intervally" in opposite to "greedy" will wait until whole {@code period} will be elapsed before regenerate {@code tokens}. *
* In additional to {@link #refillIntervally(long, Duration)} it is possible to specify the time when first refill should happen via {@code timeOfFirstRefill}. * This option can be used to configure clear interval boundary i.e. start of second, minute, hour, day. * Also for refill type, initial amount of tokens in the bandwidth will be calculated by following formula: *
{@code
         *     Math.min(capacity, Math.max(0, bandwidthCapacity - refillTokens) + (timeOfFirstRefillMillis - nowMillis)/refillPeriod * refillTokens)
         * }
*
Bellow the list of examples of how does this formula can be applied: *
         * {@code
         *         // imagine that wall clock is 16:20, the first refill will happen at 17:00
         *         // first refill will happen in the beginning of next hour
         *         Instant firstRefillTime = ZonedDateTime.now()
         *                 .truncatedTo(ChronoUnit.HOURS)
         *                 .plus(1, ChronoUnit.HOURS)
         *                 .toInstant();
         *
         *        // initial tokens will be 266
         *        Bandwidth.builder(limit ->
         *             limit.capacity(400)
         *             .refillIntervallyAlignedWithAdaptiveInitialTokens(400, Duration.ofHours(1), firstRefillTime)
         *        )
         *        // calculated by formula min(400, max(0, 400 - 400) +  40/60*400) = min(400, 0 + 266) = 266
         *
         *        // initial tokens will be 300
         *        Bandwidth.builder(limit ->
         *              limit.capacity(400)
         *              .refillIntervallyAlignedWithAdaptiveInitialTokens(300, Duration.ofHours(1), firstRefillTime)
         *        )
         *        // calculated by formula min(400, max(0, 400 - 300) +  40/60*300) = min(400, 100 + 200) = 300
         *
         *        // initial tokens will be 333
         *        Bandwidth.builder(limit ->
         *               limit.capacity(400)
         *               .refillIntervallyAlignedWithAdaptiveInitialTokens(200, Duration.ofHours(1), firstRefillTime)
         *        )
         *        // calculated by formula min(400, max(0, 400 - 200) +  40/60*200) = min(400, 200 + 133) = 333
         *
         *        // initial tokens will be 366
         *        Bandwidth.builder(limit ->
         *                limit.capacity(400)
         *                .refillIntervallyAlignedWithAdaptiveInitialTokens(100, Duration.ofHours(1), firstRefillTime)
         *        )
         *        // calculated by formula min(400, max(0, 400 - 100) +  40/60*100) = min(400, 300 + 66) = 366
         * }
* * * Restrictions: *
    *
  • * If {@code useAdaptiveInitialTokens} is {@code true} then any attempt to explicitly specify initial amount of tokens via {@link BandwidthBuilderBuildStage#initialTokens(long)} * will fail with exception, because it is impossible at the same time to specify tokens in explicitly and adaptively manners. *
  • *
  • * It is impossible to use this method together with nanoTime based clock {@link LocalBucketBuilder#withNanosecondPrecision()}, because we need in {@link System#currentTimeMillis()} based clock * in order to properly measure the distance from {@code timeOfFirstRefill} *
  • *
* * * @param tokens amount of tokens * @param period the period within {@code tokens} will be fully regenerated * @param timeOfFirstRefill the time of first refill, typically it should be a moment in the future * * @return the final build stage */ BandwidthBuilderBuildStage refillIntervallyAlignedWithAdaptiveInitialTokens(long tokens, Duration period, Instant timeOfFirstRefill); } /** * The final build stage with ability to configure optional parameters of bandwidth, like id or initial tokens */ public interface BandwidthBuilderBuildStage { /** * Creates the new instance of {@link Bandwidth} * * @return new instance of {@link Bandwidth} */ Bandwidth build(); /** * By default, new created bandwidth has no ID. * This method allows to specify unique identifier of bandwidth that can be used for bandwidth comparison during configuration replacement {@link Bucket#replaceConfiguration(BucketConfiguration, TokensInheritanceStrategy)} * * @param id unique identifier of bandwidth that can be used for bandwidth comparison during configuration replacement {@link Bucket#replaceConfiguration(BucketConfiguration, TokensInheritanceStrategy)} * * @return the copy of this bandwidth with new value of initial tokens. */ BandwidthBuilderBuildStage id(String id); /** * By default, new created {@link Bandwidth} has amount tokens that equals its capacity. * This method allows to replace initial tokens. * * @param initialTokens * * @return the copy of this bandwidth with new value of initial tokens. */ BandwidthBuilderBuildStage initialTokens(long initialTokens); } public static BandwidthBuilderCapacityStage builder() { return new BandwidthBuilderImpl(); } private final static class BandwidthBuilderImpl implements BandwidthBuilderCapacityStage, BandwidthBuilderRefillStage, BandwidthBuilderBuildStage { private long capacity; private long refillPeriodNanos; private long refillTokens; private long initialTokens; private boolean refillIntervally; private long timeOfFirstRefillMillis = UNSPECIFIED_TIME_OF_FIRST_REFILL; private boolean useAdaptiveInitialTokens; private String id = UNDEFINED_ID; @Override public BandwidthBuilderRefillStage capacity(long tokens) { if (tokens <= 0) { throw BucketExceptions.nonPositiveCapacity(tokens); } this.capacity = tokens; this.initialTokens = tokens; return this; } @Override public BandwidthBuilderBuildStage refillGreedy(long tokens, Duration period) { setRefill(tokens, period, false); return this; } @Override public BandwidthBuilderBuildStage refillIntervally(long tokens, Duration period) { setRefill(tokens, period, true); return this; } @Override public BandwidthBuilderBuildStage refillIntervallyAligned(long tokens, Duration period, Instant timeOfFirstRefill) { long timeOfFirstRefillMillis = timeOfFirstRefill.toEpochMilli(); if (timeOfFirstRefillMillis < 0) { throw BucketExceptions.nonPositiveTimeOfFirstRefill(timeOfFirstRefill); } setRefill(tokens, period, true); this.timeOfFirstRefillMillis = timeOfFirstRefillMillis; return this; } @Override public BandwidthBuilderBuildStage refillIntervallyAlignedWithAdaptiveInitialTokens(long tokens, Duration period, Instant timeOfFirstRefill) { long timeOfFirstRefillMillis = timeOfFirstRefill.toEpochMilli(); if (timeOfFirstRefillMillis < 0) { throw BucketExceptions.nonPositiveTimeOfFirstRefill(timeOfFirstRefill); } setRefill(tokens, period, true); this.timeOfFirstRefillMillis = timeOfFirstRefillMillis; this.useAdaptiveInitialTokens = true; return this; } @Override public BandwidthBuilderBuildStage id(String id) { this.id = id; return this; } @Override public BandwidthBuilderBuildStage initialTokens(long initialTokens) { if (initialTokens < 0) { throw BucketExceptions.nonPositiveInitialTokens(initialTokens); } if (timeOfFirstRefillMillis != UNSPECIFIED_TIME_OF_FIRST_REFILL && useAdaptiveInitialTokens) { throw BucketExceptions.intervallyAlignedRefillWithAdaptiveInitialTokensIncompatipleWithManualSpecifiedInitialTokens(); } this.initialTokens = initialTokens; return this; } @Override public Bandwidth build() { return new Bandwidth(capacity, refillPeriodNanos, refillTokens, initialTokens, refillIntervally, timeOfFirstRefillMillis, useAdaptiveInitialTokens, id); } private void setRefill(long tokens, Duration period, boolean refillIntervally) { if (period == null) { throw BucketExceptions.nullRefillPeriod(); } if (tokens <= 0) { throw BucketExceptions.nonPositivePeriodTokens(tokens); } long refillPeriodNanos = period.toNanos(); if (refillPeriodNanos <= 0) { throw BucketExceptions.nonPositivePeriod(refillPeriodNanos); } if (tokens > refillPeriodNanos) { throw BucketExceptions.tooHighRefillRate(refillPeriodNanos, tokens); } this.refillPeriodNanos = refillPeriodNanos; this.refillIntervally = refillIntervally; this.refillTokens = tokens; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy