/*
 * Copyright (c) 2004, 2013, 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     5024531
 * @summary Validate open types mapped for the MXBeans in the platform
 *          MBeanServer.
 * @author  Mandy Chung
 *
 * @compile ValidateOpenTypes.java
 * @run main/othervm -verbose:gc ValidateOpenTypes
 */
import java.lang.management.*;
import javax.management.*;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;
import static java.lang.management.ManagementFactory.*;
import java.util.List;
import java.util.Map;
import com.sun.management.GcInfo;

public class ValidateOpenTypes {
    private static MBeanServer server =
        ManagementFactory.getPlatformMBeanServer();
    private static ObjectName memory;
    private static ObjectName thread;
    private static ObjectName runtime;
    private static ObjectName os;
    private static ObjectName heapPool = null;
    private static ObjectName nonHeapPool = null;

    public static void main(String[] argv) throws Exception {
        memory = new ObjectName(MEMORY_MXBEAN_NAME);
        runtime = new ObjectName(RUNTIME_MXBEAN_NAME);
        thread = new ObjectName(THREAD_MXBEAN_NAME);
        os = new ObjectName(OPERATING_SYSTEM_MXBEAN_NAME);

        List<MemoryPoolMXBean> pools = getMemoryPoolMXBeans();
        for (MemoryPoolMXBean p : pools) {
            if (heapPool == null &&
                p.getType() == MemoryType.HEAP &&
                p.isUsageThresholdSupported() &&
                p.isCollectionUsageThresholdSupported()) {
                heapPool = new ObjectName(MEMORY_POOL_MXBEAN_DOMAIN_TYPE +
                               ",name=" + p.getName());
            }
            if (nonHeapPool == null &&
                p.getType() == MemoryType.NON_HEAP &&
                p.isUsageThresholdSupported()) {
                nonHeapPool = new ObjectName(MEMORY_POOL_MXBEAN_DOMAIN_TYPE +
                               ",name=" + p.getName());
            }
        }

        // Check notification emitters
        MyListener listener = new MyListener();
        server.addNotificationListener(memory, listener, null, null);
        server.removeNotificationListener(memory, listener);

        checkEnum();
        checkList();
        checkMap();
        checkMemoryUsage();
        checkThreadInfo();

        checkOS();
        checkSunGC();

        System.out.println("Test passed.");
    }

    private static void checkEnum() throws Exception {
        String type = (String) server.getAttribute(heapPool, "Type");
        if (!type.equals("HEAP")) {
            throw new RuntimeException("TEST FAILED: " +
                " incorrect memory type for " + heapPool);
        }

        type = (String) server.getAttribute(nonHeapPool, "Type");
        if (!type.equals("NON_HEAP")) {
            throw new RuntimeException("TEST FAILED: " +
                " incorrect memory type for " + nonHeapPool);
        }
    }

    private static final String OPTION = "-verbose:gc";
    private static void checkList() throws Exception {
        String[] args = (String[]) server.getAttribute(runtime,
                                                       "InputArguments");
        if (args.length < 1) {
           throw new RuntimeException("TEST FAILED: " +
                " empty input arguments");
        }
        // check if -verbose:gc exists
        boolean found = false;
        for (String option : args) {
           if (option.equals(OPTION)) {
               found = true;
               break;
           }
        }
        if (!found) {
            throw new RuntimeException("TEST FAILED: " +
                "VM option " + OPTION + " not found");
        }
    }

    private static final String KEY1   = "test.property.key1";
    private static final String VALUE1 = "test.property.value1";
    private static final String KEY2   = "test.property.key2";
    private static final String VALUE2 = "test.property.value2";
    private static final String KEY3   = "test.property.key3";
    private static void checkMap() throws Exception {
        // Add new system properties
        System.setProperty(KEY1, VALUE1);
        System.setProperty(KEY2, VALUE2);

        TabularData props1 = (TabularData)
            server.getAttribute(runtime, "SystemProperties");

        String value1 = getProperty(props1, KEY1);
        if (value1 == null || !value1.equals(VALUE1)) {
            throw new RuntimeException("TEST FAILED: " +
                 KEY1 + " property found" +
                 " with value = " + value1 +
                 " but expected to be " + VALUE1);
        }

        String value2 = getProperty(props1, KEY2);
        if (value2 == null || !value2.equals(VALUE2)) {
            throw new RuntimeException("TEST FAILED: " +
                 KEY2 + " property found" +
                 " with value = " + value2 +
                 " but expected to be " + VALUE2);
        }

        String value3 = getProperty(props1, KEY3);
        if (value3 != null) {
            throw new RuntimeException("TEST FAILED: " +
                 KEY3 + " property found" +
                 " but should not exist" );
        }
    }
    private static String getProperty(TabularData td, String propName) {
        CompositeData cd = td.get(new Object[] { propName});
        if (cd != null) {
            String key = (String) cd.get("key");
            if (!propName.equals(key)) {
                 throw new RuntimeException("TEST FAILED: " +
                     key + " property found" +
                     " but expected to be " + propName);
            }
            return (String) cd.get("value");
        }
        return null;
    }

