1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.lang;
19
20import java.lang.ref.WeakReference;
21import java.util.ArrayList;
22import java.util.Iterator;
23import java.util.List;
24import libcore.util.CollectionUtils;
25
26/**
27 * {@code ThreadGroup} is a means of organizing threads into a hierarchical structure.
28 * This class is obsolete. See <i>Effective Java</i> Item 73, "Avoid thread groups" for details.
29 * @see Thread
30 */
31public class ThreadGroup implements Thread.UncaughtExceptionHandler {
32
33    // Name of this ThreadGroup
34    // VM needs this field name for debugging.
35    private String name;
36
37    // Maximum priority for Threads inside this ThreadGroup
38    private int maxPriority = Thread.MAX_PRIORITY;
39
40    // The ThreadGroup to which this ThreadGroup belongs
41    // VM needs this field name for debugging.
42    final ThreadGroup parent;
43
44    /**
45     * Weak references to the threads in this group.
46     * Access is guarded by synchronizing on this field.
47     */
48    private final List<WeakReference<Thread>> threadRefs = new ArrayList<WeakReference<Thread>>(5);
49
50    /**
51     * View of the threads.
52     * Access is guarded by synchronizing on threadRefs.
53     */
54    private final Iterable<Thread> threads = CollectionUtils.dereferenceIterable(threadRefs, true);
55
56    /**
57     * Thread groups. Access is guarded by synchronizing on this field.
58     */
59    private final List<ThreadGroup> groups = new ArrayList<ThreadGroup>(3);
60
61    // Whether this ThreadGroup is a daemon ThreadGroup or not
62    private boolean isDaemon;
63
64    // Whether this ThreadGroup has already been destroyed or not
65    private boolean isDestroyed;
66
67    /* the VM uses these directly; do not rename */
68    static final ThreadGroup mSystem = new ThreadGroup();
69    static final ThreadGroup mMain = new ThreadGroup(mSystem, "main");
70
71    /**
72     * Constructs a new {@code ThreadGroup} with the given name. The new {@code ThreadGroup}
73     * will be child of the {@code ThreadGroup} to which the calling thread belongs.
74     *
75     * @param name the name
76     * @see Thread#currentThread
77     */
78    public ThreadGroup(String name) {
79        this(Thread.currentThread().getThreadGroup(), name);
80    }
81
82    /**
83     * Constructs a new {@code ThreadGroup} with the given name, as a child of the
84     * given {@code ThreadGroup}.
85     *
86     * @param parent the parent
87     * @param name the name
88     * @throws NullPointerException if {@code parent == null}
89     * @throws IllegalThreadStateException if {@code parent} has been
90     *         destroyed already
91     */
92    public ThreadGroup(ThreadGroup parent, String name) {
93        if (parent == null) {
94            throw new NullPointerException("parent == null");
95        }
96        this.name = name;
97        this.parent = parent;
98        if (parent != null) {
99            parent.add(this);
100            this.setMaxPriority(parent.getMaxPriority());
101            if (parent.isDaemon()) {
102                this.setDaemon(true);
103            }
104        }
105    }
106
107    /**
108     * Initialize the special "system" ThreadGroup. Was "main" in Harmony,
109     * but we have an additional group above that in Android.
110     */
111    private ThreadGroup() {
112        this.name = "system";
113        this.parent = null;
114    }
115
116    /**
117     * Returns the number of running {@code Thread}s which are children of this thread group,
118     * directly or indirectly.
119     *
120     * @return the number of children
121     */
122    public int activeCount() {
123        int count = 0;
124        synchronized (threadRefs) {
125            for (Thread thread : threads) {
126                if (thread.isAlive()) {
127                    count++;
128                }
129            }
130        }
131        synchronized (groups) {
132            for (ThreadGroup group : groups) {
133                count += group.activeCount();
134            }
135        }
136        return count;
137    }
138
139    /**
140     * Returns the number of {@code ThreadGroup}s which are children of this group,
141     * directly or indirectly.
142     *
143     * @return the number of children
144     */
145    public int activeGroupCount() {
146        int count = 0;
147        synchronized (groups) {
148            for (ThreadGroup group : groups) {
149                // One for this group & the subgroups
150                count += 1 + group.activeGroupCount();
151            }
152        }
153        return count;
154    }
155
156    /**
157     * Adds a {@code ThreadGroup} to this thread group.
158     *
159     * @param g ThreadGroup to add
160     * @throws IllegalThreadStateException if this group has been destroyed already
161     */
162    private void add(ThreadGroup g) throws IllegalThreadStateException {
163        synchronized (groups) {
164            if (isDestroyed) {
165                throw new IllegalThreadStateException();
166            }
167            groups.add(g);
168        }
169    }
170
171    /**
172     * Does nothing. The definition of this method depends on the deprecated
173     * method {@link #suspend()}. The exact behavior of this call was never
174     * specified.
175     *
176     * @param b Used to control low memory implicit suspension
177     * @return {@code true} (always)
178     *
179     * @deprecated Required deprecated method suspend().
180     */
181    @Deprecated
182    public boolean allowThreadSuspension(boolean b) {
183        // Does not apply to this VM, no-op
184        return true;
185    }
186
187    /**
188     * Does nothing.
189     */
190    public final void checkAccess() {
191    }
192
193    /**
194     * Destroys this thread group and recursively all its subgroups. It is only legal
195     * to destroy a {@code ThreadGroup} that has no threads in it. Any daemon
196     * {@code ThreadGroup} is destroyed automatically when it becomes empty (no threads
197     * or thread groups in it).
198     *
199     * @throws IllegalThreadStateException if this thread group or any of its
200     *         subgroups has been destroyed already or if it still contains
201     *         threads.
202     */
203    public final void destroy() {
204        synchronized (threadRefs) {
205            synchronized (groups) {
206                if (isDestroyed) {
207                    throw new IllegalThreadStateException(
208                            "Thread group was already destroyed: "
209                            + (this.name != null ? this.name : "n/a"));
210                }
211                if (threads.iterator().hasNext()) {
212                    throw new IllegalThreadStateException(
213                            "Thread group still contains threads: "
214                            + (this.name != null ? this.name : "n/a"));
215                }
216                // Call recursively for subgroups
217                while (!groups.isEmpty()) {
218                    // We always get the first element - remember, when the
219                    // child dies it removes itself from our collection. See
220                    // below.
221                    groups.get(0).destroy();
222                }
223
224                if (parent != null) {
225                    parent.remove(this);
226                }
227
228                // Now that the ThreadGroup is really destroyed it can be tagged as so
229                this.isDestroyed = true;
230            }
231        }
232    }
233
234    /*
235     * Auxiliary method that destroys this thread group and recursively all its
236     * subgroups if this is a daemon ThreadGroup.
237     *
238     * @see #destroy
239     * @see #setDaemon
240     * @see #isDaemon
241     */
242    private void destroyIfEmptyDaemon() {
243        // Has to be non-destroyed daemon to make sense
244        synchronized (threadRefs) {
245            if (isDaemon && !isDestroyed && !threads.iterator().hasNext()) {
246                synchronized (groups) {
247                    if (groups.isEmpty()) {
248                        destroy();
249                    }
250                }
251            }
252        }
253    }
254
255    /**
256     * Iterates over all active threads in this group (and its sub-groups) and
257     * stores the threads in the given array. Returns when the array is full or
258     * no more threads remain, whichever happens first.
259     *
260     * <p>Note that this method will silently ignore any threads that don't fit in the
261     * supplied array.
262     *
263     * @param threads the array into which the {@code Thread}s will be copied
264     * @return the number of {@code Thread}s that were copied
265     */
266    public int enumerate(Thread[] threads) {
267        return enumerate(threads, true);
268    }
269
270    /**
271     * Iterates over all active threads in this group (and, optionally, its
272     * sub-groups) and stores the threads in the given array. Returns when the
273     * array is full or no more threads remain, whichever happens first.
274     *
275     * <p>Note that this method will silently ignore any threads that don't fit in the
276     * supplied array.
277     *
278     * @param threads the array into which the {@code Thread}s will be copied
279     * @param recurse indicates whether {@code Thread}s in subgroups should be
280     *        recursively copied as well
281     * @return the number of {@code Thread}s that were copied
282     */
283    public int enumerate(Thread[] threads, boolean recurse) {
284        return enumerateGeneric(threads, recurse, 0, true);
285    }
286
287    /**
288     * Iterates over all thread groups in this group (and its sub-groups) and
289     * and stores the groups in the given array. Returns when the array is full
290     * or no more groups remain, whichever happens first.
291     *
292     * <p>Note that this method will silently ignore any thread groups that don't fit in the
293     * supplied array.
294     *
295     * @param groups the array into which the {@code ThreadGroup}s will be copied
296     * @return the number of {@code ThreadGroup}s that were copied
297     */
298    public int enumerate(ThreadGroup[] groups) {
299        return enumerate(groups, true);
300    }
301
302    /**
303     * Iterates over all thread groups in this group (and, optionally, its
304     * sub-groups) and stores the groups in the given array. Returns when
305     * the array is full or no more groups remain, whichever happens first.
306     *
307     * <p>Note that this method will silently ignore any thread groups that don't fit in the
308     * supplied array.
309     *
310     * @param groups the array into which the {@code ThreadGroup}s will be copied
311     * @param recurse indicates whether {@code ThreadGroup}s in subgroups should be
312     *        recursively copied as well or not
313     * @return the number of {@code ThreadGroup}s that were copied
314     */
315    public int enumerate(ThreadGroup[] groups, boolean recurse) {
316        return enumerateGeneric(groups, recurse, 0, false);
317    }
318
319    /**
320     * Copies into <param>enumeration</param> starting at
321     * <param>enumerationIndex</param> all Threads or ThreadGroups in the
322     * receiver. If <param>recurse</param> is true, recursively enumerate the
323     * elements in subgroups.
324     *
325     * If the array passed as parameter is too small no exception is thrown -
326     * the extra elements are simply not copied.
327     *
328     * @param enumeration array into which the elements will be copied
329     * @param recurse Indicates whether subgroups should be enumerated or not
330     * @param enumerationIndex Indicates in which position of the enumeration
331     *        array we are
332     * @param enumeratingThreads Indicates whether we are enumerating Threads or
333     *        ThreadGroups
334     * @return How many elements were enumerated/copied over
335     */
336    private int enumerateGeneric(Object[] enumeration, boolean recurse, int enumerationIndex,
337            boolean enumeratingThreads) {
338        if (enumeratingThreads) {
339            synchronized (threadRefs) {
340                // walk the references directly so we can iterate in reverse order
341                for (int i = threadRefs.size() - 1; i >= 0; --i) {
342                    Thread thread = threadRefs.get(i).get();
343                    if (thread != null && thread.isAlive()) {
344                        if (enumerationIndex >= enumeration.length) {
345                            return enumerationIndex;
346                        }
347                        enumeration[enumerationIndex++] = thread;
348                    }
349                }
350            }
351        } else {
352            synchronized (groups) {
353                for (int i = groups.size() - 1; i >= 0; --i) {
354                    if (enumerationIndex >= enumeration.length) {
355                        return enumerationIndex;
356                    }
357                    enumeration[enumerationIndex++] = groups.get(i);
358                }
359            }
360        }
361
362        if (recurse) {
363            synchronized (groups) {
364                for (ThreadGroup group : groups) {
365                    if (enumerationIndex >= enumeration.length) {
366                        return enumerationIndex;
367                    }
368                    enumerationIndex = group.enumerateGeneric(enumeration, recurse,
369                            enumerationIndex, enumeratingThreads);
370                }
371            }
372        }
373        return enumerationIndex;
374    }
375
376    /**
377     * Returns the maximum allowed priority for a {@code Thread} in this thread group.
378     *
379     * @return the maximum priority
380     *
381     * @see #setMaxPriority
382     */
383    public final int getMaxPriority() {
384        return maxPriority;
385    }
386
387    /**
388     * Returns the name of this thread group.
389     *
390     * @return the group's name
391     */
392    public final String getName() {
393        return name;
394    }
395
396    /**
397     * Returns this thread group's parent {@code ThreadGroup}. It can be null if this
398     * is the the root ThreadGroup.
399     *
400     * @return the parent
401     */
402    public final ThreadGroup getParent() {
403        return parent;
404    }
405
406    /**
407     * Interrupts every {@code Thread} in this group and recursively in all its
408     * subgroups.
409     *
410     * @see Thread#interrupt
411     */
412    public final void interrupt() {
413        synchronized (threadRefs) {
414            for (Thread thread : threads) {
415                thread.interrupt();
416            }
417        }
418        synchronized (groups) {
419            for (ThreadGroup group : groups) {
420                group.interrupt();
421            }
422        }
423    }
424
425    /**
426     * Checks whether this thread group is a daemon {@code ThreadGroup}.
427     *
428     * @return true if this thread group is a daemon {@code ThreadGroup}
429     *
430     * @see #setDaemon
431     * @see #destroy
432     */
433    public final boolean isDaemon() {
434        return isDaemon;
435    }
436
437    /**
438     * Checks whether this thread group has already been destroyed.
439     *
440     * @return true if this thread group has already been destroyed
441     * @see #destroy
442     */
443    public synchronized boolean isDestroyed() {
444        return isDestroyed;
445    }
446
447    /**
448     * Outputs to {@code System.out} a text representation of the
449     * hierarchy of {@code Thread}s and {@code ThreadGroup}s in this thread group (and recursively).
450     * Proper indentation is used to show the nesting of groups inside groups
451     * and threads inside groups.
452     */
453    public void list() {
454        // We start in a fresh line
455        System.out.println();
456        list(0);
457    }
458
459    /*
460     * Outputs to {@code System.out}a text representation of the
461     * hierarchy of Threads and ThreadGroups in this thread group (and recursively).
462     * The indentation will be four spaces per level of nesting.
463     *
464     * @param levels How many levels of nesting, so that proper indentation can
465     * be output.
466     */
467    private void list(int levels) {
468        indent(levels);
469        System.out.println(this.toString());
470
471        ++levels;
472        synchronized (threadRefs) {
473            for (Thread thread : threads) {
474                indent(levels);
475                System.out.println(thread);
476            }
477        }
478        synchronized (groups) {
479            for (ThreadGroup group : groups) {
480                group.list(levels);
481            }
482        }
483    }
484
485    private void indent(int levels) {
486        for (int i = 0; i < levels; i++) {
487            System.out.print("    "); // 4 spaces for each level
488        }
489    }
490
491    /**
492     * Checks whether this thread group is a direct or indirect parent group of a
493     * given {@code ThreadGroup}.
494     *
495     * @param g the potential child {@code ThreadGroup}
496     * @return true if this thread group is parent of {@code g}
497     */
498    public final boolean parentOf(ThreadGroup g) {
499        while (g != null) {
500            if (this == g) {
501                return true;
502            }
503            g = g.parent;
504        }
505        return false;
506    }
507
508    /**
509     * Removes an immediate subgroup.
510     *
511     * @param g ThreadGroup to remove
512     *
513     * @see #add(Thread)
514     * @see #add(ThreadGroup)
515     */
516    private void remove(ThreadGroup g) {
517        synchronized (groups) {
518            for (Iterator<ThreadGroup> i = groups.iterator(); i.hasNext(); ) {
519                ThreadGroup threadGroup = i.next();
520                if (threadGroup.equals(g)) {
521                    i.remove();
522                    break;
523                }
524            }
525        }
526        destroyIfEmptyDaemon();
527    }
528
529    /**
530     * Resumes every thread in this group and recursively in all its
531     * subgroups.
532     *
533     * @see Thread#resume
534     * @see #suspend
535     *
536     * @deprecated Requires deprecated method Thread.resume().
537     */
538    @SuppressWarnings("deprecation")
539    @Deprecated
540    public final void resume() {
541        synchronized (threadRefs) {
542            for (Thread thread : threads) {
543                thread.resume();
544            }
545        }
546        synchronized (groups) {
547            for (ThreadGroup group : groups) {
548                group.resume();
549            }
550        }
551    }
552
553    /**
554     * Sets whether this is a daemon {@code ThreadGroup} or not. Daemon
555     * thread groups are automatically destroyed when they become empty.
556     *
557     * @param isDaemon the new value
558     * @see #isDaemon
559     * @see #destroy
560     */
561    public final void setDaemon(boolean isDaemon) {
562        this.isDaemon = isDaemon;
563    }
564
565    /**
566     * Configures the maximum allowed priority for a {@code Thread} in this group and
567     * recursively in all its subgroups.
568     *
569     * <p>A caller can never increase the maximum priority of a thread group.
570     * Such an attempt will not result in an exception, it will
571     * simply leave the thread group with its current maximum priority.
572     *
573     * @param newMax the new maximum priority to be set
574     *
575     * @throws IllegalArgumentException if the new priority is greater than
576     *         Thread.MAX_PRIORITY or less than Thread.MIN_PRIORITY
577     *
578     * @see #getMaxPriority
579     */
580    public final void setMaxPriority(int newMax) {
581        if (newMax <= this.maxPriority) {
582            if (newMax < Thread.MIN_PRIORITY) {
583                newMax = Thread.MIN_PRIORITY;
584            }
585
586            int parentPriority = parent == null ? newMax : parent.getMaxPriority();
587            this.maxPriority = parentPriority <= newMax ? parentPriority : newMax;
588            synchronized (groups) {
589                for (ThreadGroup group : groups) {
590                    group.setMaxPriority(newMax);
591                }
592            }
593        }
594    }
595
596    /**
597     * Stops every thread in this group and recursively in all its subgroups.
598     *
599     * @see Thread#stop()
600     * @see Thread#stop(Throwable)
601     * @see ThreadDeath
602     *
603     * @deprecated Requires deprecated method Thread.stop().
604     */
605    @SuppressWarnings("deprecation")
606    @Deprecated
607    public final void stop() {
608        if (stopHelper()) {
609            Thread.currentThread().stop();
610        }
611    }
612
613    @SuppressWarnings("deprecation")
614    private boolean stopHelper() {
615        boolean stopCurrent = false;
616        synchronized (threadRefs) {
617            Thread current = Thread.currentThread();
618            for (Thread thread : threads) {
619                if (thread == current) {
620                    stopCurrent = true;
621                } else {
622                    thread.stop();
623                }
624            }
625        }
626        synchronized (groups) {
627            for (ThreadGroup group : groups) {
628                stopCurrent |= group.stopHelper();
629            }
630        }
631        return stopCurrent;
632    }
633
634    /**
635     * Suspends every thread in this group and recursively in all its
636     * subgroups.
637     *
638     * @see Thread#suspend
639     * @see #resume
640     *
641     * @deprecated Requires deprecated method Thread.suspend().
642     */
643    @SuppressWarnings("deprecation")
644    @Deprecated
645    public final void suspend() {
646        if (suspendHelper()) {
647            Thread.currentThread().suspend();
648        }
649    }
650
651    @SuppressWarnings("deprecation")
652    private boolean suspendHelper() {
653        boolean suspendCurrent = false;
654        synchronized (threadRefs) {
655            Thread current = Thread.currentThread();
656            for (Thread thread : threads) {
657                if (thread == current) {
658                    suspendCurrent = true;
659                } else {
660                    thread.suspend();
661                }
662            }
663        }
664        synchronized (groups) {
665            for (ThreadGroup group : groups) {
666                suspendCurrent |= group.suspendHelper();
667            }
668        }
669        return suspendCurrent;
670    }
671
672    @Override
673    public String toString() {
674        return getClass().getName() + "[name=" + getName()
675                + ",maxPriority=" + getMaxPriority() + "]";
676    }
677
678    /**
679     * Handles uncaught exceptions. Any uncaught exception in any {@code Thread}
680     * is forwarded to the thread's {@code ThreadGroup} by invoking this
681     * method.
682     *
683     * <p>New code should use {@link Thread#setUncaughtExceptionHandler} instead of thread groups.
684     *
685     * @param t the Thread that terminated with an uncaught exception
686     * @param e the uncaught exception itself
687     */
688    public void uncaughtException(Thread t, Throwable e) {
689        if (parent != null) {
690            parent.uncaughtException(t, e);
691        } else if (Thread.getDefaultUncaughtExceptionHandler() != null) {
692            // TODO The spec is unclear regarding this. What do we do?
693            Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, e);
694        } else if (!(e instanceof ThreadDeath)) {
695            // No parent group, has to be 'system' Thread Group
696            e.printStackTrace(System.err);
697        }
698    }
699
700    /**
701     * Called by the Thread constructor.
702     */
703    final void addThread(Thread thread) throws IllegalThreadStateException {
704        synchronized (threadRefs) {
705            if (isDestroyed) {
706                throw new IllegalThreadStateException();
707            }
708            threadRefs.add(new WeakReference<Thread>(thread));
709        }
710    }
711
712    /**
713     * Called by the VM when a Thread dies.
714     */
715    final void removeThread(Thread thread) throws IllegalThreadStateException {
716        synchronized (threadRefs) {
717            for (Iterator<Thread> i = threads.iterator(); i.hasNext(); ) {
718                if (i.next().equals(thread)) {
719                    i.remove();
720                    break;
721                }
722            }
723        }
724        destroyIfEmptyDaemon();
725    }
726}
727