ErrorContext.java
package com.morphiqlabs.wavelet.exception;
import com.morphiqlabs.wavelet.api.Wavelet;
import com.morphiqlabs.wavelet.api.DiscreteWavelet;
import com.morphiqlabs.wavelet.api.BoundaryMode;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Builder for creating enhanced error messages with debugging context.
*
* <p>This class helps construct detailed error messages that include:</p>
* <ul>
* <li>Contextual information about the operation being performed</li>
* <li>Details about the wavelet, signal, and configuration</li>
* <li>Remediation suggestions</li>
* <li>Error codes for programmatic handling</li>
* </ul>
*
* <p>Example usage:</p>
* <pre>{@code
* throw new InvalidSignalException(
* ErrorCode.VAL_NOT_POWER_OF_TWO,
* ErrorContext.builder("Signal length must be power of two for DWT")
* .withSignalInfo(signal.length)
* .withWavelet(wavelet)
* .withSuggestion("Use MODWT for arbitrary length signals")
* .withSuggestion("Pad signal to nearest power of two: " + nextPowerOfTwo)
* .build()
* );
* }</pre>
*/
public class ErrorContext {
private final String baseMessage;
private final Map<String, Object> context = new LinkedHashMap<>();
private final StringBuilder suggestions = new StringBuilder();
private int suggestionCount = 0;
private ErrorContext(String baseMessage) {
this.baseMessage = baseMessage;
}
/**
* Creates a new error context builder.
*
* @param baseMessage The base error message
* @return A new builder instance
*/
public static ErrorContext builder(String baseMessage) {
return new ErrorContext(baseMessage);
}
/**
* Adds signal information to the error context.
*
* @param signalLength The length of the signal
* @return This builder for chaining
*/
public ErrorContext withSignalInfo(int signalLength) {
context.put("Signal length", signalLength);
return this;
}
/**
* Adds signal information with additional details.
*
* @param signalLength The length of the signal
* @param hasNonFinite Whether the signal contains NaN or Infinity
* @return This builder for chaining
*/
public ErrorContext withSignalInfo(int signalLength, boolean hasNonFinite) {
context.put("Signal length", signalLength);
if (hasNonFinite) {
context.put("Signal validity", "Contains NaN or Infinity values");
}
return this;
}
/**
* Adds wavelet information to the error context.
*
* @param wavelet The wavelet being used
* @return This builder for chaining
*/
public ErrorContext withWavelet(Wavelet wavelet) {
if (wavelet != null) {
context.put("Wavelet", wavelet.name());
context.put("Wavelet type", wavelet.getClass().getSimpleName());
if (wavelet instanceof DiscreteWavelet) {
DiscreteWavelet dw = (DiscreteWavelet) wavelet;
context.put("Filter length", dw.lowPassDecomposition().length);
}
}
return this;
}
/**
* Adds boundary mode information.
*
* @param mode The boundary mode
* @return This builder for chaining
*/
public ErrorContext withBoundaryMode(BoundaryMode mode) {
if (mode != null) {
context.put("Boundary mode", mode.name());
}
return this;
}
/**
* Adds decomposition level information.
*
* @param requestedLevel The requested decomposition level
* @param maxLevel The maximum supported level
* @return This builder for chaining
*/
public ErrorContext withLevelInfo(int requestedLevel, int maxLevel) {
context.put("Requested level", requestedLevel);
context.put("Maximum level", maxLevel);
return this;
}
/**
* Adds arbitrary context information.
*
* @param key The context key
* @param value The context value
* @return This builder for chaining
*/
public ErrorContext withContext(String key, Object value) {
context.put(key, value);
return this;
}
/**
* Adds a remediation suggestion.
*
* @param suggestion The suggestion text
* @return This builder for chaining
*/
public ErrorContext withSuggestion(String suggestion) {
if (suggestionCount > 0) {
suggestions.append("\n - ");
} else {
suggestions.append("\n\nSuggestions:\n - ");
}
suggestions.append(suggestion);
suggestionCount++;
return this;
}
/**
* Adds array dimension information.
*
* @param expected Expected dimensions
* @param actual Actual dimensions
* @return This builder for chaining
*/
public ErrorContext withArrayDimensions(String expected, String actual) {
context.put("Expected dimensions", expected);
context.put("Actual dimensions", actual);
return this;
}
/**
* Adds performance context for optimization errors.
*
* @param operation The operation being performed
* @param size The data size
* @return This builder for chaining
*/
public ErrorContext withPerformanceContext(String operation, int size) {
context.put("Operation", operation);
context.put("Data size", size);
return this;
}
/**
* Builds the final error message with all context.
*
* @return The complete error message
*/
public String build() {
StringBuilder message = new StringBuilder(baseMessage);
if (!context.isEmpty()) {
message.append("\n\nContext:");
context.forEach((key, value) ->
message.append("\n ").append(key).append(": ").append(value)
);
}
if (suggestionCount > 0) {
message.append(suggestions);
}
return message.toString();
}
/**
* Builds the error message and creates an exception.
*
* @param errorCode The error code
* @param exceptionClass The exception class to create
* @param <T> The exception type
* @return A new exception instance
*/
public <T extends WaveletTransformException> T buildException(
ErrorCode errorCode, Class<T> exceptionClass) {
String message = build();
try {
return exceptionClass
.getConstructor(ErrorCode.class, String.class)
.newInstance(errorCode, message);
} catch (Exception e) {
// Fallback to base exception if specific type can't be created
throw new WaveletTransformException(errorCode, message);
}
}
}