import otherpackage.OtherPackageClass;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

public class ClassAttrs {
    ClassAttrs() {
        /* local, not anonymous, not member */
        class ConsInnerNamed {
            public void showMe() {
                printClassAttrs(this.getClass());
            }
        }

        ConsInnerNamed cinner = new ConsInnerNamed();
        cinner.showMe();
    }

    public class PublicInnerClass {
    }

    protected class ProtectedInnerClass {
    }

    private class PrivateInnerClass {
    }

    class PackagePrivateInnerClass {
    }

    private static void showModifiers(Class<?> c) {
        System.out.println(Modifier.toString(c.getModifiers()) + " " + c.getName());
    }

    // https://code.google.com/p/android/issues/detail?id=56267
    private static void test56267() {
        // Primitive classes.
        showModifiers(int.class);
        showModifiers(int[].class);

        // Regular classes.
        showModifiers(Object.class);
        showModifiers(Object[].class);

        // Inner classes.
        showModifiers(PublicInnerClass.class);
        showModifiers(PublicInnerClass[].class);
        showModifiers(ProtectedInnerClass.class);
        showModifiers(ProtectedInnerClass[].class);
        showModifiers(PrivateInnerClass.class);
        showModifiers(PrivateInnerClass[].class);
        showModifiers(PackagePrivateInnerClass.class);
        showModifiers(PackagePrivateInnerClass[].class);
    }

    public static void main() {
        test56267();

        printClassAttrs(ClassAttrs.class);
        printClassAttrs(OtherClass.class);
        printClassAttrs(OtherPackageClass.class);

        /* local, not anonymous, not member */
        class InnerNamed {
            public void showMe() {
                printClassAttrs(this.getClass());
            }
        }
        InnerNamed inner = new InnerNamed();
        inner.showMe();

        ClassAttrs attrs = new ClassAttrs();

        /* anonymous, not local, not member */
        printClassAttrs((new OtherClass() { int i = 5; }).getClass());

        /* member, not anonymous, not local */
        printClassAttrs(MemberClass.class);

        /* fancy */
        printClassAttrs(FancyClass.class);

        try {
            Constructor cons;
            cons = MemberClass.class.getConstructor(
                    new Class[] { MemberClass.class });
            System.out.println("constructor signature: "
                    + getSignatureAttribute(cons));

            Method meth;
            meth = MemberClass.class.getMethod("foo", (Class[]) null);
            System.out.println("method signature: "
                    + getSignatureAttribute(meth));

            Field field;
            field = MemberClass.class.getField("mWha");
            System.out.println("field signature: "
                    + getSignatureAttribute(field));
        } catch (NoSuchMethodException nsme) {
            System.err.println("FAILED: " + nsme);
        } catch (NoSuchFieldException nsfe) {
            System.err.println("FAILED: " + nsfe);
        } catch (RuntimeException re) {
            System.err.println("FAILED: " + re);
            re.printStackTrace();
        }
    }

    /* to call the (out-of-scope) <code>getSignatureAttribute</code> methods */
    public static String getSignatureAttribute(Object obj) {
        Method method;
        try {
            if (obj instanceof AccessibleObject) {
                method = AccessibleObject.class.getDeclaredMethod(
                        "getSignatureAttribute");
            } else {
                // Should be a Class.
                method = Class.class.getDeclaredMethod(
                        "getSignatureAttribute");
            }
            method.setAccessible(true);
        } catch (NoSuchMethodException ex) {
            //System.err.println("getSignatureAttribute() not defined.");
            //ex.printStackTrace();
            return "<unknown>";
        }

        try {
            return (String) method.invoke(obj);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(ex);
        } catch (InvocationTargetException ex) {
            throw new RuntimeException(ex);
        }
    }

    /* for reflection testing */
    static class MemberClass<XYZ> {
        public MemberClass<XYZ> mWha;

        public MemberClass(MemberClass<XYZ> memb) {
            mWha = memb;
        }

        public Class<XYZ> foo() throws NoSuchMethodException {
            return null;
        }
    }

    /* for reflection testing (getClasses vs getDeclaredClasses) */
    static public class PublicMemberClass {
        float mBlah;
    }

    /*
     * Dump a variety of class attributes.
     */
    public static void printClassAttrs(Class clazz) {
        Class clazz2;

        System.out.println("***** " + clazz + ":");

        System.out.println("  name: "
            + clazz.getName());
        System.out.println("  canonical: "
            + clazz.getCanonicalName());
        System.out.println("  simple: "
            + clazz.getSimpleName());
        System.out.println("  genericSignature: "
            + getSignatureAttribute(clazz));

        System.out.println("  super: "
            + clazz.getSuperclass());
        System.out.println("  genericSuperclass: "
            + clazz.getGenericSuperclass());
        System.out.println("  declaring: "
            + clazz.getDeclaringClass());
        System.out.println("  enclosing: "
            + clazz.getEnclosingClass());
        System.out.println("  enclosingCon: "
            + clazz.getEnclosingConstructor());
        System.out.println("  enclosingMeth: "
            + clazz.getEnclosingMethod());
        System.out.println("  modifiers: "
            + clazz.getModifiers());
        System.out.println("  package: "
            + clazz.getPackage());

        System.out.println("  declaredClasses: "
            + stringifyTypeArray(clazz.getDeclaredClasses()));
        System.out.println("  member classes: "
            + stringifyTypeArray(clazz.getClasses()));

        System.out.println("  isAnnotation: "
            + clazz.isAnnotation());
        System.out.println("  isAnonymous: "
            + clazz.isAnonymousClass());
        System.out.println("  isArray: "
            + clazz.isArray());
        System.out.println("  isEnum: "
            + clazz.isEnum());
        System.out.println("  isInterface: "
            + clazz.isInterface());
        System.out.println("  isLocalClass: "
            + clazz.isLocalClass());
        System.out.println("  isMemberClass: "
            + clazz.isMemberClass());
        System.out.println("  isPrimitive: "
            + clazz.isPrimitive());
        System.out.println("  isSynthetic: "
            + clazz.isSynthetic());

        System.out.println("  genericInterfaces: "
            + stringifyTypeArray(clazz.getGenericInterfaces()));

        TypeVariable<Class<?>>[] typeParameters = clazz.getTypeParameters();
        System.out.println("  typeParameters: "
            + stringifyTypeArray(typeParameters));
    }

    /*
     * Convert an array of Type into a string.  Start with an array count.
     */
    private static String stringifyTypeArray(Type[] types) {
        StringBuilder stb = new StringBuilder();
        boolean first = true;

        stb.append("[" + types.length + "]");

        for (Type t: types) {
            if (first) {
                stb.append(" ");
                first = false;
            } else {
                stb.append(", ");
            }
            stb.append(t.toString());
        }

        return stb.toString();
    }
}
