SquirrelJME/SquirrelJME

View on GitHub
modules/cldc-compact/src/main/java/cc/squirreljme/jvm/launch/SuiteScanner.java

Summary

Maintainability
A
0 mins
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package cc.squirreljme.jvm.launch;

import cc.squirreljme.jvm.mle.RuntimeShelf;
import cc.squirreljme.jvm.mle.brackets.JarPackageBracket;
import cc.squirreljme.jvm.mle.constants.VMStatisticType;
import cc.squirreljme.jvm.suite.SuiteUtils;
import cc.squirreljme.runtime.cldc.debug.Debugging;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * This is a scanner which can read all the application groups that are
 * available.
 *
 * @since 2020/12/28
 */
public final class SuiteScanner
{
    /** The shelf to access. */
    protected final VirtualJarPackageShelf shelf;
    
    /** Allow parallel scanning? */
    protected final boolean parallel;
    
    /**
     * Initializes the base suite scanner.
     * 
     * @param __parallel Allow parallel scanning?
     * @since 2020/12/28
     */
    public SuiteScanner(boolean __parallel)
    {
        this(__parallel, new DefaultJarPackageShelf());
    }
    
    /**
     * Initializes the suite scanner.
     *
     * @param __parallel Allow parallel scanning?
     * @param __shelf The shelf to initialize from.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/06
     */
    public SuiteScanner(boolean __parallel, VirtualJarPackageShelf __shelf)
        throws NullPointerException
    {
        if (__shelf == null)
            throw new NullPointerException("NARG");
        
        this.shelf = __shelf;
        this.parallel = __parallel;
    }
    
    /**
     * Scans all the available suites and returns information that is needed
     * for them to properly launch.
     * 
     * @return The state of scanned suites.
     * @since 2020/12/28
     */
    public AvailableSuites scanSuites()
    {
        return this.scanSuites(null);
    }
    
    /**
     * Scans all the available suites and returns information that is needed
     * for them to properly launch.
     * 
     * @param __listener The listener for suites as they are scanned, used to
     * indicate progress.
     * @return The state of scanned suites.
     * @since 2020/12/29
     */
    public AvailableSuites scanSuites(SuiteScanListener __listener)
    {
        // Get all the available libraries
        JarPackageBracket[] jars = this.shelf.libraries();
        int numJars = jars.length;
        
        // Load the names of all JARs and map to brackets, this is later used
        // for i-mode lookup.
        Map<String, JarPackageBracket> nameToJar = new HashMap<>();
        synchronized (nameToJar)
        {
            for (JarPackageBracket jar : jars)
            {
                String name = this.shelf.libraryPath(jar);
                if (name != null)
                {
                    // Full path
                    nameToJar.put(name, jar);
                    
                    // Short path
                    nameToJar.put(SuiteUtils.baseName(name), jar);
                }
            }
        }
        
        // Libraries are lazily handled on launching
        __Libraries__ libs = new __Libraries__();
        
        // Applications which should be loaded
        List<Application> result = new LinkedList<>();
        
        // How many CPUs are available? This is so we can split the suite
        // loading operations across multiple threads at once for faster
        // scanning... On SpringCoat scans can take a while, so we want to
        // make it as fast as we can...
        int numThreads;
        if (this.parallel)
            numThreads = Math.max(1, (int)RuntimeShelf.vmStatistic(
                VMStatisticType.CPU_THREAD_COUNT));
        else
            numThreads = 1;
        
        // Locate programs via the library path, single threaded, so we do not
        // need to anything more complex
        __SuiteScannerCounter__ jarIndexCount = new __SuiteScannerCounter__();
        if (numThreads <= 1)
            this.__loadStripe(__listener, jars, numJars, nameToJar,
                libs, result, 0, numJars, jarIndexCount);
        
        // Stripe loads to each CPU that is available
        else
        {
            // Counter for stripe load status
            __SuiteScannerCounter__ done = new __SuiteScannerCounter__();
            
            // Divide 
            int baseSplitLen = Math.max(1, numJars / numThreads);
            int actualSplits = 0;
            for (int at = 0, stripe = 0; at < numJars; at += baseSplitLen,
                    stripe++)
            {
                // Ignore the first stripe, it is run in this thread
                if (stripe == 0)
                    continue;
                
                // Make it actual!
                actualSplits++;
                
                // Setup thread and start it
                Thread thread = new Thread(new __SuiteScannerStripe__(done,
                    __listener, jars, numJars, nameToJar, libs, result, at,
                    Math.min(numJars, at + baseSplitLen), jarIndexCount,
                    this),
                    "SquirrelJMESuiteScanStripe-" + stripe);
                thread.start();
            }
            
            // Run our first stripe in this thread, so we do not waste it
            this.__loadStripe(__listener, jars, numJars, nameToJar,
                libs, result, 0, baseSplitLen, jarIndexCount);
            
            // Wait until everything is done
            for (;;)
                synchronized (done)
                {
                    // Is everything done?
                    if (done._count >= actualSplits)
                        break;
                    
                    // Not everything was done, just wait around a bit
                    try
                    {
                        done.wait(1000);
                    }
                    catch (InterruptedException ignored)
                    {
                    }
                }
        }
        
        // Finalize suite list
        synchronized (result)
        {
            return new AvailableSuites(this.shelf, libs,
                result.<Application>toArray(new Application[result.size()]));
        }
    }
    
    /**
     * Loads a stripe of JARs.
     * 
     * @param __listener The listener used.
     * @param __jars The jars to load.
     * @param __numJars The number of maximum JARs.
     * @param __nameToJar The name to JAR mapping.
     * @param __libs The library cache.
     * @param __result The output result.
     * @param __startPos The start position to run the scan
     * @param __endPos The end position, exclusive.
     * @param __jarIndexCount The JAR index counter, for multi-threaded
     * accuracy in the totals.
     * @since 2022/10/03
     */
    void __loadStripe(SuiteScanListener __listener,
        JarPackageBracket[] __jars, int __numJars,
        Map<String, JarPackageBracket> __nameToJar, __Libraries__ __libs,
        List<Application> __result, int __startPos, int __endPos,
        __SuiteScannerCounter__ __jarIndexCount)
    {
        // Process stripe
        for (int i = __startPos, end = Math.min(__endPos, __numJars);
             i < end; i++)
        {
            JarPackageBracket jar = __jars[i];
            
            // Get an accurate count for this JAR, if there are invalid JARs
            // the numbers could potentially be skipped
            int accurateJarIndex;
            synchronized (__jarIndexCount)
            {
                accurateJarIndex = __jarIndexCount._count++;
            }
            
            // Ignore non-JARs
            String libPath = this.shelf.libraryPath(jar);
            if (!SuiteUtils.isJar(libPath))
                continue;
            
            // Debug
            Debugging.debugNote("Checking %s...", libPath);
            
            // Setup parser state
            ApplicationParserState state = new ApplicationParserState(
                __listener, __numJars, __libs, __nameToJar, __result,
                accurateJarIndex, jar, this.shelf);
            
            // Scan for all the application types accordingly
            for (ApplicationParser parser : ApplicationParser.values())
                parser.parse(state);
        }
    }
}