/*
 * @(#)ThreadGroup.java	1.23 96/03/25  
 *
 * Copyright (c) 1994 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL purposes and without
 * fee is hereby granted provided that this copyright notice
 * appears in all copies. Please refer to the file "copyright.html"
 * for further important copyright and licensing information.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 */

package java.lang;

import java.io.PrintStream;

/**
 * A group of Threads. A Thread group can contain a set of Threads
 * as well as a set of other Thread groups. A Thread can access its
 * Thread group, but it can't access the parent of its Thread group.
 * This makes it possible to encapsulate a Thread in a Thread group
 * and stop it from manipulating Threads in the parent group.
 *
 * @version 	1.23, 25 Mar 1996
 * @author	Arthur van Hoff
 */
public
class ThreadGroup {
    ThreadGroup parent;
    String name;
    int maxPriority;
    boolean destroyed;
    boolean daemon;

    int nthreads;
    Thread threads[];

    int ngroups;
    ThreadGroup groups[];

    /**
     * Creates an empty Thread group that is not in any Thread group. 
     * This method is used to create the system Thread group.
     */
    private ThreadGroup() {	// called from C code
	this.name = "system";
	this.maxPriority = Thread.MAX_PRIORITY;
    }

    /**
     * Creates a new ThreadGroup. Its parent will be the Thread group
     * of the current Thread.
     * @param name the name of the new Thread group created
     */
    public ThreadGroup(String name) {
	this(Thread.currentThread().getThreadGroup(), name);
    }

    /**
     * Creates a new ThreadGroup with a specified name in the specified Thread group.
     * @param parent the specified parent Thread group
     * @param name the name of the new Thread group being created
     * @exception NullPointerException If the given thread group is equal to null.
     */
    public ThreadGroup(ThreadGroup parent, String name) {
	if (parent == null) {
	    throw new NullPointerException();
	}
	parent.checkAccess();
	this.name = name;
	this.maxPriority = parent.maxPriority;
	this.daemon = parent.daemon;
	this.parent = parent;
	parent.add(this);
    }

    /**
     * Gets the name of this Thread group.
     */
    public final String getName() {
	return name;
    }

    /**
     * Gets the parent of this Thread group.
     */
    public final ThreadGroup getParent() {
	return parent;
    }

    /**
     * Gets the maximum priority of the group. Threads that are
     * part of this group cannot have a higher priority than the maximum priority.
     */
    public final int getMaxPriority() {
	return maxPriority;
    }

    /**
     * Returns the daemon flag of the Thread group. A daemon Thread group
     * is automatically destroyed when it is found empty after a Thread
     * group or Thread is removed from it.
     */
    public final boolean isDaemon() {
	return daemon;
    }

    /**
     * Changes the daemon status of this group.
     * @param daemon the daemon boolean which is to be set.
     */
    public final void setDaemon(boolean daemon) {
	checkAccess();
	this.daemon = daemon;
    }

    /**
     * Sets the maximum priority of the group. Threads
     * that are already in the group <b>can</b> have a higher priority than the
     * set maximum.
     * @param pri the priority of the Thread group
     */
    public final synchronized void setMaxPriority(int pri) {
	checkAccess();
	if (pri < Thread.MIN_PRIORITY) {
	    maxPriority = Thread.MIN_PRIORITY;
	} else if (pri < maxPriority) {
	    maxPriority = pri;
	}
	for (int i = 0 ; i < ngroups ; i++) {
	    groups[i].setMaxPriority(pri);
	}
    }

    /**
     * Checks to see if this Thread group is a parent of or is equal to
     * another Thread group.
     * @param g the Thread group to be checked
     * @return true if this Thread group is equal to or is the parent of another Thread
     * group; false otherwise.
     */
    public final boolean parentOf(ThreadGroup g) {
	for (; g != null ; g = g.parent) {
	    if (g == this) {
		return true;
	    }
	}
	return false;
    }

