Fixed FELIX-3502 Improve Threads web console printer
https://issues.apache.org/jira/browse/FELIX-3502

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1337062 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ThreadDumper.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ThreadDumper.java
new file mode 100644
index 0000000..5e3024e
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ThreadDumper.java
@@ -0,0 +1,395 @@
+/*

+ * Licensed to the Apache Software Foundation (ASF) under one

+ * or more contributor license agreements.  See the NOTICE file

+ * distributed with this work for additional information

+ * regarding copyright ownership.  The ASF licenses this file

+ * to you under the Apache License, Version 2.0 (the

+ * "License"); you may not use this file except in compliance

+ * with the License.  You may obtain a copy of the License at

+ *

+ *   http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing,

+ * software distributed under the License is distributed on an

+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+ * KIND, either express or implied.  See the License for the

+ * specific language governing permissions and limitations

+ * under the License.

+ */

+package org.apache.felix.webconsole.internal.misc;

+

+import java.io.PrintWriter;

+import java.lang.reflect.Array;

+import java.lang.reflect.Method;

+import java.net.URL;

+import java.net.URLClassLoader;

+import java.util.ArrayList;

+import java.util.Arrays;

+import java.util.Comparator;

+

+/**

+ * This is a helper class for dumping thread stacks.

+ */

+public class ThreadDumper

