Contributed by Chris Rathman

This is an attempt at implementing a generic covariant dispatcher based on the ideas in Covariant Specialization in Java.

Covariance.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;
   }

}

CovarianceException.java

class CovarianceException extends Throwable {
   CovarianceException() { super(); }
   CovarianceException(String s) { super(s); }
}

Shape.java

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);

      }
   }
}

Rectangle.java

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());
   }
}

Circle.java

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());
   }
}

TryMe.java

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("");
   }

}

Output

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

Chris Rathman / Chris.Rathman@tx.rr.com