/*
 * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @bug     4982289 8198253
 * @summary Test ThreadInfo.from to return a valid
 *          ThreadInfo object. Or throw exception if
 *          the input CompositeData is invalid.
 * @author  Mandy Chung
 *
 * @modules java.management/sun.management
 * @build ThreadInfoCompositeData OpenTypeConverter
 * @run testng/othervm ThreadInfoCompositeData
 */


import javax.management.openmbean.*;
import java.lang.management.LockInfo;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;

import org.testng.annotations.Test;
import static org.testng.Assert.*;

public class ThreadInfoCompositeData {
    private static String lockClassName = "myClass";
    private static int lockIdentityHashCode = 123456;
    private static String lockName = lockClassName + '@' +
        Integer.toHexString(lockIdentityHashCode);
    private static LockInfo lockInfo =
        new LockInfo(lockClassName, lockIdentityHashCode);

    @Test
    public static void createGoodCompositeData() throws Exception {
        CompositeData cd = Factory.makeThreadInfoCompositeData();
        ThreadInfo info = ThreadInfo.from(cd);
        checkThreadInfo(info);
    }

    /*
     * An invalid CompositeData with JDK 9 attributes but missing JDK 6 attributes
     */
    @Test
    public static void badMissingCompositeData() throws Exception {
        CompositeData cd = Factory.makeCompositeDataMissingV6();
        try {
            ThreadInfo info = ThreadInfo.from(cd);
            throw new RuntimeException("IllegalArgumentException not thrown");
        } catch (IllegalArgumentException e) {}
    }

    static final StackTraceElement STE =
        new StackTraceElement("FooClass", "getFoo", "Foo.java", 100);


    /*
     * Current version of ThreadInfo but an older version of StackTraceElement
     */
    @Test
    public static void withV5StackTraceCompositeData() throws Exception {
        CompositeData cd = Factory.makeThreadInfoWithV5StackTrace();
        try {
            ThreadInfo info = ThreadInfo.from(cd);
            throw new RuntimeException("IllegalArgumentException not thrown");
        } catch (IllegalArgumentException e) {}
    }

    /*
     * Current version of ThreadInfo but an older version of MonitorInfo
     * and the value of "lockedStackFrame" attribute is null.
     */
    @Test
    public static void withInvalidMonitorInfoCompositeData() throws Exception {
        CompositeData cd = Factory.makeThreadInfoWithIncompatibleMonitorInfo();

        // verify MonitorInfo is valid
        CompositeData[] monitors = (CompositeData[])cd.get("lockedMonitors");
        CompositeData ste = (CompositeData)monitors[0].get("lockedStackFrame");
        if (((Integer)monitors[0].get("lockedStackDepth")) >= 0 || ste != null) {
            throw new RuntimeException("Expected negative stack depth and null stack frame");
        }
        MonitorInfo minfo = MonitorInfo.from(monitors[0]);
        checkLockInfo(minfo);
        if (minfo.getLockedStackFrame() != null) {
            throw new RuntimeException("Expected null stack frame");
        }

        try {
            ThreadInfo info = ThreadInfo.from(cd);
            throw new RuntimeException("IllegalArgumentException not thrown");
        } catch (IllegalArgumentException e) {}
    }

    /*
     * ThreadInfo of version N can accept lockedMonitors of version >= N
     */
    @Test
    public static void withNewMonitorInfoCompositeData() throws Exception {
        CompositeData cd = Factory.makeThreadInfoWithNewMonitorInfo();
        ThreadInfo info = ThreadInfo.from(cd);
        checkThreadInfo(info);
    }

    /*
     * Test CompositeData representing JDK 5 ThreadInfo
     */
    @Test
    public static void createV5ThreadInfo() throws Exception {
        CompositeData cd = Factory.makeThreadInfoV5CompositeData();
        ThreadInfo info = ThreadInfo.from(cd);
        checkThreadInfoV5(info);
    }

