/**

Copyright (c) 1999-2001 tagtraum industries.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. All advertising materials mentioning features or use of this software must
   display the following acknowledgment: "This product includes software
   developed by tagtraum industries (http://www.tagtraum.com/)"

4. The names "tagtraum industries" and "jo!" must not be used to endorse or
   promote products derived from this software without prior written
   permission.

5. Products derived from this software may not be called "jo!" nor
   may "jo!" nor "tagtraum industries" appear in their names without prior
   written permission.

6. Redistributions of any form whatsoever must retain the following
   acknowledgment: "This product includes software developed by tagtraum
   industries (http://www.tagtraum.com/)"

7. This release contains software by Sun Microsystems. Therefore
   the following conditions have to be met, too. They apply to the
   files lib/mail.jar, lib/activation.jar, lib/jsse.jar, lib/jcert.jar
   jaxp.jar, parser.jar and lib/jnet.jar contained in this release.

7.a Licensee may not modify the Java Platform
   Interface (JPI, identified as classes contained within the javax
   package or any subpackages of the javax package), by creating additional
   classes within the JPI or otherwise causing the addition to or modification
   of the classes in the JPI.  In the event that Licensee creates any 
   Java-related API and distribute such API to others for applet or
   application development, you must promptly publish broadly, an accurate 
   specification for such API for free use by all developers of Java-based 
   software.  

7.b Software is confidential copyrighted information of Sun and 
   title to all copies is retained by Sun and/or its licensors.  Licensee 
   shall not modify, decompile, disassemble, decrypt, extract, or otherwise 
   reverse engineer Software.  Software may not be leased, assigned, or 
   sublicensed, in whole or in part.  Software is not designed or intended 
   for use in on-line control of aircraft, air traffic, aircraft navigation 
   or aircraft communications; or in the design, construction, operation or 
   maintenance of any nuclear facility.  Licensee warrants that it will not 
   use or redistribute the Software for such purposes.

7.c Software is provided "AS IS," without a warranty 
   of any kind. ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES, 
   INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A 
   PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.  

7.d This License is effective until terminated.  Licensee may 
   terminate this License at any time by destroying all copies of Software.  
   This License will terminate immediately without notice from Sun if Licensee
   fails to comply with any provision of this License.  Upon such termination,
   Licensee must destroy all copies of Software.

7.e Software, including technical data, is subject to U.S.
   export control laws, including the U.S.  Export Administration Act and its
   associated regulations, and may be subject to export or import regulations
   in other countries.  Licensee agrees to comply strictly with all such 
   regulations and acknowledges that it has the responsibility to obtain
   licenses to export, re-export, or import Software.  Software may not be 
   downloaded, or otherwise exported or re-exported (i) into, or to a national
   or resident of, Cuba, Iraq, Iran, North Korea, Libya, Sudan, Syria or any 
   country to which the U.S. has embargoed goods; or (ii) to anyone on the 
   U.S. Treasury Department's list of Specially Designated Nations or the U.S.
   Commerce Department's Table of Denial Orders.

8. This software contains the DOM Level 1 interfaces by W3C. The following
   copyright applies to the classes org.w3c.dom.*:

   Copyright (c) World Wide Web Consortium , (Massachusetts Institute of
   Technology , Institut National de Recherche en Informatique et en
   Automatique , Keio University ). All Rights Reserved.

   You can get the DOM Level 1 document and source code of DOM interfaces
   from http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001/

9. This product includes code licensed from RSA Data Security.

10.This Agreement is governed by the laws of the Federal Republic of Germany. 
   Choice of law rules of any jurisdiction shall not apply.
   tagtraum industries reserves the right to choose a German court any action
   shall be brought to.

THIS SOFTWARE IS PROVIDED BY TAGTRAUM INDUSTRIES "AS IS" AND ANY EXPRESSED
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
EVENT SHALL TAGTRAUM INDUSTRIES OR HENDRIK SCHREIBER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

We encourage your feedback and suggestions and want to use your feedback to
improve the Software. Send all such feedback to:
feedback@tagtraum.com

For more information on tagtraum industries, Hendrik Schreiber and jo!
please see <http://www.tagtraum.com/>.

*/


