/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed 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.
 */

import java.lang.reflect.*;
import java.io.IOException;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Reflection test.
 */
public class Main {
    private static boolean FULL_ACCESS_CHECKS = false;  // b/5861201
    public Main() {}
    public Main(ArrayList<Integer> stuff) {}

    void printMethodInfo(Method meth) {
        Class[] params, exceptions;
        int i;

        System.out.println("Method name is " + meth.getName());
        System.out.println(" Declaring class is "
            + meth.getDeclaringClass().getName());
        params = meth.getParameterTypes();
        for (i = 0; i < params.length; i++)
            System.out.println(" Arg " + i + ": " + params[i].getName());
        exceptions = meth.getExceptionTypes();
        for (i = 0; i < exceptions.length; i++)
            System.out.println(" Exc " + i + ": " + exceptions[i].getName());
        System.out.println(" Return type is " + meth.getReturnType().getName());
        System.out.println(" Access flags are 0x"
            + Integer.toHexString(meth.getModifiers()));
        //System.out.println(" GenericStr is " + meth.toGenericString());
    }

    void printFieldInfo(Field field) {
        System.out.println("Field name is " + field.getName());
        System.out.println(" Declaring class is "
            + field.getDeclaringClass().getName());
        System.out.println(" Field type is " + field.getType().getName());
        System.out.println(" Access flags are 0x"
            + Integer.toHexString(field.getModifiers()));
    }

    private void showStrings(Target instance)
        throws NoSuchFieldException, IllegalAccessException {

        Class target = Target.class;
        String one, two, three, four;
        Field field = null;

        field = target.getField("string1");
        one = (String) field.get(instance);

        field = target.getField("string2");
        two = (String) field.get(instance);

        field = target.getField("string3");
        three = (String) field.get(instance);

        System.out.println("  ::: " + one + ":" + two + ":" + three);
    }

    public static void checkAccess() {
        try {
            Class target = otherpackage.Other.class;
            Object instance = new otherpackage.Other();
            Method meth;

            meth = target.getMethod("publicMethod", (Class[]) null);
            meth.invoke(instance);

            try {
                meth = target.getMethod("packageMethod", (Class[]) null);
                System.err.println("succeeded on package-scope method");
            } catch (NoSuchMethodException nsme) {
                // good
            }


            instance = otherpackage.Other.getInnerClassInstance();
            target = instance.getClass();
            meth = target.getMethod("innerMethod", (Class[]) null);
            try {
                if (!FULL_ACCESS_CHECKS) { throw new IllegalAccessException(); }
                meth.invoke(instance);
                System.err.println("inner-method invoke unexpectedly worked");
            } catch (IllegalAccessException iae) {
                // good
            }

            Field field = target.getField("innerField");
            try {
                int x = field.getInt(instance);
                if (!FULL_ACCESS_CHECKS) { throw new IllegalAccessException(); }
                System.err.println("field get unexpectedly worked: " + x);
            } catch (IllegalAccessException iae) {
                // good
            }
        } catch (Exception ex) {
            System.out.println("----- unexpected exception -----");
            ex.printStackTrace();
        }
    }