    /*
     * Test ThreadInfoCompositeData.toCompositeData
     */
    @Test
    public static void internalToCompositeData() throws Exception {
        CompositeData cd = Factory.makeThreadInfoCompositeData();
        ThreadInfo info = ThreadInfo.from(cd);
        cd = sun.management.ThreadInfoCompositeData.toCompositeData(info);
        info = ThreadInfo.from(cd);
        checkThreadInfo(info);
    }

   static void checkThreadInfoV5(ThreadInfo info) {
       Object[] values = Factory.VALUES;

       if (info.getThreadId() != ((Long) values[THREAD_ID]).longValue()) {
            throw new RuntimeException("Thread Id = " + info.getThreadId() +
               " expected = " + values[THREAD_ID]);
        }
        if (!info.getThreadName().equals(values[THREAD_NAME])) {
            throw new RuntimeException("Thread Name = " +
               info.getThreadName() + " expected = " + values[THREAD_NAME]);
        }
        if (info.getThreadState() != Thread.State.RUNNABLE) {
            throw new RuntimeException("Thread Name = " +
               info.getThreadName() + " expected = " + Thread.State.RUNNABLE);
        }
        if (info.getBlockedTime() != ((Long) values[BLOCKED_TIME]).longValue()) {
            throw new RuntimeException("blocked time = " +
               info.getBlockedTime() +
               " expected = " + values[BLOCKED_TIME]);
        }
        if (info.getBlockedCount() != ((Long) values[BLOCKED_COUNT]).longValue()) {
            throw new RuntimeException("blocked count = " +
               info.getBlockedCount() +
               " expected = " + values[BLOCKED_COUNT]);
        }
        if (info.getWaitedTime() != ((Long) values[WAITED_TIME]).longValue()) {
            throw new RuntimeException("waited time = " +
               info.getWaitedTime() +
               " expected = " + values[WAITED_TIME]);
        }
        if (info.getWaitedCount() != ((Long) values[WAITED_COUNT]).longValue()) {
            throw new RuntimeException("waited count = " +
               info.getWaitedCount() +
               " expected = " + values[WAITED_COUNT]);
        }
        if (!info.getLockName().equals(values[LOCK_NAME])) {
            throw new RuntimeException("Lock Name = " +
               info.getLockName() + " expected = " + values[LOCK_NAME]);
        }
        if (info.getLockOwnerId() !=
                ((Long) values[LOCK_OWNER_ID]).longValue()) {
            throw new RuntimeException(
               "LockOwner Id = " + info.getLockOwnerId() +
               " expected = " + values[LOCK_OWNER_ID]);
        }
        if (!info.getLockOwnerName().equals(values[LOCK_OWNER_NAME])) {
            throw new RuntimeException("LockOwner Name = " +
               info.getLockOwnerName() + " expected = " +
               values[LOCK_OWNER_NAME]);
        }

        checkStackTrace(info.getStackTrace());
        checkLockInfo(info.getLockInfo());
   }

    static void checkThreadInfo(ThreadInfo info) {
        Object[] values = Factory.VALUES;

        checkThreadInfoV5(info);

        if (!values[DAEMON].equals(info.isDaemon())) {
            throw new RuntimeException("Daemon = " +
               info.isDaemon() + " expected = " + values[DAEMON]);
        }
    }

