FELIX-3339 New consolidated Thread Dumper tool contributed by Simo Tripodi (thanks alot)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1494960 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/threaddump/pom.xml b/threaddump/pom.xml
new file mode 100644
index 0000000..3a8901d
--- /dev/null
+++ b/threaddump/pom.xml
@@ -0,0 +1,146 @@
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
+>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>felix-parent</artifactId>
+        <version>2.1</version>
+        <relativePath></relativePath>
+    </parent>
+
+    <artifactId>org.apache.felix.threaddump</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Felix Thread Dump</name>
+    <description>Thread Dump classes for OSGi.</description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/threaddump</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/threaddump</developerConnection>
+        <url>http://svn.apache.org/repos/asf/felix/threaddump</url>
+    </scm>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>4.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.inventory</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>2.3.7</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>
+                            org.apache.felix.threaddump.internal.ThreadDumpActivator
+                        </Bundle-Activator>
+                    </instructions>
+                </configuration>
+            </plugin>
+            
+            <!--
+                Check API compliance for Java 1.4, 5, and 6.
+                Unfortunately the <ignores> sections do not seem to
+                properly work, so the sniffer is disabled by default.
+                
+                It is recommended to regularly and manually run the
+                sniffer and check for the following rules:
+                
+                * Classes in the ..internal package must be compliant
+                  with Java 1.4
+                * Classes in the ..internal.jdk5 package must be compliant
+                  with Java 5 
+                * Classes in the ..internal.jdk6 package must be compliant
+                  with Java 6
+             -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>animal-sniffer-maven-plugin</artifactId>
+                <version>1.8</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>test-1.4</id>
+                        <phase>test</phase>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                        <configuration>
+                            <signature>
+                                <groupId>org.codehaus.mojo.signature</groupId>
+                                <artifactId>java14</artifactId>
+                                <version>1.0</version>
+                            </signature>
+                            <ignores>
+                                <ignore>org.apache.felix.threaddump.internal.jdk5.*</ignore>
+                                <ignore>org.apache.felix.threaddump.internal.jdk6.*</ignore>
+                            </ignores>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>test-1.5</id>
+                        <phase>test</phase>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                        <configuration>
+                            <signature>
+                                <groupId>org.codehaus.mojo.signature</groupId>
+                                <artifactId>java15</artifactId>
+                                <version>1.0</version>
+                            </signature>
+                            <skip>true</skip>
+                            <ignores>
+                                <ignore>org.apache.felix.threaddump.internal.jdk6.*</ignore>
+                            </ignores>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>test-1.6</id>
+                        <phase>test</phase>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                        <configuration>
+                            <signature>
+                                <groupId>org.codehaus.mojo.signature</groupId>
+                                <artifactId>java16</artifactId>
+                                <version>1.0</version>
+                            </signature>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+        </plugins>
+    </build>
+
+</project>
diff --git a/threaddump/src/main/java/org/apache/felix/threaddump/internal/Jdk14ThreadDumper.java b/threaddump/src/main/java/org/apache/felix/threaddump/internal/Jdk14ThreadDumper.java
new file mode 100644
index 0000000..30b0e76
--- /dev/null
+++ b/threaddump/src/main/java/org/apache/felix/threaddump/internal/Jdk14ThreadDumper.java
@@ -0,0 +1,90 @@
+/*
+ * 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.threaddump.internal;
+
+/**
+ * {@link ThreadDumper} implementation which relies on regular Java API.
+ */
+final class Jdk14ThreadDumper implements ThreadDumper
+{
+
+    /**
+     * {@inheritDoc}
+     */
+    public void printThreads(ThreadWriter threadWriter)
+    {
+        // first get the root thread group
+        ThreadGroup rootGroup = getRootThreadGroup();
+
+        printThreadGroup(threadWriter, rootGroup);
+
+        int numGroups = rootGroup.activeGroupCount();
+        ThreadGroup[] groups = new ThreadGroup[2 * numGroups];
+        rootGroup.enumerate(groups);
+        for (int i = 0; i < groups.length; i++)
+        {
+            printThreadGroup(threadWriter, groups[i]);
+        }
+    }
+
+    private static ThreadGroup getRootThreadGroup()
+    {
+        ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
+        while (rootGroup.getParent() != null)
+        {
+            rootGroup = rootGroup.getParent();
+        }
+        return rootGroup;
+    }
+
+    private static void printThreadGroup(ThreadWriter threadWriter, ThreadGroup group)
+    {
+        if (group != null)
+        {
+            int numThreads = group.activeCount();
+            Thread[] threads = new Thread[numThreads * 2];
+            group.enumerate(threads, false);
+            for (int i = 0; i < threads.length; i++)
+            {
+                printThread(threadWriter, threads[i]);
+            }
+        }
+    }
+
+    private static void printThread(ThreadWriter threadWriter, Thread thread)
+    {
+        if (thread != null)
+        {
+            short status = ThreadWriter.NEW;
+            if (thread.isAlive())
+            {
+                status = ThreadWriter.RUNNABLE;
+            }
+            else if (thread.isInterrupted())
+            {
+                status = ThreadWriter.TERMINATED;
+            }
+            // TODO there are missing cases here!
+
+            threadWriter.printThread(thread.getName(), thread.isDaemon(), thread.getPriority(), -1, status);
+            threadWriter.printEmptyLine();
+        }
+    }
+
+}
diff --git a/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadDumpActivator.java b/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadDumpActivator.java
new file mode 100644
index 0000000..60ef92b
--- /dev/null
+++ b/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadDumpActivator.java
@@ -0,0 +1,69 @@
+/*
+ * 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.threaddump.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.inventory.InventoryPrinter;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * OSGi bundle activator which takes care of (un)binding the
+ * Thread Dump Inventory Printer service.
+ */
+public final class ThreadDumpActivator implements BundleActivator
+{
+
+    /**
+     * The service description.
+     */
+    private static final String SERVICE_TITLE = "Apache Felix Thread Dump";
+
+    /**
+     * The service identifier.
+     */
+    private static final String SERVICE_NAME = "threaddump";
+
+    /**
+     * The ThreadDumper ServiceRegistration reference.
+     */
+    private ServiceRegistration threadDumperRegistration;
+
+    public void start(BundleContext context)
+    {
+        final Dictionary props = new Hashtable();
+        props.put(Constants.SERVICE_VENDOR, context.getBundle().getHeaders(Constants.BUNDLE_VENDOR));
+        props.put(Constants.SERVICE_DESCRIPTION, SERVICE_TITLE);
+        props.put(InventoryPrinter.NAME, SERVICE_NAME);
+        props.put(InventoryPrinter.TITLE, SERVICE_TITLE);
+
+        threadDumperRegistration = context.registerService(InventoryPrinter.SERVICE, new ThreadDumpInventoryPrinter(),
+            props);
+    }
+
+    public void stop(BundleContext context)
+    {
+        threadDumperRegistration.unregister();
+    }
+
+}
diff --git a/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadDumpInventoryPrinter.java b/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadDumpInventoryPrinter.java
new file mode 100644
index 0000000..ec734f4
--- /dev/null
+++ b/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadDumpInventoryPrinter.java
@@ -0,0 +1,85 @@
+/*
+ * 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.threaddump.internal;
+
+import java.io.PrintWriter;
+
+import org.apache.felix.inventory.Format;
+import org.apache.felix.inventory.InventoryPrinter;
+import org.apache.felix.threaddump.internal.jdk5.Jdk15ThreadDumper;
+import org.apache.felix.threaddump.internal.jdk6.Jdk16ThreadDumper;
+
+/**
+ * A composite {@link ThreadDumper} implementation that:
+ * <ul>
+ * <li>uses Java 6 JMX API if available;
+ * <li>
+ * <li>falls back to Java 5 JMX API if not Java 6;</li>
+ * <li>falls back to regular Java API as a last step.</li>
+ * </ul>
+ */
+final class ThreadDumpInventoryPrinter implements InventoryPrinter
+{
+
+    /**
+     * The <code>java.specification.version</code> string constant.
+     */
+    private static final String JAVA_SPECIFICATION_VERSION = "java.specification.version";
+
+    /**
+     * The <code>1.6</code> string constant.
+     */
+    private static final String JDK16_SPECIFICATION_VERSION = "1.6";
+
+    /**
+     * The <code>1.5</code> string constant.
+     */
+    private static final String JDK15_SPECIFICATION_VERSION = "1.5";
+
+    /**
+     * {@inheritDoc}
+     */
+    public void print(PrintWriter printWriter, Format format, boolean isZip)
+    {
+        ThreadDumper delegated;
+
+        final String javaSpecificationVersion = System.getProperty(JAVA_SPECIFICATION_VERSION);
+
+        // JDK 1.6, 1.7 and 1.8 have same APIs
+        if (JDK16_SPECIFICATION_VERSION.compareToIgnoreCase(javaSpecificationVersion) <= 0)
+        {
+            delegated = new Jdk16ThreadDumper();
+        }
+        else if (JDK15_SPECIFICATION_VERSION.equalsIgnoreCase(javaSpecificationVersion))
+        {
+            delegated = new Jdk15ThreadDumper();
+        }
+        else
+        {
+            // Falls back to regular Java API as a last step
+            delegated = new Jdk14ThreadDumper();
+        }
+
+        ThreadWriter threadWriter = new ThreadWriter(printWriter);
+
+        threadWriter.printHeader();
+        delegated.printThreads(threadWriter);
+    }
+
+}
diff --git a/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadDumper.java b/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadDumper.java
new file mode 100644
index 0000000..b0cac4d
--- /dev/null
+++ b/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadDumper.java
@@ -0,0 +1,34 @@
+/*
+ * 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.threaddump.internal;
+
+/**
+ * Service that dumps current running threads in the JVM.
+ */
+public interface ThreadDumper
+{
+
+    /**
+     * Dumps current running threads in the JVM.
+     * 
+     * @param writer the target writer where dumping the threads.
+     */
+    void printThreads(ThreadWriter writer);
+
+}
diff --git a/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadWriter.java b/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadWriter.java
new file mode 100644
index 0000000..cc4e41a
--- /dev/null
+++ b/threaddump/src/main/java/org/apache/felix/threaddump/internal/ThreadWriter.java
@@ -0,0 +1,128 @@
+/*
+ * 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.threaddump.internal;
+
+import java.io.PrintWriter;
+import java.text.MessageFormat;
+import java.util.Date;
+
+/**
+ *
+ */
+public final class ThreadWriter
+{
+
+    public static final short NEW = 0;
+
+    public static final short RUNNABLE = 1;
+
+    public static final short BLOCKED = 2;
+
+    public static final short WAITING = 3;
+
+    public static final short TIMED_WAITING = 4;
+
+    public static final short TERMINATED = 5;
+
+    private static final String DATE = "{0,date,yyyy-MM-dd HH:mm:ss}";
+
+    private static final String HEADER = "Full thread dump {0} ({1} {2}):";
+
+    // nid is unknown
+    private static final String THREAD = "\"{0}\" {1}prio={2} tid=0x{3} nid=0x{4} {5,choice,0#new|1#runnable|2#waiting for monitor entry|3#in Object.wait()|4#timed_waiting|5#terminated}";
+
+    private static final String THREAD_STATUS = "   java.lang.Thread.State: {0,choice,0#NEW|1#RUNNABLE|2#BLOCKED|3#WAITING (on object monitor)|4#TIMED_WAITING|5#TERMINATED}";
+
+    private static final String STACKTRACE_ELEMENT = "\tat {0}";
+
+    private final PrintWriter writer;
+
+    public ThreadWriter(PrintWriter writer)
+    {
+        this.writer = writer;
+    }
+
+    /**
+     * Full thread dump identifier
+     */
+    public void printHeader()
+    {
+        println(DATE, new Object[]
+            { new Date() });
+        println(HEADER, getSystemProperties(new String[]
+            { "java.vm.name", "java.runtime.version", "java.vm.info" }));
+        printEmptyLine();
+    }
+
+    public void printThread(String name, boolean isDaemon, long priority, long id, short status)
+    {
+        String daemon = isDaemon ? "daemon " : "";
+        println(THREAD, new Object[]
+            { name, daemon, String.valueOf(priority), Long.toHexString(id), Integer.toHexString(-1), // nid
+                new Short(status) });
+        println(THREAD_STATUS, new Object[]
+            { new Short(status) });
+    }
+
+    public void printStackTrace(StackTraceElement[] stackTrace)
+    {
+        if (stackTrace != null)
+        {
+            for (int i = 0; i < stackTrace.length; i++)
+            {
+                printStackTraceElement(stackTrace[i]);
+            }
+        }
+    }
+
+    public void printStackTraceElement(StackTraceElement element)
+    {
+        println(STACKTRACE_ELEMENT, new Object[]
+            { element });
+    }
+
+    public void printEmptyLine()
+    {
+        writer.println();
+    }
+
+    public void println(String message)
+    {
+        writer.println(message);
+    }
+
+    public void println(String pattern, Object[] arguments)
+    {
+        String result = MessageFormat.format(pattern, arguments);
+        writer.println(result);
+    }
+
+    private static Object[] getSystemProperties(String[] keys)
+    {
+        Object[] values = new Object[keys.length];
+
+        for (int i = 0; i < keys.length; i++)
+        {
+            values[i] = System.getProperty(keys[i]);
+        }
+
+        return values;
+    }
+
+}
diff --git a/threaddump/src/main/java/org/apache/felix/threaddump/internal/jdk5/Jdk15ThreadDumper.java b/threaddump/src/main/java/org/apache/felix/threaddump/internal/jdk5/Jdk15ThreadDumper.java
new file mode 100644
index 0000000..eb0afec
--- /dev/null
+++ b/threaddump/src/main/java/org/apache/felix/threaddump/internal/jdk5/Jdk15ThreadDumper.java
@@ -0,0 +1,202 @@
+/*
+ * 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.threaddump.internal.jdk5;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.threaddump.internal.ThreadDumper;
+import org.apache.felix.threaddump.internal.ThreadWriter;
+
+/**
+ * {@link ThreadDumper} implementation which relies on JMX APIs in JDK1.5.
+ */
+public class Jdk15ThreadDumper implements ThreadDumper
+{
+
+    private static final String DEADLOCK = "Found {0} {0,choice,1#deadlock|1<deadlocks}.";
+
+    public void printThreads(ThreadWriter threadWriter)
+    {
+        final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
+
+        // thread Infos
+        final ThreadInfo[] infos = getThreadInfo(threadMXBean);
+
+        // map thread ids to infos idx
+        final Map/* <long, int> */id2idx = new HashMap();
+        for (int i = 0; i < infos.length; i++)
+        {
+            id2idx.put(Long.valueOf(infos[i].getThreadId()), Integer.valueOf(i));
+        }
+
+        // create an array of all Thread objects indexed equivalent to Infos
+        final Thread[] threads = getThreads(id2idx);
+
+        // print the thread information
+        for (int i = 0; i < infos.length; i++)
+        {
+            printThreadInfo(threadWriter, threads[i], infos[i]);
+            threadWriter.printEmptyLine();
+        }
+
+        // dupm deadlock information
+        long[] deadlockedThreadsIds = findDeadlockedThreads(threadMXBean);
+        if (deadlockedThreadsIds != null)
+        {
+
+            List/* <List<int>> */deadlocks = new ArrayList();
+            for (int i = 0; i < deadlockedThreadsIds.length; i++)
+            {
+                Long l = Long.valueOf(deadlockedThreadsIds[i]);
+                Integer idx = (Integer) id2idx.remove(l);
+                if (idx != null)
+                {
+                    List/* <int> */idxs = new ArrayList();
+                    deadlocks.add(idxs);
+
+                    do
+                    {
+                        idxs.add(idx);
+                        ThreadInfo info = infos[idx.intValue()];
+                        if (info != null)
+                        {
+                            idx = (Integer) id2idx.remove(Long.valueOf(info.getLockOwnerId()));
+                        }
+                        else
+                        {
+                            idx = null;
+                        }
+                    }
+                    while (idx != null);
+                }
+            }
+
+            for (Iterator di = deadlocks.iterator(); di.hasNext();)
+            {
+                List idxs = (List) di.next();
+
+                threadWriter.printEmptyLine();
+                threadWriter.println("Found one Java-level deadlock:");
+                threadWriter.println("=============================");
+
+                for (Iterator ii = idxs.iterator(); ii.hasNext();)
+                {
+                    Integer idx = (Integer) ii.next();
+                    ThreadInfo info = infos[idx.intValue()];
+
+                    printDeadlockedThreadInfo(threadWriter, info);
+                }
+
+                threadWriter.printEmptyLine();
+                threadWriter.println("Java stack information for the threads listed above:");
+                threadWriter.println("===================================================");
+
+                for (Iterator ii = idxs.iterator(); ii.hasNext();)
+                {
+                    int idx = ((Integer) ii.next()).intValue();
+                    printThreadInfo(threadWriter, threads[idx], infos[idx]);
+                }
+            }
+
+            threadWriter.printEmptyLine();
+            threadWriter.println(DEADLOCK, new Object[]
+                { Integer.valueOf(deadlocks.size()) });
+            threadWriter.printEmptyLine();
+
+        }
+    }
+
+    private void printThreadInfo(ThreadWriter threadWriter, Thread t, ThreadInfo info)
+    {
+        if (t == null)
+        {
+            return;
+        }
+
+        short status = ThreadStateConverter.toStatus(t.getState());
+        threadWriter.printThread(t.getName(), t.isDaemon(), t.getPriority(), t.getId(), status);
+
+        printStackTrace(threadWriter, info);
+    }
+
+    protected ThreadInfo[] getThreadInfo(ThreadMXBean threadMXBean)
+    {
+        long[] threadIds = threadMXBean.getAllThreadIds();
+        return threadMXBean.getThreadInfo(threadIds, Integer.MAX_VALUE);
+    }
+
+    protected long[] findDeadlockedThreads(ThreadMXBean threadMXBean)
+    {
+        return threadMXBean.findMonitorDeadlockedThreads();
+    }
+
+    protected void printStackTrace(ThreadWriter threadWriter, ThreadInfo info)
+    {
+        threadWriter.printStackTrace(info.getStackTrace());
+    }
+
+    protected void printDeadlockedThreadInfo(ThreadWriter threadWriter, ThreadInfo info)
+    {
+        threadWriter.println("\"{0}\":", new Object[]
+            { info.getThreadName() });
+        threadWriter.println("  waiting to lock monitor,");
+        threadWriter.println("  which is held by \"{0}\"", new Object[]
+            { info.getLockOwnerName() });
+    }
+
+    private static Thread[] getThreads(final Map/* <long, int> */id2idx)
+    {
+        // find root thread group
+        ThreadGroup g = Thread.currentThread().getThreadGroup();
+        while (g.getParent() != null)
+        {
+            g = g.getParent();
+        }
+        int numThreads = g.activeCount();
+        Thread[] threads = new Thread[numThreads * 2];
+        int actualThreads = g.enumerate(threads);
+        if (threads.length == actualThreads)
+        {
+            // some threads have been missed !!
+        }
+
+        Thread[] result = new Thread[id2idx.size()];
+        for (int i = 0; i < threads.length; i++)
+        {
+            Thread t = threads[i];
+            if (t != null)
+            {
+                Integer idx = (Integer) id2idx.get(Long.valueOf(t.getId()));
+                if (idx != null)
+                {
+                    result[idx.intValue()] = t;
+                }
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/threaddump/src/main/java/org/apache/felix/threaddump/internal/jdk5/ThreadStateConverter.java b/threaddump/src/main/java/org/apache/felix/threaddump/internal/jdk5/ThreadStateConverter.java
new file mode 100644
index 0000000..99fffbf
--- /dev/null
+++ b/threaddump/src/main/java/org/apache/felix/threaddump/internal/jdk5/ThreadStateConverter.java
@@ -0,0 +1,61 @@
+/*
+ * 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.threaddump.internal.jdk5;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.felix.threaddump.internal.ThreadWriter;
+
+/**
+ *
+ */
+public final class ThreadStateConverter
+{
+
+    private static final ThreadStateConverter INSTANCE = new ThreadStateConverter();
+
+    private final Map statuses = new HashMap();
+
+    private ThreadStateConverter()
+    {
+        register(Thread.State.NEW, ThreadWriter.NEW);
+        register(Thread.State.RUNNABLE, ThreadWriter.RUNNABLE);
+        register(Thread.State.BLOCKED, ThreadWriter.BLOCKED);
+        register(Thread.State.WAITING, ThreadWriter.WAITING);
+        register(Thread.State.TIMED_WAITING, ThreadWriter.TIMED_WAITING);
+        register(Thread.State.TERMINATED, ThreadWriter.TERMINATED);
+    }
+
+    private void register(Thread.State state, short status)
+    {
+        statuses.put(state, Short.valueOf(status));
+    }
+
+    private short getStatus(Thread.State state)
+    {
+        return ((Short) statuses.get(state)).shortValue();
+    }
+
+    public static short toStatus(Thread.State state)
+    {
+        return INSTANCE.getStatus(state);
+    }
+
+}
diff --git a/threaddump/src/main/java/org/apache/felix/threaddump/internal/jdk6/Jdk16ThreadDumper.java b/threaddump/src/main/java/org/apache/felix/threaddump/internal/jdk6/Jdk16ThreadDumper.java
new file mode 100644
index 0000000..f55f460
--- /dev/null
+++ b/threaddump/src/main/java/org/apache/felix/threaddump/internal/jdk6/Jdk16ThreadDumper.java
@@ -0,0 +1,118 @@
+/*
+ * 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.threaddump.internal.jdk6;
+
+import java.lang.management.LockInfo;
+import java.lang.management.MonitorInfo;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+
+import org.apache.felix.threaddump.internal.ThreadDumper;
+import org.apache.felix.threaddump.internal.ThreadWriter;
+import org.apache.felix.threaddump.internal.jdk5.Jdk15ThreadDumper;
+
+/**
+ * {@link ThreadDumper} implementation which relies on JMX APIs in JDK1.5.
+ */
+public final class Jdk16ThreadDumper extends Jdk15ThreadDumper
+{
+
+    private static final String LOCKED = "\t- locked <0x{0}> (a {1})";
+
+    private static final String BLOCKED = "\t- waiting to lock <0x{0}> (a {1}) owned by \"{2}\" tid=0x{3}";
+
+    protected ThreadInfo[] getThreadInfo(ThreadMXBean threadMXBean)
+    {
+        return threadMXBean.dumpAllThreads(true, true);
+    }
+
+    protected long[] findDeadlockedThreads(ThreadMXBean threadMXBean)
+    {
+        return threadMXBean.findDeadlockedThreads();
+    }
+
+    protected void printStackTrace(ThreadWriter threadWriter, ThreadInfo info)
+    {
+        StackTraceElement[] trace = info.getStackTrace();
+        if (trace.length > 0)
+        {
+            MonitorInfo[] locks = info.getLockedMonitors();
+            int currentLock = 0;
+
+            for (int idx = 0; idx < trace.length; idx++)
+            {
+                threadWriter.printStackTraceElement(trace[idx]);
+
+                if (idx == 0)
+                {
+                    LockInfo locked = info.getLockInfo();
+                    if (locked != null)
+                    {
+                        printLockInfo(threadWriter, BLOCKED, locked, info.getLockOwnerName(), info.getLockOwnerId());
+                    }
+                }
+
+                if (currentLock < locks.length && locks[currentLock].getLockedStackDepth() == idx)
+                {
+                    printLockInfo(threadWriter, LOCKED, locks[currentLock]);
+                    currentLock++;
+                }
+            }
+
+            // print synchronizers ...
+            LockInfo[] syncs = info.getLockedSynchronizers();
+            if (syncs != null && syncs.length > 0)
+            {
+                threadWriter.printEmptyLine();
+                threadWriter.println("   Locked ownable synchronizers:");
+                for (int j = 0; j < syncs.length; j++)
+                {
+                    LockInfo sync = syncs[j];
+                    printLockInfo(threadWriter, LOCKED, sync);
+                }
+            }
+        }
+    }
+
+    protected void printDeadlockedThreadInfo(ThreadWriter threadWriter, ThreadInfo info)
+    {
+        threadWriter.println("\"{0}\":", new Object[]
+            { info.getThreadName() });
+        threadWriter.println("  waiting to lock monitor {0} (object {1}, a {2}),", new Object[]
+            { "7f8a5595d180" /* ? */, Integer.toHexString(info.getLockInfo().getIdentityHashCode()),
+                info.getLockInfo().getClassName() });
+        threadWriter.println("  which is held by \"{0}\"", new Object[]
+            { info.getLockOwnerName() });
+    }
+
+    private static void printLockInfo(ThreadWriter threadWriter, String pattern, LockInfo lockInfo)
+    {
+        printLockInfo(threadWriter, pattern, lockInfo, null, -1);
+    }
+
+    private static void printLockInfo(ThreadWriter threadWriter, String pattern, LockInfo lockInfo,
+        String lockOwnerName, long lockOwnerId)
+    {
+        threadWriter.println(
+            pattern,
+            new Object[]
+                { Integer.toHexString(lockInfo.getIdentityHashCode()), lockInfo.getClassName(), lockOwnerName,
+                    Long.valueOf(lockOwnerId) });
+    }
+}