Incrementor.java

/*
 * 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
 *
 *      https://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.
 */

/*
 * This is not the original file distributed by the Apache Software Foundation
 * It has been modified by the Hipparchus project
 */
package org.hipparchus.util;

import org.hipparchus.exception.LocalizedCoreFormats;
import org.hipparchus.exception.MathIllegalArgumentException;
import org.hipparchus.exception.MathIllegalStateException;
import org.hipparchus.exception.NullArgumentException;

/**
 * Utility that increments a counter until a maximum is reached, at
 * which point, the instance will by default throw a
 * {@link MathIllegalStateException}.
 * However, the user is able to override this behaviour by defining a
 * custom {@link MaxCountExceededCallback callback}, in order to e.g.
 * select which exception must be thrown.
 */
public class Incrementor {
    /** Default callback. */
    private static final MaxCountExceededCallback DEFAULT_CALLBACK =
        (int max) -> {
            throw new MathIllegalStateException(LocalizedCoreFormats.MAX_COUNT_EXCEEDED, max);
        };

    /** Upper limit for the counter. */
    private final int maximalCount;
    /** Function called at counter exhaustion. */
    private final MaxCountExceededCallback maxCountCallback;
    /** Current count. */
    private int count;

    /**
     * Defines a method to be called at counter exhaustion.
     * The {@link #trigger(int) trigger} method should usually throw an exception.
     */
    public interface MaxCountExceededCallback {
        /**
         * Function called when the maximal count has been reached.
         *
         * @param maximalCount Maximal count.
         * @throws MathIllegalStateException at counter exhaustion
         */
        void trigger(int maximalCount) throws MathIllegalStateException;
    }

    /**
     * Creates an Incrementor.
     * <p>
     * The maximal value will be set to {@code Integer.MAX_VALUE}.
     */
    public Incrementor() {
        this(Integer.MAX_VALUE);
    }

    /**
     * Creates an Incrementor.
     *
     * @param max Maximal count.
     * @throws MathIllegalArgumentException if {@code max} is negative.
     */
    public Incrementor(int max) {
        this(max, DEFAULT_CALLBACK);
    }

    /**
     * Creates an Incrementor.
     *
     * @param max Maximal count.
     * @param cb Function to be called when the maximal count has been reached.
     * @throws NullArgumentException if {@code cb} is {@code null}.
     * @throws MathIllegalArgumentException if {@code max} is negative.
     */
    public Incrementor(int max,
                        MaxCountExceededCallback cb)
        throws NullArgumentException {
        this(0, max, cb);
    }

    /**
     * Creates an Incrementor.
     *
     * @param count Initial counter value.
     * @param max Maximal count.
     * @param cb Function to be called when the maximal count has been reached.
     * @throws NullArgumentException if {@code cb} is {@code null}.
     * @throws MathIllegalArgumentException if {@code max} is negative.
     */
    private Incrementor(int count,
                        int max,
                        MaxCountExceededCallback cb)
        throws NullArgumentException {
        if (cb == null) {
            throw new NullArgumentException();
        }
        if (max < 0) {
            throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL, max, 0);
        }
        this.maximalCount = max;
        this.maxCountCallback = cb;
        this.count = count;
    }

    /**
     * Creates a new instance and set the counter to the given value.
     *
     * @param value Value of the counter.
     * @return a new instance.
     */
    public Incrementor withCount(int value) {
        return new Incrementor(value,
                               this.maximalCount,
                               this.maxCountCallback);
    }

    /**
     * Creates a new instance with a given maximal count.
     * The counter is reset to 0.
     *
     * @param max Maximal count.
     * @return a new instance.
     * @throws MathIllegalArgumentException if {@code max} is negative.
     */
    public Incrementor withMaximalCount(int max) {
        return new Incrementor(0,
                               max,
                               this.maxCountCallback);
    }

    /**
     * Creates a new instance with a given callback.
     * The counter is reset to 0.
     *
     * @param cb Callback to be called at counter exhaustion.
     * @return a new instance.
     */
    public Incrementor withCallback(MaxCountExceededCallback cb) {
        return new Incrementor(0,
                               this.maximalCount,
                               cb);
    }

    /**
     * Gets the upper limit of the counter.
     *
     * @return the counter upper limit.
     */
    public int getMaximalCount() {
        return maximalCount;
    }

    /**
     * Gets the current count.
     *
     * @return the current count.
     */
    public int getCount() {
        return count;
    }

    /**
     * Checks whether incrementing the counter {@code nTimes} is allowed.
     *
     * @return {@code false} if calling {@link #increment()}
     * will trigger a {@code MathIllegalStateException},
     * {@code true} otherwise.
     */
    public boolean canIncrement() {
        return canIncrement(1);
    }

    /**
     * Checks whether incrementing the counter several times is allowed.
     *
     * @param nTimes Number of increments.
     * @return {@code false} if calling {@link #increment(int)
     * increment(nTimes)} would call the {@link MaxCountExceededCallback callback}
     * {@code true} otherwise.
     * @throws MathIllegalArgumentException if {@code nTimes} is negative.
     */
    public boolean canIncrement(int nTimes) {
        if (nTimes < 0) {
            throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
                                                   nTimes, 0);
        }
        return count <= maximalCount - nTimes;
    }

    /**
     * Performs multiple increments.
     *
     * @param nTimes Number of increments.
     * @throws MathIllegalArgumentException if {@code nTimes} is negative.
     *
     * @see #increment()
     */
    public void increment(int nTimes) {
        if (nTimes < 0) {
            throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
                                                   nTimes, 0);
        }

        for (int i = 0; i < nTimes; i++) {
            increment();
        }
    }

    /**
     * Adds the increment value to the current iteration count.
     * At counter exhaustion, this method will call the
     * {@link MaxCountExceededCallback#trigger(int) trigger} method of the
     * callback object passed to the
     * {@link #withCallback(MaxCountExceededCallback)} method.
     *
     * @see #increment(int)
     */
    public void increment() {
        if (count > maximalCount - 1) {
            maxCountCallback.trigger(maximalCount);
        }
        ++count;
    }

    /**
     * Resets the counter to 0.
     */
    public void reset() {
        count = 0;
    }
}