    public void run() {
        Class target = Target.class;
        Method meth = null;
        Field field = null;
        boolean excep;

        try {
            meth = target.getMethod("myMethod", new Class[] { int.class });

            if (meth.getDeclaringClass() != target)
                throw new RuntimeException();
            printMethodInfo(meth);

            meth = target.getMethod("myMethod", new Class[] { float.class });
            printMethodInfo(meth);

            meth = target.getMethod("myNoargMethod", (Class[]) null);
            printMethodInfo(meth);

            meth = target.getMethod("myMethod",
                new Class[] { String[].class, float.class, char.class });
            printMethodInfo(meth);

            Target instance = new Target();
            Object[] argList = new Object[] {
                new String[] { "hi there" },
                new Float(3.1415926f),
                new Character('Q')
            };
            System.out.println("Before, float is "
                + ((Float)argList[1]).floatValue());

            Integer boxval;
            boxval = (Integer) meth.invoke(instance, argList);
            System.out.println("Result of invoke: " + boxval.intValue());

            System.out.println("Calling no-arg void-return method");
            meth = target.getMethod("myNoargMethod", (Class[]) null);
            meth.invoke(instance, (Object[]) null);

            /* try invoking a method that throws an exception */
            meth = target.getMethod("throwingMethod", (Class[]) null);
            try {
                meth.invoke(instance, (Object[]) null);
                System.out.println("GLITCH: didn't throw");
            } catch (InvocationTargetException ite) {
                System.out.println("Invoke got expected exception:");
                System.out.println(ite.getClass().getName());
                System.out.println(ite.getCause());
            }
            catch (Exception ex) {
                System.out.println("GLITCH: invoke got wrong exception:");
                ex.printStackTrace();
            }
            System.out.println("");


            field = target.getField("string1");
            if (field.getDeclaringClass() != target)
                throw new RuntimeException();
            printFieldInfo(field);
            String strVal = (String) field.get(instance);
            System.out.println("  string1 value is '" + strVal + "'");

            showStrings(instance);

            field.set(instance, new String("a new string"));
            strVal = (String) field.get(instance);
            System.out.println("  string1 value is now '" + strVal + "'");

            showStrings(instance);

            try {
                field.set(instance, new Object());
                System.out.println("WARNING: able to store Object into String");
            }
            catch (IllegalArgumentException iae) {
                System.out.println("  got expected illegal obj store exc");
            }


            try {
                String four;
                field = target.getField("string4");
                four = (String) field.get(instance);
                System.out.println("WARNING: able to access string4: "
                    + four);
            }
            catch (IllegalAccessException iae) {
                System.out.println("  got expected access exc");
            }
            catch (NoSuchFieldException nsfe) {
                System.out.println("  got the other expected access exc");
            }
            try {
                String three;
                field = target.getField("string3");
                three = (String) field.get(this);
                System.out.println("WARNING: able to get string3 in wrong obj: "
                    + three);
            }
            catch (IllegalArgumentException iae) {
                System.out.println("  got expected arg exc");
            }

            /*
             * Try setting a field to null.
             */
            String four;
            field = target.getDeclaredField("string3");
            field.set(instance, null);

            /*
             * Do some stuff with long.
             */
            long longVal;
            field = target.getField("pubLong");
            longVal = field.getLong(instance);
            System.out.println("pubLong initial value is " +
                Long.toHexString(longVal));
            field.setLong(instance, 0x9988776655443322L);
            longVal = field.getLong(instance);
            System.out.println("pubLong new value is " +
                Long.toHexString(longVal));


            field = target.getField("superInt");
            if (field.getDeclaringClass() == target)
                throw new RuntimeException();
            printFieldInfo(field);
            int intVal = field.getInt(instance);
            System.out.println("  superInt value is " + intVal);
            Integer boxedIntVal = (Integer) field.get(instance);
            System.out.println("  superInt boxed is " + boxedIntVal);

            field.set(instance, new Integer(20202));
            intVal = field.getInt(instance);
            System.out.println("  superInt value is now " + intVal);
            field.setShort(instance, (short)30303);
            intVal = field.getInt(instance);
            System.out.println("  superInt value (from short) is now " +intVal);
            field.setInt(instance, 40404);
            intVal = field.getInt(instance);
            System.out.println("  superInt value is now " + intVal);
            try {
                field.set(instance, new Long(123));
                System.out.println("FAIL: expected exception not thrown");
            }
            catch (IllegalArgumentException iae) {
                System.out.println("  got expected long->int failure");
            }
            try {
                field.setLong(instance, 123);
                System.out.println("FAIL: expected exception not thrown");
            }
            catch (IllegalArgumentException iae) {
                System.out.println("  got expected long->int failure");
            }
            try {
                field.set(instance, new String("abc"));
                System.out.println("FAIL: expected exception not thrown");
            }
            catch (IllegalArgumentException iae) {
                System.out.println("  got expected string->int failure");
            }

            try {
                field.getShort(instance);
                System.out.println("FAIL: expected exception not thrown");
            }
            catch (IllegalArgumentException iae) {
                System.out.println("  got expected int->short failure");
            }

            field = target.getField("superClassInt");
            printFieldInfo(field);
            int superClassIntVal = field.getInt(instance);
            System.out.println("  superClassInt value is " + superClassIntVal);

            field = target.getField("staticDouble");
            printFieldInfo(field);
            double staticDoubleVal = field.getDouble(null);
            System.out.println("  staticDoubleVal value is " + staticDoubleVal);

            try {
                field.getLong(instance);
                System.out.println("FAIL: expected exception not thrown");
            }
            catch (IllegalArgumentException iae) {
                System.out.println("  got expected double->long failure");
            }

            excep = false;
            try {
                field = target.getField("aPrivateInt");
                printFieldInfo(field);
            }
            catch (NoSuchFieldException nsfe) {
                System.out.println("as expected: aPrivateInt not found");
                excep = true;
            }
            if (!excep)
                System.out.println("BUG: got aPrivateInt");


            field = target.getField("constantString");
            printFieldInfo(field);
            String val = (String) field.get(instance);
            System.out.println("  Constant test value is " + val);


            field = target.getField("cantTouchThis");
            printFieldInfo(field);
            intVal = field.getInt(instance);
            System.out.println("  cantTouchThis is " + intVal);
            try {
                field.setInt(instance, 99);
                System.out.println("ERROR: set-final did not throw exception");
            } catch (IllegalAccessException iae) {
                System.out.println("  as expected: set-final throws exception");
            }
            intVal = field.getInt(instance);
            System.out.println("  cantTouchThis is still " + intVal);

            System.out.println("  " + field + " accessible=" + field.isAccessible());
            field.setAccessible(true);
            System.out.println("  " + field + " accessible=" + field.isAccessible());
            field.setInt(instance, 87);     // exercise int version
            intVal = field.getInt(instance);
            System.out.println("  cantTouchThis is now " + intVal);
            field.set(instance, 88);        // exercise Object version
            intVal = field.getInt(instance);
            System.out.println("  cantTouchThis is now " + intVal);

            Constructor<Target> cons;
            Target targ;
            Object[] args;

            cons = target.getConstructor(new Class[] { int.class,float.class });
            args = new Object[] { new Integer(7), new Float(3.3333) };
            System.out.println("cons modifiers=" + cons.getModifiers());
            targ = cons.newInstance(args);
            targ.myMethod(17);

            try {
                Thrower thrower = Thrower.class.newInstance();
                System.out.println("ERROR: Class.newInstance did not throw exception");
            } catch (UnsupportedOperationException uoe) {
                System.out.println("got expected exception for Class.newInstance");
            } catch (Exception e) {
                System.out.println("ERROR: Class.newInstance got unexpected exception: " +
                                   e.getClass().getName());
            }

            try {
                Constructor<Thrower> constructor = Thrower.class.getDeclaredConstructor();
                Thrower thrower = constructor.newInstance();
                System.out.println("ERROR: Constructor.newInstance did not throw exception");
            } catch (InvocationTargetException ite) {
                System.out.println("got expected exception for Constructor.newInstance");
            } catch (Exception e) {
                System.out.println("ERROR: Constructor.newInstance got unexpected exception: " +
                                   e.getClass().getName());
            }

        } catch (Exception ex) {
            System.out.println("----- unexpected exception -----");
            ex.printStackTrace();
        }

        System.out.println("ReflectTest done!");
    }

