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 |