001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3;
018
019import java.time.Duration;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.List;
024
025import org.apache.commons.lang3.time.DurationUtils;
026
027/**
028 * <p>
029 * Helpers for {@code java.lang.Thread} and {@code java.lang.ThreadGroup}.
030 * </p>
031 * <p>
032 * #ThreadSafe#
033 * </p>
034 *
035 * @see java.lang.Thread
036 * @see java.lang.ThreadGroup
037 * @since 3.5
038 */
039public class ThreadUtils {
040
041    /**
042     * A predicate implementation which always returns true.
043     */
044    private static final class AlwaysTruePredicate implements ThreadPredicate, ThreadGroupPredicate {
045
046        private AlwaysTruePredicate() {
047        }
048
049        @Override
050        public boolean test(final Thread thread) {
051            return true;
052        }
053
054        @Override
055        public boolean test(final ThreadGroup threadGroup) {
056            return true;
057        }
058    }
059
060    /**
061     * A predicate implementation which matches a thread or threadgroup name.
062     */
063    public static class NamePredicate implements ThreadPredicate, ThreadGroupPredicate {
064
065        private final String name;
066
067        /**
068         * Predicate constructor
069         *
070         * @param name thread or threadgroup name
071         * @throws IllegalArgumentException if the name is {@code null}
072         */
073        public NamePredicate(final String name) {
074            Validate.notNull(name, "name");
075            this.name = name;
076        }
077
078        @Override
079        public boolean test(final Thread thread) {
080            return thread != null && thread.getName().equals(name);
081        }
082
083        @Override
084        public boolean test(final ThreadGroup threadGroup) {
085            return threadGroup != null && threadGroup.getName().equals(name);
086        }
087    }
088
089    /**
090     * A predicate for selecting threadgroups.
091     */
092    // When breaking BC, replace this with Predicate<ThreadGroup>
093    @FunctionalInterface
094    public interface ThreadGroupPredicate {
095
096        /**
097         * Evaluates this predicate on the given threadgroup.
098         * @param threadGroup the threadgroup
099         * @return {@code true} if the threadGroup matches the predicate, otherwise {@code false}
100         */
101        boolean test(ThreadGroup threadGroup);
102    }
103
104    /**
105     * A predicate implementation which matches a thread id.
106     */
107    public static class ThreadIdPredicate implements ThreadPredicate {
108
109        private final long threadId;
110
111        /**
112         * Predicate constructor
113         *
114         * @param threadId the threadId to match
115         * @throws IllegalArgumentException if the threadId is zero or negative
116         */
117        public ThreadIdPredicate(final long threadId) {
118            if (threadId <= 0) {
119                throw new IllegalArgumentException("The thread id must be greater than zero");
120            }
121            this.threadId = threadId;
122        }
123
124        @Override
125        public boolean test(final Thread thread) {
126            return thread != null && thread.getId() == threadId;
127        }
128    }
129
130    /**
131     * A predicate for selecting threads.
132     */
133    // When breaking BC, replace this with Predicate<Thread>
134    @FunctionalInterface
135    public interface ThreadPredicate {
136
137        /**
138         * Evaluates this predicate on the given thread.
139         * @param thread the thread
140         * @return {@code true} if the thread matches the predicate, otherwise {@code false}
141         */
142        boolean test(Thread thread);
143    }
144
145    /**
146     * Predicate which always returns true.
147     */
148    public static final AlwaysTruePredicate ALWAYS_TRUE_PREDICATE = new AlwaysTruePredicate();
149
150    /**
151     * Finds the active thread with the specified id.
152     *
153     * @param threadId The thread id
154     * @return The thread with the specified id or {@code null} if no such thread exists
155     * @throws IllegalArgumentException if the specified id is zero or negative
156     * @throws  SecurityException
157     *          if the current thread cannot access the system thread group
158     *
159     * @throws  SecurityException  if the current thread cannot modify
160     *          thread groups from this thread's thread group up to the system thread group
161     */
162    public static Thread findThreadById(final long threadId) {
163        final Collection<Thread> result = findThreads(new ThreadIdPredicate(threadId));
164        return result.isEmpty() ? null : result.iterator().next();
165    }
166
167    /**
168     * Finds the active thread with the specified id if it belongs to a thread group with the specified group name.
169     *
170     * @param threadId The thread id
171     * @param threadGroupName The thread group name
172     * @return The threads which belongs to a thread group with the specified group name and the thread's id match the specified id.
173     * {@code null} is returned if no such thread exists
174     * @throws IllegalArgumentException if the specified id is zero or negative or the group name is null
175     * @throws  SecurityException
176     *          if the current thread cannot access the system thread group
177     *
178     * @throws  SecurityException  if the current thread cannot modify
179     *          thread groups from this thread's thread group up to the system thread group
180     */
181    public static Thread findThreadById(final long threadId, final String threadGroupName) {
182        Validate.notNull(threadGroupName, "threadGroupName");
183        final Thread thread = findThreadById(threadId);
184        if (thread != null && thread.getThreadGroup() != null && thread.getThreadGroup().getName().equals(threadGroupName)) {
185            return thread;
186        }
187        return null;
188    }
189
190    /**
191     * Finds the active thread with the specified id if it belongs to the specified thread group.
192     *
193     * @param threadId The thread id
194     * @param threadGroup The thread group
195     * @return The thread which belongs to a specified thread group and the thread's id match the specified id.
196     * {@code null} is returned if no such thread exists
197     * @throws IllegalArgumentException if the specified id is zero or negative or the group is null
198     * @throws  SecurityException
199     *          if the current thread cannot access the system thread group
200     *
201     * @throws  SecurityException  if the current thread cannot modify
202     *          thread groups from this thread's thread group up to the system thread group
203     */
204    public static Thread findThreadById(final long threadId, final ThreadGroup threadGroup) {
205        Validate.notNull(threadGroup, "threadGroup");
206        final Thread thread = findThreadById(threadId);
207        if (thread != null && threadGroup.equals(thread.getThreadGroup())) {
208            return thread;
209        }
210        return null;
211    }
212
213    /**
214     * Select all active threadgroups which match the given predicate and which is a subgroup of the given thread group (or one of its subgroups).
215     *
216     * @param group the thread group
217     * @param recurse if {@code true} then evaluate the predicate recursively on all threadgroups in all subgroups of the given group
218     * @param predicate the predicate
219     * @return An unmodifiable {@code Collection} of active threadgroups which match the given predicate and which is a subgroup of the given thread group
220     * @throws IllegalArgumentException if the given group or predicate is null
221     * @throws  SecurityException  if the current thread cannot modify
222     *          thread groups from this thread's thread group up to the system thread group
223     */
224    public static Collection<ThreadGroup> findThreadGroups(final ThreadGroup group, final boolean recurse, final ThreadGroupPredicate predicate) {
225        Validate.notNull(group, "group");
226        Validate.notNull(predicate, "predicate");
227
228        int count = group.activeGroupCount();
229        ThreadGroup[] threadGroups;
230        do {
231            threadGroups = new ThreadGroup[count + (count / 2) + 1]; //slightly grow the array size
232            count = group.enumerate(threadGroups, recurse);
233            //return value of enumerate() must be strictly less than the array size according to javadoc
234        } while (count >= threadGroups.length);
235
236        final List<ThreadGroup> result = new ArrayList<>(count);
237        for (int i = 0; i < count; ++i) {
238            if (predicate.test(threadGroups[i])) {
239                result.add(threadGroups[i]);
240            }
241        }
242        return Collections.unmodifiableCollection(result);
243    }
244
245    /**
246     * Select all active threadgroups which match the given predicate.
247     *
248     * @param predicate the predicate
249     * @return An unmodifiable {@code Collection} of active threadgroups matching the given predicate
250     * @throws IllegalArgumentException if the predicate is null
251     * @throws  SecurityException
252     *          if the current thread cannot access the system thread group
253     * @throws  SecurityException  if the current thread cannot modify
254     *          thread groups from this thread's thread group up to the system thread group
255     */
256    public static Collection<ThreadGroup> findThreadGroups(final ThreadGroupPredicate predicate) {
257        return findThreadGroups(getSystemThreadGroup(), true, predicate);
258    }
259
260    /**
261     * Finds active thread groups with the specified group name.
262     *
263     * @param threadGroupName The thread group name
264     * @return the thread groups with the specified group name or an empty collection if no such thread group exists. The collection returned is always unmodifiable.
265     * @throws IllegalArgumentException if group name is null
266     * @throws  SecurityException
267     *          if the current thread cannot access the system thread group
268     *
269     * @throws  SecurityException  if the current thread cannot modify
270     *          thread groups from this thread's thread group up to the system thread group
271     */
272    public static Collection<ThreadGroup> findThreadGroupsByName(final String threadGroupName) {
273        return findThreadGroups(new NamePredicate(threadGroupName));
274    }
275
276    /**
277     * Select all active threads which match the given predicate and which belongs to the given thread group (or one of its subgroups).
278     *
279     * @param group the thread group
280     * @param recurse if {@code true} then evaluate the predicate recursively on all threads in all subgroups of the given group
281     * @param predicate the predicate
282     * @return An unmodifiable {@code Collection} of active threads which match the given predicate and which belongs to the given thread group
283     * @throws IllegalArgumentException if the given group or predicate is null
284     * @throws  SecurityException  if the current thread cannot modify
285     *          thread groups from this thread's thread group up to the system thread group
286     */
287    public static Collection<Thread> findThreads(final ThreadGroup group, final boolean recurse, final ThreadPredicate predicate) {
288        Validate.notNull(group, "The group must not be null");
289        Validate.notNull(predicate, "The predicate must not be null");
290
291        int count = group.activeCount();
292        Thread[] threads;
293        do {
294            threads = new Thread[count + (count / 2) + 1]; //slightly grow the array size
295            count = group.enumerate(threads, recurse);
296            //return value of enumerate() must be strictly less than the array size according to javadoc
297        } while (count >= threads.length);
298
299        final List<Thread> result = new ArrayList<>(count);
300        for (int i = 0; i < count; ++i) {
301            if (predicate.test(threads[i])) {
302                result.add(threads[i]);
303            }
304        }
305        return Collections.unmodifiableCollection(result);
306    }
307
308    /**
309     * Select all active threads which match the given predicate.
310     *
311     * @param predicate the predicate
312     * @return An unmodifiable {@code Collection} of active threads matching the given predicate
313     *
314     * @throws IllegalArgumentException if the predicate is null
315     * @throws  SecurityException
316     *          if the current thread cannot access the system thread group
317     * @throws  SecurityException  if the current thread cannot modify
318     *          thread groups from this thread's thread group up to the system thread group
319     */
320    public static Collection<Thread> findThreads(final ThreadPredicate predicate) {
321        return findThreads(getSystemThreadGroup(), true, predicate);
322    }
323
324    /**
325     * Finds active threads with the specified name.
326     *
327     * @param threadName The thread name
328     * @return The threads with the specified name or an empty collection if no such thread exists. The collection returned is always unmodifiable.
329     * @throws IllegalArgumentException if the specified name is null
330     * @throws  SecurityException
331     *          if the current thread cannot access the system thread group
332     *
333     * @throws  SecurityException  if the current thread cannot modify
334     *          thread groups from this thread's thread group up to the system thread group
335     */
336    public static Collection<Thread> findThreadsByName(final String threadName) {
337        return findThreads(new NamePredicate(threadName));
338    }
339
340    /**
341     * Finds active threads with the specified name if they belong to a thread group with the specified group name.
342     *
343     * @param threadName The thread name
344     * @param threadGroupName The thread group name
345     * @return The threads which belongs to a thread group with the specified group name and the thread's name match the specified name,
346     * An empty collection is returned if no such thread exists. The collection returned is always unmodifiable.
347     * @throws IllegalArgumentException if the specified thread name or group name is null
348     * @throws  SecurityException
349     *          if the current thread cannot access the system thread group
350     *
351     * @throws  SecurityException  if the current thread cannot modify
352     *          thread groups from this thread's thread group up to the system thread group
353     */
354    public static Collection<Thread> findThreadsByName(final String threadName, final String threadGroupName) {
355        Validate.notNull(threadName, "threadName");
356        Validate.notNull(threadGroupName, "threadGroupName");
357
358        final Collection<ThreadGroup> threadGroups = findThreadGroups(new NamePredicate(threadGroupName));
359
360        if (threadGroups.isEmpty()) {
361            return Collections.emptyList();
362        }
363
364        final Collection<Thread> result = new ArrayList<>();
365        final NamePredicate threadNamePredicate = new NamePredicate(threadName);
366        for (final ThreadGroup group : threadGroups) {
367            result.addAll(findThreads(group, false, threadNamePredicate));
368        }
369        return Collections.unmodifiableCollection(result);
370    }
371
372    /**
373     * Finds active threads with the specified name if they belong to a specified thread group.
374     *
375     * @param threadName The thread name
376     * @param threadGroup The thread group
377     * @return The threads which belongs to a thread group and the thread's name match the specified name,
378     * An empty collection is returned if no such thread exists. The collection returned is always unmodifiable.
379     * @throws IllegalArgumentException if the specified thread name or group is null
380     * @throws  SecurityException
381     *          if the current thread cannot access the system thread group
382     *
383     * @throws  SecurityException  if the current thread cannot modify
384     *          thread groups from this thread's thread group up to the system thread group
385     */
386    public static Collection<Thread> findThreadsByName(final String threadName, final ThreadGroup threadGroup) {
387        return findThreads(threadGroup, false, new NamePredicate(threadName));
388    }
389
390    /**
391     * Gets all active thread groups excluding the system thread group (A thread group is active if it has been not destroyed).
392     *
393     * @return all thread groups excluding the system thread group. The collection returned is always unmodifiable.
394     * @throws  SecurityException
395     *          if the current thread cannot access the system thread group
396     *
397     * @throws  SecurityException  if the current thread cannot modify
398     *          thread groups from this thread's thread group up to the system thread group
399     */
400    public static Collection<ThreadGroup> getAllThreadGroups() {
401        return findThreadGroups(ALWAYS_TRUE_PREDICATE);
402    }
403
404    /**
405     * Gets all active threads (A thread is active if it has been started and has not yet died).
406     *
407     * @return all active threads. The collection returned is always unmodifiable.
408     * @throws  SecurityException
409     *          if the current thread cannot access the system thread group
410     *
411     * @throws  SecurityException  if the current thread cannot modify
412     *          thread groups from this thread's thread group up to the system thread group
413     */
414    public static Collection<Thread> getAllThreads() {
415        return findThreads(ALWAYS_TRUE_PREDICATE);
416    }
417
418    /**
419     * Gets the system thread group (sometimes also referred as "root thread group").
420     *
421     * @return the system thread group
422     * @throws  SecurityException  if the current thread cannot modify
423     *          thread groups from this thread's thread group up to the system thread group
424     */
425    public static ThreadGroup getSystemThreadGroup() {
426        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
427        while (threadGroup.getParent() != null) {
428            threadGroup = threadGroup.getParent();
429        }
430        return threadGroup;
431    }
432
433    /**
434     * Waits for the given thread to die for the given duration. Implemented using {@link Thread#join(long, int)}.
435     *
436     * @param thread The thread to join.
437     * @param duration How long to wait.
438     * @throws InterruptedException if any thread has interrupted the current thread.
439     * @see Thread#join(long, int)
440     * @since 3.12.0
441     */
442    public static void join(final Thread thread, final Duration duration) throws InterruptedException {
443        DurationUtils.accept(thread::join, duration);
444    }
445
446    /**
447     * Sleeps the current thread for the given duration. Implemented using {@link Thread#sleep(long, int)}.
448     *
449     * @param duration How long to sleep.
450     * @throws InterruptedException if any thread has interrupted the current thread.
451     * @see Thread#sleep(long, int)
452     * @since 3.12.0
453     */
454    public static void sleep(final Duration duration) throws InterruptedException {
455        DurationUtils.accept(Thread::sleep, duration);
456    }
457
458    /**
459     * <p>
460     * ThreadUtils instances should NOT be constructed in standard programming. Instead, the class should be used as
461     * {@code ThreadUtils.getAllThreads()}
462     * </p>
463     * <p>
464     * This constructor is public to permit tools that require a JavaBean instance to operate.
465     * </p>
466     */
467    public ThreadUtils() {
468    }
469}