Contributed by Chris Rathman
This is an attempt at implementing a generic covariant dispatcher based on the ideas in Covariant Specialization in Java.
import java.lang.reflect.*;
abstract class Covariance {
// This routine needs some additional work
public static Object dispatch(Class dispatchClass, Object receiver, String methodName, Object[] args)
throws CovarianceException, IllegalAccessException, InvocationTargetException {
// get the type of each parameter argument
Class[] inherentArgTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
inherentArgTypes[i] = args[i].getClass();
}
// get the instanceMethods available for the receiver class
Method[] instanceMethods = receiver.getClass().getMethods();
Method chosenMethod = null;
Class[] chosenParameterTypes = null;
// try to find the most applicable method
Loop: for (int i = 0; i < instanceMethods.length; i++) {
// ignore functions with different name
if (!instanceMethods[i].getName().equals(methodName)) continue Loop;
// ignore covariance on static instanceMethods
if (Modifier.isStatic(instanceMethods[i].getModifiers())) continue Loop;
Class[] parameterTypes = instanceMethods[i].getParameterTypes();
// ignore functions with wrong number of parameters
if (parameterTypes.length != args.length) continue Loop;
// coerce the primitives to objects
boxPrimitives(parameterTypes);
// ignore functions with incompatible parameter types
for (int j = 0; j < parameterTypes.length; j++) {
if (!parameterTypes[j].isAssignableFrom(inherentArgTypes[j])) continue Loop;
}
// if this is the first match then use it
if (chosenMethod == null) {
chosenMethod = instanceMethods[i];
chosenParameterTypes = parameterTypes;
continue Loop;
}
// if this method is more specific in compatibility then use it
for (int j = 0; j < chosenParameterTypes.length; j++) {
if (!chosenParameterTypes[j].isAssignableFrom(parameterTypes[j])) continue Loop;
}
// this is the best fit so far
chosenMethod = instanceMethods[i];
chosenParameterTypes = parameterTypes;
}
// return to the caller indicating that method was not found
if (chosenMethod == null) {
throw(new CovarianceException("Method not found"));
}
// return to the caller indicating that a covariant method was not found
if (chosenMethod.getDeclaringClass() == dispatchClass) {
throw(new CovarianceException("Covariant method not found"));
}
// invoke the covariant method and return the result
return chosenMethod.invoke(receiver, args);
}
// convert any primitive types in the parameter list to correlated object type
public static Class[] boxPrimitives(Class[] parameterTypes) {
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] == byte.class) parameterTypes[i] = Byte.class;
if (parameterTypes[i] == short.class) parameterTypes[i] = Short.class;
if (parameterTypes[i] == int.class) parameterTypes[i] = Integer.class;
if (parameterTypes[i] == long.class) parameterTypes[i] = Long.class;
if (parameterTypes[i] == boolean.class) parameterTypes[i] = Boolean.class;
if (parameterTypes[i] == float.class) parameterTypes[i] = Float.class;
if (parameterTypes[i] == double.class) parameterTypes[i] = Double.class;
if (parameterTypes[i] == char.class) parameterTypes[i] = Character.class;
}
return parameterTypes;
}
}
|
class CovarianceException extends Throwable {
CovarianceException() { super(); }
CovarianceException(String s) { super(s); }
}
|
import java.lang.reflect.*;
abstract class Shape {
private int x;
private int y;
// constructor
Shape(int newx, int newy) {
moveTo(newx, newy);
}
// accessors for x and y
int getX() { return x; }
int getY() { return y; }
void setX(int newx) { x = newx; }
void setY(int newy) { y = newy; }
// move the x and y position
void moveTo(int newx, int newy) {
setX(newx);
setY(newy);
}
void rMoveTo(int deltax, int deltay) {
moveTo(getX() + deltax, getY() + deltay);
}
// virtual draw method
abstract void draw();
// novariant method - if signature is base class, this is always used
public void add_novariance(Shape that) {
rMoveTo(that.getX(), that.getY());
}
// covariant method - use reflection to specialize the call
public void add_covariance(Shape that) {
try {
Covariance.dispatch(Shape.class, this, "add_covariance", new Object[]{ that });
} catch(CovarianceException e) {
rMoveTo(that.getX(), that.getY());
} catch(IllegalAccessException e) {
System.out.println(e);
} catch(InvocationTargetException e) {
System.out.println(e);
}
}
}
|
class Rectangle extends Shape {
private int width;
private int height;
// constructor
Rectangle(int newx, int newy, int newwidth, int newheight) {
super(newx, newy);
setWidth(newwidth);
setHeight(newheight);
}
// accessors for the width and height
int getWidth() { return width; }
int getHeight() { return height; }
void setWidth(int newwidth) { width = newwidth; }
void setHeight(int newheight) { height = newheight; }
// draw the rectangle
void draw() {
System.out.println("Drawing a Rectangle at:(" + getX() + ", " + getY() +
"), width " + getWidth() + ", height " + getHeight());
}
// novariant method
public void add_novariance(Rectangle that) {
rMoveTo(that.getX(), that.getY());
setWidth(getWidth() + that.getWidth());
setHeight(getHeight() + that.getHeight());
}
// covariant method
public void add_covariance(Rectangle that) {
rMoveTo(that.getX(), that.getY());
setWidth(getWidth() + that.getWidth());
setHeight(getHeight() + that.getHeight());
}
}
|
class Circle extends Shape {
private int radius;
// constructor
Circle(int newx, int newy, int newradius) {
super(newx, newy);
setRadius(newradius);
}
// accessors for the radius
int getRadius() { return radius; }
void setRadius(int newradius) { radius = newradius; }
// draw the circle
void draw() {
System.out.println("Drawing a Circle at:(" + getX() + ", " + getY() +
"), radius " + getRadius());
}
// novariant method
public void add_novariance(Circle that) {
rMoveTo(that.getX(), that.getY());
setRadius(getRadius() + that.getRadius());
}
// covariant method
public void add_covariance(Circle that) {
rMoveTo(that.getX(), that.getY());
setRadius(getRadius() + that.getRadius());
}
}
|
class TryMe {
public static void main(String[] argv) {
System.out.println("No variance using the base class signature");
Rectangle arect = new Rectangle(10, 20, 5, 6);
Circle acirc = new Circle(15, 25, 8);
arect.add_novariance((Shape)(new Rectangle(10, 20, 5, 6)));
acirc.add_novariance((Shape)(new Circle(15, 25, 8)));
arect.draw();
acirc.draw();
System.out.println("");
System.out.println("No variance using the subclass signature");
arect = new Rectangle(10, 20, 5, 6);
acirc = new Circle(15, 25, 8);
arect.add_novariance(new Rectangle(10, 20, 5, 6));
acirc.add_novariance(new Circle(15, 25, 8));
arect.draw();
acirc.draw();
System.out.println("");
System.out.println("Covariance using the base class signature");
arect = new Rectangle(10, 20, 5, 6);
acirc = new Circle(15, 25, 8);
arect.add_covariance((Shape)(new Rectangle(10, 20, 5, 6)));
acirc.add_covariance((Shape)(new Circle(15, 25, 8)));
arect.draw();
acirc.draw();
System.out.println("");
System.out.println("Covariance using the subclass signature");
arect = new Rectangle(10, 20, 5, 6);
acirc = new Circle(15, 25, 8);
arect.add_covariance(new Rectangle(10, 20, 5, 6));
acirc.add_covariance(new Circle(15, 25, 8));
arect.draw();
acirc.draw();
System.out.println("");
}
}
|
No variance using the base class signature Drawing a Rectangle at:(20, 40), width 5, height 6 Drawing a Circle at:(30, 50), radius 8 No variance using the subclass signature Drawing a Rectangle at:(20, 40), width 10, height 12 Drawing a Circle at:(30, 50), radius 16 Covariance using the base class signature Drawing a Rectangle at:(20, 40), width 10, height 12 Drawing a Circle at:(30, 50), radius 16 Covariance using the subclass signature Drawing a Rectangle at:(20, 40), width 10, height 12 Drawing a Circle at:(30, 50), radius 16 |