    private static void checkMemoryUsage() throws Exception {
        // sanity check to have non-negative usage
        Object u1 = server.getAttribute(memory, "HeapMemoryUsage");
        Object u2 = server.getAttribute(memory, "NonHeapMemoryUsage");
        Object u3 = server.getAttribute(heapPool, "Usage");
        Object u4 = server.getAttribute(nonHeapPool, "Usage");
        if (getCommitted(u1) < 0 ||
            getCommitted(u2) < 0 ||
            getCommitted(u3) < 0 ||
            getCommitted(u4) < 0) {
            throw new RuntimeException("TEST FAILED: " +
                " expected non-negative committed usage");
        }
        server.invoke(memory, "gc", new Object[0], new String[0]);
        Object u5 = server.getAttribute(heapPool, "CollectionUsage");
        if (getCommitted(u5) < 0) {
            throw new RuntimeException("TEST FAILED: " +
                " expected non-negative committed collected usage");
        }
    }

    private static long getCommitted(Object data) {
        MemoryUsage u = MemoryUsage.from((CompositeData) data);
        return u.getCommitted();
    }

    private static void checkThreadInfo() throws Exception {
        // assume all threads stay alive
        long[] ids = (long[]) server.getAttribute(thread, "AllThreadIds");
        Object result = server.invoke(thread,
                                      "getThreadInfo",
                                      new Object[] { ids },
                                      new String[] { "[J" });
        for (CompositeData cd : (CompositeData[]) result) {
            printThreadInfo(cd);
        }

        result = server.invoke(thread,
                               "getThreadInfo",
                               new Object[] { ids, new Integer(2) },
                               new String[] { "[J", "int" });
        for (CompositeData cd : (CompositeData[]) result) {
            printThreadInfo(cd);
        }

        long id = Thread.currentThread().getId();
        result = server.invoke(thread,
                               "getThreadInfo",
                               new Object[] { new Long(id) },
                               new String[] { "long" });
        printThreadInfo((CompositeData) result);

        result = server.invoke(thread,
                               "getThreadInfo",
                               new Object[] { new Long(id), new Integer(2) },
                               new String[] { "long", "int" });
        printThreadInfo((CompositeData) result);
    }

    private static void printThreadInfo(CompositeData cd) {
        ThreadInfo info = ThreadInfo.from(cd);
        if (info == null) {
            throw new RuntimeException("TEST FAILED: " +
                " Null ThreadInfo");
        }

        System.out.print(info.getThreadName());
        System.out.print(" id=" + info.getThreadId());
        System.out.println(" " + info.getThreadState());

        for (StackTraceElement s : info.getStackTrace()) {
            System.out.println(s);
        }
    }

    private static void checkOS() throws Exception {
        Integer cpus = (Integer) server.getAttribute(os, "AvailableProcessors");
        System.out.println("# CPUs = " + cpus);
        Long vmem = (Long) server.getAttribute(os, "CommittedVirtualMemorySize");
        System.out.println("Committed virtual memory = " + vmem);
    }

    private static void checkSunGC() throws Exception {
       // Test com.sun.management proxy
        List<GarbageCollectorMXBean> gcs = getGarbageCollectorMXBeans();
        for (GarbageCollectorMXBean gc : gcs) {
            ObjectName sunGc =
                new ObjectName(GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE +
                               ",name=" + gc.getName());
            CompositeData cd = (CompositeData) server.getAttribute(sunGc, "LastGcInfo");
            if (cd != null) {
                System.out.println("GC statistic for : " + gc.getName());
                printGcInfo(cd);
            }
        }
    }

    private static void printGcInfo(CompositeData cd) throws Exception {
        GcInfo info = GcInfo.from(cd);
        System.out.print("GC #" + info.getId());
        System.out.print(" start:" + info.getStartTime());
        System.out.print(" end:" + info.getEndTime());
        System.out.println(" (" + info.getDuration() + "ms)");
        Map<String,MemoryUsage> usage = info.getMemoryUsageBeforeGc();

        for (Map.Entry<String,MemoryUsage> entry : usage.entrySet()) {
            String poolname = entry.getKey();
            MemoryUsage busage = entry.getValue();
            MemoryUsage ausage = info.getMemoryUsageAfterGc().get(poolname);
            if (ausage == null) {
                throw new RuntimeException("After Gc Memory does not exist" +
                    " for " + poolname);
            }
            System.out.println("Usage for pool " + poolname);
            System.out.println("   Before GC: " + busage);
            System.out.println("   After GC: " + ausage);
        }
    }

    static class MyListener implements NotificationListener {
        public void handleNotification(Notification notif, Object handback) {
            return;
        }
    }
}