    public static void checkType() {
        Method m;

        try {
            m = Collections.class.getDeclaredMethod("checkType",
                            Object.class, Class.class);
        } catch (NoSuchMethodException nsme) {
            nsme.printStackTrace();
            return;
        }
        System.out.println(m + " accessible=" + m.isAccessible());
        m.setAccessible(true);
        System.out.println(m + " accessible=" + m.isAccessible());
        try {
            m.invoke(null, new Object(), Object.class);
        } catch (IllegalAccessException iae) {
            iae.printStackTrace();
            return;
        } catch (InvocationTargetException ite) {
            ite.printStackTrace();
            return;
        }

        try {
            String s = "Should be ignored";
            m.invoke(s, new Object(), Object.class);
        } catch (IllegalAccessException iae) {
            iae.printStackTrace();
            return;
        } catch (InvocationTargetException ite) {
            ite.printStackTrace();
            return;
        }

        try {
            System.out.println("checkType invoking null");
            m.invoke(null, new Object(), int.class);
            System.out.println("ERROR: should throw InvocationTargetException");
        } catch (InvocationTargetException ite) {
            System.out.println("checkType got expected exception");
        } catch (IllegalAccessException iae) {
            iae.printStackTrace();
            return;
        }
    }

    public static void checkClinitForFields() throws Exception {
      // Loading a class constant shouldn't run <clinit>.
      System.out.println("calling const-class FieldNoisyInitUser.class");
      Class niuClass = FieldNoisyInitUser.class;
      System.out.println("called const-class FieldNoisyInitUser.class");

      // Getting the declared fields doesn't run <clinit>.
      Field[] fields = niuClass.getDeclaredFields();
      System.out.println("got fields");

      Field field = niuClass.getField("staticField");
      System.out.println("got field");
      field.get(null);
      System.out.println("read field value");

      // FieldNoisyInitUser should now be initialized, but FieldNoisyInit shouldn't be initialized yet.
      FieldNoisyInitUser niu = new FieldNoisyInitUser();
      FieldNoisyInit ni = new FieldNoisyInit();

      System.out.println("");
    }