package com.tagtraum.framework.util;

import java.lang.reflect.*;
import java.util.*;
import java.beans.*;


/**
 * Räumt mit Unzulänglichkeiten des Reflect-API auf.
 * <br>
 * Ermöglicht das Finden von Methoden und Konstruktoren, obwohl die
 * genaue Signatur (Methodenname bekannt, Parametertypes aber nicht)
 * nicht bekannt ist.
 * <br>Gibt außerdem Felder zurück, die in Oberklassen definiert sind.
 * 
 * @author Hendrik Schreiber
 * @version $Id: ReflectHelper.java,v 1.18 2001/01/21 17:14:26 Hendrik Exp $
 */
public class ReflectHelper {

    /**
     * Source-Version
     */
    public static String	vcid = 
	"$Id: ReflectHelper.java,v 1.18 2001/01/21 17:14:26 Hendrik Exp $";

    /**
     * Cache für WriteMethoden von Beans.
     */
    public static Hashtable writeMethodsCache = new Hashtable();

    /**
     * Cache für ReadMethoden von Beans.
     */
    public static Hashtable readMethodsCache = new Hashtable();


    /**
     * Cache für Methoden-Objekte.
     */
    public static Hashtable     myMethodCache = new Hashtable();

    /**
     * Cache für Konstruktor-Objekte.
     */
    public static Hashtable     myConstructorCache = new Hashtable();

    /**
     * Tabelle, die für einen Primitiv-Typ ein Klassen-Objekt zurückgeben kann.
     */
    public static Hashtable     myPrimitiveTable = new Hashtable();

    /**
     * Table that returns a primitive class for a name.
     */
    public static HashMap     myPrimitiveClassTable = new HashMap();

    // Mapping Type => Class
    static {
        myPrimitiveTable.put(Short.TYPE, Short.class);
        myPrimitiveTable.put(Integer.TYPE, Integer.class);
        myPrimitiveTable.put(Long.TYPE, Long.class);
        myPrimitiveTable.put(Float.TYPE, Float.class);
        myPrimitiveTable.put(Double.TYPE, Double.class);
        myPrimitiveTable.put(Byte.TYPE, Byte.class);
        myPrimitiveTable.put(Character.TYPE, Character.class);
        myPrimitiveTable.put(Boolean.TYPE, Boolean.class);

        myPrimitiveClassTable.put(Short.TYPE.getName(), Short.TYPE);
        myPrimitiveClassTable.put(Integer.TYPE.getName(), Integer.TYPE);
        myPrimitiveClassTable.put(Long.TYPE.getName(), Long.TYPE);
        myPrimitiveClassTable.put(Float.TYPE.getName(), Float.TYPE);
        myPrimitiveClassTable.put(Double.TYPE.getName(), Double.TYPE);
        myPrimitiveClassTable.put(Byte.TYPE.getName(), Byte.TYPE);
        myPrimitiveClassTable.put(Character.TYPE.getName(), Character.TYPE);
        myPrimitiveClassTable.put(Boolean.TYPE.getName(), Boolean.TYPE);
    } 

    /**
     * Returns a class for a name, including primitive types.
     */
    public static Class getClassForName(String name) throws ClassNotFoundException {
        return getClassForName(name, ReflectHelper.class.getClassLoader());
    }

    /**
     * Returns a class for a name, including primitive types.
     */
    public static Class getClassForName(String name, ClassLoader loader) throws ClassNotFoundException {
        Class klass = (Class)myPrimitiveClassTable.get(name);
        if (klass == null) klass = loader.loadClass(name);
        return klass;
    }