    private static void checkStackTrace(StackTraceElement[] s) {
        if (s.length != 1) {
            throw new RuntimeException("Stack Trace length = " +
                s.length + " expected = 1");
        }

        StackTraceElement s1 = STE;
        StackTraceElement s2 = s[0];

        // these attributes may be null
        if (!Objects.equals(s1.getClassLoaderName(), s2.getClassLoaderName())) {
            throw new RuntimeException("Class loader name = " +
                s2.getClassLoaderName() + " expected = " + s1.getClassLoaderName());
        }
        if (!Objects.equals(s1.getModuleName(), s2.getModuleName())) {
            throw new RuntimeException("Module name = " +
                s2.getModuleName() + " expected = " + s1.getModuleName());
        }
        if (!Objects.equals(s1.getModuleVersion(), s2.getModuleVersion())) {
            throw new RuntimeException("Module version = " +
                s2.getModuleVersion() + " expected = " + s1.getModuleVersion());
        }

        if (!s1.getClassName().equals(s2.getClassName())) {
            throw new RuntimeException("Class name = " +
                s2.getClassName() + " expected = " + s1.getClassName());
        }
        if (!s1.getMethodName().equals(s2.getMethodName())) {
            throw new RuntimeException("Method name = " +
                s2.getMethodName() + " expected = " + s1.getMethodName());
        }
        if (!s1.getFileName().equals(s2.getFileName())) {
            throw new RuntimeException("File name = " +
                s2.getFileName() + " expected = " + s1.getFileName());
        }
        if (s1.getLineNumber() != s2.getLineNumber()) {
            throw new RuntimeException("Line number = " +
                s2.getLineNumber() + " expected = " + s1.getLineNumber());
        }
    }

    private static void checkLockInfo(LockInfo li) {
        if (!li.getClassName().equals(lockInfo.getClassName())) {
            throw new RuntimeException("Class Name = " +
                li.getClassName() + " expected = " + lockInfo.getClassName());
        }
        if (li.getIdentityHashCode() != lockInfo.getIdentityHashCode()) {
            throw new RuntimeException("Class Name = " +
                li.getIdentityHashCode() + " expected = " +
                lockInfo.getIdentityHashCode());
        }
    }

    @Test
    public static void badNameCompositeData() throws Exception {
        CompositeData cd = Factory.makeCompositeDataWithBadNames();
        try {
            ThreadInfo info = ThreadInfo.from(cd);
            throw new RuntimeException("IllegalArgumentException not thrown");
        } catch (IllegalArgumentException e) { }
    }

    @Test
    public static void badTypeCompositeData() throws Exception {
        CompositeData cd = Factory.makeCompositeDataWithBadTypes();

        try {
            ThreadInfo info = ThreadInfo.from(cd);
            throw new RuntimeException("IllegalArgumentException not thrown");
        } catch (IllegalArgumentException e) { }
    }

    private static final int THREAD_ID = 0;
    private static final int THREAD_NAME = 1;
    private static final int THREAD_STATE = 2;
    private static final int BLOCKED_TIME = 3;
    private static final int BLOCKED_COUNT = 4;
    private static final int WAITED_TIME = 5;
    private static final int WAITED_COUNT = 6;
    private static final int LOCK_NAME = 7;
    private static final int LOCK_OWNER_ID = 8;
    private static final int LOCK_OWNER_NAME = 9;
    private static final int STACK_TRACE = 10;
    private static final int SUSPENDED = 11;
    private static final int IN_NATIVE = 12;
    // JDK 6 ThreadInfo attributes
    private static final int LOCK_INFO = 13;
    private static final int LOCKED_MONITORS = 14;
    private static final int LOCKED_SYNCS = 15;
    // JDK 9 ThreadInfo attributes
    private static final int DAEMON = 16;
    private static final int PRIORITY = 17;

    private static class Factory {

        static final CompositeType STE_COMPOSITE_TYPE;
        static final CompositeType LOCK_INFO_COMPOSITE_TYPE;
        static final CompositeType MONITOR_INFO_COMPOSITE_TYPE;
        static final ArrayType STE_ARRAY_COMPOSITE_TYPE;
        static final ArrayType LOCK_INFO_ARRAY_COMPOSITE_TYPE;
        static final ArrayType MONITOR_INFO_ARRAY_COMPOSITE_TYPE;

