WaveletValidationUtils.java

package com.morphiqlabs.wavelet.util;

import com.morphiqlabs.wavelet.api.DiscreteWavelet;
import com.morphiqlabs.wavelet.api.Wavelet;
import com.morphiqlabs.wavelet.exception.InvalidArgumentException;
import com.morphiqlabs.wavelet.exception.InvalidConfigurationException;

/**
 * Utility class for wavelet-specific validation operations.
 * Provides centralized validation methods for wavelet parameters and configurations.
 */
public final class WaveletValidationUtils {

    /**
     * Private constructor to prevent instantiation.
     */
    private WaveletValidationUtils() {
        throw new AssertionError("Utility class - do not instantiate");
    }

    /**
     * Validates that a wavelet is not null.
     *
     * @param wavelet       the wavelet to validate
     * @param parameterName the parameter name for error messages
     * @throws InvalidArgumentException if wavelet is null
     */
    public static void validateWaveletNotNull(Wavelet wavelet, String parameterName) {
        if (wavelet == null) {
            throw InvalidArgumentException.nullArgument(parameterName);
        }
    }

    /**
     * Validates that a wavelet is discrete (not continuous).
     *
     * @param wavelet the wavelet to validate
     * @throws InvalidConfigurationException if wavelet is not discrete
     */
    public static void validateDiscreteWavelet(Wavelet wavelet) {
        validateWaveletNotNull(wavelet, "wavelet");

        if (!(wavelet instanceof DiscreteWavelet)) {
            throw InvalidConfigurationException.unsupportedOperation(
                    wavelet.getClass().getSimpleName(),
                    "discrete wavelet transform operations"
            );
        }
    }

    /**
     * Validates decomposition level bounds.
     *
     * @param level    the level to validate
     * @param maxLevel the maximum allowed level
     * @param context  additional context for error message
     * @throws InvalidArgumentException if level is out of bounds
     */
    public static void validateDecompositionLevel(int level, int maxLevel, String context) {
        if (level < 1) {
            throw new InvalidArgumentException(
                    String.format("Decomposition level must be at least 1, got: %d. %s", level, context)
            );
        }

        if (level > maxLevel) {
            throw new InvalidArgumentException(
                    String.format("Decomposition level %d exceeds maximum %d. %s", level, maxLevel, context)
            );
        }
    }

    /**
     * Validates coefficient arrays have matching lengths.
     *
     * @param approxLength length of approximation coefficients
     * @param detailLength length of detail coefficients
     * @param context      additional context for error message
     * @throws InvalidArgumentException if lengths don't match
     */
    public static void validateCoefficientLengths(int approxLength, int detailLength, String context) {
        if (approxLength != detailLength) {
            throw new InvalidArgumentException(
                    String.format("Coefficient arrays must have same length. " +
                            "Approximation: %d, Detail: %d. %s", approxLength, detailLength, context)
            );
        }
    }

    /**
     * Calculates the maximum decomposition levels for a given signal length and wavelet.
     *
     * @param signalLength the length of the signal
     * @param wavelet      the wavelet to use
     * @param maxAllowed   the maximum allowed levels from configuration
     * @return the maximum feasible decomposition levels
     */
    public static int calculateMaxDecompositionLevels(int signalLength, Wavelet wavelet, int maxAllowed) {
        validateDiscreteWavelet(wavelet);

        DiscreteWavelet discreteWavelet = (DiscreteWavelet) wavelet;
        int filterLength = discreteWavelet.lowPassDecomposition().length;

        int maxLevels = 0;
        int currentLength = signalLength;

        // Each level halves the length; stop when length < filter length
        while (currentLength >= filterLength && maxLevels < maxAllowed) {
            currentLength = (currentLength + 1) / 2;
            maxLevels++;
        }

        return Math.max(1, maxLevels);
    }
}