    /**
     * Besorge das Feld mit dem angegebenen Namen. Falls
     * dies nicht in der Klasse gefunden wird, suche in
     * den Oberklassen bis Object.
     * 
     * @param aClass Die Klasse des Felds
     * @exception SecurityException Auf das Feld durfte nicht zugegriffen werden
     * @exception NoSuchFieldException Das Feld ist in der Klasse nicht vorhanden
     */
    public final static Field getField(Class aClass, String field) 
	    throws SecurityException, NoSuchFieldException {
	Field   theField = null;

	try {
	    return aClass.getDeclaredField(field);
	} catch (NoSuchFieldException e) {
	    if (aClass.getSuperclass() != null) {
		return getField(aClass.getSuperclass(), field);
	    } 

	    throw new NoSuchFieldException(field);
	} 
    } 


    /**
     * Diese Methode gibt die spezifischste Methode zurück. Sollten zwei oder mehr
     * Methoden gleich spezifisch sein, wird entgegen des Verhaltens des
     * Java-Compilers keine Fehlermeldung zurückgegeben.
     * 
     * @param aClass die Klasse, deren Methode gefragt ist.
     * @param aMethodname der gefragte Methodenname (ohne Signatur)
     * @param aParameterTypes die Signatur der Methode
     * @return die spezifischste Methode
     * @exception SecurityException
     * @exception NoSuchMethodException
     */
    public final static Method getMethod(Class aClass, String aMethodname, Class[] aParameterTypes) 
	    throws SecurityException, NoSuchMethodException {
	Method  theMethod = null;

	try {
	    theMethod = aClass.getMethod(aMethodname, aParameterTypes);
	} catch (NoSuchMethodException nsme) {
	    if (aParameterTypes == null) {
		aParameterTypes = new Class[0];
	    } 

	    StringBuffer	methodKey = new StringBuffer(256);

	    methodKey.append(aClass.getName());
	    methodKey.append('#');
	    methodKey.append(aMethodname);

	    for (int i = 0; i < aParameterTypes.length; i++) {
		methodKey.append(',');

		if (aParameterTypes[i] != null) {
		    methodKey.append(aParameterTypes[i].getName());
		} 
	    } 

	    if ((theMethod = (Method)myMethodCache.get(methodKey.toString())) != null) {
		return theMethod;
	    } 

	    theMethod = getMostSpecificMethod(aClass, aMethodname, aParameterTypes);

	    if (theMethod == null) {
		throw new NoSuchMethodException("Method " + aMethodname + "(" 
						+ getTypesString(aParameterTypes) 
						+ ") is not a member of class " + aClass.getName());
	    } 

	    myMethodCache.put(methodKey.toString(), theMethod);
	} 

	return theMethod;
    } 

    /**
     * Gibt die spezifischste Methode zurück.
     * 
     * @param aClass die Klasse, deren Methode gefragt ist.
     * @param methodname der gefragte Methodenname (ohne Signatur)
     * @param aParameterTypes die Signatur der Methode
     * @return die spezifischste Methode
     * @exception SecurityException
     */
    protected final static Method getMostSpecificMethod(Class aClass, String methodname, 
	    Class[] aParameterTypes) throws SecurityException {
	boolean		methodFound = false;
	Class[]		bestParameters = null;
	Method		bestMethod = null;
	// hole alle Methoden
	Method[]	methods = aClass.getMethods();

	// finde die Beste!
	for (int i = 0; i < methods.length; i++) {
	    if (methods[i].getName().equals(methodname)) {
		Class[] aMethodsParameterTypes = methods[i].getParameterTypes();

		if (aMethodsParameterTypes.length == aParameterTypes.length) {
		    if (isAssignable(aMethodsParameterTypes, aParameterTypes)) {
			if (!methodFound) {
			    methodFound = true;
			    // initialisiere bestParameters
			    bestParameters = new Class[aParameterTypes.length];

			    for (int j = 0; j < aParameterTypes.length; j++) {
				bestParameters[j] = Object.class;
			    } 
			} 

			if (isAssignable(bestParameters, aMethodsParameterTypes)) {
			    bestParameters = aMethodsParameterTypes;
			    bestMethod = methods[i];
			} 
		    } 
		} 
	    } 
	} 

	return bestMethod;
    } 