+{

+

+    private final Method getStackTrace;

+    private final Method getId;

+

+    /**

+     * Base constructor.

+     */

+    public ThreadDumper()

+    {

+        Method _getStackTrace = null;

+        Method _getId = null;

+        final Class[] nullArgs = null;

+        try

+        {

+            _getStackTrace = Thread.class.getMethod("getStackTrace", nullArgs); //$NON-NLS-1$

+            _getId = Thread.class.getMethod("getId", nullArgs); //$NON-NLS-1$

+        }

+        catch (Throwable e)

+        {

+            /* ignore - stack traces will be unavailable */

+        }

+        getStackTrace = _getStackTrace;

+        getId = _getId;

+    }

+

+    /**

+     * Prints all available thread groups, threads and a summary of threads availability. 

+     * The thread groups and the threads will be sorted alphabetically regardless of the case.

+     * 

+     * @param pw the writer where to print the threads information

+     * @param withStackTrace to include or not the stack traces

+     */

+    public final void printThreads(PrintWriter pw, boolean withStackTrace)

+    {

+        // first get the root thread group

+        ThreadGroup rootGroup = getRootThreadGroup();

+

+        // enumerate all other threads

+        int numGroups = rootGroup.activeGroupCount();

+        final ThreadGroup[] groups = new ThreadGroup[2 * numGroups];

+        numGroups = rootGroup.enumerate(groups);

+        Arrays.sort(groups, ThreadGroupComparator.getInstance());

+

+        printSummary(pw, rootGroup, groups);

+        printThreadGroup(pw, rootGroup, withStackTrace);

+

+        for (int i = 0; i < numGroups; i++)

+        {

+            printThreadGroup(pw, groups[i], withStackTrace);

+        }

+

+        pw.println();

+    }

+

+    /**

+     * Prints information for the given thread.

+     * 

+     * @param pw the writer where to print the threads information

+     * @param thread the thread for which to print the information

+     * @param withStackTrace to include or not the stack traces

+     */

+    public final void printThread(PrintWriter pw, Thread thread, boolean withStackTrace)

+    {

+        if (thread != null)

+        {

+

+            pw.print("  Thread ");

+            pw.print(getId(thread));

+            pw.print('/');

+            pw.print(thread.getName());

+            pw.print(" ["); //$NON-NLS-1$

+            pw.print("priority=");

+            pw.print(thread.getPriority());

+            pw.print(", alive=");

+            pw.print(thread.isAlive());

+            pw.print(", daemon=");

+            pw.print(thread.isDaemon());

+            pw.print(", interrupted=");

+            pw.print(thread.isInterrupted());

+            pw.print(", loader=");

+            pw.print(thread.getContextClassLoader());

+            pw.println(']');

+

+            if (withStackTrace)

+            {

+                printClassLoader(pw, thread.getContextClassLoader());

+                printStackTrace(pw, getStackTrace(thread));

+                pw.println();

+            }

+        }

+    }

+

+    private final void printThreadGroup(PrintWriter pw, ThreadGroup group,

+        boolean withStackTrace)

+    {

+        if (group != null)

+        {

+            pw.print("ThreadGroup ");

+            pw.print(group.getName());

+            pw.print(" ["); //$NON-NLS-1$

+            pw.print("maxprio=");

+            pw.print(group.getMaxPriority());

+

+            pw.print(", parent=");

+            if (group.getParent() != null)

+            {

+                pw.print(group.getParent().getName());

+            }

+            else

+            {

+                pw.print('-');

+            }

+

+            pw.print(", isDaemon=");

+            pw.print(group.isDaemon());

+            pw.print(", isDestroyed=");

+            pw.print(group.isDestroyed());

+            pw.println(']');

+

+            int numThreads = group.activeCount();

+            Thread[] threads = new Thread[numThreads * 2];

+            group.enumerate(threads, false);

+            Arrays.sort(threads, ThreadComparator.getInstance());

+            for (int i = 0; i < threads.length; i++)

+            {

+                printThread(pw, threads[i], withStackTrace);

+            }

+

+            pw.println();

+        }

+    }

+

+    private final void printClassLoader(PrintWriter pw, ClassLoader classLoader)

+    {

+        if (classLoader != null)

+        {

+            pw.print("    ClassLoader=");

+            pw.println(classLoader);

+            pw.print("      Parent=");

+            pw.println(classLoader.getParent());

+

+            if (classLoader instanceof URLClassLoader)

+            {

+                URLClassLoader loader = (URLClassLoader) classLoader;

+                URL[] urls = loader.getURLs();

+                if (urls != null && urls.length > 0)

+                {

+                    for (int i = 0; i < urls.length; i++)

+                    {

+                        pw.print("      ");

+                        pw.print(i);

+                        pw.print(" - ");

+                        pw.println(urls[i]);

+                    }

+                }

+            }

+        }

+    }

+

+    private final void printStackTrace(PrintWriter pw, Object stackTrace)

+    {

+        pw.println("    Stacktrace");

+        if (stackTrace == null || Array.getLength(stackTrace) == 0)

+        {

+            pw.println("      -"); //$NON-NLS-1$

+        }

+        else

+        {

+            for (int i = 0, len = Array.getLength(stackTrace); i < len; i++)

+            {

+                Object/*StackTraceElement*/stackTraceElement = Array.get(stackTrace, i);

+                pw.print("      "); //$NON-NLS-1$

+                pw.println(stackTraceElement);

+            }

+        }

+    }

+

+    private final void printSummary(PrintWriter pw, ThreadGroup rootGroup,

+        ThreadGroup[] groups)

+    {

+        int alive = 0;

+        int daemon = 0;

+        int interrupted = 0;

+

+        int threadCount = 0;

+        int threadGroupCount = 0;

+        int threadGroupDestroyed = 0;

+

+        ArrayList/*<ThreadGroup>*/list = new ArrayList(groups.length + 1);

+        list.add(rootGroup);

+        list.addAll(Arrays.asList(groups));

+        for (int j = 0; j < list.size(); j++)

+        {

+            ThreadGroup group = (ThreadGroup) list.get(j);

+            if (null == group)

+            {

+                continue;

+            }

+            threadGroupCount++;

+            if (group.isDestroyed())

+            {

+                threadGroupDestroyed++;

+            }

+

+            Thread[] threads = new Thread[group.activeCount()];

+            group.enumerate(threads);

+            for (int i = 0, size = threads.length; i < size; i++)

+            {

+                Thread thread = threads[i];

+                if (null != thread)

+                {

+                    if (thread.isAlive())

+                    {

+                        alive++;

+                    }

+                    if (thread.isDaemon())

+                    {

+                        daemon++;

+                    }

+                    if (thread.isInterrupted())

+                    {

+                        interrupted++;

+                    }

+                    threadCount++;

+                }

+            }

+        }

+

+        pw.print("Status: ");

+        pw.print(threadCount);

+        pw.print(" threads (");

+        pw.print(alive);

+        pw.print(" alive/");

+        pw.print(daemon);

+        pw.print(" daemon/");

+        pw.print(interrupted);

+        pw.print(" interrupted) in ");

+        pw.print(threadGroupCount);

+        pw.print(" groups (");

+        pw.print(threadGroupDestroyed);

+        pw.print(" destroyed).");

+        pw.println();

+        pw.println();

+    }

+

+    private final String getId(Thread thread)

+    {

+        String ret = ""; //$NON-NLS-1$

+        if (null != getId)

+        {

+            try

+            {

+                ret = "#" + getId.invoke(thread, null); //$NON-NLS-1$

+            }

+            catch (Throwable e)

+            {

+                /* ignore */

+            }

+        }

+        return ret;

+    }

+

+    private final Object/*StackTraceElement[]*/getStackTrace(Thread thread)

+    {

+        Object/*StackTraceElement[]*/ret = null;

+        if (null != getStackTrace)

+        {

+            try

+            {

+

+                ret = getStackTrace.invoke(thread, null);

+

+            }

+            catch (Throwable e)

+            {

+                // ignore - no traces available

+            }

+        }

+        return ret;

+    }

+

+    private static final ThreadGroup getRootThreadGroup()

+    {

+        ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();

+        while (rootGroup.getParent() != null)

+        {

+            rootGroup = rootGroup.getParent();

+        }

+        return rootGroup;

+    }

+}

