/*
 * Copyright (c) 2008, 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 6713777
 * @summary Test that exception messages include all relevant information
 * @author Eamonn McManus
 */

import java.beans.ConstructorProperties;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

public class ExceptionDiagnosisTest {
    private static volatile String failure;

    // ------ Illegal MXBeans ------

    // Test that all of BdelloidMXBean, Rotifer, and File appear in the
    // exception messages.  File is not an allowed type because of recursive
    // getters like "File getParentFile()".
    public static interface BdelloidMXBean {
        public Rotifer getRotifer();
    }

    public static class Bdelloid implements BdelloidMXBean {
        public Rotifer getRotifer() {
            return null;
        }
    }

    public static class Rotifer {
        public File getFile() {
            return null;
        }
    }

    // Test that all of IndirectHashMapMXBean, HashMapContainer, and
    // HashMap<String,String> appear in the exception messages.
    // HashMap<String,String> is not an allowed type because only the
    // java.util interface such as Map are allowed with generic parameters,
    // not their concrete implementations like HashMap.
    public static interface IndirectHashMapMXBean {
        public HashMapContainer getContainer();
    }

    public static class IndirectHashMap implements IndirectHashMapMXBean {
        public HashMapContainer getContainer() {
            return null;
        }
    }

    public static class HashMapContainer {
        public HashMap<String, String> getHashMap() {return null;}
    }

    // ------ MXBeans that are legal but where proxies are not ------

    // Test that all of BlimMXBean, BlimContainer, Blim, and Blam appear
    // in the exception messages for a proxy for this MXBean.  Blam is
    // legal in MXBeans but is not reconstructible so you cannot make
    // a proxy for BlimMXBean.
    public static interface BlimMXBean {
        public BlimContainer getBlimContainer();
    }

    public static class BlimImpl implements BlimMXBean {
        public BlimContainer getBlimContainer() {
            return null;
        }
    }

    public static class BlimContainer {
        public Blim getBlim() {return null;}
        public void setBlim(Blim blim) {}
    }

    public static class Blim {
        public Blam getBlam() {return null;}
        public void setBlam(Blam blam) {}
    }

    public static class Blam {
        public Blam(int x) {}

        public int getX() {return 0;}
    }


    // ------ Property name differing only in case ------

    public static interface CaseProbMXBean {
        public CaseProb getCaseProb();
    }

    public static class CaseProbImpl implements CaseProbMXBean {
        public CaseProb getCaseProb() {return null;}
    }

    public static class CaseProb {
        @ConstructorProperties({"urlPath"})
        public CaseProb(String urlPath) {}

        public String getURLPath() {return null;}
    }


    public static void main(String[] args) throws Exception {
        testMXBeans(new Bdelloid(), BdelloidMXBean.class, Rotifer.class, File.class);
        testMXBeans(new IndirectHashMap(),
                IndirectHashMapMXBean.class, HashMapContainer.class,
                HashMapContainer.class.getMethod("getHashMap").getGenericReturnType());

        testProxies(new BlimImpl(), BlimMXBean.class, BlimMXBean.class,
                BlimContainer.class, Blim.class, Blam.class);

        testCaseProb();

        if (failure == null)
            System.out.println("TEST PASSED");
        else
            throw new Exception("TEST FAILED: " + failure);
    }

    private static void testMXBeans(Object mbean, Type... expectedTypes)
            throws Exception {
        try {
            MBeanServer mbs = MBeanServerFactory.newMBeanServer();
            ObjectName name = new ObjectName("a:b=c");
            mbs.registerMBean(mbean, name);
            fail("No exception from registerMBean for " + mbean);
        } catch (NotCompliantMBeanException e) {
            checkExceptionChain("MBean " + mbean, e, expectedTypes);
        }
    }

    private static <T> void testProxies(
            Object mbean, Class<T> mxbeanClass, Type... expectedTypes)
            throws Exception {
        MBeanServer mbs = MBeanServerFactory.newMBeanServer();
        ObjectName name = new ObjectName("a:b=c");
        mbs.registerMBean(mbean, name);
        T proxy = JMX.newMXBeanProxy(mbs, name, mxbeanClass);
        List<Method> methods = new ArrayList<Method>();
        for (Method m : mxbeanClass.getMethods()) {
            if (m.getDeclaringClass() == mxbeanClass)
                methods.add(m);
        }
        if (methods.size() != 1) {
            fail("TEST BUG: expected to find exactly one method in " +
                    mxbeanClass.getName() + ": " + methods);
        }
        Method getter = methods.get(0);
        try {
            try {
                getter.invoke(proxy);
                fail("No exception from proxy method " + getter.getName() +
                        " in " + mxbeanClass.getName());
            } catch (InvocationTargetException e) {
                Throwable cause = e.getCause();
                if (cause instanceof Exception)
                    throw (Exception) cause;
                else
                    throw (Error) cause;
            }
        } catch (IllegalArgumentException e) {
            checkExceptionChain(
                    "Proxy for " + mxbeanClass.getName(), e, expectedTypes);
        }
    }

    private static void testCaseProb() throws Exception {
        MBeanServer mbs = MBeanServerFactory.newMBeanServer();
        ObjectName name = new ObjectName("a:b=c");
        mbs.registerMBean(new CaseProbImpl(), name);
        CaseProbMXBean proxy = JMX.newMXBeanProxy(mbs, name, CaseProbMXBean.class);
        try {
            CaseProb prob = proxy.getCaseProb();
            fail("No exception from proxy method getCaseProb");
        } catch (IllegalArgumentException e) {
            String messageChain = messageChain(e);
            if (messageChain.contains("URLPath")) {
                System.out.println("Message chain contains URLPath as required: "
                        + messageChain);
            } else {
                fail("Exception chain for CaseProb does not mention property" +
                        " URLPath differing only in case");
                System.out.println("Full stack trace:");
                e.printStackTrace(System.out);
            }
        }
    }

    private static void checkExceptionChain(
            String what, Throwable e, Type[] expectedTypes) {
        System.out.println("Exceptions in chain for " + what + ":");
        for (Throwable t = e; t != null; t = t.getCause())
            System.out.println(".." + t);

        String messageChain = messageChain(e);

        // Now check that each of the classes is mentioned in those messages
        for (Type type : expectedTypes) {
            String name = (type instanceof Class) ?
                ((Class<?>) type).getName() : type.toString();
            if (!messageChain.contains(name)) {
                fail("Exception chain for " + what + " does not mention " +
                        name);
                System.out.println("Full stack trace:");
                e.printStackTrace(System.out);
            }
        }

        System.out.println();
    }

    private static String messageChain(Throwable t) {
        String msg = "//";
        for ( ; t != null; t = t.getCause())
            msg += " " + t.getMessage() + " //";
        return msg;
    }

    private static void fail(String why) {
        failure = why;
        System.out.println("FAIL: " + why);
    }
}