    /**
     * Diese Methode gibt den spezifischsten Konstruktor zurück. Sollten zwei oder mehr
     * Konstruktoren gleich spezifisch sein, wird entgegen des Verhaltens des
     * Java-Compilers keine Fehlermeldung zurückgegeben.
     * 
     * @param aClass die Klasse, deren Konstruktor gefragt ist.
     * @param aParameterTypes die Signatur der Methode
     * @exception SecurityException
     * @exception NoSuchMethodException
     * @return der spezifischste Konstruktor
     */
    // - welche Methode nun tatsaechlich zurückgegeben wird, müßte man also noch austesten.(rik)
    public final static Constructor getConstructor(Class aClass, 
	    Class[] aParameterTypes) throws SecurityException, NoSuchMethodException {
	Constructor     theConstructor = null;

	try {
	    theConstructor = aClass.getConstructor(aParameterTypes);
	} catch (NoSuchMethodException nsme) {
	    if (aParameterTypes == null) {
		aParameterTypes = new Class[0];
	    } 

	    StringBuffer	constrKey = new StringBuffer(256);

	    constrKey.append(aClass.getName());

	    for (int i = 0; i < aParameterTypes.length; i++) {
		constrKey.append(',');

		if (aParameterTypes[i] != null) {
		    constrKey.append(aParameterTypes[i].getName());
		} 
	    } 

	    if ((theConstructor = (Constructor)myConstructorCache.get(constrKey.toString())) 
		    != null) {
		return theConstructor;
	    } 

	    theConstructor = getMostSpecificConstructor(aClass, aParameterTypes);

	    if (theConstructor == null) {
		throw new NoSuchMethodException("Constructor " + aClass.getName() + "(" 
						+ getTypesString(aParameterTypes) 
						+ ") does not exist.");
	    } 

	    myConstructorCache.put(constrKey.toString(), theConstructor);
	} 

	return theConstructor;
    } 

    /**
     * Gibt den spezifischste Konstruktor zurück.
     * 
     * @param aClass die Klasse, deren Methode gefragt ist.
     * @param aParameterTypes die Signatur der Methode
     * @return der spezifischste Konstruktor
     * @exception SecurityException
     */
    protected final static Constructor getMostSpecificConstructor(Class aClass, 
	    Class[] aParameterTypes) throws SecurityException {
	boolean		constructorFound = false;
	Class[]		bestParameters = null;
	Constructor     bestConstructor = null;
	// hole alle Methoden
	Constructor[]   constructors = aClass.getConstructors();

	// finde die Beste!
	for (int i = 0; i < constructors.length; i++) {
	    Class[]     aConstructorsParameterTypes = constructors[i].getParameterTypes();

	    if (aConstructorsParameterTypes.length == aParameterTypes.length) {
		if (isAssignable(aConstructorsParameterTypes, aParameterTypes)) {
		    if (!constructorFound) {
			constructorFound = true;
			// initialisiere bestParameters
			bestParameters = new Class[aParameterTypes.length];

			for (int j = 0; j < aParameterTypes.length; j++) {
			    bestParameters[j] = Object.class;
			} 
		    } 

		    if (isAssignable(bestParameters, aConstructorsParameterTypes)) {
			bestParameters = aConstructorsParameterTypes;
			bestConstructor = constructors[i];
		    } 
		} 
	    } 
	} 

	return bestConstructor;
    } 