    public static void checkClinitForMethods() throws Exception {
      // Loading a class constant shouldn't run <clinit>.
      System.out.println("calling const-class MethodNoisyInitUser.class");
      Class niuClass = MethodNoisyInitUser.class;
      System.out.println("called const-class MethodNoisyInitUser.class");

      // Getting the declared methods doesn't run <clinit>.
      Method[] methods = niuClass.getDeclaredMethods();
      System.out.println("got methods");

      Method method = niuClass.getMethod("staticMethod", (Class[]) null);
      System.out.println("got method");
      method.invoke(null);
      System.out.println("invoked method");

      // MethodNoisyInitUser should now be initialized, but MethodNoisyInit shouldn't be initialized yet.
      MethodNoisyInitUser niu = new MethodNoisyInitUser();
      MethodNoisyInit ni = new MethodNoisyInit();

      System.out.println("");
    }


    /*
     * Test some generic type stuff.
     */
    public List<String> dummy;
    public Map<Integer,String> fancyMethod(ArrayList<String> blah) { return null; }
    public static void checkGeneric() {
        Field field;
        try {
            field = Main.class.getField("dummy");
        } catch (NoSuchFieldException nsfe) {
            throw new RuntimeException(nsfe);
        }
        Type listType = field.getGenericType();
        System.out.println("generic field: " + listType);

        Method method;
        try {
            method = Main.class.getMethod("fancyMethod",
                new Class[] { ArrayList.class });
        } catch (NoSuchMethodException nsme) {
            throw new RuntimeException(nsme);
        }
        Type[] parmTypes = method.getGenericParameterTypes();
        Type ret = method.getGenericReturnType();
        System.out.println("generic method " + method.getName() + " params='"
            + stringifyTypeArray(parmTypes) + "' ret='" + ret + "'");

        Constructor ctor;
        try {
            ctor = Main.class.getConstructor(new Class[] { ArrayList.class });
        } catch (NoSuchMethodException nsme) {
            throw new RuntimeException(nsme);
        }
        parmTypes = ctor.getGenericParameterTypes();
        System.out.println("generic ctor " + ctor.getName() + " params='"
            + stringifyTypeArray(parmTypes) + "'");
    }