        static {
            CompositeType steCType = null;
            CompositeType lockInfoCType = null;
            CompositeType monitorInfoCType = null;
            ArrayType steArrayType = null;
            ArrayType lockInfoArrayType = null;
            ArrayType monitorInfoArrayType = null;

            try {
                steCType = (CompositeType) OpenTypeConverter.toOpenType(StackTraceElement.class);
                lockInfoCType = (CompositeType) OpenTypeConverter.toOpenType(LockInfo.class);
                monitorInfoCType = (CompositeType) OpenTypeConverter.toOpenType(MonitorInfo.class);
                steArrayType = new ArrayType(1, steCType);
                lockInfoArrayType = new ArrayType(1, lockInfoCType);
                monitorInfoArrayType = new ArrayType(1, monitorInfoCType);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            STE_COMPOSITE_TYPE = steCType;
            LOCK_INFO_COMPOSITE_TYPE = lockInfoCType;
            MONITOR_INFO_COMPOSITE_TYPE = monitorInfoCType;
            STE_ARRAY_COMPOSITE_TYPE = steArrayType;
            LOCK_INFO_ARRAY_COMPOSITE_TYPE = lockInfoArrayType;
            MONITOR_INFO_ARRAY_COMPOSITE_TYPE = monitorInfoArrayType;
        }

        static CompositeData makeThreadInfoCompositeData() throws OpenDataException {
            CompositeType ct = new CompositeType("MyCompositeType",
                "CompositeType for ThreadInfo",
                ITEM_NAMES,
                ITEM_NAMES,
                ITEM_TYPES);
            return new CompositeDataSupport(ct, ITEM_NAMES, VALUES);
        }

        static CompositeData makeThreadInfoV5CompositeData() throws OpenDataException {
            CompositeType ct = new CompositeType("MyCompositeType",
                "CompositeType for JDK 5 ThreadInfo",
                V5_ITEM_NAMES,
                V5_ITEM_NAMES,
                V5_ITEM_TYPES);
            return new CompositeDataSupport(ct, V5_ITEM_NAMES, V5_VALUES);
        }

        static CompositeData makeCompositeDataWithBadTypes() throws OpenDataException {
            OpenType[] badItemTypes = {
                SimpleType.LONG,
                SimpleType.STRING,
                SimpleType.STRING,
                SimpleType.LONG,
                SimpleType.LONG,
                SimpleType.LONG,
                SimpleType.LONG,
                SimpleType.STRING,
                SimpleType.LONG,
                SimpleType.STRING,
                SimpleType.LONG,  // bad type
                SimpleType.BOOLEAN,
                SimpleType.BOOLEAN,
                SimpleType.LONG,  // bad type
                SimpleType.LONG,  // bad type
                SimpleType.LONG,  // bad type
                SimpleType.BOOLEAN,
                SimpleType.INTEGER,
            };

            CompositeType ct =
                new CompositeType("Bad item types",
                    "CompositeType for ThreadInfo",
                    ITEM_NAMES,
                    ITEM_NAMES,
                    badItemTypes);

            // Copy before mutating to avoid affecting other tests.
            Object[] localValues = VALUES.clone();

            // patch values[STACK_TRACE] to Long
            localValues[STACK_TRACE] = Long.valueOf(1000);
            localValues[LOCK_INFO] = Long.valueOf(1000);
            localValues[LOCKED_MONITORS] = Long.valueOf(1000);
            localValues[LOCKED_SYNCS] = Long.valueOf(1000);
            return new CompositeDataSupport(ct, ITEM_NAMES, localValues);
        }

        static CompositeData makeCompositeDataWithBadNames() throws OpenDataException {
            String[] badItemNames = ITEM_NAMES.clone();
            badItemNames[STACK_TRACE] = "BadStackTrace"; // bad item name

            CompositeType ct = new CompositeType("Bad item names",
                "CompositeType for ThreadInfo",
                badItemNames,
                badItemNames,
                ITEM_TYPES);
            return new CompositeDataSupport(ct,
                badItemNames,
                VALUES);
        }

        /*
         * Create a CompositeData of ThreadInfo without JDK 6 attributes
         */
        static CompositeData makeCompositeDataMissingV6() throws OpenDataException {
            String[] itemNames = concat(V5_ITEM_NAMES, V9_ITEM_NAMES).toArray(String[]::new);
            OpenType[] itemTypes = concat(V5_ITEM_TYPES, V9_ITEM_TYPES).toArray(OpenType[]::new);
            Object[] values = concat(V5_VALUES, V9_VALUES).toArray(Object[]::new);

            CompositeType ct =
                new CompositeType("InvalidCompositeType",
                    "CompositeType for ThreadInfo",
                    itemNames,
                    itemNames,
                    itemTypes);
            return new CompositeDataSupport(ct, itemNames, values);
        }

        static CompositeData makeStackTraceElement() {
            Object[] steValue = {
                STE.getClassLoaderName(),
                STE.getModuleName(),
                STE.getModuleVersion(),
                STE.getClassName(),
                STE.getMethodName(),
                STE.getFileName(),
                Integer.valueOf(STE.getLineNumber()),
                Boolean.valueOf(STE.isNativeMethod()),
            };

            try {
                return new CompositeDataSupport(STE_COMPOSITE_TYPE,
                                                STE_ITEM_NAMES,
                                                steValue);
            } catch (OpenDataException e) {
                throw new RuntimeException(e);
            }
        }

        static CompositeData makeStackTraceElementV5() throws OpenDataException {
            CompositeType steV5CType =
                new CompositeType("JDK 5 StackTraceElement",
                    "CompositeType for JDK 5 StackTraceElement",
                    STE_V5_ITEM_NAMES,
                    STE_V5_ITEM_NAMES,
                    STE_V5_ITEM_TYPES);

            Object[] steV5Value = {
                STE.getClassName(),
                STE.getMethodName(),
                STE.getFileName(),
                Integer.valueOf(STE.getLineNumber()),
                Boolean.valueOf(STE.isNativeMethod()),
            };

            return new CompositeDataSupport(steV5CType, STE_V5_ITEM_NAMES, steV5Value);
        }

        /*
         * Create a CompositeData of ThreadInfo without JDK 5 StackTraceElement
         */
        static CompositeData makeThreadInfoWithV5StackTrace() throws OpenDataException {
            OpenType[] badTypes = ITEM_TYPES.clone();
            Object[] badValues = VALUES.clone();

            CompositeData[] stackTrace = new CompositeData[1];
            stackTrace[0] = makeStackTraceElementV5();
            badTypes[STACK_TRACE] = new ArrayType(1, stackTrace[0].getCompositeType());
            badValues[STACK_TRACE] = stackTrace;
            CompositeType ct = new CompositeType("CompositeType",
                "ThreadInfo with JDK 5 StackTraceElement",
                ITEM_NAMES,
                ITEM_NAMES,
                badTypes);
            return new CompositeDataSupport(ct, ITEM_NAMES, badValues);
        }

        /*
         * Create MonitorInfo with JDK 5 StackTraceElement (i.e. JDK 6 MonitorInfo)
         * The value of "lockedStackFrame" attribute is null to ensure that
         * the validation is done.
         */
        static CompositeData makeV6MonitorInfo() throws OpenDataException {
            CompositeData steV5 = makeStackTraceElementV5();

            String[] names = MONITOR_INFO_COMPOSITE_TYPE.keySet().toArray(new String[0]);
            OpenType[] types = new OpenType[names.length];
            for (int i=0; i < names.length; i++) {
                String n = names[i];
                types[i] = "lockedStackFrame".equals(n)
                                ? steV5.getCompositeType()
                                : MONITOR_INFO_COMPOSITE_TYPE.getType(n);
            }

            CompositeType ctype =
                new CompositeType("JDK 6 MonitorInfo",
                    "CompositeType for JDK 6 MonitorInfo",
                    names,
                    names,
                    types);

            Object[] values = {
                lockClassName,
                lockIdentityHashCode,
                -1,
                null
            };

            return new CompositeDataSupport(ctype, names, values);
        }

        /*
         * Create a CompositeData of ThreadInfo with incompatible MonitorInfo
         */
        static CompositeData makeThreadInfoWithIncompatibleMonitorInfo() throws OpenDataException {
            OpenType[] badTypes = ITEM_TYPES.clone();
            Object[] badValues = VALUES.clone();

            CompositeData[] lockedMonitors = new CompositeData[1];
            lockedMonitors[0] = makeV6MonitorInfo();
            badTypes[LOCKED_MONITORS] = new ArrayType(1, lockedMonitors[0].getCompositeType());
            badValues[LOCKED_MONITORS] = lockedMonitors;
            CompositeType ct = new CompositeType("CompositeType",
                "ThreadInfo with incompatible MonitorInfo",
                ITEM_NAMES,
                ITEM_NAMES,
                badTypes);
            return new CompositeDataSupport(ct, ITEM_NAMES, badValues);
        }

        static CompositeData makeNewMonitorInfo() throws OpenDataException {
            String[] names = Stream.concat(MONITOR_INFO_COMPOSITE_TYPE.keySet().stream(),
                                           Stream.of("extra")).toArray(String[]::new);
            OpenType[] types = new OpenType[names.length];
            for (int i=0; i < names.length; i++) {
                String n = names[i];
                types[i] = "extra".equals(n)
                                ? SimpleType.STRING
                                : MONITOR_INFO_COMPOSITE_TYPE.getType(n);
            }

            CompositeType compositeType =
                new CompositeType("JDK X MonitorInfo",
                    "CompositeType for JDK X MonitorInfo",
                    names,
                    names,
                    types);

            Object[] values = {
                lockClassName,
                lockIdentityHashCode,
                Integer.valueOf(1),
                makeStackTraceElement(),
                "extra"
            };

            return new CompositeDataSupport(compositeType, names, values);
        }

        /*
         * Create a CompositeData of ThreadInfo with a newer version of MonitorInfo
         */
        static CompositeData makeThreadInfoWithNewMonitorInfo() throws OpenDataException {
            OpenType[] types = ITEM_TYPES.clone();
            Object[] badValues = VALUES.clone();

            CompositeData[] lockedMonitors = new CompositeData[1];
            lockedMonitors[0] = makeNewMonitorInfo();
            types[LOCKED_MONITORS] = new ArrayType(1, lockedMonitors[0].getCompositeType());
            badValues[LOCKED_MONITORS] = lockedMonitors;
            CompositeType ct = new CompositeType("CompositeType",
                            "ThreadInfo with JDK 5 MonitorInfo",
                            ITEM_NAMES,
                            ITEM_NAMES,
                            types);
            return new CompositeDataSupport(ct, ITEM_NAMES, badValues);
        }

        static CompositeData makeLockInfo() {
            Object[] lockInfoValue = {
                lockInfo.getClassName(),
                lockInfo.getIdentityHashCode(),
            };

            try {
                return new CompositeDataSupport(LOCK_INFO_COMPOSITE_TYPE,
                    LOCK_INFO_ITEM_NAMES,
                    lockInfoValue);
            } catch (OpenDataException e) {
                throw new RuntimeException(e);
            }
        }

        static CompositeData[] makeLockedSynchronizers() {
            CompositeData[] lockedSyncs = new CompositeData[1];
            lockedSyncs[0] = makeLockInfo();
            return lockedSyncs;
        }

        static CompositeData[] makeLockedMonitors() {
            CompositeData[] lockedMonitorsCD = new CompositeData[1];

            Object[] lockedMonitorsValue = {
                lockInfo.getClassName(),
                lockInfo.getIdentityHashCode(),
                makeStackTraceElement(),
                Integer.valueOf(1),
            };
            try {
                lockedMonitorsCD[0] =
                    new CompositeDataSupport(MONITOR_INFO_COMPOSITE_TYPE,
                        LOCKED_MONITORS_ITEM_NAMES,
                        lockedMonitorsValue);
            } catch (OpenDataException e) {
                throw new RuntimeException(e);
            }
            return lockedMonitorsCD;
        }

        static final String[] V5_ITEM_NAMES = {
            "threadId",
            "threadName",
            "threadState",
            "blockedTime",
            "blockedCount",
            "waitedTime",
            "waitedCount",
            "lockName",
            "lockOwnerId",
            "lockOwnerName",
            "stackTrace",
            "suspended",
            "inNative",
        };

        static final String[] V6_ITEM_NAMES = {
            "lockInfo",
            "lockedMonitors",
            "lockedSynchronizers",
        };

        static final String[] V9_ITEM_NAMES = {
            "daemon",
            "priority",
        };

        static final OpenType[] V5_ITEM_TYPES = {
            SimpleType.LONG,
            SimpleType.STRING,
            SimpleType.STRING,
            SimpleType.LONG,
            SimpleType.LONG,
            SimpleType.LONG,
            SimpleType.LONG,
            SimpleType.STRING,
            SimpleType.LONG,
            SimpleType.STRING,
            STE_ARRAY_COMPOSITE_TYPE,
            SimpleType.BOOLEAN,
            SimpleType.BOOLEAN,
        };

        static final OpenType[] V6_ITEM_TYPES = {
            LOCK_INFO_COMPOSITE_TYPE,
            MONITOR_INFO_ARRAY_COMPOSITE_TYPE,
            LOCK_INFO_ARRAY_COMPOSITE_TYPE,
        };

        static final OpenType[] V9_ITEM_TYPES = {
            SimpleType.BOOLEAN,
            SimpleType.INTEGER,
        };

        static final String[] STE_ITEM_NAMES = {
            "classLoaderName",
            "moduleName",
            "moduleVersion",
            "className",
            "methodName",
            "fileName",
            "lineNumber",
            "nativeMethod",
        };

        static final String[] STE_V5_ITEM_NAMES = Arrays.copyOfRange(STE_ITEM_NAMES, 3, 8);

        static final OpenType[] STE_V5_ITEM_TYPES = {
            SimpleType.STRING,
            SimpleType.STRING,
            SimpleType.STRING,
            SimpleType.INTEGER,
            SimpleType.BOOLEAN
        };

        static final String[] LOCK_INFO_ITEM_NAMES = {
            "className",
            "identityHashCode",
        };

        static final String[] LOCKED_MONITORS_ITEM_NAMES = {
            LOCK_INFO_ITEM_NAMES[0],
            LOCK_INFO_ITEM_NAMES[1],
            "lockedStackFrame",
            "lockedStackDepth",
        };

        static final Object[] V5_VALUES = {
            Long.valueOf(100),
            "FooThread",
            "RUNNABLE",
            Long.valueOf(200),
            Long.valueOf(10),
            Long.valueOf(300),
            Long.valueOf(20),
            lockName,
            Long.valueOf(99),
            "BarThread",
            new CompositeData[] { makeStackTraceElement() },
            Boolean.valueOf(false),
            Boolean.valueOf(false),
        };

        static final Object[] V6_VALUES = {
            makeLockInfo(),
            makeLockedMonitors(),
            makeLockedSynchronizers(),
        };

        static final Object[] V9_VALUES = {
            Boolean.valueOf(true),
            Thread.NORM_PRIORITY,
        };

        static final String[] ITEM_NAMES =
            concat(V5_ITEM_NAMES, V6_ITEM_NAMES, V9_ITEM_NAMES).toArray(String[]::new);

        static final OpenType[] ITEM_TYPES =
            concat(V5_ITEM_TYPES, V6_ITEM_TYPES, V9_ITEM_TYPES).toArray(OpenType[]::new);

        static final Object[] VALUES =
            concat(V5_VALUES, V6_VALUES, V9_VALUES).toArray(Object[]::new);

        static <T> Stream<T> concat(T[]... streams) {
            return Stream.of(streams).flatMap(a -> Arrays.stream(a));
        }
    }
}