/*
 * 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.logging.log4j.message;

import java.lang.management.LockInfo;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;

/**
 * Provides information on locks and monitors in the thread dump. This class requires Java 1.6 to compile and
 * run.
 */
class ExtendedThreadInformation implements ThreadInformation {

    private final ThreadInfo info;


    public ExtendedThreadInformation(final ThreadInfo thread) {
        this.info = thread;
    }

    @Override
    public void printThreadInfo(final StringBuilder sb) {
        sb.append("\"").append(info.getThreadName()).append("\"");
        sb.append(" Id=").append(info.getThreadId()).append(" ");
        formatState(sb, info);
        if (info.isSuspended()) {
            sb.append(" (suspended)");
        }
        if (info.isInNative()) {
            sb.append(" (in native)");
        }
        sb.append('\n');
    }

    @Override
    public void printStack(final StringBuilder sb, final StackTraceElement[] stack) {
        int i = 0;
        for (final StackTraceElement element : stack) {
            sb.append("\tat ").append(element.toString());
            sb.append('\n');
            if (i == 0 && info.getLockInfo() != null) {
                final Thread.State ts = info.getThreadState();
                switch (ts) {
                    case BLOCKED:
                        sb.append("\t-  blocked on ");
                        formatLock(sb, info.getLockInfo());
                        sb.append('\n');
                        break;
                    case WAITING:
                        sb.append("\t-  waiting on ");
                        formatLock(sb, info.getLockInfo());
                        sb.append('\n');
                        break;
                    case TIMED_WAITING:
                        sb.append("\t-  waiting on ");
                        formatLock(sb, info.getLockInfo());
                        sb.append('\n');
                        break;
                    default:
                }
            }

            for (final MonitorInfo mi : info.getLockedMonitors()) {
                if (mi.getLockedStackDepth() == i) {
                    sb.append("\t-  locked ");
                    formatLock(sb, mi);
                    sb.append('\n');
                }
            }
            ++i;
        }

        final LockInfo[] locks = info.getLockedSynchronizers();
        if (locks.length > 0) {
            sb.append("\n\tNumber of locked synchronizers = ").append(locks.length).append('\n');
            for (final LockInfo li : locks) {
                sb.append("\t- ");
                formatLock(sb, li);
                sb.append('\n');
            }
        }
    }

    private void formatLock(final StringBuilder sb, final LockInfo lock) {
        sb.append("<").append(lock.getIdentityHashCode()).append("> (a ");
        sb.append(lock.getClassName()).append(")");
    }

    private void formatState(final StringBuilder sb, final ThreadInfo info) {
        final Thread.State state = info.getThreadState();
        sb.append(state);
        switch (state) {
            case BLOCKED: {
                sb.append(" (on object monitor owned by \"");
                sb.append(info.getLockOwnerName()).append("\" Id=").append(info.getLockOwnerId()).append(")");
                break;
            }
            case WAITING: {
                final StackTraceElement element = info.getStackTrace()[0];
                final String className = element.getClassName();
                final String method = element.getMethodName();
                if (className.equals("java.lang.Object") && method.equals("wait")) {
                    sb.append(" (on object monitor");
                    if (info.getLockOwnerName() != null) {
                        sb.append(" owned by \"");
                        sb.append(info.getLockOwnerName()).append("\" Id=").append(info.getLockOwnerId());
                    }
                    sb.append(")");
                } else if (className.equals("java.lang.Thread") && method.equals("join")) {
                    sb.append(" (on completion of thread ").append(info.getLockOwnerId()).append(")");
                } else {
                    sb.append(" (parking for lock");
                    if (info.getLockOwnerName() != null) {
                        sb.append(" owned by \"");
                        sb.append(info.getLockOwnerName()).append("\" Id=").append(info.getLockOwnerId());
                    }
                    sb.append(")");
                }
                break;
            }
            case TIMED_WAITING: {
                final StackTraceElement element = info.getStackTrace()[0];
                final String className = element.getClassName();
                final String method = element.getMethodName();
                if (className.equals("java.lang.Object") && method.equals("wait")) {
                    sb.append(" (on object monitor");
                    if (info.getLockOwnerName() != null) {
                        sb.append(" owned by \"");
                        sb.append(info.getLockOwnerName()).append("\" Id=").append(info.getLockOwnerId());
                    }
                    sb.append(")");
                } else if (className.equals("java.lang.Thread") && method.equals("sleep")) {
                    sb.append(" (sleeping)");
                } else if (className.equals("java.lang.Thread") && method.equals("join")) {
                    sb.append(" (on completion of thread ").append(info.getLockOwnerId()).append(")");
                } else {
                    sb.append(" (parking for lock");
                    if (info.getLockOwnerName() != null) {
                        sb.append(" owned by \"");
                        sb.append(info.getLockOwnerName()).append("\" Id=").append(info.getLockOwnerId());
                    }
                    sb.append(")");
                }
                break;
            }
            default:
                break;
        }
    }
}

@@ -1373,20 +1373,19 @@
 .append(
-%22%5C%22%22
+'%22'
 ).append
@@ -1418,12 +1418,11 @@
 end(
-%22%5C%22%22
+'%22'
 );%0A 
@@ -1484,11 +1484,11 @@
 end(
-%22 %22
+' '
 );%0A 
@@ -3634,11 +3634,11 @@
 end(
-%22%3C%22
+'%3C'
 ).ap
@@ -3729,27 +3729,27 @@
 e()).append(
-%22)%22
+')'
 );%0A    %7D%0A%0A  
@@ -4120,35 +4120,35 @@
 nerId()).append(
-%22)%22
+')'
 );%0A             
@@ -4802,35 +4802,35 @@
       sb.append(
-%22)%22
+')'
 );%0A             
@@ -4997,35 +4997,35 @@
 nerId()).append(
-%22)%22
+')'
 );%0A             
@@ -5350,35 +5350,35 @@
       sb.append(
-%22)%22
+')'
 );%0A             
@@ -6056,35 +6056,35 @@
       sb.append(
-%22)%22
+')'
 );%0A             
@@ -6390,35 +6390,35 @@
 nerId()).append(
-%22)%22
+')'
 );%0A             
@@ -6755,11 +6755,11 @@
 end(
-%22)%22
+')'
 );%0A 

Replace single char strings with chars.