    /*
     * 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();
    }

    public static void checkUnique() {
        Field field1, field2;
        try {
            field1 = Main.class.getField("dummy");
            field2 = Main.class.getField("dummy");
        } catch (NoSuchFieldException nsfe) {
            throw new RuntimeException(nsfe);
        }
        if (field1 == field2) {
            System.out.println("ERROR: fields shouldn't have reference equality");
        } else {
            System.out.println("fields are unique");
        }
        if (field1.hashCode() == field2.hashCode() && field1.equals(field2)) {
            System.out.println("fields are .equals");
        } else {
            System.out.println("ERROR: fields fail equality");
        }
        Method method1, method2;
        try {
            method1 = Main.class.getMethod("fancyMethod", new Class[] { ArrayList.class });
            method2 = Main.class.getMethod("fancyMethod", new Class[] { ArrayList.class });
        } catch (NoSuchMethodException nsme) {
            throw new RuntimeException(nsme);
        }
        if (method1 == method2) {
            System.out.println("ERROR: methods shouldn't have reference equality");
        } else {
            System.out.println("methods are unique");
        }
        if (method1.hashCode() == method2.hashCode() && method1.equals(method2)) {
            System.out.println("methods are .equals");
        } else {
            System.out.println("ERROR: methods fail equality");
        }
    }

    public static void checkParametrizedTypeEqualsAndHashCode() {
        Method method1;
        Method method2;
        Method method3;
        try {
            method1 = ParametrizedTypeTest.class.getDeclaredMethod("aMethod", Set.class);
            method2 = ParametrizedTypeTest.class.getDeclaredMethod("aMethod", Set.class);
            method3 = ParametrizedTypeTest.class.getDeclaredMethod("aMethodIdentical", Set.class);
        } catch (NoSuchMethodException nsme) {
            throw new RuntimeException(nsme);
        }

        List<Type> types1 = Arrays.asList(method1.getGenericParameterTypes());
        List<Type> types2 = Arrays.asList(method2.getGenericParameterTypes());
        List<Type> types3 = Arrays.asList(method3.getGenericParameterTypes());

        Type type1 = types1.get(0);
        Type type2 = types2.get(0);
        Type type3 = types3.get(0);

        if (type1 instanceof ParameterizedType) {
            System.out.println("type1 is a ParameterizedType");
        }
        if (type2 instanceof ParameterizedType) {
            System.out.println("type2 is a ParameterizedType");
        }
        if (type3 instanceof ParameterizedType) {
            System.out.println("type3 is a ParameterizedType");
        }

        if (type1.equals(type2)) {
            System.out.println("type1("+type1+") equals type2("+type2+")");
        } else {
            System.out.println("type1("+type1+") does not equal type2("+type2+")");
        }

        if (type1.equals(type3)) {
            System.out.println("type1("+type1+") equals type3("+type3+")");
        } else {
            System.out.println("type1("+type1+") does not equal type3("+type3+")");
        }
        if (type1.hashCode() == type2.hashCode()) {
            System.out.println("type1("+type1+") hashCode equals type2("+type2+") hashCode");
        } else {
            System.out.println(
                   "type1("+type1+") hashCode does not equal type2("+type2+") hashCode");
        }

        if (type1.hashCode() == type3.hashCode()) {
            System.out.println("type1("+type1+") hashCode equals type3("+type3+") hashCode");
        } else {
            System.out.println(
                    "type1("+type1+") hashCode does not equal type3("+type3+") hashCode");
        }
    }

    public static void checkGenericArrayTypeEqualsAndHashCode() {
        Method method1;
        Method method2;
        Method method3;
        try {
            method1 = GenericArrayTypeTest.class.getDeclaredMethod("aMethod", Object[].class);
            method2 = GenericArrayTypeTest.class.getDeclaredMethod("aMethod", Object[].class);
            method3 = GenericArrayTypeTest.class.getDeclaredMethod("aMethodIdentical", Object[].class);
        } catch (NoSuchMethodException nsme) {
            throw new RuntimeException(nsme);
        }

        List<Type> types1 = Arrays.asList(method1.getGenericParameterTypes());
        List<Type> types2 = Arrays.asList(method2.getGenericParameterTypes());
        List<Type> types3 = Arrays.asList(method3.getGenericParameterTypes());

        Type type1 = types1.get(0);
        Type type2 = types2.get(0);
        Type type3 = types3.get(0);

        if (type1 instanceof GenericArrayType) {
            System.out.println("type1 is a GenericArrayType");
        }
        if (type2 instanceof GenericArrayType) {
            System.out.println("type2 is a GenericArrayType");
        }
        if (type3 instanceof GenericArrayType) {
            System.out.println("type3 is a GenericArrayType");
        }

        if (type1.equals(type2)) {
            System.out.println("type1("+type1+") equals type2("+type2+")");
        } else {
            System.out.println("type1("+type1+") does not equal type2("+type2+")");
        }

        if (type1.equals(type3)) {
            System.out.println("type1("+type1+") equals type3("+type3+")");
        } else {
            System.out.println("type1("+type1+") does not equal type3("+type3+")");
        }
        if (type1.hashCode() == type2.hashCode()) {
            System.out.println("type1("+type1+") hashCode equals type2("+type2+") hashCode");
        } else {
            System.out.println(
                   "type1("+type1+") hashCode does not equal type2("+type2+") hashCode");
        }

        if (type1.hashCode() == type3.hashCode()) {
            System.out.println("type1("+type1+") hashCode equals type3("+type3+") hashCode");
        } else {
            System.out.println(
                    "type1("+type1+") hashCode does not equal type3("+type3+") hashCode");
        }
    }

    public static void main(String[] args) throws Exception {
        Main test = new Main();
        test.run();

        checkAccess();
        checkType();
        checkClinitForFields();
        checkClinitForMethods();
        checkGeneric();
        checkUnique();
        checkParametrizedTypeEqualsAndHashCode();
        checkGenericArrayTypeEqualsAndHashCode();
    }
}


class SuperTarget {
    public SuperTarget() {
        System.out.println("SuperTarget constructor ()V");
        superInt = 1010101;
        superClassInt = 1010102;
    }

    public int myMethod(float floatArg) {
        System.out.println("myMethod (F)I " + floatArg);
        return 6;
    }

    public int superInt;
    public static int superClassInt;
}

class Target extends SuperTarget {
    public Target() {
        System.out.println("Target constructor ()V");
    }

    public Target(int ii, float ff) {
        System.out.println("Target constructor (IF)V : ii="
            + ii + " ff=" + ff);
        anInt = ii;
    }

    public int myMethod(int intarg) throws NullPointerException, IOException {
        System.out.println("myMethod (I)I");
        System.out.println(" arg=" + intarg + " anInt=" + anInt);
        return 5;
    }

    public int myMethod(String[] strarg, float f, char c) {
        System.out.println("myMethod: " + strarg[0] + " " + f + " " + c + " !");
        return 7;
    }

    public static void myNoargMethod() {
        System.out.println("myNoargMethod ()V");
    }

    public void throwingMethod() {
        System.out.println("throwingMethod");
        throw new NullPointerException("gratuitous throw!");
    }

    public void misc() {
        System.out.println("misc");
    }

    public int anInt;
    public String string1 = "hey";
    public String string2 = "yo";
    public String string3 = "there";
    private String string4 = "naughty";
    public static final String constantString = "a constant string";
    private int aPrivateInt;

    public final int cantTouchThis = 77;

    public long pubLong = 0x1122334455667788L;

    public static double staticDouble = 3.3;
}

class FieldNoisyInit {
  static {
    System.out.println("FieldNoisyInit is initializing");
    //Throwable th = new Throwable();
    //th.printStackTrace();
  }
}

class FieldNoisyInitUser {
  static {
    System.out.println("FieldNoisyInitUser is initializing");
  }
  public static int staticField;
  public static FieldNoisyInit noisy;
}

class MethodNoisyInit {
  static {
    System.out.println("MethodNoisyInit is initializing");
    //Throwable th = new Throwable();
    //th.printStackTrace();
  }
}

class MethodNoisyInitUser {
  static {
    System.out.println("MethodNoisyInitUser is initializing");
  }
  public static void staticMethod() {}
  public void createMethodNoisyInit(MethodNoisyInit ni) {}
}

class Thrower {
  public Thrower() throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }
}

class ParametrizedTypeTest {
    public void aMethod(Set<String> names) {}
    public void aMethodIdentical(Set<String> names) {}
}

class GenericArrayTypeTest<T> {
    public void aMethod(T[] names) {}
    public void aMethodIdentical(T[] names) {}
}