PlatformDetector.java

package com.morphiqlabs.wavelet.util;

/**
 * Utility class for detecting platform characteristics.
 * 
 * <p>Provides centralized platform detection logic to enable better testing
 * and maintainability across the wavelet transform library.</p>
 */
public final class PlatformDetector {
    
    /**
     * Platform types supported by the library.
     */
    public enum Platform {
        /** Apple Silicon (M1, M2, etc.) */
        APPLE_SILICON,
        /** x86-64 (Intel/AMD) */
        X86_64,
        /** ARM (non-Apple) */
        ARM,
        /** Unknown platform */
        UNKNOWN
    }
    
    /**
     * Operating system types.
     */
    public enum OperatingSystem {
        /** macOS */
        MACOS,
        /** Microsoft Windows */
        WINDOWS,
        /** Linux distributions */
        LINUX,
        /** Other/unknown OS */
        OTHER
    }
    
    /**
     * Cache configuration for the platform.
     *
     * @param l1DataCacheSize L1 data cache size in bytes
     * @param l2CacheSize L2 cache size in bytes
     * @param cacheLineSize Cache line size in bytes
     */
    public record CacheInfo(
        int l1DataCacheSize,
        int l2CacheSize,
        int cacheLineSize
    ) {
        public static final CacheInfo APPLE_SILICON = new CacheInfo(128 * 1024, 4 * 1024 * 1024, 64);
        public static final CacheInfo X86_64 = new CacheInfo(32 * 1024, 256 * 1024, 64);
        public static final CacheInfo ARM_DEFAULT = new CacheInfo(64 * 1024, 1024 * 1024, 64);
        public static final CacheInfo DEFAULT = new CacheInfo(32 * 1024, 256 * 1024, 64);
    }
    
    private static final Platform CURRENT_PLATFORM = detectPlatform();
    private static final OperatingSystem CURRENT_OS = detectOperatingSystem();
    private static final CacheInfo CACHE_INFO = detectCacheInfo();
    
    // Prevent instantiation
    private PlatformDetector() {
        throw new AssertionError("Utility class should not be instantiated");
    }
    
    /**
     * Gets the detected platform type.
     * 
     * @return the current platform
     */
    public static Platform getPlatform() {
        return CURRENT_PLATFORM;
    }
    
    /**
     * Gets the cache configuration for the current platform.
     * 
     * @return cache information
     */
    public static CacheInfo getCacheInfo() {
        return CACHE_INFO;
    }
    
    /**
     * Checks if the current platform is Apple Silicon.
     * 
     * @return true if running on Apple Silicon
     */
    public static boolean isAppleSilicon() {
        return CURRENT_PLATFORM == Platform.APPLE_SILICON;
    }
    
    /**
     * Checks if the current platform is x86-64.
     * 
     * @return true if running on x86-64
     */
    public static boolean isX86_64() {
        return CURRENT_PLATFORM == Platform.X86_64;
    }
    
    /**
     * Checks if the current platform is ARM-based.
     * 
     * @return true if running on any ARM platform (including Apple Silicon)
     */
    public static boolean isARM() {
        return CURRENT_PLATFORM == Platform.APPLE_SILICON || CURRENT_PLATFORM == Platform.ARM;
    }
    
    /**
     * Detects the current platform.
     * 
     * <p>This method can be overridden for testing by setting system properties:
     * <ul>
     *   <li>{@code com.morphiqlabs.test.platform} - Override platform detection</li>
     *   <li>{@code com.morphiqlabs.test.arch} - Override architecture detection</li>
     *   <li>{@code com.morphiqlabs.test.os} - Override OS detection</li>
     * </ul>
     * 
     * @return the detected platform
     */
    private static Platform detectPlatform() {
        // Check for test overrides first
        String testPlatform = System.getProperty("com.morphiqlabs.test.platform");
        if (testPlatform != null) {
            try {
                return Platform.valueOf(testPlatform.toUpperCase());
            } catch (IllegalArgumentException ignored) {
                // Fall through to normal detection
            }
        }
        
        // Get architecture and OS information
        String arch = System.getProperty("com.morphiqlabs.test.arch", 
                                       System.getProperty("os.arch", "")).toLowerCase();
        String osName = System.getProperty("com.morphiqlabs.test.os",
                                         System.getProperty("os.name", "")).toLowerCase();
        
        // Detect platform based on architecture and OS
        if (arch.contains("aarch64") || arch.contains("arm64")) {
            if (osName.contains("mac") || osName.contains("darwin")) {
                return Platform.APPLE_SILICON;
            } else {
                return Platform.ARM;
            }
        } else if (arch.contains("amd64") || arch.contains("x86_64")) {
            return Platform.X86_64;
        } else if (arch.contains("arm")) {
            return Platform.ARM;
        }
        
        return Platform.UNKNOWN;
    }
    
