package java.beans;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.TooManyListenersException;

/*
 * Java core library component.
 *
 * Copyright (c) 1997, 1998
 *      Transvirtual Technologies, Inc.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file.
 */
public class Introspector
{
	private static String[] beansearch = { "kaffe.beans.infos" };

public static String decapitalize(String name)
	{
	if (name.length() > 2 && Character.isUpperCase(name.charAt(0)) && Character.isUpperCase(name.charAt(1))) {
		return (name);
	}
	else {
		return (Character.toLowerCase(name.charAt(0)) + name.substring(1));
	}
}

public static BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException
{
	return (getBeanInfo(beanClass, null));
}

public static BeanInfo getBeanInfo(Class beanClass, Class stopClass) throws IntrospectionException
{
	BeanInfo bean = loadBeanInfo(beanClass);
	if (bean != null) {
		return (bean);
	}

	// Okay, we must introspect

	if (stopClass != null) {
		stopClass = stopClass.getSuperclass();
	}
	if (stopClass != null && !beanClass.isInstance(stopClass)) {
		throw new IntrospectionException("Illegal stop class");
	}

	GenericBeanInfo gbean = new GenericBeanInfo(new BeanDescriptor(beanClass), getProperties(beanClass, stopClass), getMethods(beanClass, stopClass), getListeners(beanClass, stopClass));

	return ((BeanInfo)gbean);
}

public static String[] getBeanInfoSearchPath()
	{
	return (beansearch);
}

private static EventSetDescriptor[] getListeners(Class startClass, Class stopClass) throws IntrospectionException
{
	Hashtable addMethods = new Hashtable();
	Hashtable removeMethods = new Hashtable();
	Hashtable keys = new Hashtable();

	for (Class cls = startClass; cls != stopClass; cls = cls.getSuperclass()) {
		Method[] meths = cls.getDeclaredMethods();
		for (int i = 0; i < meths.length; i++) {
			if (Modifier.isPublic(meths[i].getModifiers()) && !Modifier.isStatic(meths[i].getModifiers())) {
				String mname = meths[i].getName();
				if (mname.startsWith("add") && mname.endsWith("Listener")) {
					mname = mname.substring(3, mname.length() - 8);
					keys.put(mname, mname);
					if (addMethods.get(mname) == null) {
						addMethods.put(mname, meths[i]);
					}
				}
				else if (mname.startsWith("remove") && mname.endsWith("Listener")) {
					mname = mname.substring(6, mname.length() - 8);
					keys.put(mname, mname);
					if (removeMethods.get(mname) == null) {
						removeMethods.put(mname, meths[i]);
					}
				}
			}
		}
	}

	// Now look through add/remove methods and create descriptors.
	EventSetDescriptor props[] = new EventSetDescriptor[keys.size()];
	Enumeration k = keys.elements();
	for (int i = 0; i < props.length; i++) {
		String key = (String)k.nextElement();
		Method add = (Method)addMethods.get(key);
		Method remove = (Method)removeMethods.get(key);
		if (add != null && remove != null) {
			props[i] = new EventSetDescriptor(decapitalize(key), (Class)null, (Method[])null, add, remove);
			Class except[] = add.getExceptionTypes();
			if (except != null) {
				for (int j = 0; j < except.length; j++) {
					try {
						if (except[j].newInstance() instanceof TooManyListenersException) {
							props[i].setUnicast(true);
							break;
						}
					}
					catch (IllegalAccessException _) {
					}
					catch (InstantiationException _) {
					}
				}
			}
		}
	}

	return (props);
}

private static MethodDescriptor[] getMethods(Class startClass, Class stopClass) throws IntrospectionException
{
	Hashtable hash = new Hashtable();

	for (Class cls = startClass; cls != stopClass; cls = cls.getSuperclass()) {
		Method[] meths = cls.getDeclaredMethods();
		for (int i = 0; i < meths.length; i++) {

			if (!Modifier.isPublic(meths[i].getModifiers())) {
				continue;
			}

			String mname = meths[i].toString();
			if (hash.get(mname) == null) {
				hash.put(mname, meths[i]);
			}
		}
	}

	MethodDescriptor[] methdesc = new MethodDescriptor[hash.size()];
	Enumeration e = hash.elements();
	for (int i = 0; i < methdesc.length; i++) {
		methdesc[i] = new MethodDescriptor((Method)e.nextElement());
	}

	return (methdesc);
}

private static PropertyDescriptor[] getProperties(Class startClass, Class stopClass) throws IntrospectionException
{
	Hashtable setMethods = new Hashtable();
	Hashtable getMethods = new Hashtable();
	Hashtable setIdxMethods = new Hashtable();
	Hashtable getIdxMethods = new Hashtable();
	Hashtable keys = new Hashtable();

	for (Class cls = startClass; cls != stopClass; cls = cls.getSuperclass()) {
		Method[] meths = cls.getDeclaredMethods();
		for (int i = 0; i < meths.length; i++) {

			Method meth = meths[i];

			// We should only consider public non-statics.
			int mod = meth.getModifiers();
			if (!Modifier.isPublic(mod) || Modifier.isStatic(mod)) {
				continue;
			}

			String mname = meth.getName();
			Class[] mclasses = meth.getParameterTypes();

			switch (mclasses.length) {
			case 0:
				if (mname.startsWith("get")) {
					mname = mname.substring(3);
					keys.put(mname, mname);
					if (getMethods.get(mname) == null) {
						getMethods.put(mname, meth);
					}
				}
				else if (mname.startsWith("is")) {
					mname = mname.substring(2);
					keys.put(mname, mname);
					if (getMethods.get(mname) == null) {
						getMethods.put(mname, meth);
					}
				}
				break;

			case 1:
				if (mname.startsWith("set")) {
					mname = mname.substring(3);
					keys.put(mname, mname);
					if (setMethods.get(mname) == null) {
						setMethods.put(mname, meth);
					}
				}
				else if (mname.startsWith("get") && mclasses[0] == Integer.TYPE) {
					mname = mname.substring(3);
					keys.put(mname, mname);
					if (getIdxMethods.get(mname) == null) {
						getIdxMethods.put(mname, meth);
					}
				}
				break;

			case 2:
				if (mname.startsWith("set") && mclasses[0] == Integer.TYPE) {
					mname = mname.substring(3);
					keys.put(mname, mname);
					if (setIdxMethods.get(mname) == null) {
						setIdxMethods.put(mname, meth);
					}
				}
			}

		}
	}

	// Now look through set/get methods and create desciptors.
	PropertyDescriptor props[] = new PropertyDescriptor[keys.size()];

	Enumeration k = keys.elements();
	for (int i = 0; i < props.length; i++) {
		String key = (String)k.nextElement();
		Method set = (Method)setMethods.get(key);
		Method get = (Method)getMethods.get(key);
		Method setidx = (Method)setIdxMethods.get(key);
		Method getidx = (Method)getIdxMethods.get(key);
		if (setidx == null && getidx == null) {
			props[i] = new PropertyDescriptor(decapitalize(key), get, set);
		}
		else {
			props[i] = new IndexedPropertyDescriptor(decapitalize(key), get, set, getidx, setidx);
		}
	}

	return (props);
}

private static BeanInfo loadBeanInfo(Class beanClass)
	{
	String bname = beanClass.getName();

	// First try to load bean info from package.
	BeanInfo bean = loadNamedBean(bname + "BeanInfo");
	if (bean != null) {
		return (bean);
	}

	// Extract the name of the bean without package information.
	// We make allowances for both '.' and '/' seperators.
	int pos = bname.lastIndexOf('.');
	int spos = bname.lastIndexOf('/');
	if (spos > pos) {
		pos = spos;
	}
	bname = bname.substring(pos+1);

	// Next try the search paths
	for (int i = 0; i < beansearch.length; i++) {
		bean = loadNamedBean(beansearch[i] + "." + bname + "BeanInfo");
		if (bean != null) {
			return (bean);
		}
	}

	return (null);
}

private static BeanInfo loadNamedBean(String bname)
	{
	try {
		return ((BeanInfo)Class.forName(bname).newInstance());
	}
	catch (ClassNotFoundException _) {
	}
	catch (ClassCastException _) {
	}
	catch (IllegalAccessException _) {
	}
	catch (InstantiationException _) {
	}
	return (null);
}

public static void setBeanInfoSearchPath(String path[])
	{
	beansearch = path;
}
}