    /**
     * Checks to see if the current Thread is allowed to modify this group.
     * @exception SecurityException If the current Thread is not allowed 
     * to access this Thread group.
     */
    public final void checkAccess() {
	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkAccess(this);
	}
    }

    /**
     * Returns an estimate of the number of active Threads in the
     * Thread group.
     */
    public synchronized int activeCount() {
	if (destroyed) {
            return 0;
	}
	int n = nthreads;
	for (int i = 0 ; i < ngroups ; i++) {
	    n += groups[i].activeCount();
	}
	return n;
    }

    /**
     * Copies, into the specified array, references to every active Thread in this Thread group.
     * You can use the activeCount() method to get an estimate of how big
     * the array should be.
     * @param list an array of Threads
     * @return the number of Threads put into the array
     */
    public int enumerate(Thread list[]) {
	return enumerate(list, 0, true);
    }

    /**
     * Copies, into the specified array, references to every active Thread in this Thread group.
     * You can use the activeCount() method to get an estimate of how big
     * the array should be.
     * @param list an array list of Threads
     * @param recurse a boolean indicating whether a Thread has reapearred
     * @return the number of Threads placed into the array.
     */
    public int enumerate(Thread list[], boolean recurse) {
	return enumerate(list, 0, recurse);
    }

    private synchronized int enumerate(Thread list[], int n, boolean recurse) {
	if (destroyed) {
            return 0;
	}
	int nt = nthreads;
	if (nt > list.length - n) {
	    nt = list.length - n;
	}
	if (nt > 0) {
	    System.arraycopy(threads, 0, list, n, nt);
	    n += nt;
	}
	if (recurse) {
	    for (int i = 0 ; i < ngroups ; i++) {
		n = groups[i].enumerate(list, n, true);
	    }
	}
	return n;
    }

    /**
     * Returns an estimate of the number of active groups in the
     * Thread group.
     */
    public synchronized int activeGroupCount() {
	if (destroyed) {
            return 0;
	}
	int n = ngroups;
	for (int i = 0 ; i < ngroups ; i++) {
	    n += groups[i].activeGroupCount();
	}
	return n;
    }

    /**
     * Copies, into the specified array, references to every active Thread group in this Thread 
     * group.  You can use the activeGroupCount() method to get an estimate of how big
     * the array should be.
     * @param list an array of Thread groups
     * @return the number of Thread groups placed into the array.
     */
    public int enumerate(ThreadGroup list[]) {
	return enumerate(list, 0, true);
    }

    /**
     * Copies, into the specified array, references to every active Thread group in this Thread 
     * group.  You can use the activeGroupCount() method to get an estimate of how big
     * the array should be.
     * @param list an array list of Thread groups
     * @param recurse a boolean indicating if a Thread group has reappeared
     * @return the number of Thread groups placed into the array.
     */
    public int enumerate(ThreadGroup list[], boolean recurse) {
	return enumerate(list, 0, recurse);
    }

    private synchronized int enumerate(ThreadGroup list[], int n, boolean recurse) {
	if (destroyed) {
            return 0;
	}
	int ng = ngroups;
	if (ng > list.length - n) {
	    ng = list.length - n;
	}
	if (ng > 0) {
	    System.arraycopy(groups, 0, list, n, ng);
	    n += ng;
	}
	if (recurse) {
	    for (int i = 0 ; i < ngroups ; i++) {
		n = groups[i].enumerate(list, n, true);
	    }
	}
	return n;
    }

    /**
     * Stops all the Threads in this Thread group and all of its sub groups.
     */
    public final synchronized void stop() {
	checkAccess();
	for (int i = 0 ; i < ngroups ; i++) {
	    groups[i].stop();
	}
	for (int i = 0 ; i < nthreads ; i++) {
	    threads[i].stop();
	}
    }

    /**
     * Suspends all the Threads in this Thread group and all of its sub groups.
     */
    public final synchronized void suspend() {
	checkAccess();
	for (int i = 0 ; i < ngroups ; i++) {
	    groups[i].suspend();
	}
	for (int i = 0 ; i < nthreads ; i++) {
	    threads[i].suspend();
	}
    }

    /**
     * Resumes all the Threads in this Thread group and all of its sub groups.
     */
    public final synchronized void resume() {
	checkAccess();
	for (int i = 0 ; i < ngroups ; i++) {
	    groups[i].resume();
	}
	for (int i = 0 ; i < nthreads ; i++) {
	    threads[i].resume();
	}
    }

    /**
     * Destroys a Thread group. This does <b>NOT</b> stop the Threads
     * in the Thread group.
     * @exception IllegalThreadStateException If the Thread group is not empty
     * 		or if the Thread group was already destroyed.
     */
    public final synchronized void destroy() {
	checkAccess();
	if (destroyed || (nthreads > 0)) {
	    throw new IllegalThreadStateException();
	}
	while (ngroups > 0) {
	    groups[0].destroy();
	}
	if (parent != null) {
	    destroyed = true;
	    groups = null;
	    threads = null;
	    parent.remove(this);
	}
    }

    /**
     * Adds the specified Thread group to this group.
     * @param g the specified Thread group to be added
     * @exception IllegalThreadStateException If the Thread group has been destroyed.
     */
    private final synchronized void add(ThreadGroup g){
	if (destroyed) {
	    throw new IllegalThreadStateException();
	}
	if (groups == null) {
	    groups = new ThreadGroup[4];
	} else if (ngroups == groups.length) {
	    ThreadGroup newgroups[] = new ThreadGroup[ngroups * 2];
	    System.arraycopy(groups, 0, newgroups, 0, ngroups);
	    groups = newgroups;
	}
	groups[ngroups] = g;

	// This is done last so it doesn't matter in case the
	// thread is killed
	ngroups++;
    }

    /**
     * Removes the specified Thread group from this group.
     * @param g the Thread group to be removed
     * @return if this Thread has already been destroyed.
     */
    private synchronized void remove(ThreadGroup g) {
	if (destroyed) {
	    return;
	}
	for (int i = 0 ; i < ngroups ; i++) {
	    if (groups[i] == g) {
		System.arraycopy(groups, i + 1, groups, i, --ngroups - i);
		// Zap dangling reference to the dead group so that
		// the garbage collector will collect it.
		groups[ngroups] = null;
		break;
	    }
	}
	if (nthreads == 0) {
	    notifyAll();
	}
	if (daemon && (nthreads == 0) && (ngroups == 0)) {
	    destroy();
	}
    }
    
    /**
     * Adds the specified Thread to this group.
     * @param t the Thread to be added
     * @exception IllegalThreadStateException If the Thread group has been destroyed.
     */
    synchronized void add(Thread t) {
	if (destroyed) {
	    throw new IllegalThreadStateException();
	}
	if (threads == null) {
	    threads = new Thread[4];
	} else if (nthreads == threads.length) {
	    Thread newthreads[] = new Thread[nthreads * 2];
	    System.arraycopy(threads, 0, newthreads, 0, nthreads);
	    threads = newthreads;
	}
	threads[nthreads] = t;

	// This is done last so it doesn't matter in case the
	// thread is killed
	nthreads++;
    }

    /**
     * Removes the specified Thread from this group.
     * @param t the Thread to be removed
     * @return if the Thread has already been destroyed.
     */
    synchronized void remove(Thread t) {
	if (destroyed) {
	    return;
	}
	for (int i = 0 ; i < nthreads ; i++) {
	    if (threads[i] == t) {
		System.arraycopy(threads, i + 1, threads, i, --nthreads - i);
		// Zap dangling reference to the dead thread so that
		// the garbage collector will collect it.
		threads[nthreads] = null;
		break;
	    }
	}
	if (nthreads == 0) {
	    notifyAll();
	}
	if (daemon && (nthreads == 0) && (ngroups == 0)) {
	    destroy();
	}
    }

    /**
     * Lists this Thread group. Useful for debugging only.
     */
     public synchronized void list() {
	list(System.out, 0);
     }
     void list(PrintStream out, int indent) {
	for (int j = 0 ; j < indent ; j++) {
	    out.print(" ");
	}
	out.println(this);
	indent += 4;
	for (int i = 0 ; i < nthreads ; i++) {
	    for (int j = 0 ; j < indent ; j++) {
		out.print(" ");
	    }
	    out.println(threads[i]);
	}
	for (int i = 0 ; i < ngroups ; i++) {
	    groups[i].list(out, indent);
	}
     }

    /**
     * Called when a thread in this group exists because of
     * an uncaught exception.
     */
    public void uncaughtException(Thread t, Throwable e) {
	if (parent != null) {
	    parent.uncaughtException(t, e);
	} else if (!(e instanceof ThreadDeath)) {
	    e.printStackTrace(System.err);
	}
    }

    /**
     * Returns a String representation of the Thread group.
     */
    public String toString() {
	return getClass().getName() + "[name=" + getName() + ",maxpri=" + maxPriority + "]";
    }
}