//
// Copyright (C) 2013 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package java.lang;
import java.io.PrintStream;
/**
* our model of java.lang.ThreadGroup, which we need since the VM relies on certain fields
* during bootstrapping
*
* Note that we don't have a native peer, which would help with atomic container updates, because
* most of these methods are called by the VM and we don't want to introduce a dependency from
* VM to the peers. This is esp. true since we would have to dig out & cast the peer, call it,
* and then go backwards extracting the *Info objects we already had in the calling context.
*
* If we ever want to introduce a peer, it could just delegate to the respective ThreadInfo methods.
*/
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
private final ThreadGroup parent;
//--- those are package visible, so we better keep names and access modifiers
String name;
int maxPriority;
int nthreads;
Thread[] threads;
int ngroups;
ThreadGroup[] groups;
int nUnstartedThreads;
boolean destroyed;
boolean daemon;
boolean vmAllowSuspension;
public ThreadGroup (String name){
this( Thread.currentThread().getThreadGroup(), name);
}
public ThreadGroup (ThreadGroup parent, String name){
this.parent = parent;
this.name = name;
// those are inherited from the parent, which therefore can't be null
this.maxPriority = parent.maxPriority;
this.daemon = parent.daemon;
parent.add(this);
}
//--- the container updates
// group updates are just class internal, whereas thread updates happen from java.lang.Thread and hence need
// package visibility
private synchronized void add (ThreadGroup childGroup){
if (destroyed){
throw new IllegalThreadStateException();
}
if (groups == null){
groups = new ThreadGroup[1];
} else {
ThreadGroup[] ng = new ThreadGroup[ngroups+1];
System.arraycopy(groups, 0, ng, 0, ngroups);
groups = ng;
}
groups[ngroups++] = childGroup;
}
private synchronized void remove (ThreadGroup childGroup){
if (!destroyed){
for (int i=0; i<ngroups; i++){
if (groups[i] == childGroup){
if (ngroups > 1){
int ngroups1 = ngroups - 1;
ThreadGroup[] ng = new ThreadGroup[ngroups1];
if (i > 0) {
System.arraycopy(groups, 0, ng, 0, i);
}
if (i < ngroups1) {
System.arraycopy(groups, i + 1, ng, i, ngroups - i);
}
groups = ng;
ngroups = ngroups1;
} else {
groups = null;
ngroups = 0;
}
if (nthreads == 0){
notifyAll();
}
// check if its time for a suicide in case we are a daemon group
if (daemon){
if ((nthreads == 0) && (nUnstartedThreads == 0) && (ngroups == 0)){
destroy();
}
}
break;
}
}
}
}
// thread add/remove happens from the native peer, to avoid additional CGs during creation/termination
// since the ThreadGroup is per se a shared object. We just include these methods here as a specification
// for ThreadInfo
/**
* called from the Thread ctor, i.e. before it is started
* NOTE - this is implemented as a native method to avoid shared field CGs
*/
synchronized void addUnstarted (){
if (destroyed){
throw new IllegalThreadStateException();
}
nUnstartedThreads++;
}
/**
* this is called during Thread.start()
* NOTE - this is implemented as a native method to avoid shared field CGs
*/
synchronized void add (Thread newThread){
if (destroyed){
throw new IllegalThreadStateException();
}
if (threads == null){
threads = new Thread[1];
} else {
Thread[] nt = new Thread[nthreads+1];
System.arraycopy(threads, 0, nt, 0, nthreads);
threads = nt;
}
threads[nthreads++] = newThread;
nUnstartedThreads--;
}
/**
* this is called automatically upon thread termination
*/
synchronized void threadTerminated (Thread terminatedThread){
if (!destroyed){
for (int i=0; i<nthreads; i++){
if (threads[i] == terminatedThread){
if (nthreads == 1){
threads = null;
nthreads = 0;
} else {
int nthreads1 = nthreads - 1;
Thread[] a = new Thread[nthreads1];
if (i > 0) {
System.arraycopy(threads, 0, a, 0, i);
}
if (i < nthreads1) {
System.arraycopy(groups, i + 1, a, i, ngroups - i);
}
threads = a;
nthreads = nthreads1;
}
// OpenJDK does this no matter if the group was destroyed, but that looks like a bug
// that could lead to multiple destroys and notifyAll() signals
if (nthreads == 0) {
notifyAll();
}
if (daemon) {
if ((nthreads == 0) && (nUnstartedThreads == 0) && (ngroups == 0)) {
destroy();
}
}
break;
}
}
}
}
//--- public getters and setters
public final String getName(){
return name;
}
public final ThreadGroup getParent(){
return parent;
}
public final int getMaxPriority(){
return maxPriority;
}
public final boolean isDaemon(){
return daemon;
}
public synchronized boolean isDestroyed(){
return destroyed;
}
public final void setDaemon (boolean daemon){
this.daemon = daemon;
}
/**
* this sets the maxPriority to the min of the argument and the parents maxPriority
* and raises maxPriority for all our threads that have a lower one
*/
public final synchronized void setMaxPriority (int newMaxPriority){
// do nothing if newMaxPriority is outside limits
if (newMaxPriority >= Thread.MIN_PRIORITY && newMaxPriority <= Thread.MAX_PRIORITY){
maxPriority = (parent != null) ? Math.min(parent.maxPriority, newMaxPriority) : newMaxPriority;
for (int i=0; i<groups.length; i++){
groups[i].setMaxPriority(newMaxPriority);
}
}
}
public final boolean parentOf (ThreadGroup tg){
while (true){
if (tg == this) return true;
ThreadGroup tgParent = tg.parent;
if (tgParent == null) return false;
tg = tgParent;
}
}
public final void checkAccess(){
// not sure what to do here
}
/**
* return number of threads including all sub-groups
*/
public synchronized int activeCount() {
if (destroyed){
return 0;
} else {
int nActive = nthreads;
if (ngroups > 0){
for (int i=0; i<ngroups; i++){
nActive += groups[i].activeCount();
}
}
return nActive;
}
}
/**
* copy threads into provided list
* kind of a misnomer
*/
public int enumerate (Thread[] dst){
return enumerate(dst, 0, true);
}
public int enumerate (Thread[] dst, boolean recurse){
return enumerate(dst, 0, recurse);
}
private synchronized int enumerate (Thread[] dst, int idx, boolean recurse){
int n = 0;
int len = nthreads;
if ((idx + len) > dst.length){
len = dst.length - idx;
}
for (int i = 0; i < len; i++) {
if (threads[i].isAlive()) {
dst[idx++] = threads[i];
n++;
}
}
if (recurse && (idx < dst.length)) {
for (int j = 0; j < ngroups; j++) {
n += groups[j].enumerate(dst, idx, true);
}
}
return n;
}
public synchronized int activeGroupCount() {
if (destroyed){
return 0;
} else {
int nActive = ngroups;
if (ngroups > 0){
for (int i=0; i<ngroups; i++){
nActive += groups[i].activeGroupCount();
}
}
return nActive;
}
}
public int enumerate (ThreadGroup[] dst){
return enumerate(dst, 0, true);
}
public int enumerate (ThreadGroup[] dst, boolean recurse){
return enumerate(dst, 0, recurse);
}
private synchronized int enumerate (ThreadGroup[] dst, int idx, boolean recurse){
int n = 0;
int len = ngroups;
if ((idx + len) > dst.length){
len = dst.length - idx;
}
for (int i = 0; i < len; i++) {
dst[idx++] = groups[i];
n++;
}
if (recurse && (idx < dst.length)) {
for (int j = 0; j < ngroups; j++) {
n += groups[j].enumerate(dst, idx, true);
}
}
return n;
}
static final int OP_STOP = 1;
static final int OP_INTERRUPT = 2;
static final int OP_SUSPEND = 3;
static final int OP_RESUME = 4;
public final void stop(){
if (doRecursively(OP_STOP)){
Thread.currentThread().stop();
}
}
public final void interrupt(){
doRecursively(OP_INTERRUPT);
}
public final void suspend(){
if (doRecursively(OP_SUSPEND)){
Thread.currentThread().suspend();
}
}
public final void resume(){
doRecursively(OP_RESUME);
}
private synchronized boolean doRecursively (int op){
boolean suicide = false;
for (int i=0; i<nthreads; i++){
Thread t = threads[i];
switch (op){
case OP_STOP:
if (t == Thread.currentThread()) {
suicide = true; // defer
} else {
t.stop();
}
break;
case OP_INTERRUPT: t.interrupt(); break;
case OP_SUSPEND:
if (t == Thread.currentThread()) {
suicide = true; // defer
} else {
t.suspend();
}
break;
case OP_RESUME: t.resume(); break;
}
}
for (int j=0; j<ngroups; j++){
suicide = suicide || groups[j].doRecursively(op);
}
return suicide;
}
public synchronized final void destroy(){
if (destroyed || (nthreads > 0)) {
throw new IllegalThreadStateException();
}
for (int i=0; i<ngroups; i++){
groups[i].destroy();
}
if (parent != null){ // no destroying the system group
nthreads = 0;
threads = null;
ngroups = 0;
groups = null;
destroyed = true;
parent.remove(this);
}
}
//--- just for debugging
public void list(){
list( System.out, 0);
}
synchronized void list (PrintStream ps, int indent){
for (int i=0; i<indent; i++) ps.print(' ');
ps.println(toString());
indent+= 4;
for (int j=0; j<nthreads; j++){
for (int i=0; i<indent; i++) ps.print(' ');
ps.println( threads[j]);
}
for (int k=0; k<ngroups; k++){
groups[k].list( ps, indent);
}
}
public boolean allowThreadSuspension (boolean allowSuspension){
vmAllowSuspension = allowSuspension;
return true;
}
public String toString () {
return getClass().getName() + "[name=" + name + ",maxpri=" + maxPriority + ']';
}
// we handle this in ThreadInfo, but again this is a public method that could be called from anywhere
public void uncaughtException (Thread t, Throwable x) {
if (parent != null) { // if we have a parent, delegate
parent.uncaughtException(t, x);
} else { // check if there is a default handler
Thread.UncaughtExceptionHandler xh = Thread.getDefaultUncaughtExceptionHandler();
if (xh != null) {
xh.uncaughtException(t, x);
} else { // last resort - just print to Ssytem.err
if (!(x instanceof ThreadDeath)) {
System.err.print("Exception in thread \"" + t.getName() + '"');
x.printStackTrace(System.err);
}
}
}
}
}