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

com.jnape.palatable.lambda.iteration.RateLimitingIterator Maven / Gradle / Ivy

package com.jnape.palatable.lambda.iteration;

import com.jnape.palatable.lambda.adt.hlist.Tuple3;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Supplier;

import static com.jnape.palatable.lambda.adt.Try.trying;
import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size;
import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter;
import static com.jnape.palatable.lambda.functions.builtin.fn2.GTE.gte;
import static com.jnape.palatable.lambda.functions.builtin.fn2.LT.lt;
import static com.jnape.palatable.lambda.functions.builtin.fn2.LTE.lte;
import static com.jnape.palatable.lambda.semigroup.builtin.Max.max;
import static java.lang.Thread.sleep;
import static java.util.Collections.emptyList;

public final class RateLimitingIterator implements Iterator {
    private final Iterator                                                   asIterator;
    private final Set>>                rateLimits;
    private final Map>, List> timeSlicesByRateLimit;

    public RateLimitingIterator(Iterator asIterator, Set>> rateLimits) {
        this.asIterator = asIterator;
        this.rateLimits = rateLimits;
        timeSlicesByRateLimit = new HashMap<>();
    }

    @Override
    public boolean hasNext() {
        return asIterator.hasNext();
    }

    @Override
    public A next() {
        if (!hasNext())
            throw new NoSuchElementException();
        awaitNextTimeSlice();
        return asIterator.next();
    }

    private void awaitNextTimeSlice() {
        rateLimits.forEach(rateLimit -> {
            awaitNextTimeSliceForRateLimit(rateLimit);
            List timeSlicesForRateLimit = timeSlicesByRateLimit.getOrDefault(rateLimit, new ArrayList<>());
            timeSlicesForRateLimit.add(rateLimit._3().get());
            timeSlicesByRateLimit.put(rateLimit, timeSlicesForRateLimit);
        });
    }

    private void awaitNextTimeSliceForRateLimit(Tuple3> rateLimit) {
        while (rateLimitExhaustedInTimeSlice(rateLimit)) {
            trying(() -> sleep(0)).biMapL(IterationInterruptedException::new).orThrow();
        }
    }

    private boolean rateLimitExhaustedInTimeSlice(Tuple3> rateLimit) {
        List timeSlicesForRateLimit = timeSlicesByRateLimit.getOrDefault(rateLimit, emptyList());
        return rateLimit.into((limit, duration, instantSupplier) -> {
            Instant timeSliceEnd = instantSupplier.get();
            Instant previousTimeSliceEnd = timeSliceEnd.minus(duration);
            timeSlicesForRateLimit.removeIf(lt(previousTimeSliceEnd));
            return max(0L, limit - size(filter(mark -> lte(mark, previousTimeSliceEnd) && gte(mark, timeSliceEnd), timeSlicesForRateLimit))) == 0;
        });
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy