// Copyright (c) 1996-2002 Brian D. Carlstrom

package bdc.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

public final class ClassUtil
{
    /**
        Make sure a class' static inits have run. This method loads
        the specified class to make sure that it's static methods have
        run.

        @param name the name of the class to load.
       
    */
    public static void classTouch (String name)
    {
       classForName(name, false);
    }

/**
        Find a Class for the specified class name. A warning will be
        printed if the class was not found.

        @param className the name of the class to find

        @return the Class for the given <B>className</B>, or null if
        the Class doesn't exist.

        @see #newInstance
        
    */
    public static Class classForName (String className)
    {
        return classForName(className, true);
    }

    private static final GrowOnlyHashtable ClassForNameCache =
        new GrowOnlyHashtable();

    private static final Object NoCachedClassFound = Constants.NullObject;

    private static final GrowOnlyHashtable LocaleCache =
        new GrowOnlyHashtable();
    
    /**
        Find a Class for the specified class name. Prints a warning
        message if the class can't be found and <B>warning</B> is
        true.

        @param className the name of the class to find
        @param warning if <b>true</b> and the class can not be found,
        a warning will be printed

        @return the Class for the given <B>className</B>, or null if
        the Class doesn't exist.

        @see #newInstance
    */
    public static Class classForName (String className, boolean warning)
    {
        if (className == null) {
            return null;
        }

        Object cachedClass = ClassForNameCache.get(className);
        if (cachedClass == NoCachedClassFound) {
            if (warning) {
                Log.util.log(Level.WARNING,
                             "class name {0} not found in classForName",
                             className);
            }
            return null;
        }
        if (cachedClass != null) {
                // any cached value other than noCachedClassFound is a
                // class that can be returned
            return (Class)cachedClass;
        }

            // if it was not found in the cache, check for the real
            // class object
        try {
            Class classObj = Class.forName(className); // OK
                // save the value
            ClassForNameCache.put(className, classObj);
            return(classObj);
        }
        catch (ClassNotFoundException e) {
                // if there was an exception store that object was not
                // found and call again. (This lets there be a single path
                // for the log error message.)
            ClassForNameCache.put(className, NoCachedClassFound);
            return classForName(className, warning);
        }
        catch (NoClassDefFoundError e) {
                // Supposed you as for class foo.bar.Bazqux, and on NT
                // it finds the class file foo/bar/BazQux.class. On
                // JDK11, this was just a ClassNotFoundException. On
                // JDK12, this throws a NoClassDefFoundError. We
                // always report the error in this case, because
                // someone probing for the class probably really wants
                // to know about the typo.
            ClassForNameCache.put(className, NoCachedClassFound);
            return classForName(className, true);
        }
    }
    
    /**
        Find a Class for the specified class name. Throws an exception
        if the class is not found.
        <p>
        The java spec does not define behavior with a null
        className. If a null is passed in a ClassNotFoundException
        will be thrown.
        
        @param className the name of the class to find

        @return the Class for the given <B>className</B>

        @exception ClassNotFoundException if the class can not be
        found.

        @see #newInstance
    */
    public static Class classForNameWithException (String className)
      throws ClassNotFoundException
    {
        Class returnVal = classForName(className, false);
        if (returnVal == null) {
            throw new ClassNotFoundException(Fmt.S("Could not find class %s",
                                                   className));
        }
        return returnVal;
    }

    /**
        Check if an object is an instance of a class identified by
        it's name.

        @param object the object to test the instance of
        @param className the name of the class being tested for

        @return true if <B>object</B> is an instance the class
        specified by <B>className</B>, including subclasses;
        <b>false</b> otherwise
    */
    public static boolean instanceOf (Object object, String className)
    {
        return instanceOf(object.getClass(), classForName(className));
    }

    /**
        Check if one class inherits from another class.

        @param instance the class to check the inheritance tree of
        @param target the class to check against

        @return <b>true</b> if the class <B>instance</B> is, or
        inherits from, the class <B>target</B>; <b>false</b> otherwise
    */
    public static boolean instanceOf (Class instance, Class target)
    {
        return (target.isAssignableFrom(instance));
    }

    /**
        Returns the name of the class of the specified object. It is
        shorthand for o.getClass().getName()

        @param o the object to find the class name of
        @return the name as returned by o.getClass().getName()
    */
    public static String getClassNameOfObject (Object o)
    {
        if (o == null) {
            return "null";
        }
        return o.getClass().getName();
    }

    /**
        Creates a new instance of the specified class. If the class
        <B>className</B> is derived from BaseObject and you want the
        new instance to be properly initialized, you should call
        BaseObject.New() instead. If the class can not be found a
        warning will be printed.

        @param className the name of class to create a new instance of

        @return a new instance of the class specified by
        <B>className</B>. If the class can not be found, <b>null</b>
        will be returned.

        @see #classForName
    */
    public static Object newInstance (String className)
    {
        return newInstance(className, true);
    }

    /**
        Creates a new instance of the specified class. If the class
        <B>className</B> is derived from BaseObject and you want the
        new instance to be properly initialized, you should call
        BaseObject.New() instead. If the class can not be found an
        optional warning may be printed.

        @param className the name of class to create a new instance of
        @param warning if <b>true</b> and the class can not be found,
        a warning will be printed

        @return a new instance of the class specified by
        <B>className</B>. If the class can not be found, <b>null</b>
        will be returned.

        @see #classForName
    */
    public static Object newInstance (String className, boolean warning)
    {
        return newInstance(classForName(className, warning));
    }


    /**
        Creates a new Instance of the specified class with error
        checking.

        Use this when the new object created should be a subclass of
        some known class.

        @param classname the class to create a new instance of
        @param supposedSuperclassName The name of the required
        superclass for the new class
        @param warning if <b>true</b> and the class can not be found,
        a warning will be printed

        @return a new instance of the class <b>clazz</b>. If there
        is an error, <b>null</b> will be returned.
    */
    public static Object newInstance (String className,
                                      String supposedSuperclassName,
                                      boolean warning)
    {
        Object obj = newInstance(classForName(className, warning));

        if (obj != null) {
            if (instanceOf(obj, supposedSuperclassName)) {
                return obj;
            }
            else {
                Log.util.log(
                    Level.WARNING,
                    "ClassUtil.newInstance: " +
                    "className={0} is not an instance of supposedSuperclassName={1}",
                    new Object[] {className, supposedSuperclassName}
                    );
                return null;
            }
        }
        else {
            return null;
        }
    }

    /**
        Creates a new Instance of the specified class with error
        checking. 

        Use this when the new object created should be a subclass of
        some known class. Prints an error message if the class cannot
        be created.

        @param classname the class to create a new instance of
        @param supposedSuperclassName The name of the required
        superclass for the new class

        @return a new instance of the class <b>clazz</b>. If there
        is an error, <b>null</b> will be returned.
    */
    public static Object newInstance (String className,
                                      String supposedSuperclassName)
    {
        return newInstance(className, supposedSuperclassName, true);
    }

    /**
        Creates a new instance of the specified class.

        If the class <B>clazz</B> is derived from BaseObject and
        you want the new instance to be properly initialized, you
        should call BaseObject.New() instead. If the instance can not
        be created, a warning will be printed.

        @param clazz the class to create a new instance of

        @return a new instance of the class <b>clazz</b>. If there
        is an error, <b>null</b> will be returned.

        @see #classForName
    */
    public static Object newInstance (Class clazz)
    {
        if (clazz == null) {
            return null;
        }
        try {
            return clazz.newInstance();
        }
        catch (InstantiationException e) {
            Log.util.log(
                Level.WARNING,
                "class name {0} not instantiable via ClassUtil.newInstance(): {1}",
                new Object[] {clazz.getName(), e}
                );
        }
        catch (IllegalAccessException e) {
            Log.util.log(
                Level.WARNING,
                "class name {0} not accessable via ClassUtil.newInstance(): {1}",
                new Object[] {clazz.getName(), e}
                );
        }
        return null;
    }

    /**
        Strips any package specifiers from the given class name. For
        instance, "java.util.List" will become "List".

        @param className class name to strip

        @return the class name without the package prefix
    */
    public static String stripPackageFromClassName (String className)
    {
        int pos = className.lastIndexOf('.');
        if (pos > 0) {
            return className.substring(pos + 1);
        }
        return className;
    }

    /**
        Find the package specifier for a given class name. For
        instance, "java.util.List" will become "java.util".

        @param className class name to strip

        @return the package specifier for the given <B>className</B>
    */
    public static String stripClassFromClassName (String className)
    {
        int pos = className.lastIndexOf('.');
        if (pos > 0) {
            return className.substring(0, pos);
        }
        return "";
    }

    /**
        Invokes the specified static method of the specified class. If
        thrown, NoSuchMethodException, ClassNotFoundException,
        InvocationTargetException, and IllegalAccessException are
        silently caught and null is returned.

        @param className the name of the class to invoke the static
        method on
        @param methodName the name of the method to call

        @return the result of the method invocation, or if there was
        an exception while trying to find or invoke the method, null
        is returned.

        @see java.lang.reflect.Method#invoke
    */
    public static Object invokeStaticMethod (String   className,
                                             String   methodName)
    {
        try {
            Class c = ClassUtil.classForName(className);
            if (c != null) {
                Method m = c.getMethod(methodName, null);
                return m.invoke(null, null);
            }
        }
        catch (NoSuchMethodException e) {
        }
        catch (InvocationTargetException e) {
        }
        catch (IllegalAccessException e) {
        }
        return null;
    }

    /**
        Invokes the specified static method of the specified class. 

        @param className the name of the class to invoke the static
        method on
        @param methodName the name of the method to call
        @param paramTypes an array of Class types that *exactly* match
        the signature of the method being invoked.
        @param args an array of Object arguments to the method

        @return the result of the method invocation, or if there was
        an exception while trying to find or invoke the method, null
        is returned.

        @see java.lang.reflect.Method#invoke
    */
    public static Object invokeStaticMethod (String   className,
                                             String   methodName,
                                             Class[]  paramTypes,
                                             Object[] args)
    {
        try {
            Class c = ClassUtil.classForName(className);
            if (c != null) {
                Method m = c.getMethod(methodName, paramTypes);
                return m.invoke(null, args);
            }
        }
        catch (NoSuchMethodException e) {
            Assert.that(false, "NoSuchMethod :%s", SystemUtil.stackTrace(e));
        }
        catch (InvocationTargetException e) {
            Assert.that(false, "InvocationTargetException :%s", SystemUtil.stackTrace(e));
        }
        catch (IllegalAccessException e) {
            Assert.that(false, "IllegalAccessException :%s", SystemUtil.stackTrace(e));
        }
        return null;
    }

    
    private static final Map typeToVMMap = new HashMap();
    static {
        typeToVMMap.put(Constants.CharPrimitiveType,
                        Constants.JavaCharAbbreviation);
        typeToVMMap.put(Constants.BytePrimitiveType,
                        Constants.JavaByteAbbreviation);
        typeToVMMap.put(Constants.ShortPrimitiveType,
                        Constants.JavaShortAbbreviation);
        typeToVMMap.put(Constants.IntPrimitiveType,
                        Constants.JavaIntAbbreviation);
        typeToVMMap.put(Constants.LongPrimitiveType,
                        Constants.JavaLongAbbreviation);
        typeToVMMap.put(Constants.FloatPrimitiveType,
                        Constants.JavaFloatAbbreviation);
        typeToVMMap.put(Constants.DoublePrimitiveType,
                        Constants.JavaDoubleAbbreviation);
        typeToVMMap.put(Constants.BooleanPrimitiveType,
                        Constants.JavaBooleanAbbreviation);
    }
    /**
        Convert from a class name into an internal java representation
        of that class String

        int -> I
        java.lang.String -> Ljava.lang.String;
    */
    public static String typeToVMType (String type)
    {
        String abbrev = (String)typeToVMMap.get(type);
        if (abbrev != null) {
            return abbrev;
        }
        return Fmt.S("L%s;", type);
    }

    /**
        prevent people from creating this class
    */
    private ClassUtil ()
    {
    }
}
