SquirrelJME/SquirrelJME

View on GitHub
modules/cldc-compact/src/main/java/java/lang/Thread.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 java.lang;

import cc.squirreljme.jvm.mle.ObjectShelf;
import cc.squirreljme.jvm.mle.ThreadShelf;
import cc.squirreljme.jvm.mle.brackets.VMThreadBracket;
import cc.squirreljme.runtime.cldc.annotation.Api;
import org.jetbrains.annotations.Blocking;
import org.jetbrains.annotations.Range;

/**
 * A thread represents literally a single stream of execution that can
 * execute concurrently (or not).
 *
 * SquirrelJME may be running with multiple threads executing at once or it
 * may also be executing cooperatively (only a single thread at a time). If
 * SquirrelJME is running cooperatively then only locking,
 * {@link Thread#sleep(long, int)}, or {@link Thread#yield()} will allow
 * another thread to run.
 *
 * @since 2018/12/07
 */
@Api
public class Thread
    implements Runnable
{
    /** Maximum supported priority. */
    @Api
    public static final int MAX_PRIORITY =
        10;
    
    /** Minimum supported priority. */
    @Api
    public static final int MIN_PRIORITY =
        1;
    
    /** Default priority. */
    @Api
    public static final int NORM_PRIORITY =
        5;
    
    /** Second in nano seconds. */
    private static final long _NS_SECOND =
        1_000_000L;
    
    /** The runnable that this thread uses for its main code, if applicable. */
    @SuppressWarnings({"unused", "FieldCanBeLocal"})
    private final Runnable _runnable;
    
    /** The virtual machine thread this uses. */
    private final VMThreadBracket _vmThread;
    
    /** The name of this thread. */
    private volatile String _name;
    
    /** Has this thread been started? */
    @SuppressWarnings("unused")
    private volatile boolean _started;
    
    /** Is this thread alive? */
    @SuppressWarnings("unused")
    private volatile boolean _isAlive;
    
    /** The priority of the thread. */
    private volatile int _priority =
        Thread.NORM_PRIORITY;
    
    /** Is this thread interrupted? */
    volatile boolean _interrupted;
    
    /**
     * Initializes the thread which invokes this object's {@link #run()} and
     * uses a default thread name.
     *
     * @since 2018/11/17
     */
    @Api
    public Thread()
    {
        this(null, false, null);
    }
    
    /**
     * Initializes the thread which invokes this object's {@link #run()} and
     * uses a default thread name.
     *
     * @param __r The runnable to execute.
     * @since 2018/11/17
     */
    @Api
    public Thread(Runnable __r)
    {
        this(__r, false, null);
    }
    
    /**
     * Initializes the thread which invokes this object's {@link #run()} and
     * uses the specified thread name.
     *
     * @param __n The thread's name.
     * @throws NullPointerException If the thread name is null.
     * @since 2018/11/17
     */
    @Api
    public Thread(String __n)
        throws NullPointerException
    {
        this(null, true, __n);
    }
    
    /**
     * Initializes the thread which invokes the given {@link Runnable} and uses
     * the given name.
     *
     * @param __r The runnable to execute.
     * @param __n The thread's name.
     * @throws NullPointerException If the thread name is null.
     * @since 2018/11/17
     */
    @Api
    public Thread(Runnable __r, String __n)
        throws NullPointerException
    {
        this(__r, true, __n);
    }
    
    /**
     * Initializes the thread.
     * 
     * @param __runnable The runnable to use.
     * @param __hasName Does this have a name?
     * @param __name The name of the thread.
     * @since 2022/09/24
     */
    @Api
    private Thread(Runnable __runnable, boolean __hasName, String __name)
    {
        if (__hasName && __name == null)
            throw new NullPointerException("NARG");
        
        VMThreadBracket vmThread = ThreadShelf.createVMThread(this,
            __name);
        this._vmThread = vmThread;
        
        this._runnable = __runnable;
        this._name = Thread.__defaultName(__name, vmThread);
    }
    
    /**
     * Checks that the thread has access to perform some operations.
     *
     * @throws SecurityException If access is denied.
     * @since 2018/11/21
     */
    @Api
    public final void checkAccess()
        throws SecurityException
    {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null)
            sm.checkAccess(this);
    }
    
    /**
     * Returns the ID of this thread.
     *
     * @return The thread ID.
     * @since 2018/11/20
     */
    @Api
    public long getId()
    {
        return ThreadShelf.vmThreadId(this._vmThread);
    }
    
    /**
     * Returns the name of this thread.
     *
     * @return The thread name.
     * @since 2018/11/17
     */
    @Api
    public final String getName()
    {
        return this._name;
    }
    
    /**
     * Returns the priority of the thread.
     *
     * @return The thread priority.
     * @since 2018/12/07
     */
    @Api
    public final int getPriority()
    {
        return this._priority;
    }
    
    /**
     * Interrupts the thread.
     * 
     * This will call {@link #checkAccess()} if this thread is not the current
     * thread.
     *
     * @throws SecurityException If the current thread is not permitted to
     * interrupt this thread.
     * @since 2018/11/21
     */
    @Api
    public void interrupt()
        throws SecurityException
    {
        // If this is not the current thread, we need to talk to the security
        // manager
        if (ThreadShelf.currentJavaThread() != this)
            this.checkAccess();
        
        // Signal software interrupt
        this._interrupted = true;
        
        // Signal hardware interrupt
        ThreadShelf.vmThreadInterrupt(this._vmThread);
    }
    
    /**
     * Is this thread currently alive?
     *
     * @return If this thread is alive.
     * @since 2018/11/20
     */
    @Api
    public final boolean isAlive()
    {
        return this._isAlive;
    }
    
    /**
     * Is this thread interrupted?
     *
     * @return If this thread is interrupted.
     * @since 2018/11/21
     */
    @Api
    public boolean isInterrupted()
    {
        return this._interrupted;
    }
    
    /**
     * Waits forever for a thread to die or until interrupted.
     * 
     * If the current thread is interrupted, then the interrupt status will
     * be cleared for the current thread.
     *
     * @throws InterruptedException If the thread was interrupted while
     * waiting.
     * @since 2018/12/07
     */
    @Api
    public final void join()
        throws InterruptedException
    {
        this.join(0, 0);
    }
    
    /**
     * Waits for a thread to die.
     * 
     * If the current thread is interrupted, then the interrupt status will
     * be cleared for the current thread.
     *
     * @param __ms The milliseconds to wait for, if this is zero then this
     * will wait forever.
     * @throws IllegalArgumentException If the timeout is negative.
     * @throws InterruptedException If the thread was interrupted while
     * waiting.
     * @since 2018/12/07
     */
    @Api
    public final void join(long __ms)
        throws IllegalArgumentException, InterruptedException
    {
        this.join(__ms, 0);
    }
    
    /**
     * Waits for a thread to die.
     *
     * If both milliseconds and nanoseconds are zero this will wait forever.
     * 
     * If the current thread is interrupted, then the interrupt status will
     * be cleared for the current thread.
     *
     * @param __ms The milliseconds to wait for.
     * @param __ns The nanoseconds to wait for.
     * @throws IllegalArgumentException If the timeout is negative.
     * @throws InterruptedException If the thread was interrupted while
     * waiting.
     * @since 2018/12/07
     */
    @Api
    public final void join(long __ms, int __ns)
        throws IllegalArgumentException, InterruptedException
    {
        // The end time, since our thread could be notified
        long end = (__ms == 0 && __ns == 0 ? Long.MAX_VALUE :
            System.nanoTime() + (__ms * Thread._NS_SECOND) + __ns);
        
        // Lock on self
        synchronized (this)
        {
            // Loop constantly until the thread is dead
            for (;;)
            {
                // Time ended?
                long now;
                if ((now = System.nanoTime()) >= end)
                    return;
                
                // Did the thread die yet?
                if (ThreadShelf.javaThreadIsStarted(this) &&
                    !this.isAlive())
                    return;
                
                // Otherwise wait on our own monitor
                long diff = end - now;
                this.wait(diff / Thread._NS_SECOND,
                    (int)(diff % Thread._NS_SECOND));
            }
        }
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/11/17
     */
    @Override
    public void run()
    {
        // Does nothing
    }
    
    /**
     * Sets the name of the thread.
     *
     * @param __n The new name of the thread.
     * @throws NullPointerException On null arguments.
     * @since 2018/11/21
     */
    @Api
    public final void setName(String __n)
        throws NullPointerException
    {
        if (__n == null)
            throw new NullPointerException("NARG");
        
        // Check access first
        this.checkAccess();
        
        // Set new name
        synchronized (this)
        {
            this._name = __n;
        }
    }
    
    /**
     * Sets the priority of the thread.
     *
     * @param __p The thread priority.
     * @throws IllegalArgumentException If the priority is not valid.
     * @throws SecurityException If setting the priority is not permitted.
     * @since 2018/12/07
     */
    @Api
    public final void setPriority(int __p)
        throws IllegalArgumentException, SecurityException
    {
        /* {@squirreljme.error ZZ20 Invalid priority.} */
        if (__p < Thread.MIN_PRIORITY || __p > Thread.MAX_PRIORITY)
            throw new IllegalArgumentException("ZZ20");
        
        // Check access
        this.checkAccess();
        
        // Store for later
        this._priority = __p;
        
        // Set the thread's hardware priority
        ThreadShelf.vmThreadSetPriority(this._vmThread, __p);
    }
    
    /**
     * Starts the specified thread.
     *
     * @throws IllegalThreadStateException If the thread was already started
     * or failed to start.
     * @since 2018/11/17
     */
    @Api
    public void start()
        throws IllegalThreadStateException
    {
        synchronized (this)
        {
            /* {@squirreljme.error ZZ21 A thread may only be started once.} */
            if (ThreadShelf.javaThreadIsStarted(this))
                throw new IllegalThreadStateException("ZZ21");
            
            /* {@squirreljme.error ZZ22 Failed to start the thread.} */
            if (!ThreadShelf.vmThreadStart(this._vmThread))
                throw new IllegalThreadStateException("ZZ22");
        }
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/11/20
     */
    @Override
    public String toString()
    {
        // JavaSE is in the format of `Thread[name,priority,group]` but
        // we do not have thread groups here
        return "Thread[" + this._name + "," + this._priority + "]";
    }
    
    /**
     * Returns the number of threads which are currently alive.
     *
     * @return The number of alive threads.
     * @since 2018/11/20
     */
    @Api
    public static int activeCount()
    {
        return ThreadShelf.aliveThreadCount(
            true, true);
    }
    
    /**
     * Returns the current thread.
     *
     * @return The current thread.
     * @since 2018/11/20
     */
    @Api
    public static Thread currentThread()
    {
        return ThreadShelf.currentJavaThread();
    }
    
    /**
     * Checks if the current thread holds the monitor for the given object.
     *
     * @param __o The object to check.
     * @return If the thread owns the monitor.
     * @throws NullPointerException On null arguments.
     * @since 2018/11/21
     */
    @Api
    public static boolean holdsLock(Object __o)
        throws NullPointerException
    {
        if (__o == null)
            throw new NullPointerException("NARG");
        
        return ObjectShelf.holdsLock(ThreadShelf.currentJavaThread(), __o);
    }
    
    /**
     * Checks if the current thread was interrupted, if it was then the
     * interrupt status will be cleared.
     *
     * @return If this thread was interrupted.
     * @since 2018/11/21
     */
    @Api
    public static boolean interrupted()
    {
        return ThreadShelf.javaThreadClearInterrupt(Thread.currentThread());
    }
    
    /**
     * Causes the thread to sleep for the given amount of milliseconds.
     *
     * @param __ms The number of milliseconds to sleep for.
     * @throws InterruptedException If the thread was interrupted.
     * @since 2018/11/04
     */
    @Api
    @Blocking
    public static void sleep(
        @Range(from = 0, to = Integer.MAX_VALUE) long __ms)
        throws InterruptedException
    {
        Thread.sleep(__ms, 0);
    }
    
    /**
     * Causes the thread to sleep for the given milliseconds and nanoseconds.
     * 
     * If the current thread is interrupted, then the interrupt status will
     * be cleared for the current thread.
     *
     * @param __ms The milliseconds to sleep for.
     * @param __ns The nanoseconds to sleep for, in the range of 0-999999.
     * @throws IllegalArgumentException If the milliseconds and/or nanoseconds
     * are out of range.
     * @throws InterruptedException If the thread was interrupted.
     * @since 2018/11/04
     */
    @Api
    @Blocking
    @SuppressWarnings("MagicNumber")
    public static void sleep(
        @Range(from = 0, to = Long.MAX_VALUE) long __ms,
        @Range(from = 0, to = 999999) int __ns)
        throws IllegalArgumentException, InterruptedException
    {
        /* {@squirreljme.error ZZ23 Invalid sleep arguments.} */
        if (__ms < 0 || __ns < 0 || __ns > 999999)
            throw new IllegalArgumentException("ZZ23");
        
        // Convert to integer but do not sleep for too long
        int ims = (__ms > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)__ms);
        
        // Perform sleep, if it was interrupted then the return status will
        // be non-zero!
        if (ThreadShelf.sleep(ims, __ns))
        {
            // The interrupt status becomes cleared for our current thread
            ThreadShelf.javaThreadClearInterrupt(Thread.currentThread());
            
            /* {@squirreljme.error ZZ24 Sleep was interrupted.} */
            throw new InterruptedException("ZZ24");
        }
    }
    
    /**
     * Yields the current thread giving up its execution slice, but allowing
     * it to continue instantly resuming as needed.
     *
     * @since 2018/12/05
     */
    @Api
    @Blocking
    public static void yield()
    {
        // Zero times means to yield
        try
        {
            Thread.sleep(0, 0);
        }
        catch (InterruptedException ignored)
        {
            // Ignore
        }
    }
    
    /**
     * Determines a default name for the given thread.
     *
     * @param __name The supplied name of the thread.
     * @param __vm The virtual machine thread.
     * @return The name to use for the thread.
     * @throws NullPointerException On null arguments.
     * @since 2020/06/17
     */
    private static String __defaultName(String __name, VMThreadBracket __vm)
        throws NullPointerException
    {
        if (__vm == null)
            throw new NullPointerException("NARG");
        
        // Use the given name
        if (__name != null)
            return __name;
        
        // Otherwise, it is just the attached thread ID
        return "Thread-" + ThreadShelf.vmThreadId(__vm);
    }
}