+

+final class ThreadComparator implements Comparator

+{

+

+    private ThreadComparator()

+    {

+        // prevent instantiation

+    }

+

+    private static final Comparator instance = new ThreadComparator();

+

+    public static final Comparator getInstance()

+    {

+        return instance;

+    }

+

+    public int compare(Object thread1, Object thread2)

+    {

+        if (null == thread1 || null == thread2)

+            return -1;

+        String t1 = ((Thread) thread1).getName();

+        String t2 = ((Thread) thread2).getName();

+        if (null == t1)

+        {

+            t1 = ""; //$NON-NLS-1$

+        }

+        if (null == t2)

+        {

+            t2 = ""; //$NON-NLS-1$

+        }

+

+        return t1.toLowerCase().compareTo(t2.toLowerCase());

+    }

+

+}

+

+final class ThreadGroupComparator implements Comparator

+{

+

+    private ThreadGroupComparator()

+    {

+        // prevent instantiation

+    }

+

+    private static final Comparator instance = new ThreadGroupComparator();

+

+    public static final Comparator getInstance()

+    {

+        return instance;

+    }

+

+    public int compare(Object thread1, Object thread2)

+    {

+        if (null == thread1 || null == thread2)

+            return -1;

+        String t1 = ((ThreadGroup) thread1).getName();

+        String t2 = ((ThreadGroup) thread2).getName();

+        if (null == t1)

+        {

+            t1 = ""; //$NON-NLS-1$

+        }

+        if (null == t2)

+        {

+            t2 = ""; //$NON-NLS-1$

+        }

+

+        return t1.toLowerCase().compareTo(t2.toLowerCase());

+    }

+

+}

diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ThreadPrinter.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ThreadPrinter.java
index 8e8c89e..eb979f1 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ThreadPrinter.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ThreadPrinter.java
@@ -19,228 +19,45 @@
 package org.apache.felix.webconsole.internal.misc;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
 
+import org.apache.felix.webconsole.ModeAwareConfigurationPrinter;
 import org.apache.felix.webconsole.internal.AbstractConfigurationPrinter;
 