    /**
     * Detects cache configuration for the current platform.
     * 
     * <p>Cache sizes can be overridden using system properties:
     * <ul>
     *   <li>{@code com.morphiqlabs.cache.l1.size} - L1 data cache size in bytes</li>
     *   <li>{@code com.morphiqlabs.cache.l2.size} - L2 cache size in bytes</li>
     *   <li>{@code com.morphiqlabs.cache.line.size} - Cache line size in bytes</li>
     * </ul>
     * 
     * @return cache configuration
     */
    private static CacheInfo detectCacheInfo() {
        // Check for property overrides
        Integer l1Size = getSystemPropertyInt("com.morphiqlabs.cache.l1.size");
        Integer l2Size = getSystemPropertyInt("com.morphiqlabs.cache.l2.size");
        Integer lineSize = getSystemPropertyInt("com.morphiqlabs.cache.line.size");
        
        // If all properties are set, use them
        if (l1Size != null && l2Size != null && lineSize != null) {
            return new CacheInfo(l1Size, l2Size, lineSize);
        }
        
        // Otherwise, use platform defaults with property overrides
        CacheInfo platformDefault = switch (CURRENT_PLATFORM) {
            case APPLE_SILICON -> CacheInfo.APPLE_SILICON;
            case X86_64 -> CacheInfo.X86_64;
            case ARM -> CacheInfo.ARM_DEFAULT;
            case UNKNOWN -> CacheInfo.DEFAULT;
        };
        
        return new CacheInfo(
            l1Size != null ? l1Size : platformDefault.l1DataCacheSize,
            l2Size != null ? l2Size : platformDefault.l2CacheSize,
            lineSize != null ? lineSize : platformDefault.cacheLineSize
        );
    }
    
    /**
     * Gets an integer system property value.
     * 
     * @param key the property key
     * @return the integer value, or null if not set or invalid
     */
    private static Integer getSystemPropertyInt(String key) {
        String value = System.getProperty(key);
        if (value != null) {
            try {
                return Integer.parseInt(value);
            } catch (NumberFormatException ignored) {
                // Return null for invalid values
            }
        }
        return null;
    }
    
    /**
     * Gets the detected operating system.
     * 
     * @return the current operating system
     */
    public static OperatingSystem getOperatingSystem() {
        return CURRENT_OS;
    }
    
    /**
     * Detects the current operating system.
     * 
     * @return the detected operating system
     */
    private static OperatingSystem detectOperatingSystem() {
        String osName = System.getProperty("com.morphiqlabs.test.os",
                                         System.getProperty("os.name", "")).toLowerCase();
        
        if (osName.contains("mac") || osName.contains("darwin")) {
            return OperatingSystem.MACOS;
        } else if (osName.contains("win")) {
            return OperatingSystem.WINDOWS;
        } else if (osName.contains("nux") || osName.contains("nix") || osName.contains("aix")) {
            return OperatingSystem.LINUX;
        }
        
        return OperatingSystem.OTHER;
    }
    
    /**
     * Checks if the platform likely supports AVX2 instructions.
     * This is a heuristic based on platform type.
     * 
     * @return true if AVX2 is likely supported
     */
    public static boolean hasAVX2Support() {
        return switch (CURRENT_PLATFORM) {
            case X86_64 -> true; // Most modern x86-64 CPUs have AVX2
            case APPLE_SILICON -> true; // Apple Silicon has NEON which is comparable
            default -> false;
        };
    }
    
    /**
     * Checks if the platform likely supports AVX-512 instructions.
     * This is a heuristic based on platform type.
     * 
     * @return true if AVX-512 is likely supported
     */
    public static boolean hasAVX512Support() {
        // AVX-512 is only on some x86-64 processors, not on ARM
        return CURRENT_PLATFORM == Platform.X86_64;
    }
    
    /**
     * Gets the recommended SIMD threshold for the current platform.
     * 
     * @return recommended minimum array size for SIMD operations
     */
    public static int getRecommendedSIMDThreshold() {
        return switch (CURRENT_PLATFORM) {
            case APPLE_SILICON -> 8; // Apple Silicon benefits from SIMD with smaller arrays
            case ARM -> 8; // ARM NEON also efficient with smaller arrays
            case X86_64 -> 16; // x86-64 needs larger arrays to amortize overhead
            case UNKNOWN -> 32; // Conservative default
        };
    }
    
    /**
     * Gets platform-specific optimization hints.
     * 
     * @return optimization hints as a formatted string
     */
    public static String getPlatformOptimizationHints() {
        StringBuilder hints = new StringBuilder();
        hints.append("Platform: ").append(CURRENT_PLATFORM).append("\n");
        hints.append("OS: ").append(CURRENT_OS).append("\n");
        hints.append("SIMD Threshold: ").append(getRecommendedSIMDThreshold()).append(" elements\n");
        
        if (CURRENT_PLATFORM == Platform.APPLE_SILICON) {
            hints.append("Optimizations: NEON SIMD, unified memory architecture\n");
            hints.append("Recommendations: Use smaller block sizes, leverage cache-friendly access patterns\n");
        } else if (CURRENT_PLATFORM == Platform.X86_64) {
            hints.append("Optimizations: ");
            if (hasAVX512Support()) hints.append("AVX-512 ");
            if (hasAVX2Support()) hints.append("AVX2 ");
            hints.append("SSE\n");
            hints.append("Recommendations: Use larger block sizes for SIMD, consider NUMA effects\n");
        }
        
        hints.append("Cache: L1=").append(CACHE_INFO.l1DataCacheSize / 1024).append("KB, ");
        hints.append("L2=").append(CACHE_INFO.l2CacheSize / 1024).append("KB, ");
        hints.append("Line=").append(CACHE_INFO.cacheLineSize).append("B");
        
        return hints.toString();
    }
    
    /**
     * Gets a human-readable description of the current platform.
     * 
     * @return platform description
     */
    public static String getDescription() {
        return String.format("Platform: %s, OS: %s, L1 Cache: %d KB, L2 Cache: %d MB, Cache Line: %d bytes",
            CURRENT_PLATFORM,
            CURRENT_OS,
            CACHE_INFO.l1DataCacheSize / 1024,
            CACHE_INFO.l2CacheSize / (1024 * 1024),
            CACHE_INFO.cacheLineSize
        );
    }
}