MODWTResultImpl.java

package com.morphiqlabs.wavelet.modwt;

import com.morphiqlabs.wavelet.util.ValidationUtils;
import java.util.Arrays;
import java.util.Objects;

/**
 * Default implementation of the MODWTResult interface.
 * 
 * <p>This implementation provides defensive copying of coefficient arrays
 * and validation to ensure data integrity.</p>
 * 
 * <h2>Thread Safety:</h2>
 * <p>This class is immutable and thread-safe after construction.</p>
 */
final class MODWTResultImpl implements MODWTResult {
    
    private final double[] approximationCoeffs;
    private final double[] detailCoeffs;
    private final int signalLength;
    
    /**
     * Constructs a new MODWT result with the given coefficient arrays.
     * 
     * @param approximationCoeffs the approximation (low-pass) coefficients
     * @param detailCoeffs        the detail (high-pass) coefficients
     * @throws NullPointerException     if any parameter is null
     * @throws IllegalArgumentException if coefficient arrays have different lengths,
     *                                  are empty, or contain invalid values
     */
    public MODWTResultImpl(double[] approximationCoeffs, double[] detailCoeffs) {
        Objects.requireNonNull(approximationCoeffs, "approximationCoeffs cannot be null");
        Objects.requireNonNull(detailCoeffs, "detailCoeffs cannot be null");
        
        if (approximationCoeffs.length != detailCoeffs.length) {
            throw new IllegalArgumentException(
                "Approximation and detail coefficients must have the same length. " +
                "Got approximation length: " + approximationCoeffs.length + 
                ", detail length: " + detailCoeffs.length);
        }
        
        if (approximationCoeffs.length == 0) {
            throw new IllegalArgumentException("Coefficient arrays cannot be empty");
        }
        
        // Validate coefficient values
        ValidationUtils.validateFiniteValues(approximationCoeffs, "approximationCoeffs");
        ValidationUtils.validateFiniteValues(detailCoeffs, "detailCoeffs");
        
        // Store defensive copies
        this.approximationCoeffs = Arrays.copyOf(approximationCoeffs, approximationCoeffs.length);
        this.detailCoeffs = Arrays.copyOf(detailCoeffs, detailCoeffs.length);
        this.signalLength = approximationCoeffs.length;
    }
    
    @Override
    public double[] approximationCoeffs() {
        return Arrays.copyOf(approximationCoeffs, approximationCoeffs.length);
    }
    
    @Override
    public double[] detailCoeffs() {
        return Arrays.copyOf(detailCoeffs, detailCoeffs.length);
    }
    
    @Override
    public int getSignalLength() {
        return signalLength;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        
        MODWTResultImpl that = (MODWTResultImpl) obj;
        return signalLength == that.signalLength &&
               Arrays.equals(approximationCoeffs, that.approximationCoeffs) &&
               Arrays.equals(detailCoeffs, that.detailCoeffs);
    }
    
    @Override
    public int hashCode() {
        int result = Objects.hash(signalLength);
        result = 31 * result + Arrays.hashCode(approximationCoeffs);
        result = 31 * result + Arrays.hashCode(detailCoeffs);
        return result;
    }
    
    @Override
    public String toString() {
        return String.format("MODWTResult{signalLength=%d, approximation=%s, detail=%s}",
                signalLength,
                Arrays.toString(Arrays.copyOf(approximationCoeffs, Math.min(5, approximationCoeffs.length))),
                Arrays.toString(Arrays.copyOf(detailCoeffs, Math.min(5, detailCoeffs.length))));
    }
}