-public class ThreadPrinter extends AbstractConfigurationPrinter
+/**
+ * This class provides the Threads tab in the configuration status.
+ */
+public class ThreadPrinter extends AbstractConfigurationPrinter implements ModeAwareConfigurationPrinter
 {
 
     private static final String TITLE = "Threads";
 
     private static final String LABEL = "_threads";
-    
+
+    private final ThreadDumper dumper = new ThreadDumper();
+
+    /**
+     * @see org.apache.felix.webconsole.ConfigurationPrinter#getTitle()
+     */
     public String getTitle()
     {
         return TITLE;
     }
 
+    /**
+     * @see org.apache.felix.webconsole.ConfigurationPrinter#printConfiguration(java.io.PrintWriter)
+     */
     public void printConfiguration(PrintWriter pw)
     {
-        // first get the root thread group
-        ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
-        while (rootGroup.getParent() != null)
-        {
-            rootGroup = rootGroup.getParent();
-        }
-
-        int numGroups = rootGroup.activeGroupCount();
-        ThreadGroup[] groups = new ThreadGroup[2 * numGroups];
-        rootGroup.enumerate(groups);
-        Arrays.sort(groups, ThreadGroupComparator.getInstance());
-
-        printStatusLine(pw, rootGroup, groups);
-        printThreadGroup(pw, rootGroup);
-
-        for (int i = 0; i < groups.length; i++)
-        {
-            printThreadGroup(pw, groups[i]);
-        }
+        printConfiguration(pw, MODE_TXT);
     }
 
-    private void printStatusLine(PrintWriter pw, ThreadGroup rootGroup,
-        ThreadGroup[] groups)
+    /**
+     * @see ModeAwareConfigurationPrinter#printConfiguration(java.io.PrintWriter, java.lang.String)
+     */
+    public void printConfiguration(PrintWriter pw, String mode)
     {
-        int alive = 0;
-        int daemon = 0;
-        int interrupted = 0;
 
-        int threadCount = 0;
-        int threadGroupCount = 0;
-        int threadGroupDestroyed = 0;
-
-        ArrayList/*<ThreadGroup>*/list = new ArrayList(groups.length + 1);
-        list.add(rootGroup);
-        list.addAll(Arrays.asList(groups));
-        for (int j = 0; j < list.size(); j++)
-        {
-            ThreadGroup group = (ThreadGroup) list.get(j);
-            if (null == group)
-            {
-                continue;
-            }
-            threadGroupCount++;
-            if (group.isDestroyed())
-            {
-                threadGroupDestroyed++;
-            }
-
-            Thread[] threads = new Thread[group.activeCount()];
-            group.enumerate(threads);
-            for (int i = 0, size = threads.length; i < size; i++)
-            {
-                Thread thread = threads[i];
-                if (null != thread)
-                {
-                    if (thread.isAlive())
-                    {
-                        alive++;
-                    }
-                    if (thread.isDaemon())
-                    {
-                        daemon++;
-                    }
-                    if (thread.isInterrupted())
-                    {
-                        interrupted++;
-                    }
-                    threadCount++;
-                }
-            }
-        }
-
-        ConfigurationRender.infoLine(pw, "", null, "Status: " + threadCount
-            + " threads (" + alive + " alive/" + daemon + " daemon/" + interrupted
-            + " interrupted) in " + threadGroupCount + " groups ("
-            + threadGroupDestroyed + " destroyed).");
-        pw.println();
-    }
-
-    private static final void printThreadGroup(PrintWriter pw, ThreadGroup group)
-    {
-        if (group != null)
-        {
-            StringBuffer info = new StringBuffer();
-            info.append("ThreadGroup ").append(group.getName());
-            info.append(" [");
-            info.append("maxprio=").append(group.getMaxPriority());
-
-            info.append(", parent=");
-            if (group.getParent() != null)
-            {
-                info.append(group.getParent().getName());
-            }
-            else
-            {
-                info.append('-');
-            }
-
-            info.append(", isDaemon=").append(group.isDaemon());
-            info.append(", isDestroyed=").append(group.isDestroyed());
-            info.append(']');
-
-            ConfigurationRender.infoLine(pw, null, null, info.toString());
-
-            int numThreads = group.activeCount();
-            Thread[] threads = new Thread[numThreads * 2];
-            group.enumerate(threads, false);
-            Arrays.sort(threads, ThreadComparator.getInstance());
-            for (int i = 0; i < threads.length; i++)
-            {
-                printThread(pw, threads[i]);
-            }
-
-            pw.println();
-        }
-    }
-
-    private static final void printThread(PrintWriter pw, Thread thread)
-    {
-        if (thread != null)
-        {
-            StringBuffer info = new StringBuffer();
-            info.append("Thread ").append(thread.getName());
-            info.append(" [");
-            info.append("priority=").append(thread.getPriority());
-            info.append(", alive=").append(thread.isAlive());
-            info.append(", daemon=").append(thread.isDaemon());
-            info.append(", interrupted=").append(thread.isInterrupted());
-            info.append(", loader=").append(thread.getContextClassLoader());
-            info.append(']');
-
-            ConfigurationRender.infoLine(pw, "  ", null, info.toString());
-        }
-    }
-}
-
-final class ThreadComparator implements Comparator
-{
-
-    private ThreadComparator()
-    {
-        // prevent instantiation
-    }
-
-    private static final Comparator instance = new ThreadComparator();
-
-    public static final Comparator getInstance()
-    {
-        return instance;
-    }
-
-    public int compare(Object thread1, Object thread2)
-    {
-        if (null == thread1 || null == thread2)
-            return -1;
-        String t1 = ((Thread) thread1).getName();
-        String t2 = ((Thread) thread2).getName();
-        if (null == t1)
-        {
-            t1 = ""; //$NON-NLS-1$
-        }
-        if (null == t2)
-        {
-            t2 = ""; //$NON-NLS-1$
-        }
-
-        return t1.compareTo(t2);
-    }
-
-}
-
-final class ThreadGroupComparator implements Comparator
-{
-
-    private ThreadGroupComparator()
-    {
-        // prevent instantiation
-    }
-
-    private static final Comparator instance = new ThreadGroupComparator();
-
-    public static final Comparator getInstance()
-    {
-        return instance;
-    }
-
-    public int compare(Object thread1, Object thread2)
-    {
-        if (null == thread1 || null == thread2)
-            return -1;
-        String t1 = ((ThreadGroup) thread1).getName();
-        String t2 = ((ThreadGroup) thread2).getName();
-        if (null == t1)
-        {
-            t1 = ""; //$NON-NLS-1$
-        }
-        if (null == t2)
-        {
-            t2 = ""; //$NON-NLS-1$
-        }
-
-        return t1.compareTo(t2);
+        dumper.printThreads(pw, MODE_ZIP.equals(mode));
     }
 
 }