package relational;

import java.lang.reflect.*;
import java.io.*;

/**
 * Describes a ``foreign field,'' that is, a reference to another
 * class.
 */
public class FieldDescriptorForeign
	extends FieldDescriptor {

	protected Method m;	// points to decoder for Foriegn key
	static Class noClass[] = new Class[0];
	static Object noObject[] = new Object[0];

	public static FieldDescriptor get(Field f) {
		// don't need to cache at this level; FieldDescriptor generic
		// factory does that.
		String fieldName = f.getName();
		fieldName = "get"
			+fieldName.substring(0,1).toUpperCase()
			+fieldName.substring(1);
		if (fieldName.endsWith("_fk")) {
			fieldName = fieldName.substring(0,fieldName.length()-3);
		}
		try {
			FieldDescriptorForeign fd = new FieldDescriptorForeign();
			Method m = f.getDeclaringClass()
				.getMethod(fieldName, noClass);
			// no exception => get-method exists.
			if (Relational.class.isAssignableFrom(m.getReturnType())) {
				// and it is indeed a foreign key.
				fd.m = m;
				return fd;
			}
			// not a foreign key, just happens to have a get-method. fail.
			// (FieldDescriptor.get() will use FieldDescriptorField instead.)
		} catch (NoSuchMethodException ex) {
		} catch (SecurityException ex) {
		}
		return null;
	}

	public Object get(Row source) {
		if (source instanceof Relational) {
			// source must then be a basic instance, of the right Class
			try {
				return m.invoke(source, noObject);
			} catch (IllegalAccessException ex) {
			} catch (IllegalArgumentException ex) {
			} catch (InvocationTargetException ex) {
			}
		} else {
			return ((Row)source).getField(this);
		}
		return null;
	}

	public Class getDeclaringClass() {
		return m.getDeclaringClass();
	}

	public Class getType() {
		return m.getReturnType();
	}

	public String toString() {
		return getDeclaringClass()+"."+m.getName();
	}

	public boolean equals(Object o) {
		if (o instanceof FieldDescriptorForeign) {
			return ((FieldDescriptorForeign)o).m.equals(this.m);
		}
		return false;
	}

	private void writeObject(ObjectOutputStream out)
		throws IOException {
		out.writeObject(m.getDeclaringClass());
		out.writeObject(m.getName());
		out.writeObject(m.getParameterTypes());
	}

	private void readObject(ObjectInputStream in)
		throws IOException, ClassNotFoundException {
		Class c = (Class) in.readObject();
		String name = (String) in.readObject();
		Class[] parameterTypes = (Class[]) in.readObject();
		try {
			m = c.getMethod(name, parameterTypes);
		} catch (NoSuchMethodException ex) {
			// can't really do anything sane here, since we can't
			// hope to remedy the mismatch.
			throw new RuntimeException("Class mismatch at receiver.");
		}
	}
}