    /**
     * Testet, ob ein Array von ParameterTypen in einen anderen gecastet werden kann.
     * 
     * @param fromTypes ClassArray, der gecastet werden soll
     * @param toTypes ClassArray, zu dem gecastet werden soll
     * @return boolean, ob erfolgreich oder nicht.
     */
    public final static boolean isAssignable(Class[] toTypes, Class[] fromTypes) {
	boolean castable = true;

	for (int i = 0; castable && i < toTypes.length; i++) {
	    if (fromTypes[i] != null) {
		if (!(toTypes[i].isAssignableFrom(fromTypes[i]))) {
		    if (!(getPrimitiveType(toTypes[i]).isAssignableFrom(getPrimitiveType(fromTypes[i])))) {
			castable = false;
		    } 
		} 
	    } 
	} 

	return castable;
    } 

    /**
     * Gibt, falls möglich, den Primitiv-Typ der Klasse zurück.
     * @param aClass die Ausgangsklasse
     * @return der Primitiv-Typ, fall vorhanden, sonst
     * das ursprüngliche Klassenobject
     */
    public final static Class getPrimitiveType(Class aClass) {
	if (myPrimitiveTable.containsKey(aClass)) {
	    return (Class)myPrimitiveTable.get(aClass);
	} 

	return aClass;
    } 

    /**
     * Gibt einen String für einen Array von Classes zurück.
     */
    protected final static String getTypesString(Class[] aTypes) {
	StringBuffer    sb = new StringBuffer();

	for (int i = 0; i < aTypes.length; i++) {
	    if (aTypes[i] != null) {
		sb.append(aTypes[i].toString());
	    } else {
		sb.append("null");
	    } 

	    if (i + 1 < aTypes.length) {
		sb.append(", ");
	    } 
	} 

	return sb.toString();
    } 


    /**
     * Gibt die Setter einer Klasse zurueck.
     * Alle Setter werden aus Performancegruenden gecachet.
     * Methoden der Klasse Object werden ignoriert.
     *
     * @param beanClass Klasse
     * @return HashMap mit den set-Methoden-Objekten der uebergebenen Klasse
     */
    public final static HashMap getWriteMethods(Class beanClass) {
        HashMap writeMethods = (HashMap)writeMethodsCache.get(beanClass);
        if (writeMethods == null) {
            try {
                writeMethods = new HashMap();
                BeanInfo info = Introspector.getBeanInfo(beanClass, Object.class);
                PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
                writeMethods = new HashMap();
                for (int i=0; i<descriptors.length; i++) {
                    Method method = descriptors[i].getWriteMethod();
                    if (method != null) {
                        writeMethods.put(descriptors[i].getName(), method);
                    }
                }
                writeMethodsCache.put(beanClass, writeMethods);
            }
            catch (IntrospectionException ie) {
                // sollte eigentlich nicht vorkommen...
                ie.printStackTrace();
                throw new RuntimeException(ie.toString());
            }
        }
        return writeMethods;
    }
    
    /**
     * Gibt die Getter einer Klasse zurueck.
     * Alle Getter werden aus Performancegruenden gecachet.
     * Methoden der Klasse Object werden ignoriert.
     *
     * @param beanClass Klasse
     * @return HashMap mit den get-Methoden-Objekten der uebergebenen Klasse
     */
    public final static HashMap getReadMethods(Class beanClass) {
        HashMap readMethods = (HashMap)readMethodsCache.get(beanClass);
        if (readMethods == null) {
            try {
                readMethods = new HashMap();
                BeanInfo info = Introspector.getBeanInfo(beanClass, Object.class);
                PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
                readMethods = new HashMap();
                for (int i=0; i<descriptors.length; i++) {
                    Method method = descriptors[i].getReadMethod();
                    if (method != null) {
                        readMethods.put(descriptors[i].getName(), method);
                    }
                }
                readMethodsCache.put(beanClass, readMethods);
            }
            catch (IntrospectionException ie) {
                // sollte eigentlich nicht vorkommen...
                ie.printStackTrace();
                throw new RuntimeException(ie.toString());
            }
        }
        return readMethods;
    }

}