Fwd: Roles in Eclipse Indigo

21 views
Skip to first unread message

James O. Coplien

unread,
Sep 29, 2011, 5:44:39 AM9/29/11
to dci-ev...@googlegroups.com
I've taken the liberty of re-posting this on DCI-Evolution.

Begin forwarded message:

From: Ant Kutschera <ant.ku...@gmail.com>
Date: September 29, 2011 9:18:08 GMT+02:00
To: object-composition <object-co...@googlegroups.com>
Subject: Re: Roles in Eclipse Indigo

Hi all,

There has been much said about Java being poor when it comes to being
DCI compliant.  I have risen to the challenge...

***
I've extended Java to make it more DCI compliant, bringing it in line
with languages such as Javascript and Ruby, when it comes to DCI
compliancy.  In my opinion, it is now one of the leading languages
when it comes to nailing DCI certification.  Comments are appreciated!
***

While comparing wrappers in Java to James' Ruby implementation of
Dijkstra's Algorithm, I listed a few steps which were required in
order to make Java wrappers work.  Those steps were unsatisfying.
They were:

1) Create an interface for the plain object, that declares the methods
it exposes

That is no longer required.

2&3) Get the Behaviour Injector to pass all calls to the
equals(Object) and hashCode() methods, to the original object.

That is no longer necessary, because the role implementation is
attached to the object - there is no wrapper, and there is only one
object.
Object schizophrenia is no longer an issue.

Due to this, I can also give the Nodes in the Dijkstra Algorithm any
name I want.  James named two Nodes "a" as a test, which I can now
also do.

4) Roles cannot contain state.

This statement was wrong anyway.  What I meant with it was that I
could not inject new data fields into objects in the way that James
did with the Distance_Labelled_Graph_Node.  I can now do that.

5) For similar reasons, the rebind() methods from James' code are not
present in my code.

No longer a problem.  I can create a rebind() method.  The object
implementing the role IS the domain object.

Additionally, other criticisms were:

6) In Java you are required to give a type declaration in order to use
the role methods, which is fine if there is a single role, but if you
need to merge the interface because an object plays several roles, you
need to invent a name for that merged role, and that will be something
that is not found in the mental model.  You also end up with perhaps
two or more variables all pointing to the same "object".

That is also no longer a problem.  Type declarations are no longer
required.  I can call role methods without needing any type
declaration to allow me to do so.  It works entirely dynamically.

-----

How did I do all this I hear you asking...

Well, I had a think about what I would have to do to Java, to overcome
those limitations.  I looked at hacking the compiler, but ran into the
problem that the virtual machine loads class definitions just once and
thereafter is incapable of adding fields or methods to objects of a
given class.  It's an architectural constraint in the VM.

I considered byte code manipulation too, but that didn't seem
possible.

So I extended the language itself by introducing a new syntax for the
dynamic method/field injection.  The beauty is, it will run in all
existing Java environments, because all you need in order to extend
Java in this way, is a little library that I will be publishing.  I
didn't need to modify the compiler or JVM.

The result is great.  You can now add (inject) methods and fields to
objects.  In doing so, I have overcome object schizophrenia, because I
don't need wrappers anymore.  And because this is entirely dynamic and
something which happens at runtime, there is no requirement for type
declarations on roles.

It means that I can use Java to implement Dijkstra's Algorithm in a
way that is VERY similar to the way it is done in Ruby.  The only
differences are syntactic, because I am using hacked Java rather than
Ruby.  Semantically the solutions are now identical.  I hence claim
that this promotes Java from being mostly DCI uncompliant, to being
better than Javascript and as good as Ruby, if not better.

Here is some test code to show the new syntax.  To start with, a class
for modelling plain data:

   /** a domain class - dumb */
   class Cart extends Data {
       private String name;
       private int numWheels;
       public Cart(String name, int numWheels){
           this.name = name;
           this.numWheels = numWheels;
       }
   }

//The magic is in the super class called Data.
//Now, lets look at a context containing two roles:

   /** a context */
   class TravelContext extends Context {

       /** a role */
       class Plane extends Role<Cart, TravelContext> {
           void fly(){
               self.setData("wingsExtended", true);
               System.out.println("I'm flying man!!");
           }
       }

//The role extends a class called Role, which using generics requires
//me to state the interface which role players are required to have
//(aka the role contract), as well as the parent context.
//See the "setData" call?  That adds a field to the object!
//The syntax is a little weird, and at first glance just looks like a
method call.
//But it is indeed a new syntax.
//In this case, I have added the field "wingsExtended" to the object,
giving it the
//value "true".  "self" is the object playing the role - that is also
new syntax.
//In role methods one can also access the context using the variable
"context".
//Here is another role:

       /** a role */
       class Car extends Role<Cart, TravelContext> {
           void drive(){
               System.out.println("Only driving now...");
           }
       }

//And now lets look at the execution of the context:

       /** context constructor and execution */
       TravelContext(Cart myFlyingCar){
           bind(myFlyingCar, Car.class);
           bind(myFlyingCar, Plane.class);

//Above, I have bound the roles Car and Plane to the object!  No
wrappers at all!

           System.out.println("My cool transport is called: " +
myFlyingCar.name + " and it has " + myFlyingCar.numWheels + "
wheels.");

//Now lets call a role method:

           myFlyingCar.call("drive", Void.class);

           myFlyingCar.call("fly", Void.class);

//Above, I am calling the "drive" and "fly" methods from the two
//roles (the "Void.class" tells it that I'm looking for a method
//that returns nothing).
//Again, to the uneducated eye, it might look
//like a method call, but it is the new DCI syntax which I have
//added to Java.  Reading that to the trained eye is identical to
//pseudo code like this (they are semantically identical):
//
//    myFlyingCar.fly();
//
//Once you get used to reading the new syntax, it is not a problem.
//
//And did you notice how I no longer need type declarations when
referring to roles?
//
//If there is a name clash between two roles, I can also overcome that
by adding the
//name of the class which defines the role method I want to call.  I
believe Ruby
//isn't capable of this.
//
//This is how you access fields added during contexts:

           System.out.println("Are the wings are now extended? " +
myFlyingCar.getData("wingsExtended"));

//See how I accessed the field which the role added?
//
//I can't write a test here to prove there is no object
schizophrenia,
//because there is only the single object and no wrappers!!
//
//Finally, a method to remove all the role methods and fields added
during the context.
//I don't think Ruby can do this, but I feel it is extremely
important!

           cleanup(); // housekeeping related to context stacking
       }

   }

//Here is the test code to run that context:

   void test() {

       //create a dumb domain object
       Cart myCart = new Cart("betty", 4);

       //create and execute the context:
       new TravelContext(myCart);

       //after the context, the field no longer exists on the object
       assertTrue(myCart.getData("wingsExtended") == null);
   }

Who likes and who doesn't?

Cheers,
Ant

PS. I might have been a little generous in my definitions of
"extending the language" and "syntax" here...

PPS. I have added a dynamic element to the language in the way that
role methods are called.  While the compiler and tools do not
currently support this extension, it is not really worse than anything
which say JPA does, when putting SQL related calls into code.  That
stuff is all dynamic and isn't checked by the compiler.  And in
comparison to Javascript, this solution is no worse, yet I can still
leverage all the heavy weight things Java brings with it in the
enterprise edition.

PPS. Here is my solution to the Dijkstra Algorithm:

package common;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ch.maxant.dci.util.Context;
import ch.maxant.dci.util.Data;
import ch.maxant.dci.util.Role;

/**
* here, I have removed need to use a type declaration for a merged
interface.
* there is no merged interface.  I only ever refer to objects by
their actual data
* type.
*
* Oh, and I have solved the object schizophrenia problem too.
*/
public class Runner {

   ////////////////////////////////////////////////////////////////

   static class Pair {
       private Object a;
       private Object b;

       public Pair(Object a, Object b) {
           this.a = a;
           this.b = b;
       }

       @Override
       public int hashCode() {
           final int prime = 31;
           int result = 1;
           result = prime * result + ((a == null) ? 0 :
a.hashCode());
           result = prime * result + ((b == null) ? 0 :
b.hashCode());
           return result;
       }

       @Override
       public boolean equals(Object obj) {
           if (this == obj)
               return true;
           if (obj == null)
               return false;
           if (getClass() != obj.getClass())
               return false;
           Pair other = (Pair) obj;
           if (a == null) {
               if (other.a != null)
                   return false;
           } else if (!a.equals(other.a))
               return false;
           if (b == null) {
               if (other.b != null)
                   return false;
           } else if (!b.equals(other.b))
               return false;
           return true;
       }

       @Override
       public String toString() {
           return "Pair [" + a + ", " + b + "]";
       }

   }

   ////////////////////////////////////////////////////////////////

   /**
    * use less than infinity, otherwise some of the calcs go wrong,
    * when we add tentative distances to actual distances and we end
    * up with negative numbers, because weve gone over max INT.
    */
   static final Integer INFINITY = Integer.MAX_VALUE - 10000;

   ////////////////////////////////////////////////////////////////

   /**
    * "Map" as in cartography rather than Computer Science...
    *
    * Map is technically a role from the DCI perspective. The role
    * in this example is played by an object representing a
particular
    * Manhattan geometry
   */
   static class CartographyMap extends Role<Geometry, Object> {

       Integer distance_between(Node a, Node b) {
           return self.getDistances().get(new Pair(a, b));
       }

       // These two functions presume always travelling
       // in a southern or easterly direction

       Node next_down_the_street_from(Node node) {
           return self.east_neighbor_of(node);
       }

       Node next_along_the_avenue_from(Node node) {
           return self.south_neighborOf(node);
       }

   }

   ////////////////////////////////////////////////////////////////

   /**
    * There are four roles in the algorithm: CurrentIntersection
(@current)
    * EastNeighbor, which lies DIRECTLY to the east of
CurrentIntersection
    * (@east_neighbor) SouthernNeighbor, which is DIRECTLy to its
south
    * (@south_neighbor) Destination, the target node (@destination)
    *
    * We also add a role of Map (@map) as the oracle for the geometry
    *
    * The algorithm is straight from Wikipedia:
    *
    * http://en.wikipedia.org/wiki/Dijkstra's_algorithm
    *
    * and reads directly from the distance method, below
    *
    * (use context type "Object" because this role is found in
several contexts - we still need to think how to handle duplicate code
in a better way...)
    */
   static class Distance_labeled_graph_node extends Role<Node,
Object> {

       /*
        * NOTE: This role creates a new data member in the node into
        *        which it is injected. An alernative implementation
would
        *        be to use a separate associative array
        */

       void set_tentative_distance_to(Integer x) {
           self.setData("tentative_distance", x);
       }
   }

   ////////////////////////////////////////////////////////////////

   /**
    * Consider street corners on a Manhattan grid. We want to find
the
    * minimal path from the most northeast city to the most
    * southeast city. Use Dijstra's algorithm
    *
    * Data class
    *
    * Note there is NO NEED to implement hashCode or equals!
    */
   static class Node extends Data {

       private String name;

       public Node(String name) {
           this.name = name;
       }

       public String getName() {
           return name;
       }

       /** only done for debugging purposes */
       @Override
       public String toString() {
           return "Node[name=" + name + ", hashCode=" + hashCode() +
"]";
       }
   }

   ////////////////////////////////////////////////////////////////

   /**
    * This is the main Context for shortest path calculation
    */
   static class CalculateShortestPath extends Context {

       //These are handles to internal housekeeping arrays set up in
initialize

       Map<Node, Boolean> unvisited = new HashMap<Node, Boolean>();
       Map<Node, Node> pathTo;
       Node east_neighbor;
       Node south_neighbor;
       List<Node> path;
       Geometry map;
       Node current;
       Node destination;

       // Initialization

       void rebind(Node origin_node, Geometry geometries){
           current = origin_node;
           map = geometries;

           bind(map, CartographyMap.class);

           bind(current, CurrentIntersection.class);

           east_neighbor = map.east_neighbor_of(origin_node);

           for(Node n : geometries.nodes()){
               bind(n, Distance_labeled_graph_node.class);
           }

           if(east_neighbor != null){
               bind(east_neighbor, Neighbor.class);
           }

           south_neighbor = map.south_neighborOf(origin_node);

           if(south_neighbor != null){
               bind(south_neighbor, Neighbor.class);
           }
       }

       /**
        * public initialize. It's overloaded so that the public
version doesn't
        * have to pass a lot of crap; the initialize method takes
care of
        * setting up internal data structures on the first
invocation. On
        * recursion we override the defaults
        */
       public CalculateShortestPath(Node origin_node, Node
target_node, Geometry geometries, List<Node> path_vector,
               Map<Node, Boolean> unvisited_hash, Map<Node, Node>
pathto_hash) {

           destination = target_node;

           rebind(origin_node, geometries);

           // This has to come after rebind is done
           if (path_vector == null) {

               // This is the fundamental data structure for
Dijkstra's algorithm,
               // called
               // "Q" in the Wikipedia description. It is a boolean
hash that maps
               // a
               // node onto false or true according to whether it has
been visited
               this.unvisited = new HashMap<Node, Boolean>();

               // These initializations are directly from the
description of the
               // algorithm
               for (Node n : geometries.getNodes()) {
                   this.unvisited.put(n, Boolean.TRUE);
                   n.call("set_tentative_distance_to", Void.class,
INFINITY);
               }

               current.call("set_tentative_distance_to", Void.class,
0);

               this.unvisited.remove(origin_node);

               // The path array is kept in the outermost context and
serves to
               // store the
               // return path. Each recurring context may add
something to the
               // array along
               // the way. However, because of the nature of the
algorithm,
               // individual
               // Context instances don't deliver "partial paths" as
partial
               // answers.
               this.path = new ArrayList<Node>();

               // The pathTo map is a local associative array that
remembers the
               // arrows between nodes through the array and erases
them if we
               // re-label a node with a shorter distance
               this.pathTo = new HashMap<Node, Node>();

           } else {

               this.unvisited = unvisited_hash;
               this.path = path_vector;
               this.pathTo = pathto_hash;
           }

           execute();
       }

       class CurrentIntersection extends Role<Node,
CalculateShortestPath> {

           List<Node> unvisited_neighbors() {

               //WATCHOUT: moved the access to data from the context,
from outside this method,
               //to in inside it, otherwise we are introducing state
to the role
               Map<Node, Boolean> unvisited = context.unvisited;
               Node south_neighbor = context.south_neighbor;
               Node east_neighbor = context.east_neighbor;

               List<Node> retval = new ArrayList<Node>();
               if (south_neighbor != null) {
                   Boolean addIt = unvisited.get(south_neighbor);
                   if (addIt == Boolean.TRUE) { //watch out, addIt
can be null apparently
                       retval.add(south_neighbor);
                   }
               }
               if (east_neighbor != null) {
                   Boolean addIt = unvisited.get(east_neighbor);
                   if (addIt == Boolean.TRUE) { //watch out, addIt
can be null apparently
                       retval.add(east_neighbor);
                   }

               }
               return retval;
           }
       }

       /**
        * This module serves to provide the methods both for the
east_neighbor and south_neighbor roles
        */
       class Neighbor extends Role<Node, CalculateShortestPath>  {

           boolean relable_node_as(Integer x) {
               if (x < (Integer)self.getData("tentative_distance")) {

                   self.call("set_tentative_distance_to", Void.class,
x);
                   return true;
               } else {
                   return false;
               }
           }
       }

       /**
        * This is the method that does the work. Called from
initialize
        */
       public void execute() {
           // Calculate tentative distances of unvisited neighbors
           List<Node> unvisited_neighbors =
current.call("unvisited_neighbors", List.class);
           if (unvisited_neighbors != null) {
               for (Node neighbor : unvisited_neighbors) {

                   Integer tentativeDistance = (Integer)
current.getData("tentative_distance");
                   Integer distanceBetween =
map.call("distance_between", Integer.class, current, neighbor);
                   boolean relable_node_as =
neighbor.call("relable_node_as", Boolean.class, tentativeDistance +
distanceBetween);

                   if (relable_node_as) {
                       pathTo.put(neighbor, current);
                   }
               }
           }

           unvisited.remove(current);

           // Are we done?

           if (unvisited.size() == 0) {
               save_path(this.path);
           } else {

               // The next current node is the one with the least
distance in the
               // unvisited set

               Node selection = nearest_unvisited_node_to_target();

               // Recur
               new CalculateShortestPath(selection, destination, map,
path, unvisited, pathTo);
           }
       }

       Node nearest_unvisited_node_to_target() {

           int min = INFINITY;
           Node selection = null;

           for (Node intersection : unvisited.keySet()) {
               if (unvisited.get(intersection)) {

                   if(intersection.getData("tentative_distance",
Integer.class) <= min) {

                       min =
intersection.getData("tentative_distance", Integer.class);
                       selection = intersection;
                   }
               }
           }
           return selection;
       }

       /**
        * This method does a simple traversal of the data structures
(following
        * pathTo) to build the directed traversal vector for the
minimum path
        */
       void save_path(List<Node> pathVector) {

           Node node = destination;
           do {
               pathVector.add(node);

               node = pathTo.get(node);

           } while (node != null);
       }

       public List<Node> getPath() {
           return path;
       }

   }

   ////////////////////////////////////////////////////////////////


   /**
    * This is the main Context for shortest distance calculation
    */
   static class CalculateShortestDistance extends Context {

       List<Node> path = new ArrayList<Node>();
       Geometry map;
       Node destination;
       Node current;

       //MAP ROLE: SEE COMMON CODE NEAR TOP
       //DISTANCE LABELED GRAPH NODE: SEE COMMON CODE NEAR TOP

       void rebind(Node origin_node, Geometry geometries){
           current = origin_node;
           destination = geometries.destination();
           map = geometries;

           bind(map, CartographyMap.class);

           for(Node node : map.nodes()){
               bind(node, Distance_labeled_graph_node.class);
           }
       }

       public CalculateShortestDistance(Node origin_node, Node
target_node, Geometry geometries) {

           rebind(origin_node, geometries);

           this.current.call("set_tentative_distance_to",
Integer.class, 0);

           this.path = new CalculateShortestPath(this.current,
this.destination, geometries, null, null, null).getPath();

           cleanup(); //related to context stacking
       }

       public int distance() {
           int retval = 0;
           Node previous_node = null;

           List<Node> reversed = new ArrayList<Node>(path);
           Collections.reverse(reversed);

           for (Node node : reversed) {

               if (previous_node == null) {
                   retval = 0;
               } else {
                   retval += this.map.call("distance_between",
Integer.class, previous_node, node);
               }
               previous_node = node;
           }
           return retval;
       }
   }

   ////////////////////////////////////////////////////////////////

   //because Java is not entirely dynamic like Ruby, I have created
a
   //super class to contain the common parts of the geometries.  it
also
   //helps reduce code duplication.
   static abstract class Geometry extends Data {

       List<Node> nodes;
       Node root;
       Node destination;
       Map<Pair, Integer> distances;
       Map<Node, Node> next_down_the_street_from = new HashMap<Node,
Node>();
       Map<Node, Node> next_along_the_avenue_from = new HashMap<Node,
Node>();

       public Node east_neighbor_of(Node a) {
           return next_down_the_street_from.get(a);
       }

       public Node south_neighborOf(Node a) {
           return next_along_the_avenue_from.get(a);
       }

       public Node root() {
           return root;
       }

       public Node destination() {
           return destination;
       }

       public List<Node> nodes() {
           return nodes;
       }

       public Node getRoot() {
           return root;
       }

       public Node getDestination() {
           return destination;
       }

       public List<Node> getNodes() {
           return nodes;
       }

       public Map<Pair, Integer> getDistances() {
           return distances;
       }
   }

   ////////////////////////////////////////////////////////////////

   static class ManhattanGeometry1 extends Geometry {

       public ManhattanGeometry1() {
           this.nodes = new ArrayList<Node>();
           this.distances = new HashMap<Pair, Integer>();

           String[] names = { "a", "b", "c", "d", "a", "b", "g", "h",
"i" };

           for (int i = 0; i < 3; i++) {
               for (int j = 0; j < 3; j++) {
                   this.nodes.add(new Node(names[(i * 3) + j]));
               }
           }

           // Aliases to help set up the grid. Grid is of Manhattan
form:
           //
           //    a - 2 - b - 3 - c
           //    |       |       |
           //    1       2       1
           //    |       |       |
           //    d - 1 - e - 1 - f
           //    |               |
           //    2               4
           //    |               |
           //    g - 1 - h - 2 - i
           //
           Node a = this.nodes.get(0);
           root = a;
           Node b = this.nodes.get(1);
           Node c = this.nodes.get(2);
           Node d = this.nodes.get(3);
           Node e = this.nodes.get(4);
           Node f = this.nodes.get(5);
           Node g = this.nodes.get(6);
           Node h = this.nodes.get(7);
           Node i = this.nodes.get(8);
           destination = i;

           for (int s = 0; s < 3; s++) {
               for (int t = 0; t < 3; t++) {
                   this.distances.put(new Pair(nodes.get(s),
nodes.get(t)), INFINITY);
               }
           }

           distances.put(new Pair(a, b), 2);
           distances.put(new Pair(b, c), 3);
           distances.put(new Pair(c, f), 1);
           distances.put(new Pair(f, i), 4);
           distances.put(new Pair(b, e), 2);
           distances.put(new Pair(e, f), 1);
           distances.put(new Pair(a, d), 1);
           distances.put(new Pair(d, g), 2);
           distances.put(new Pair(g, h), 1);
           distances.put(new Pair(h, i), 2);
           distances.put(new Pair(d, e), 1);

           distances = Collections.unmodifiableMap(distances);

           next_down_the_street_from.put(a, b);
           next_down_the_street_from.put(b, c);
           next_down_the_street_from.put(d, e);
           next_down_the_street_from.put(e, f);
           next_down_the_street_from.put(g, h);
           next_down_the_street_from.put(h, i);
           next_down_the_street_from = Collections
                   .unmodifiableMap(next_down_the_street_from);

           next_along_the_avenue_from.put(a, d);
           next_along_the_avenue_from.put(b, e);
           next_along_the_avenue_from.put(c, f);
           next_along_the_avenue_from.put(d, g);
           next_along_the_avenue_from.put(f, i);

           next_along_the_avenue_from = Collections
                   .unmodifiableMap(next_along_the_avenue_from);
       }

   }

   ////////////////////////////////////////////////////////////////

   static class ManhattanGeometry2 extends Geometry {

       public ManhattanGeometry2() {
           this.nodes = new ArrayList<Node>();
           this.distances = new HashMap<Pair, Integer>();

           String[] names = { "a", "b", "c", "d", "a", "b", "g", "h",
"i", "j", "k" };

           for (int j = 0; j < 11; j++) {
               nodes.add(new Node(names[j]));
           }

           // Aliases to help set up the grid. Grid is of Manhattan
form:
           //
           // a - 2 - b - 3 - c - 1 - j
           // |       |       |       |
           // 1       2       1       |
           // |       |       |       |
           // d - 1 - e - 1 - f       1
           // |       |               |
           // 2       4               |
           // |       |               |
           // g - 1 - h - 2 - i - 2 - k
           //
           Node a = nodes.get(0);
           root = a;
           Node b = nodes.get(1);
           Node c = nodes.get(2);
           Node d = nodes.get(3);
           Node e = nodes.get(4);
           Node f = nodes.get(5);
           Node g = nodes.get(6);
           Node h = nodes.get(7);
           Node i = nodes.get(8);
           Node j = nodes.get(9);
           Node k = nodes.get(10);
           destination = k;

           for (int s = 0; s < 11; s++) {
               for (int t = 0; t < 11; t++) {
                   distances.put(new Pair(nodes.get(s),
nodes.get(t)),
                           INFINITY);
               }
           }

           distances.put(new Pair(a, b), 2);
           distances.put(new Pair(b, c), 3);
           distances.put(new Pair(c, f), 1);
           distances.put(new Pair(f, i), 4);
           distances.put(new Pair(b, e), 2);
           distances.put(new Pair(e, f), 1);
           distances.put(new Pair(a, d), 1);
           distances.put(new Pair(d, g), 2);
           distances.put(new Pair(g, h), 1);
           distances.put(new Pair(h, i), 2);
           distances.put(new Pair(d, e), 1);
           distances.put(new Pair(c, j), 1);
           distances.put(new Pair(j, k), 1);
           distances.put(new Pair(i, k), 2);

           distances = Collections.unmodifiableMap(distances);

           next_down_the_street_from.put(a, b);
           next_down_the_street_from.put(b, c);
           next_down_the_street_from.put(c, j);
           next_down_the_street_from.put(d, e);
           next_down_the_street_from.put(e, f);
           next_down_the_street_from.put(g, h);
           next_down_the_street_from.put(h, i);
           next_down_the_street_from.put(i, k);

           next_down_the_street_from = Collections
                   .unmodifiableMap(next_down_the_street_from);

           next_along_the_avenue_from.put(a, d);
           next_along_the_avenue_from.put(b, e);
           next_along_the_avenue_from.put(c, f);
           next_along_the_avenue_from.put(d, g);
           next_along_the_avenue_from.put(f, i);
           next_along_the_avenue_from.put(j, k);

           next_along_the_avenue_from = Collections
                   .unmodifiableMap(next_along_the_avenue_from);
       }

   }

   ////////////////////////////////////////////////////////////////

   /** Test drivers */
   public static void main(String[] args) {

       Geometry geometries = new ManhattanGeometry1();

       CalculateShortestPath path = new
CalculateShortestPath(geometries.getRoot(),
               geometries.getDestination(), geometries, null, null,
null);

       System.out.println("Path is: ");
       for (Node node : path.getPath()) {
           System.out.println(node.getName());
       }

       System.out.println("distance is "
               + new CalculateShortestDistance(geometries.getRoot(),
                       geometries.getDestination(),
geometries).distance());

       System.out.println();

       geometries = new ManhattanGeometry2();

       path = new CalculateShortestPath(geometries.getRoot(),
               geometries.getDestination(), geometries, null, null,
null);

       System.out.println("Path is: ");
       Node last_node = null;
       for (Node node : path.getPath()) {
           if (last_node != null) {
               System.out.print(" - "
                       + geometries.distances.get(new Pair(node,
last_node))
                       + " - ");
           }
           System.out.print(node.getName());
           last_node = node;
       }

       System.out.println();
       System.out.println("distance is "
               + new CalculateShortestDistance(geometries.getRoot(),
                       geometries.getDestination(),
geometries).distance());
   }

}

The outputs are:

Path is:
i
h
g
d
a
distance is 6

Path is:
k - 1 - j - 1 - c - 3 - b - 2 - a
distance is 7

James O. Coplien

unread,
Sep 29, 2011, 5:44:47 AM9/29/11
to dci-ev...@googlegroups.com

On Sep 29, 2011, at 9:18 , Ant Kutschera wrote:

I've extended Java to make it more DCI compliant, bringing it in line
with languages such as Javascript and Ruby, when it comes to DCI
compliancy.  In my opinion, it is now one of the leading languages
when it comes to nailing DCI certification.  Comments are appreciated!

Claims like this instantly activate my bullshit detector, but I was frankly shocked and pleasantly surprised when reading on.


PS. I might have been a little generous in my definitions of
"extending the language" and "syntax" here...

We call that "poetic license."

I've re-posted here on dci-evolution; I think this discussion belongs more there than here.


Who likes and who doesn't?


What I like:

1. It appears to be real DCI in Java. This is more or less the same as one of the solutions that Agata Przybyszewska came up with in the earliest days of DCI, except the syntax is much cleaner because of to-the-wall use and encapsulation of the Java reflection API.

2. I think it's syntactically the cleanest thing we've got going in Java right now. But see below.

What I don't like:

1. The current syntax. It's too much like a Smalltalk perform operation. In years of programming I've been unable to adjust to this style. If most of my method invocations have to be wrapped in a perform, then there is still a ways to go in reducing the impedance between the code and the programmer's brain. I like it better than the Qi4j syntax.

2. The current syntax has some implications. It's going to be hard to get any JIT optimizations out of this "language" because of symbolic run-time method lookup. It is also going to be difficult to make a compiler that gives any degree of compile-time safety analysis, which is a hope worth holding out for Java.

3. The addition of a data field in the Ruby example was an experiment. I have since become convinced (owing to a comment by Stephan, message ID <3571aa03-81cc-4b04...@24g2000yqr.googlegroups.com> on 15 September) that it was a bad idea. I am working on a new version of the Ruby code. It's half-done. I think that the feature of supporting an extra data member is a chimera and is peripheral to the concern about DCI.

4. Do you still really need that crappy overloaded equals method in the Dijkstra example? If so, there's something fundamentally wrong here.

Puzzles:

1. How does this compare (on many axes) with Qi4j? I like the syntax better. If the run-time performance is the same (i.e., the reflection layer is being used underneath in the same way) this implementation has my vote for the best Java adaptation. We still need to look at the amount of static analysis available / possible; I don't have any intuition for how well Qi4j fares on that count.

2. I'm interested in seeing the implementation. One way you might have done this would be to use the reflection API to create a new class on-the-fly, composing the original class and the "role" class at the point of injection, and to re-wire the method tables to re-type the extant object. (Does the VM allow that — in particular, the latter re-wiring?) If you could do that then this looks a lot like the Scala implementation and one might even be able to talk about interoperability between Java and Scala in a DCI world.

3. Does this implementation offer scoping advantages (particularly of role names) over in Qi4j? It might have some advantages because it's more integrated into the base language syntax instead of using annotations.

rune funch

unread,
Sep 29, 2011, 5:52:24 AM9/29/11
to dci-ev...@googlegroups.com
Den 29/09/2011 kl. 11.45 skrev "James O. Coplien" <jcop...@gmail.com>:

> 2. I'm interested in seeing the implementation. One way you might have done this would be to use the reflection API to create a new class on-the-fly, composing the original class and the "role" class at the point of injection, and to re-wire the method tables to re-type the extant object. (Does the VM allow that — in particular, the latter re-wiring?) If you could do that then this looks a lot like the Scala implementation and one might even be able to talk about interoperability between Java and Scala in a DCI world.
>

Would be interesting if that was the case that's exactly what I did in
ClearMud a few month ago. Though I did cheat some what fooling the run
time

Ant Kutschera

unread,
Sep 29, 2011, 9:46:20 AM9/29/11
to dci-evolution
On Sep 29, 11:44 am, "James O. Coplien" <jcopl...@gmail.com> wrote:
> On Sep 29, 2011, at 9:18 , Ant Kutschera wrote:
>
> > I've extended Java to make it more DCI compliant, bringing it in line
> > with languages such as Javascript and Ruby, when it comes to DCI
> > compliancy. In my opinion, it is now one of the leading languages
> > when it comes to nailing DCI certification. Comments are appreciated!
>
> Claims like this instantly activate my bullshit detector, but I was frankly shocked and pleasantly surprised when reading on.

Hmmm... It is sort of bullshit, in that I can't ever see that I could
sell this to any Java people. They are too used to having the
compiler tell them when they make a mistake, and the compiler won't do
that here.

But, the argument one could use is that any Java person who uses JDBC
or JPA to talk to a database has "method calls" (i.e. SQL) written in
strings which the compiler does not check.

And anyway, one should always write unit tests - so any bugs due to
this "new" dynamic element of Java would be found anyway.

Still, I can imagine being laughed at, if I tried to sell this to my
Java SOA colleagues...

> What I don't like:
>
> 1. The current syntax. It's too much like a Smalltalk perform operation. In years of programming I've been unable to adjust to this style. If most of my method invocations have to be wrapped in a perform, then there is still a ways to go in reducing the impedance between the code and the programmer's brain. I like it better than the Qi4j syntax.

I'm not sure I can improve that. Instead of strings for method names,
I could use constants - would that help in any way?

> 2. The current syntax has some implications. It's going to be hard to get any JIT optimizations out of this "language" because of symbolic run-time method lookup. It is also going to be difficult to make a compiler that gives any degree of compile-time safety analysis, which is a hope worth holding out for Java.

Plenty of frameworks these days use reflection like this, so I am not
sure it is a problem. Yes, the compiler will not be able to do any
inlining, etc. But modern containers in Java EE use reflection so
much, I doubt we have a problem here.

I agree that writing a compiler for that syntax is going to be really
hard. That was my whole point about why type declarations are a good
thing.

> 4. Do you still really need that crappy overloaded equals method in the Dijkstra example? If so, there's something fundamentally wrong here.

Nope! There are no equals methods implemented except in the Pair
class, because the associative array implementation
(java.util.HashMap) depends on the hashcode, which defaults to
something similar to the memory address of an object. If the Geometry
classes stored the associations between nodes differently, with Nodes
as keys, rather than Pairs, then we could get away with removing the
Pair class, and with it, the last equals method in the code I posted.

> Puzzles:
>
> 1. How does this compare (on many axes) with Qi4j? I like the syntax better. If the run-time performance is the same (i.e., the reflection layer is being used underneath in the same way) this implementation has my vote for the best Java adaptation. We still need to look at the amount of static analysis available / possible; I don't have any intuition for how well Qi4j fares on that count.

I shall defer the answer to a Qi4J expert. Any takers? What's the
Dijkstra Algorigthm look like in Qi4J?

> 2. I'm interested in seeing the implementation. One way you might have done this would be to use the reflection API to create a new class on-the-fly, composing the original class and the "role" class at the point of injection, and to re-wire the method tables to re-type the extant object. (Does the VM allow that — in particular, the latter re-wiring?) If you could do that then this looks a lot like the Scala implementation and one might even be able to talk about interoperability between Java and Scala in a DCI world.

The implementation is below.

It basically works like this. All domain classes inherit from the
Data class. That contains two things. 1) An associative array of
fields - used for field injection. 2) a stack.

That stack contains objects of type ContextData. Those objects
contain a reference to the context and a reference to another
associative array which has keys which are the fully qualified name of
a role class, and values which are the instances of the role classes.

E.g.:

- myObject
- stack
- contextData1
- context
- roles
- ch.maxant.dciexample.contextname.Plane :
instanceOfPlaneClass...
- ch.maxant.dciexample.contextname.Car : instanceOfCarClass...
- contextData2
- context
- ch.maxant.dciexample.contextname.Plane :
instanceOfPlaneClass...
- ch.maxant.dciexample.contextname.Car : instanceOfCarClass...

Whenever an object and role are bound, I peek at the object's stack.
If the stack is empty, or the top of the stack references a different
context, then I push a new ContextData object onto the stack which
references the current context.

I then proceed to instantiate the role class and set the current
context and the domain object as the fields "context" and "self" in
that role instance respectively. I then store the role instance in
the contextData's associative array.

Finally I make a note of the role player, by adding it to a list. The
context knows all role players, because it needs to tidy them up at
the end.

When the cleanup() method is called on any context, that context goes
through the list of role players and pops their stack, removing
ContextData objects for that context.

When the "call" method is called on an object, it is passed the name
of the method to call, as well as the parameters. Reflection is used
to locate the method in the role on the current context. That method
is then called using reflection, and is passed the parameters.

If an object is playing two roles, and they both had a method called
"foo" then the framework takes the first one it finds, UNLESS, the
programmer provides the namespace like this:

myObject.call(Plane.class, "foo", Void.class, arg1, arg2);

The "Plane.class" is the namespace in which to find "foo". In fact,
if that Plane class is in a Java package, the namespace in which that
method is found also includes the package name, e.g.
"ch.maxant.dciexample.travelcontext.Plane".

The part about this syntax which I don't like, is that I am telling it
the return type (Void.class in the above example).
I don't have to do that. But if I leave it out, I have the problem
that the method "call" returns just a plain object, and because of
Java's type system, I need to cast it.

So I have two options:

1) int someNumber = myObject.call("someRoleMethod", Integer.class,
arg1, arg2, arg3);

2) int someNumber = (Integer) myObject.call("someRoleMethod", arg1,
arg2, arg3);

Which do you prefer?

What it also does is passes resources to roles, say for example a
database manager, so that the role can do useful stuff. Imagine this
in the context, just before the interaction is started:

addResource("theDatabase",
anEntityManagerInjectedIntoTheContextByAContainer);

And then inside the role implementation, you can have a field
annotated like this:

@Resource(name="theDatabase")
private EntityManager entityManager;

That field can then be used inside a role method, to access the
database:

em.create(anObject); //persists the object using object-relational-
mapping and SQL

The power of all that is that the context can have its resources
injected from the container in which it runs, which means that
resource configuration is entirely managed outside of the code. OK,
its not related to DCI at all, but is sexy anyway :-)

> 3. Does this implementation offer scoping advantages (particularly of role names) over in Qi4j? It might have some advantages because it's more integrated into the base language syntax instead of using annotations.

See above. As to what Qi4J can do, I again defer to an expert.

-------

Here is the impl, available under LGPL:

-----
/*
* Copyright (c) 2011 Ant Kutschera
*
* This file is part of Ant Kutschera's blog.
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
* You should have received a copy of the Lesser GNU General Public
License
* along with this software. If not, see <http://www.gnu.org/licenses/
>.
*/
package ch.maxant.dci.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import javax.annotation.Resource;

import ch.maxant.dci.util.Data.ContextData;

/**
* DCI contexts must inherit from this class
*/
public abstract class Context {

/** resources, which will be injected into roles as required. */
private Map<String, Object> resources = new HashMap<String,
Object>();

/** so that we can clean up, we keep a list of domain objects that
are assigned roles */
private Set<Data> domainObjects = new HashSet<Data>();

/**
* add a resource which can be injected into the role.
* <br><br>
* in the role, there may be a requirement to use say an {@link
EntityManager}
* in order to persist a new part of the domain model. the entity
manager could theoretically
* be passed to the role after its contruction, but the entity
manager has nothing to do
* with the users mental model - its a technical thing. so simply
let it be injected, and available,
* should it be required.
* <br><br>
* to use this, the current implementation looks for fields with the
"name" you pass. any such
* fields in either the role class, or any of its super classes,
which are marked with {@link Resource}
* get injected.
*/
public void addResource(String name, Object o){
resources.put(name, o);
}

/**
* assign an object a role. if its already in that role, nothing
happens!
* @param object the object to play the role
* @param roleClass the role to play
*/
public void bind(Data object, Class<? extends Role<?,?>> roleClass){

try {

//
// create role and inject into domain object
//
Field f = Data.class.getDeclaredField("contextData");
f.setAccessible(true);
@SuppressWarnings("unchecked")
Stack<Data.ContextData> contextData = (Stack<Data.ContextData>)
f.get(object);

if(contextData.isEmpty() || !
this.equals(contextData.peek().context)){
//never been in a context, or the context is a different one than
this
//so push this context onto the stack
contextData.push(new ContextData(this));
}

Map<String, Role<?,?>> roles = contextData.peek().tempRoles;
if(roles.containsKey(roleClass.getName())){

//
// ensure the context object contained in the role is the correct
one!
//
Role<?,?> roleInstance = roles.get(roleClass.getName());
if(!this.equals(roleInstance.context)){
//
// inject context into role
//
f = Role.class.getDeclaredField("context");
f.setAccessible(true);
f.set(roleInstance, this);
}

return;
}

@SuppressWarnings("rawtypes")
Constructor constructor = null;
int numParams = 99;
for(Constructor<?> c : roleClass.getDeclaredConstructors()){
if(c.getParameterTypes() == null){
numParams = 0;
constructor = c;
}else{
if(c.getParameterTypes().length < numParams){
numParams = c.getParameterTypes().length;
constructor = c;
}

}
}

if(constructor == null){
throw new RuntimeException("unable to find a constructor for role
" + roleClass);
}
constructor.setAccessible(true);
Role<?,?> roleInstance = (Role<?, ?>) constructor.newInstance(new
Object[numParams]);

roles.put(roleClass.getName(), roleInstance);

//
// inject self into role
//

f = Role.class.getDeclaredField("self");
f.setAccessible(true);
f.set(roleInstance, object);

//
// inject context into role
//
f = Role.class.getDeclaredField("context");
f.setAccessible(true);
f.set(roleInstance, this);

//
// inject resources into role
//

injectResources(roleInstance, roleInstance.getClass());

//
// remember this guy for later
//
domainObjects.add(object);

} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}

private void injectResources(Object roleInstance,
@SuppressWarnings("rawtypes") Class clazz) {
Field[] declaredFields = clazz.getDeclaredFields();
Field[] otherFields = clazz.getFields();
Field[] allFields = new Field[declaredFields.length +
otherFields.length];
System.arraycopy(declaredFields, 0, allFields, 0,
declaredFields.length);
System.arraycopy(otherFields, 0, allFields, declaredFields.length,
otherFields.length);

for (Field field : allFields) {
Annotation a = field.getAnnotation(Resource.class);
if (a != null) {
String name = field.getName();
Object resource = resources.get(name);
if(resource == null){
//check using the name defined in the annotation
String aName = ((Resource)a).name();
if(aName != null){
name = aName;
resource = resources.get(name);
}
}
if(resource != null){
field.setAccessible(true); //it may be private!
try {
field.set(roleInstance, resource);
} catch (Exception e) {
throw new RuntimeException("unable to set resource " + name + "
in class " + roleInstance.getClass().getName(), e);
}
}
}
}

// do it recursively, as roles may have super classes!
if(clazz.getSuperclass() != null){
injectResources(roleInstance, clazz.getSuperclass());
}

}

/** MUST BE CALLED WHEN A CONTEXT IS FINSHED WITH */
protected void cleanup(){

//cleanup needs to pop contexts, so that the context is the previous
one

for(Data d : domainObjects){
d.leavingDCIContext();
}
domainObjects.clear();
domainObjects = null; //help the GC
}

}
-----
/*
* Copyright (c) 2011 Ant Kutschera
*
* This file is part of Ant Kutschera's blog.
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
* You should have received a copy of the Lesser GNU General Public
License
* along with this software. If not, see <http://www.gnu.org/licenses/
>.
*/
package ch.maxant.dci.util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

/**
* this is a crazy class that lets us overcome object schizophrenia
* all domain classes which may need to play roles should inherit
from
* this object.
*/
public class Data {

/** context stack, used when a context calls sub-contexts or itself
*/
private Stack<ContextData> contextData = new Stack<ContextData>();

/** key = field name, value = role name */
private Map<String, Object> tempData = new HashMap<String, Object>();


/** lets you set extra data which you have added to this object, for
the lifetime of oldest parent context. */
public Object getData(String name){
return tempData.get(name);
}

/** lets you set extra data which you have added to this object, for
the lifetime of the context */
@SuppressWarnings("unchecked")
public <T> T getData(String name, Class<T> returnType) {
return (T)getData(name);
}


/** lets you set extra data on this object, for the lifetime of the
context */
public void setData(String name, Object data){
tempData.put(name, data);
}

/**
* called by the framework
*/
protected <C> void leavingDCIContext(){
ContextData old = this.contextData.pop();

if(this.contextData.isEmpty()){
this.tempData.clear();
}else{
for(Role<?,?> roleImpl : this.contextData.peek().tempRoles.values())
{
if(roleImpl.context.equals(old.context)){
//we would love to just set it, but its type isnt right - we only
know of it as
//Context, and the role class knows it as an subtype. so lets
use reflection!
//roleImpl.context = this.contextData.peek().context;
try {
Field f = roleImpl.getClass().getDeclaredField("context");
f.setAccessible(true);
f.set(roleImpl, this.contextData.peek().context);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}

/** call a role method on the object */
public <T> T call(String roleMethodName, Class<T> returnType,
Object... args) {
return call(null, roleMethodName, returnType, args);
}

/** call a role method on the object */
@SuppressWarnings("unchecked")
public <T> T call(Class<? extends Role<?,?>> role, String
roleMethodName, Class<T> returnType, Object... args) {

String roleName = null;
if(role != null){
roleName = role.getClass().getName();
}

//
// search for potential roles, based on the role name
//

List<Role<?,?>> potentialRoles = new ArrayList<Role<?,?>>();
if(roleName == null){
//search every role
potentialRoles = new ArrayList<Role<?,?
>>(contextData.peek().tempRoles.values());
}else{
//search only one role
potentialRoles.add(contextData.peek().tempRoles.get(roleName));
}

if(potentialRoles.isEmpty()) {
throw new IllegalStateException("no role found with name " +
roleName + " has this object been cast to that role?");
}

//
// search for the role method
//

Class<?>[] parameterTypes = null;
if(args != null){
parameterTypes = new Class<?>[args.length];
int i = 0;
for(Object o : args){
parameterTypes[i++] = o.getClass();
}
}
Role<?,?> roleInstance = null;
Method method = null;
for(Role<?,?> r : potentialRoles){
try{
method = r.getClass().getMethod(roleMethodName, parameterTypes);
roleInstance = r;
break;
}catch(NoSuchMethodException e){
//shame, try declared methods instead
try{
method = r.getClass().getDeclaredMethod(roleMethodName,
parameterTypes);
roleInstance = r;
break;
}catch(NoSuchMethodException e2){
//shame, keep looking
}
}
}

if(method == null) {
List<String> roleClasses = new
ArrayList<String>(potentialRoles.size());
for(Role<?,?> r : potentialRoles){
roleClasses.add(r.getClass().getName());
}
List<String> paramTypes = new
ArrayList<String>(parameterTypes.length);
for(Class<?> c : parameterTypes){
paramTypes.add(c.getName());
}
throw new UnsupportedOperationException("The method " +
roleMethodName + " with parameter types " + paramTypes + " could not
be found in role(s) " + roleClasses);
}

//
// invoke the method
//

try {
method.setAccessible(true);
return (T) method.invoke(roleInstance, args);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}

/** struct for storing stuff related to the current context */
static class ContextData {

/** the context in which the data / roles were bound */
Context context;

/** key = role name, value = object containing instance of role
class implementation */
Map<String, Role<?,?>> tempRoles = new HashMap<String, Role<?,?>>();

ContextData(Context context) {
this.context = context;
}
}

}
-----
/*
* Copyright (c) 2011 Ant Kutschera
*
* This file is part of Ant Kutschera's blog.
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
* You should have received a copy of the Lesser GNU General Public
License
* along with this software. If not, see <http://www.gnu.org/licenses/
>.
*/
package ch.maxant.dci.util;

public class Role<S, C> {

protected S self;

protected C context;

//not sure this is needed. only if in a role impl, you type
something like
// "this.equals(that)" - but you shouldn't use "this" in a role impl,
only "self"
@Override
public int hashCode() {
return self.hashCode();
}

//not sure this is needed. only if in a role impl, you type
something like
// "this.equals(that)" - but you shouldn't use "this" in a role impl,
only "self"
@Override
public boolean equals(Object obj) {
return self.equals(obj);
}

}
-----
/*
* Copyright (c) 2011 Ant Kutschera
*
* This file is part of Ant Kutschera's blog.
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
* You should have received a copy of the Lesser GNU General Public
License
* along with this software. If not, see <http://www.gnu.org/licenses/
>.
*/
package test;

import static org.junit.Assert.*;

import org.junit.Test;

import ch.maxant.dci.util.Context;
import ch.maxant.dci.util.Data;
import ch.maxant.dci.util.Role;

/**
* this is a black box test
*/
public class TestAll {

/** a domain class - dumb */
class Cart extends Data {
private String name;
private int numWheels;
public Cart(String name, int numWheels){
this.name = name;
this.numWheels = numWheels;
}
}

/** a context */
class TravelContext extends Context {

/** a role */
class Plane extends Role<Cart, TravelContext> {
void fly(){
self.setData("wingsExtended", true);
System.out.println("I'm flying man!!");
}
}

/** a role */
class Car extends Role<Cart, TravelContext> {
void drive(){
System.out.println("Only driving now...");
}
}

/** context constructor and execution */
TravelContext(Cart myFlyingCar){
bind(myFlyingCar, Car.class);
bind(myFlyingCar, Plane.class);

System.out.println("My cool transport is called: " +
myFlyingCar.name + " and it has " + myFlyingCar.numWheels + "
wheels.");

myFlyingCar.call("drive", Void.class);

myFlyingCar.call("fly", Void.class);

System.out.println("Are the wings are now extended? " +
myFlyingCar.getData("wingsExtended"));

//I can't write a test here to prove there is no object
schizophrenia,
//because there is only the single object and no wrappers!!

cleanup(); // housekeeping related to context stacking
}

}


@Test
public void test() {

Cart myCart = new Cart("betty", 4);

new TravelContext(myCart);

assertTrue(myCart.getData("wingsExtended") == null); //after the

Wenig, Stefan

unread,
Sep 29, 2011, 10:30:53 AM9/29/11
to dci-ev...@googlegroups.com
Yes, Java people sometimes accept sending commands a strings. But that's at the edge of the system, while DCI should be at its core. And just look at the lengths MS went to with LINQ to wipe out even this little aspect. As for whether dynamically or statically typed languages are a better fit for DCI, I think both will find their own fan bases. But once you settle for a static language, you want to have as much static verifiability as possible.

There's nothing wrong with unit testing. I believe without TDD dynamic languages would never have risen to where they are now. But a Java coder will expect that certain things just work, you just don't write unit tests to check on the compiler. Just as you don't write unit tests for pre/postconditions or invariants if you have language support for those. Any there are other advantages to static type systems, like more powerful refactorings, dependency analysis etc. What's the point of struggling with Java and its limited type system if you get none of those? Sounds like self-punishment, maybe DCI for Catholics?

My advice is that if you ask people whether they prefer your syntax or Qi4j, also ask them for their absolute preference. My guess is that those who favor your way over Qi4j will also prefer Ruby or something similar over both. So they won't use it, and what's the point then?

Stefan


> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of Ant Kutschera
> Sent: Thursday, September 29, 2011 3:46 PM
> To: dci-evolution
> Subject: Re: Roles in Eclipse Indigo
>

> extant object. (Does the VM allow that - in particular, the latter re-

rune funch

unread,
Sep 29, 2011, 11:32:44 AM9/29/11
to dci-ev...@googlegroups.com
Den 29/09/2011 kl. 15.46 skrev Ant Kutschera <ant.ku...@gmail.com>:

> Still, I can imagine being laughed at, if I tried to sell this to my
> Java SOA colleagues...

As Robbie Williams would say:"first they ignore you, then they laugh
at you, then they hate you, then you win"

Ant Kutschera

unread,
Sep 29, 2011, 4:41:33 PM9/29/11
to dci-evolution
On Sep 29, 4:30 pm, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
> And just look at the lengths MS went to with LINQ to wipe out even this little aspect.

Can you tell me more? Is LINQ typed? Or can the compiler do checks?

Ant Kutschera

unread,
Sep 29, 2011, 4:50:01 PM9/29/11
to dci-evolution
On Sep 29, 4:30 pm, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
> Yes, Java people sometimes accept sending commands a strings. But that's at the edge of the system, while DCI should be at its core. And just look at the lengths MS went to with LINQ to wipe out even this little aspect. As for whether dynamically or statically typed languages are a better fit for DCI, I think both will find their own fan bases. But once you settle for a static language, you want to have as much static verifiability as possible.
>
> There's nothing wrong with unit testing. I believe without TDD dynamic languages would never have risen to where they are now. But a Java coder will expect that certain things just work, you just don't write unit tests to check on the compiler. Just as you don't write unit tests for pre/postconditions or invariants if you have language support for those. Any there are other advantages to static type systems, like more powerful refactorings, dependency analysis etc. What's the point of struggling with Java and its limited type system if you get none of those? Sounds like self-punishment, maybe DCI for Catholics?
>
> My advice is that if you ask people whether they prefer your syntax or Qi4j, also ask them for their absolute preference. My guess is that those who favor your way over Qi4j will also prefer Ruby or something similar over both. So they won't use it, and what's the point then?
>
> Stefan

Hmmm.. I don't know which side to argue.

On the one hand, I have already said, I will get laughed at.

On the other hand, I choose to use Java, because it is the language
which I have to use if I want to use Java EE - that is, a mature
container which implements a bunch of standards which support me
hugely in writing business software. I stress again, I have chosen
the products, frameworks and standards, rather than the language.

So if I want to leverage the benefits which those give me, and combine
those benefits with DCI, I need a way to use DCI in Java. I can't
just use Ruby.

Either way, I am not ready at this stage to recommend anyone use this
new syntax in a productive environment. That is partly because I have
written a total of two algorithms using it, and partly because I am
not entirely ready to recommend anyone use DCI in a productive
environment - but that is an entirely different discussion.

Does anyone else have an opinion on what I have done with this new
Java syntax?

rune funch

unread,
Sep 29, 2011, 4:50:46 PM9/29/11
to dci-ev...@googlegroups.com

That's actually an interesting question. It's typed but it differs
from the rest of the type system in C# (except for foreach). It's
pattern based.

from item in collection
where item.Id > 10
select item

In the above statement the comparison is typed as any other statement
but the where and select is pattern based. That is as long as the type
of the collection has a method that matches the sought signature or
that there's an extension method that matches everything is fine

Ant Kutschera

unread,
Sep 29, 2011, 4:52:42 PM9/29/11
to dci-evolution
On Sep 29, 3:46 pm, Ant Kutschera <ant.kutsch...@gmail.com> wrote:
> On Sep 29, 11:44 am, "James O. Coplien" <jcopl...@gmail.com> wrote:
> > What I don't like:
>
> > 1. The current syntax. It's too much like a Smalltalk perform operation. In years of programming I've been unable to adjust to this style. If most of my method invocations have to be wrapped in a perform, then there is still a ways to go in reducing the impedance between the code and the programmer's brain. I like it better than the Qi4j syntax.
>
> I'm not sure I can improve that.  Instead of strings for method names,
> I could use constants - would that help in any way?

Actually, perhaps I can.

What about syntax like this:

myFlyingCar.call("aMethodWithParameters").passing(5,
4).notExpectingReturn();

int n =
myFlyingCar.call("aMethodWithReturnAndParameters").passing(6,
7).returning(Integer.class);

n =
myFlyingCar.call("aMethodWithReturnButNoParameters").returning(Integer.class);


myFlyingCar.call("aMethodWithNoReturnAndNoParameters").notExpectingReturn();

I have a working example with that, if anyone wants the source.

Is that a better syntax?

Ant Kutschera

unread,
Sep 29, 2011, 5:06:10 PM9/29/11
to dci-evolution
On Sep 29, 10:50 pm, rune funch <funchsolt...@gmail.com> wrote:
OK, that's quite cool that the compiler can check that for example
"item" has an attribute "id" (or a getter for "id"?).

In Java, if I do something similar with a database, I write something
similar to SQL, like this, for example:

query = em.createQuery("SELECT c FROM Country c where c.id = :id",
Country.class);

The compiler doesn't help check anything inside the quotes, so I am
entirely reliant on unit testing.

But the compiler could! Because "Country" is the name of a class and
in order for that statement to work, the class has to have an
attribute called "id".

Perhaps part of the problem is that this is part of the JPA
specification, which is not part of the base Java language, so they
don't think about it when making language changes. Or they are lazy
toads...

rune funch

unread,
Sep 29, 2011, 5:13:38 PM9/29/11
to dci-ev...@googlegroups.com
Den 29/09/2011 kl. 23.06 skrev Ant Kutschera <ant.ku...@gmail.com>:

> OK, that's quite cool that the compiler can check that for example
> "item" has an attribute "id" (or a getter for "id"?).

That part of the expression is typed as any other comparison in C#.
The type of item is known based on the type of the collection so
item.Id > 10 is just an ordinary expression. The select and where
however are not they are methods but not based in an interface or
type. There's several signatures possible for each and as long as one
with a matching signature is available for the source collection it
will compile

Wenig, Stefan

unread,
Sep 30, 2011, 2:50:19 AM9/30/11
to dci-ev...@googlegroups.com
There's actually a pretty straightforward answer: It is. Your sample gets translated to

collection.Where<TSource> ((TSource item) => item.Id > 10).Select<TSource> ((TSource item) => item);

and that's what gets compiled. You get full compile-time type safety, code completion, analyzability, refactoring. Anything you'd want a static language for. And that's what makes LINQ so attractive to programmers. They'll hate to write code in a matter that constantly makes them wish their language would just support this better. Making them switch to a dynamic language would be much easier.

Stefan

> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of rune funch
> Sent: Thursday, September 29, 2011 10:51 PM
> To: dci-ev...@googlegroups.com
> Subject: Re: Roles in Eclipse Indigo
>

Wenig, Stefan

unread,
Sep 30, 2011, 2:59:11 AM9/30/11
to dci-ev...@googlegroups.com
Why don't you use JRuby, Groovy or Jython? They all support the Java VM, so you'll have access to all the frameworks etc. Especially Groovy, which has been designed for the JVM and for Java programmers from the start. Ever tried any of these?

Stefan

> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of Ant Kutschera
> Sent: Thursday, September 29, 2011 10:50 PM
> To: dci-evolution
> Subject: Re: Roles in Eclipse Indigo
>

Ant Kutschera

unread,
Sep 30, 2011, 3:28:13 AM9/30/11
to dci-evolution
On Sep 30, 8:59 am, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
> Why don't you use JRuby, Groovy or Jython? They all support the Java VM, so you'll have access to all the frameworks etc. Especially Groovy, which has been designed for the JVM and for Java programmers from the start. Ever tried any of these?

Which one of them lets me elegantly inject methods, like I have done?
Show me a few lines of code to inject methods, then I'll go take a
look.

But I still want to leverage type safety in role implementations - can
I do that with JRuby, Groovy or Jython? It is only the context
implementation which suffers with my solution.

Wenig, Stefan

unread,
Sep 30, 2011, 5:46:19 AM9/30/11
to dci-ev...@googlegroups.com
> From: Ant Kutschera

> Which one of them lets me elegantly inject methods, like I have done?

I think they all do, and much more elegantly so.

> Show me a few lines of code to inject methods, then I'll go take a
> look.

Pick your language and google. Ruby has method_missing, Python has __getattr__, and I assume so do JRuby and Jython. Groovy has the meta object protocol. You can use any of those to dynamically add methods and call them without any special syntax.

Have you solved the name scoping problems in your Java version? That might be a bit hard to pull off in dynamic languages, but like I said before, if you know your active context, your implementation of method_missing etc. can respect that. You can also "unload" methods, but I'm still waiting for someone to explain to me how this solves any problem.

> But I still want to leverage type safety in role implementations - can
> I do that with JRuby, Groovy or Jython? It is only the context
> implementation which suffers with my solution.

In DCI, isn't all your interesting code going to be in RoleMethods? It seems you're looking for some kind of compromise, but which parts would you keep static? Data classes only? Static typing is a PITL thing, you'd be using it everywhere expect in the code that matters "in the large".

I don't think it's a good idea to split dynamic and static code if the dynamic part predominates, it would feel more sensible otherwise (like 'dynamic' in C#). People either go all-in on dynamic or on static. But if you can draw a good line, maybe you'd want to create your data classes in Java and your contexts and roles in Groovy? I don't know how this works in any JVM-based environment, you might not be able to use POJOs as data objects. (A C# object would have to implement IDynamicObject to allow for custom dispatching.)

In this case I'd definitely choose Groovy if possible, because it should be much easier to switch between those than between, say, Java and Ruby.)

Stefan

Rune Funch Søltoft

unread,
Sep 30, 2011, 6:14:10 AM9/30/11
to dci-ev...@googlegroups.com


2011/9/30 Wenig, Stefan <stefan...@rubicon.eu>

There's actually a pretty straightforward answer: It is. Your sample gets translated to

collection.Where<TSource> ((TSource item) => item.Id > 10).Select<TSource> ((TSource item) => item);

it's also an incorrect answer. First of all selects are not translated if there's no projection so the last part goes second of all the above is not using anything from the LINQ name space but an non generic instance method. And (not that that's part of your translation) collection is a really bad name for the object because in reality it's not a collection but a single object that has where implemented top play with monads and idioms;

The result of the where is also of a different type than the source.

There's way more to query comprehension, types and method overload resolution than you usually have to care about as a programmer. It does however create a very flexible system if you know the rules and are able to use them. See http://tomasp.net/blog/idioms-in-linq.aspx

-Rune

James O. Coplien

unread,
Sep 30, 2011, 6:58:23 AM9/30/11
to dci-ev...@googlegroups.com
Don't give up on compile-time type safety. After all, this stuff all works pretty well in C++. It isn't compile-time typing that is the main stumbling block. It's that Java sliced the type system in the wrong place.

James O. Coplien

unread,
Sep 30, 2011, 6:58:37 AM9/30/11
to dci-ev...@googlegroups.com
Stefan,

How does this contribution further DCI evolution?

DCI works well where DCI works well. There are places where it stumbles. I'm happy to see work on this list to move DCI adaptation forward in broad programming areas.

While it is good to bring DCI even to the niche, minority programming areas such as you mention, I would rather that we help Ant advance our understanding of DCI in a particular context than to terminate the dialogue — especially just after what might be a significant advance in that area.

I want people on this list to think big. The goal isn't to just make this work at home, but to influence the industry. I have a reasonable shot at motivating the Java standards effort to make changes to Java so it supports DCI. This has been my vision from early on. It was the reason I travelled to meet Rickard Öberg and his CTO and offered all the support that Trygve and I could give them in doing Qi4j.

Now, I have been invited to give an inaugural Heart of Technology Lecture in Potsdam in March, 2012. It is likely that all of the relevant people will be there, and in particular, people from the AOP community. This is an opportunity to take this community's message forward. I'm checking with the organizer to see if we can make a small group presentation. But more than that, it will be an opportunity (I hope) to meet with the likes of Guy Steele and other key Java community members to present our case. It would be nice to have a concrete list of Java changes that would support our vision.

Anyone who wants to go off and fulfill their vision of transforming the JRuby community is of course welcome to do so. My goal is to honor Trygve's DCI vision by opening the door to broad applicability, and to gain insights into the real programming problems of DCI by banging it up against one of the most broadly used programming platforms on earth. I think Ant is doing a good job of that, and if he doesn't deserve our support, he at least deserves not to be told to stop doing what he's doing.

James O. Coplien

unread,
Sep 30, 2011, 6:58:45 AM9/30/11
to dci-ev...@googlegroups.com
I think these experiments all give us hints on how we could or should change Java if we had free reign.

Start with what you think the syntax SHOULD be, and let's go from there.

Wenig, Stefan

unread,
Sep 30, 2011, 10:21:22 AM9/30/11
to dci-ev...@googlegroups.com
On Fr, Sep 30, 2011 at 12:14:10, Rune Funch Søltoft wrote:
> There's actually a pretty straightforward answer: It is. Your sample
> gets translated to
>
> collection.Where<TSource> ((TSource item) => item.Id >
> 10).Select<TSource> ((TSource item) => item);
>
>
>
> it's also an incorrect answer.

LINQ is typesafe. I don't know where you're going with the "but..." part of your answer. Query expressions are transformed to regular C#, then translated like anything else. I know LINQ internals pretty well, but your answer confused even me. There is no separate type system for LINQ, so I wanted to clarify this.

> First of all selects are not translated
> if there's no projection so the last part goes

That was just for illustration. Yes, there's a special case for 'select's that do nothing.

> second of all the above
> is not using anything from the LINQ name space but an non generic
> instance method.

Sorry, but that's just wrong. The exact translation of Ant's where clause .Where (item => item.Id > 10). Whether this will link to a Enumerable or Queryable method, another extension method or an instance method cannot be told from this snippet. Whether the methods are generic or not is unclear too. At this point, C#'s normal method resolution mechanism kicks in, and it might choose a non-generic method, or use type inferencing to choose a generic one. The usual translation is Queryable or Enumerable.Where using the type parameters I spelled out for clarity. Anything else is very rare.

> And (not that that's part of your translation)
> collection is a really bad name for the object because in reality it's
> not a collection but a single object that has where implemented top
> play with monads and idioms;

Huh? It might be a collection, or something else. Don't see the problem. The relationship of LINQ to monads is overrated, it's an abuse of the system that creates unreadable code.

> The result of the where is also of a different type than the source.

Really? You can do this, but nobody does. Usually, a 'where' just filters, and this does not change the type.

Why are you even trying to fisk me here?

Wenig, Stefan

unread,
Sep 30, 2011, 10:26:27 AM9/30/11
to dci-ev...@googlegroups.com
+10

Better slicing is what both Qi4j and re-mix are trying to achieve, and that's also what makes Scala a good fit for DCI. It's what hyperslices and use case slices are all about, so I like your use of the word slice!

I don't tell anyone to give up type safety. Actually, you can take it from my cold dead hands, and that's not because I don't see the advantages of dynamic languages but because I've made a choice and I accept the consequences. My only point is that if you cannot have type safety in the heart of your system, you're in a strange place.

Stefan

> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of James O. Coplien
> Sent: Friday, September 30, 2011 12:58 PM
> To: dci-ev...@googlegroups.com
> Subject: Re: Roles in Eclipse Indigo
>

Ant Kutschera

unread,
Sep 30, 2011, 10:41:05 AM9/30/11
to dci-evolution
On Sep 30, 11:46 am, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
> > From: Ant Kutschera
> > Which one of them lets me elegantly inject methods, like I have done?
>
> I think they all do, and much more elegantly so.
>
> > Show me a few lines of code to inject methods, then I'll go take a
> > look.
>
> Pick your language and google. Ruby has method_missing, Python has __getattr__, and I assume so do JRuby and Jython. Groovy has the meta object protocol. You can use any of those to dynamically add methods and call them without any special syntax.

Thanks for the tips.

One thing to ask one's self is whether the method injection occurs at
the object level, or at the class level. Traits for example are at
the class level. In DCI we are doing this at the object level - that
is an important difference. Scala for example currently supports
traits and it does the injection at the class level. What I have done
to Java is at the object level. Individual objects are given methods.

> Have you solved the name scoping problems in your Java version?

I think so, see my previous post. Or perhaps I don't know which
scoping problem you are talking about. You mean if a role player is
playing two roles which have identical methods, which one gets
called? Yes, that is solved, see previous comments.

> You can also "unload" methods, but I'm still waiting for someone to explain to me how this solves any problem.

If you mean remove the methods which were injected in a context, at
the end of the context, then I am of the opinion that it is extremely
important to do so.

You add methods within a context, and that makes sense within that
context. To leave those methods hanging on to the object after the
context is complete is dangerous, because the objects from the
context's interaction may go off in their directions and the context
is missing in which they can interact. Why give the programmer the
chance to do something stupid? I would rather remove the injected
methods and let the object leave the context looking the way it came
in, just with (potentially) different state. Much like an actor
removes his makeup after the play has finished.

A more concrete example: if you use my new Java, and you add a
resource like a database to a role, and inject that role into an
object, it could be fatal to then call a role-method on that role
outside of the context, because the connection to the database might
have been closed.

> In DCI, isn't all your interesting code going to be in RoleMethods?

See my Till example, or the Dijkstra example - some may be in the
context itself.

> It seems you're looking for some kind of compromise, but which parts would you keep static? Data classes only? Static typing is a PITL thing, you'd be using it everywhere expect in the code that matters "in the large".

I want to keep as much static as I can. I like that about Java, and
the tool support I get with it. But the argument about merged-
interfaces in the Dijkstra example showed that sometimes you need to
have the ability to be dynamic. In those cases, and only those cases,
I want to leverage the dynamic capability.

Mixing languages is an interesting idea, and I will consider it some
more. The problem at the moment is that I don't think there are any
tools which let you mix code within a project. E.g. in the IDE of my
choice, can I create a class in a Java project, and instantiate it in
a Groovy project? I'll look into it some more, as that could be a
viable option... What I cannot do is write Groovy and Java in the
same source file!

I've just been reading more about Groovy...

Interestingly, MOP in Groovy is similar to what I just added to Java:
http://groovy.codehaus.org/api/groovy/lang/MetaObjectProtocol.html#getMetaMethod%28java.lang.String,%20java.lang.Object[]%29

That method is pretty much what I added to Java, without me knowing
someone else had done it in Groovy... So what I have done is not as
dumb as it looks. Great minds think alike ;-)

This forum is about the evolution of a DCI. I wasn't suggesting you
take what I did and go to production with it. I was experimenting
with what you'd have to do to Java to make it work. If we didn't
experiment, we'd still be living in caves ;-)

rune funch

unread,
Sep 30, 2011, 10:42:12 AM9/30/11
to dci-ev...@googlegroups.com
Den 30/09/2011 kl. 16.21 skrev "Wenig, Stefan" <stefan...@rubicon.eu>:

> Sorry, but that's just wrong. The exact translation of Ant's where clause .Where (item => item.Id > 10). Whether this will link to a Enumerable or Queryable method, another extension method or an instance method cannot be told from this snippet.

No but the code is mine I have the implementation. It's not wrong and
I don't know how this is even remotely related to DCI.

> Whether the methods are generic or not is unclear too. At this point, C#'s normal method resolution mechanism kicks in,

That's the point it's NOT the normal method overload resolution until
the the translation has happened. Which creates a lot more flexibility
and extensibility. The latter being the reason why the compiler team
chose to make it pattern-based rather than type based.

Ant asked if LINQ was typed the answer I gave was yes but query
comprehension uses different type rules than anything else in C#
except foreach which is also pattern-based. Since the question
originated from some one trying to map a concept to a different tool I
think those details are important not to mention that you could easily
use a pattern-based type system when it comes to RolePlayers so in
regards to DCI the concept of having selected parts of the language
being based on a pattern-based type system becomes even more
interesting

-Rune

Wenig, Stefan

unread,
Sep 30, 2011, 11:07:15 AM9/30/11
to dci-ev...@googlegroups.com
On Fr, Sep 30, 2011 at 12:58:37, James O. Coplien wrote:
> While it is good to bring DCI even to the niche, minority programming
> areas such as you mention,

That's not my goal. I think that if you want to get a Java programmer to use DCI, you should either
a) offer them a way to do it in Java that meets their expectations, including static type checking, or
b) advise them to move to a dynamic language altogether.

This is not about transforming the JRuby/Jython/Groovy community. And obviously, I prefer a).

> I would rather that we help Ant advance our
> understanding of DCI in a particular context than to terminate the

> dialogue - especially just after what might be a significant advance
> in that area.

Well, he was asking for opinions, and I offered one. I have no beef with Ant's code if it's supposed to be experimental and improve our understanding. But if he worries about Java seniors laughing at him, I believe he thinks beyond the boundaries of our little google group. That's what I was talking about.

> I want people on this list to think big. The goal isn't to just make
> this work at home, but to influence the industry. I have a reasonable
> shot at motivating the Java standards effort to make changes to Java
> so it supports DCI. This has been my vision from early on. It was the
> reason I travelled to meet Rickard Öberg and his CTO and offered all
> the support that Trygve and I could give them in doing Qi4j.

There's a chance that Java will include something like C#'s 'dynamic', which would mean Ant could get rid of the strange calling syntax.

But the really interesting part, from a Java POV, would be to extend static typing to DCI. If you look at Qi4j and re-mix, we use as much static typing as we can. But there are limits and opportunities, e.g.:
- in Qi4j, you need to define your composition in a central place, which can create hard dependency problems.
- in re-mix, you usually cast objects at runtime to access the methods of their mixins, which solves the dependency problem but limits the checking that can be done at compile-time. To do better, we'd have to use code generation (which is easy enough to code, but a PITA to use)
- a DCI-ready type system could go much further (I believe you'd have to give up on some of the dynamicism you often refer to, such as unmixin mixins at runtime, though)
- if a data object has a method foo (A a), and is used in a context where role R is bound to an object of type A, a good type system could map this method to foo (R r) in the role contract
- I'm sure there's more

I haven't had a real chance to look at ObjectTeams yet, I'd be excited to learn that it has some solutions to these problems. The bad news is that no single feature added to Java will solve these hard problems. I guess Stephan has a story or two about how hard it is to design such a language. Knowing the JCP, I don't know.

You mentioned you like Ant's syntax better than Qi4j's. What is it that you like less in Qi4j? Can't be the calling syntax. The annotations? The use of interfaces? The latter would lead to a separate debate (are explicit role interfaces or role contracts a good thing or a bad thing?)

> Now, I have been invited to give an inaugural Heart of Technology
> Lecture in Potsdam in March, 2012. It is likely that all of the
> relevant people will be there, and in particular, people from the AOP
> community. This is an opportunity to take this community's message
> forward. I'm checking with the organizer to see if we can make a small
> group presentation. But more than that, it will be an opportunity (I
> hope) to meet with the likes of Guy Steele and other key Java
> community members to present our case. It would be nice to have a
> concrete list of Java changes that would support our vision.
>
> Anyone who wants to go off and fulfill their vision of transforming
> the JRuby community is of course welcome to do so. My goal is to honor
> Trygve's DCI vision by opening the door to broad applicability, and to
> gain insights into the real programming problems of DCI by banging it
> up against one of the most broadly used programming platforms on earth.

I was under the impression that you'd rather look for the perfect DCI language than succumb to any existing platform, but I have no issue with designing a new Java. There should be a clear vision up-front though. Should it be something like C#'s dynamic, or do we try and go all the way to support DCI in the type system like a Java coder would expect it?

My starting point would be to code something in Java/Qi4j or C#/re-mix and then
- find an integrated syntax for all the annotation/attribute stuff
- one by one, find solutions for the leaks that these libraries leave open, like those I mentioned above

However, just look how much effort that was for Stephan: https://www.ohloh.net/p/objectteams
I can't think of a simple modification that makes Java a good fit for DCI. Even Scala does not solve the basic dependency problems.

> I think Ant is doing a good job of that, and if he doesn't deserve our
> support, he at least deserves not to be told to stop doing what he's
> doing.

I think if he asks for opinions, he deserves an honest answer. If he's looking for a research platform, fine with me. But knowing Ant I believe he has something immediately usable in mind, not?

But maybe I'm just missing the big breakthrough here. My reading was that Ant's version brings Java closer to dynamic languages (except for the strange syntax), but loses all advantages of static typing on the way. Is there anything else that I skipped over?

Wenig, Stefan

unread,
Sep 30, 2011, 11:13:32 AM9/30/11
to dci-ev...@googlegroups.com
Ok, I was thinking you were just questioning my assertion that LINQ is strongly typed. I don't see the connection between the pattern-based pre-translation of LINQ query expressions and a pattern-based type system yet, or how patterns translate to RoleMethods. Can you elaborate?

Stefan

> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of rune funch
> Sent: Friday, September 30, 2011 4:42 PM
> To: dci-ev...@googlegroups.com
> Subject: Re: Roles in Eclipse Indigo
>

James O. Coplien

unread,
Sep 30, 2011, 11:13:53 AM9/30/11
to dci-ev...@googlegroups.com

On Sep 30, 2011, at 5:07 , Wenig, Stefan wrote:

> That's not my goal. I think that if you want to get a Java programmer to use DCI, you should either
> a) offer them a way to do it in Java that meets their expectations, including static type checking, or
> b) advise them to move to a dynamic language altogether.

It is not my goals to convert Java programmers. I actually think that is a lost cause, for many of the reasons that Ant has mentioned that people won't like his code.

I want to transform the Java leadership.


> This is not about transforming the JRuby/Jython/Groovy community. And obviously, I prefer a).

To each their own. My goal would be to get as much static type support in Java as I can for DCI. That will take some hard language design work. Scala, combined with Ant's work, and the Qi4j experience, combine to paint a hopeful picture. That's where I'm headed.

Wenig, Stefan

unread,
Sep 30, 2011, 11:20:12 AM9/30/11
to dci-ev...@googlegroups.com
> To each their own. My goal would be to get as much static type support
> in Java as I can for DCI. That will take some hard language design
> work. Scala, combined with Ant's work, and the Qi4j experience, combine
> to paint a hopeful picture. That's where I'm headed.

Love to hear that! Like I said, my interest is not in converting to dynamic, but in bringing enough compile-time type info for DCI to make the static typing struggle worthwhile.

Did you have a chance yet to check out OT/J? I would assume that it covers at least some of that ground?

Wenig, Stefan

unread,
Sep 30, 2011, 11:32:01 AM9/30/11
to dci-ev...@googlegroups.com
On Fr, Sep 30, 2011 at 16:41:05, Ant Kutschera wrote:
> Subject: Re: Roles in Eclipse Indigo
>
> On Sep 30, 11:46 am, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
> > > From: Ant Kutschera
> > > Which one of them lets me elegantly inject methods, like I have
> done?
> >
> > I think they all do, and much more elegantly so.
> >
> > > Show me a few lines of code to inject methods, then I'll go take a
> > > look.
> >
> > Pick your language and google. Ruby has method_missing, Python has
> __getattr__, and I assume so do JRuby and Jython. Groovy has the meta
> object protocol. You can use any of those to dynamically add methods
> and call them without any special syntax.
>
> Thanks for the tips.
>
> One thing to ask one's self is whether the method injection occurs at
> the object level, or at the class level. Traits for example are at
> the class level. In DCI we are doing this at the object level - that
> is an important difference. Scala for example currently supports
> traits and it does the injection at the class level. What I have done
> to Java is at the object level. Individual objects are given methods.

I understand the difference in philosophy and in implementation, I have yet to see the impact this has on DCI code. With object injection, the methods are injected on-demand. With class injection, they are always there. One difference with class injection is that the compiler can verify that the class fulfills the role contract.

> > Have you solved the name scoping problems in your Java version?
>
> I think so, see my previous post. Or perhaps I don't know which
> scoping problem you are talking about. You mean if a role player is
> playing two roles which have identical methods, which one gets called?
> Yes, that is solved, see previous comments.

Found it, thanks. Problem is, you need to provide an optional parameter. If you omit this parameter, a conflict might arise at runtime, which is harder to find, plus you'll have to go back to every call and add the qualifier. At this point, the code might be compiled and out of your hands. (Assuming that composition occurs at deployment time or runtime.)

Might seem a bit reaching, but if you build a product or platform rather than a project, this is an issue.

> > You can also "unload" methods, but I'm still waiting for someone to
> explain to me how this solves any problem.
>
> If you mean remove the methods which were injected in a context, at
> the end of the context, then I am of the opinion that it is extremely
> important to do so.
>
> You add methods within a context, and that makes sense within that
> context. To leave those methods hanging on to the object after the
> context is complete is dangerous, because the objects from the
> context's interaction may go off in their directions and the context
> is missing in which they can interact. Why give the programmer the
> chance to do something stupid? I would rather remove the injected
> methods and let the object leave the context looking the way it came
> in, just with
> (potentially) different state. Much like an actor removes his makeup
> after the play has finished.

My take is that calls are only valid from inside the context, so if you can solve this problem (e.g. using visibility), you solved the other one too. OTOH, if your only requirement is that the context must be alive, you can still make calls from outside the context. Also, chances are that a solution to visibility will also solve name conflicts.

> A more concrete example: if you use my new Java, and you add a
> resource like a database to a role, and inject that role into an
> object, it could be fatal to then call a role-method on that role
> outside of the context, because the connection to the database might have been closed.
>
> > In DCI, isn't all your interesting code going to be in RoleMethods?
>
> See my Till example, or the Dijkstra example - some may be in the
> context itself.
>
> > It seems you're looking for some kind of compromise, but which parts
> would you keep static? Data classes only? Static typing is a PITL
> thing, you'd be using it everywhere expect in the code that matters
> "in the large".
>
> I want to keep as much static as I can. I like that about Java, and
> the tool support I get with it. But the argument about merged-
> interfaces in the Dijkstra example showed that sometimes you need to
> have the ability to be dynamic. In those cases, and only those cases,
> I want to leverage the dynamic capability.
>
> Mixing languages is an interesting idea, and I will consider it some
> more. The problem at the moment is that I don't think there are any
> tools which let you mix code within a project. E.g. in the IDE of my
> choice, can I create a class in a Java project, and instantiate it in
> a Groovy project? I'll look into it some more, as that could be a
> viable option... What I cannot do is write Groovy and Java in the
> same source file!

True. It's not an ideal solution.

> I've just been reading more about Groovy...
>
> Interestingly, MOP in Groovy is similar to what I just added to Java:
> http://groovy.codehaus.org/api/groovy/lang/MetaObjectProtocol.html#get
> M
> etaMethod%28java.lang.String,%20java.lang.Object[]%29
>
> That method is pretty much what I added to Java, without me knowing
> someone else had done it in Groovy... So what I have done is not as
> dumb as it looks. Great minds think alike ;-)

No, it's not dumb by any means. But even very clever ideas can lead to quite unsatisfying results.

> This forum is about the evolution of a DCI. I wasn't suggesting you
> take what I did and go to production with it. I was experimenting
> with what you'd have to do to Java to make it work. If we didn't
> experiment, we'd still be living in caves ;-)

Right, I think I got that part wrong.

Stefan

James O. Coplien

unread,
Sep 30, 2011, 1:03:41 PM9/30/11
to dci-ev...@googlegroups.com

On Sep 30, 2011, at 5:20 , Wenig, Stefan wrote:

> Did you have a chance yet to check out OT/J? I would assume that it covers at least some of that ground?


Yes, I've looked at it pretty thoroughly. I'm not taking it much further because it isn't quite the right computational model. Identity is a crucial component of most type systems, and it is one of the three most important components of an OO type system. The identity model in OT/J isn't what I'm looking for.

James O. Coplien

unread,
Sep 30, 2011, 1:04:23 PM9/30/11
to dci-ev...@googlegroups.com

On Sep 30, 2011, at 5:32 , Wenig, Stefan wrote:

> No, it's not dumb by any means. But even very clever ideas can lead to quite unsatisfying results.


And vice versa sometimes, too.

Ant Kutschera

unread,
Sep 30, 2011, 4:21:30 PM9/30/11
to dci-evolution

Ant Kutschera

unread,
Sep 30, 2011, 5:20:34 PM9/30/11
to dci-evolution
I spent a few hours creating some really ugly code tonight.

I wondered if I could use generics and the Java type system to provide
type checking at compile time to the dynamic method call I added a few
days ago.

Here is the single nice line of code which came out of it:

String s = myFlyingCar.call(plane.fly).passing("a", 1);

So, I no longer pass a string as the method name, and that the call
returns a string, and takes a string and integer which are completely
checked by the type system. If I make changes, I get compiler
errors! Horray!!

Now the ugly bit. Put your sunglasses on, and grab a paper bag in
case it makes you sick:

The role implentation is this:

/** a role */
class Plane extends Role<Cart, TravelContext> {

//this is a constant, used to refer to
//the method, rather than using a string name.
//A tiny bit like a function pointer...
RoleMethodFly fly;

public Plane(TravelContext ctx){
fly = new RoleMethodFly(ctx);
}

class RoleMethodFly extends RoleMethod<Cart, String,
TravelContext, String, Integer, Void> {
public RoleMethodFly(TravelContext ctx){ super(ctx); }

//this is the actual role method - this is the only bit we
really need!! but it's name "run" is super ugly.
@Override
public String run(String a, Integer b, Void v) {
self.setData("wingsExtended", true);
String msg = "I'm flying man!! a=" + a + ", b=" + b + ", on
the " + context.travelDate;
log(msg);
return msg;
}
}
}


So, the role method is an object...! And the role is also
instantiated, even though that instance is never used and is unrelated
to the object playing the role.

Back to the drawing board...

But I also spent time looking at how Scala and Groovy do their magic.
They, and in fact Java too (with inner classes) create simple byte
code, which if decompiled looks nothing like the original code. For
example, a trait in Scala is implemented in byte code as an interface
and a static method. There is no magic hacking of symbol tables or
anything. It just creates ugly byte code under the hood, which
programmers never see.

So the good new is that it should be really easy for Java to have type
checked dynamic methods!

The compiler simply has to check whether the name after a "." symbol
is something it can find in the declared type, and if it isn't, it has
to generate some byte code to do what the code above does.

If only I knew the first thing about writing compilers...

Ant Kutschera

unread,
Sep 30, 2011, 5:25:53 PM9/30/11
to dci-evolution
On Sep 30, 5:07 pm, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
> However, just look how much effort that was for Stephan:https://www.ohloh.net/p/objectteams

"Estimated project cost: $23,779,538"

Wow.

Ant Kutschera

unread,
Sep 30, 2011, 5:28:09 PM9/30/11
to dci-evolution
On Sep 30, 12:58 pm, "James O. Coplien" <jcopl...@gmail.com> wrote:
> Start with what you think the syntax SHOULD be, and let's go from there.

It should be as simple as:

myFlyingPlane.fly();

If you write a compiler to generate byte code which does something
similar to what I did tonight, you'd have DCI in Java.

Ant Kutschera

unread,
Sep 30, 2011, 5:30:36 PM9/30/11
to dci-evolution
On Sep 30, 11:46 am, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
> People either go all-in on dynamic or on static.

Not so sure. Scala now supports dynamic method calls, letting the
programmer choose what is best for any case they are implementing:

http://squirrelsewer.blogspot.com/2011/02/scalas-upcoming-dynamic-capabilities.html

Why shouldn't Java do it too?

rune funch

unread,
Oct 1, 2011, 1:05:56 AM10/1/11
to dci-ev...@googlegroups.com
> On Sep 30, 11:46 am, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
>> People either go all-in on dynamic or on static
Or they choose the tool based on the task at hand. Sometime FP (eg.
Linq), dynamic (eg interop) fullOO (what the system does) etc

Trygve Reenskaug

unread,
Oct 1, 2011, 1:36:02 AM10/1/11
to dci-ev...@googlegroups.com
Looks very much like my BabyIDE code which would be
    MyFlyingPlane fly.
Role names capitalized by convention. Keep up the good works.
--

Trygve Reenskaug       mailto: try...@ifi.uio.no

Morgedalsvn. 5A         http://folk.uio.no/trygver/

N-0378 Oslo               Tel: (+47) 22 49 57 27

Norway

James O. Coplien

unread,
Oct 1, 2011, 7:23:57 AM10/1/11
to dci-ev...@googlegroups.com

On Sep 30, 2011, at 4:41 , Ant Kutschera wrote:

> You add methods within a context, and that makes sense within that
> context. To leave those methods hanging on to the object after the
> context is complete is dangerous, because the objects from the
> context's interaction may go off in their directions and the context
> is missing in which they can interact. Why give the programmer the
> chance to do something stupid?


A good type-checking system would make it unnecessary to actually remove the methods. Such a type system could also accommodate name collisions that occur from injecting different roles over time.

This is, indeed, quite an elaborate type system, but I don't see anything about it that is intractable.

There are tradeoffs between making such a type-checking system dynamic or static.

Ant Kutschera

unread,
Oct 1, 2011, 5:30:44 PM10/1/11
to dci-evolution
On Oct 1, 7:36 am, Trygve Reenskaug <tryg...@ifi.uio.no> wrote:
> Looks very much like my BabyIDE code which would be
>     MyFlyingPlane fly.
> Role names capitalized by convention. Keep up the good works.
>
> On 2011.09.30 23:28, Ant Kutschera wrote:
>
> > On Sep 30, 12:58 pm, "James O. Coplien" <jcopl...@gmail.com> wrote:
>
> >> Start with what you think the syntax SHOULD be, and let's go from there.
>
> > It should be as simple as:
>
> >     myFlyingPlane.fly();
>


I think the following might be even better:

String s = domainObject#SomeRole.roleMethod(parameter1,
parameter2);

That reads: call roleMethod from SomeRole on domainObject, passing it
parameter1 and parameter2. The return value is a String.

"SomeRole" is important to avoid name clashes.

Ant Kutschera

unread,
Oct 1, 2011, 5:43:50 PM10/1/11
to dci-evolution
A few weeks ago, I tried to argue that type declarations were
necessary because they help to ensured that an object is playing a
role. The arguement was something like this:

If the code looks like this:

Role player = assign(domainObject, Role.class);

then I am sure that "player" has the method "roleMethod", so I can
call it.

The argument against the type declaration was that a good compiler
would work out that player can have the method "roleMethod" called on
it.

Then I tried arguing that with code like this:

domainObject = ...
if(Random.nextBoolean()){
assign(domainObject, Role.class);
}

it is impossible to determine if the object is playing a role at
compile time.

Well, so what!

This type of thing happens all the time in the real world. Things
might be null, or of a different type than you expect. E.g.:

String s = "hello";
if(Random.nextBoolean()){
s = null;
}

//is s null or not?!

So, what does the programmer do? A null check:

if(s == null){
....
}else{
....
}

Same is true if an object is playing a role or not. What we need is
not for the compiler to tell us, rather we need a way to check if the
object is a role player. And if your code relies on them playing a
role and they are not, throw an exception, just as you would if your
code relied on an object not being null, and it was null.

So, for cases where you cannot tell if an object is playing a role or
not, something like this will be useful:

if(Role.isPlayedBy(domainObject){
...
}else{
...
}

That check would look at the current context to decide if the object
was playing that role in the current context.

Trygve Reenskaug

unread,
Oct 2, 2011, 5:01:11 AM10/2/11
to dci-ev...@googlegroups.com
Yes, that's even better. Qualify the name of a role method with its role name. Possibly also its Context name. This ties in with my new idea for an "ideal" DCI method execution model. (In principle simple, but waiting to get on the top of my stack to be written down)

Ant Kutschera

unread,
Oct 2, 2011, 5:38:28 AM10/2/11
to dci-evolution
I've done it! Java is now fully DCI compliant, and in doing so
supports Java's type checker as well as full IDE integration.

Quote 1:

On Sep 30, 5:32 pm, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
> > > Have you solved the name scoping problems in your Java version?
>
> > I think so, see my previous post. Or perhaps I don't know which
> > scoping problem you are talking about. You mean if a role player is
> > playing two roles which have identical methods, which one gets called?
> > Yes, that is solved, see previous comments.
>
> Found it, thanks. Problem is, you need to provide an optional parameter. If you omit this parameter, a conflict might arise at runtime, which is harder to find, plus you'll have to go back to every call and add the qualifier. At this point, the code might be compiled and out of your hands. (Assuming that composition occurs at deployment time or runtime.)

Quote 2:

On Oct 2, 11:01 am, Trygve Reenskaug <tryg...@ifi.uio.no> wrote:
> Yes, that's even better. Qualify the name of a role method with its role
> name. Possibly also its Context name. This ties in with my new idea for
> an "ideal" DCI method execution model. (In principle simple, but waiting
> to get on the top of my stack to be written down)
>
> On 2011.09.30 23:28, Ant Kutschera wrote:
> > I think the following might be even better:
> >
> > String s = domainObject#SomeRole.roleMethod(parameter1, parameter2);


Stefan & Trygve, you are geniuses! When I read this, my first thought
was "hey, ok, so we always need to fully qualify the role method we
are calling".

And in doing that, I have been able to make my little extension to
Java provide full type safety and IDE integration!

Last night I posted that the ideal role method call might look like
this:

//Listing 1
String s = domainObject#SomeRole.roleMethod(parameter1,
parameter2);

(SomeRole only exists within a given Context, so there is no need to
add that before the role name - the compiler wouldn't let me anyway,
if the role was out of scope)

So I was about a quarter way into adding that syntax to the OpenJKD
compiler, when it struck me. Why not just do it like this:

//Listing 2
String s =
myDomainObject.callRole(SomeRole.class).aRoleMethod(parameter1,
parameter2);

This syntax is a little uglier than my proposal with the "#" symbol,
but it is far better than the syntax I proposed a few days ago, where
the method to call is provided using a string. Does anyone have any
suggestions as to how to make the utility method name "callRole"
better?

I've been able to update my framework so that I can write exactly that
shown in listing 2, and it works great. The compiler is able to tell
me if parameter types are wrong, code-complete offers me all the role
methods on "SomeRole". The IDE can do refactoring, just like it can
on any other class. The IDE can jump into role methods when I hit F3,
just like it can on normal Java methods. It's perfect, from the type
checking and IDE points of view.

So, below is some test code to show how it all works.

After that, is Dijkstra's Algorithm, implemented with the new
framework, and again, semantically it is identical to that done in
Ruby, syntactically it is a little different, but much better than
what I posted a few days ago, because the compiler and tools help you
in the same way they help you when you write normal Java code.

Those interested in how I implemented this, check the third section
below - there are three classes: DomainObject, Context, Role. The
mechanisms are a little different than that described a few days ago.
But importantly, context stacking is supported, as well as the ability
to "unassign" roles when a context is finished with, so that those
roles go out of scope.

What do you think?



----- test code -----

/** a domain class - dumb */
class Cart extends DomainObject {
private String name;
private int numWheels;
public Cart(String name, int numWheels){
this.name = name;
this.numWheels = numWheels;
}
}

/** a context */
class TravelContext extends Context {

Date travelDate;

/** a role */
class Plane extends Role<Cart, TravelContext> {
void fly(){
self.setData("wingsExtended", true);
log("I'm flying on the " + new SimpleDateFormat("dd
MMM yyyy").format(context.travelDate) + " man!!");
}
}

/** a role */
class Car extends Role<Cart, TravelContext> {
void drive(){
log("Only driving now...");
}

void aMethodWithParameters(Integer a, Integer b){
log("aMethodWithParameters called with " + a + " and "
+ b);
}

int aMethodWithReturnAndParameters(Integer a, Integer b){
int result = a + b;
log("aMethodWithReturnAndParameters called with " + a
+ " and " + b + ", returning " + result);
return result;
}

int aMethodWithReturnButNoParameters(){
int result = 69;
log("aMethodWithReturnButNoParameters called,
returning 69");
return result;
}

void aMethodWithNoReturnAndNoParameters(){
log("aMethodWithNoReturnAndNoParameters called");
}

}

/** context constructor and execution
* @param log */
TravelContext(Cart myFlyingCar) throws ParseException {

travelDate = new
SimpleDateFormat("yyyyMMdd").parse("20111002");

//lets check its not already playing the role in this
context
assertFalse(myFlyingCar.isPlayingRole(Car.class));
assertFalse(myFlyingCar.isPlayingRole(Plane.class));

bind(myFlyingCar, Car.class);
bind(myFlyingCar, Plane.class);

//lets check it is now playing the role in this context
assertTrue(myFlyingCar.isPlayingRole(Car.class));
assertTrue(myFlyingCar.isPlayingRole(Plane.class));

log("My cool transport is called: " + myFlyingCar.name + "
and it has " + myFlyingCar.numWheels + " wheels.");

myFlyingCar.callRole(Car.class).drive();

myFlyingCar.callRole(Plane.class).fly();

log("Are the wings are now extended? " +
myFlyingCar.getData("wingsExtended"));

myFlyingCar.callRole(Car.class).aMethodWithParameters(5,
4);

int n =
myFlyingCar.callRole(Car.class).aMethodWithReturnAndParameters(6, 7);
assertTrue(n == 13);

n =
myFlyingCar.callRole(Car.class).aMethodWithReturnButNoParameters();
assertTrue(n == 69);


myFlyingCar.callRole(Car.class).aMethodWithNoReturnAndNoParameters();

//I can't write a test here to prove there is no object
schizophrenia,
//because there is only the single object and no
wrappers!!

cleanup(); // housekeeping related to context stacking
}

}

@Test
public void test() throws ParseException {

log.clear();

Cart myCart = new Cart("betty", 4);

//create and run context in one step
new TravelContext(myCart);

assertTrue(myCart.getData("wingsExtended") == null); //after
the context, the field no longer exists on the object

System.out.println(log);

assertEquals(16, log.size());
int i = 0;
assertEquals("My cool transport is called: betty and it has 4
wheels.", log.get(i++));
assertEquals("\r\n", log.get(i++));
assertEquals("Only driving now...", log.get(i++));
assertEquals("\r\n", log.get(i++));
assertEquals("I'm flying on the 02 Okt 2011 man!!", log.get(i+
+));
assertEquals("\r\n", log.get(i++));
assertEquals("Are the wings are now extended? true", log.get(i+
+));
assertEquals("\r\n", log.get(i++));
assertEquals("aMethodWithParameters called with 5 and 4",
log.get(i++));
assertEquals("\r\n", log.get(i++));
assertEquals("aMethodWithReturnAndParameters called with 6 and
7, returning 13", log.get(i++));
assertEquals("\r\n", log.get(i++));
assertEquals("aMethodWithReturnButNoParameters called,
returning 69", log.get(i++));
assertEquals("\r\n", log.get(i++));
assertEquals("aMethodWithNoReturnAndNoParameters called",
log.get(i++));
assertEquals("\r\n", log.get(i++));

assertFalse(myCart.isPlayingRole(Car.class));
assertFalse(myCart.isPlayingRole(Plane.class));
}

}

----- djikstra solution -----

package three;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ch.maxant.dci.util2.Context;
import ch.maxant.dci.util2.DomainObject;
import ch.maxant.dci.util2.Role;

/**
* here, I have removed need to use a type declaration for a merged
interface.
* there is no merged interface. I only ever refer to objects by
their actual data
* type.
*
* Oh, and I have solved the object schizophrenia problem too.
*/
public class Runner {

////////////////////////////////////////////////////////////////

static class Pair {
private Object a;
private Object b;

public Pair(Object a, Object b) {
this.a = a;
this.b = b;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((a == null) ? 0 :
a.hashCode());
result = prime * result + ((b == null) ? 0 :
b.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Pair other = (Pair) obj;
if (a == null) {
if (other.a != null)
return false;
} else if (!a.equals(other.a))
return false;
if (b == null) {
if (other.b != null)
return false;
} else if (!b.equals(other.b))
return false;
return true;
}

@Override
public String toString() {
return "Pair [" + a + ", " + b + "]";
}

}

////////////////////////////////////////////////////////////////

/**
* use less than infinity, otherwise some of the calcs go wrong,
* when we add tentative distances to actual distances and we end
* up with negative numbers, because weve gone over max INT.
*/
static final Integer INFINITY = Integer.MAX_VALUE - 10000;

////////////////////////////////////////////////////////////////

/**
* "Map" as in cartography rather than Computer Science...
*
* Map is technically a role from the DCI perspective. The role
* in this example is played by an object representing a
particular
* Manhattan geometry
*/
static class CartographyMap extends Role<Geometry, Object> {

Integer distance_between(Node a, Node b) {
return self.getDistances().get(new Pair(a, b));
}

// These two functions presume always travelling
// in a southern or easterly direction

Node next_down_the_street_from(Node node) {
return self.east_neighbor_of(node);
}

Node next_along_the_avenue_from(Node node) {
return self.south_neighborOf(node);
}

}

////////////////////////////////////////////////////////////////

/**
* There are four roles in the algorithm: CurrentIntersection
(@current)
* EastNeighbor, which lies DIRECTLY to the east of
CurrentIntersection
* (@east_neighbor) SouthernNeighbor, which is DIRECTLy to its
south
* (@south_neighbor) Destination, the target node (@destination)
*
* We also add a role of Map (@map) as the oracle for the geometry
*
* The algorithm is straight from Wikipedia:
*
* http://en.wikipedia.org/wiki/Dijkstra's_algorithm
*
* and reads directly from the distance method, below
*
* (use context type "Object" because this role is found in
several contexts - we still need to think how to handle duplicate code
in a better way...)
*/
static class Distance_labeled_graph_node extends Role<Node,
Object> {

/*
* NOTE: This role creates a new data member in the node into
* which it is injected. An alernative implementation
would
* be to use a separate associative array
*/

void set_tentative_distance_to(Integer x) {
self.setData("tentative_distance", x);
}
}

////////////////////////////////////////////////////////////////

/**
* Consider street corners on a Manhattan grid. We want to find
the
* minimal path from the most northeast city to the most
* southeast city. Use Dijstra's algorithm
*
* Data class
*
* Note there is NO NEED to implement hashCode or equals!
*/
static class Node extends DomainObject {

private String name;

public Node(String name) {
this.name = name;
}

public String getName() {
return name;
}

/** only done for debugging purposes */
@Override
public String toString() {
return "Node[name=" + name + ", hashCode=" + hashCode() +
"]";
}
}

////////////////////////////////////////////////////////////////

/**
* This is the main Context for shortest path calculation
*/
static class CalculateShortestPath extends Context {

//These are handles to internal housekeeping arrays set up in
initialize

Map<Node, Boolean> unvisited = new HashMap<Node, Boolean>();
Map<Node, Node> pathTo;
Node east_neighbor;
Node south_neighbor;
List<Node> path;
Geometry map;
Node current;
Node destination;

// Initialization

void rebind(Node origin_node, Geometry geometries){
current = origin_node;
map = geometries;

bind(map, CartographyMap.class);

bind(current, CurrentIntersection.class);

east_neighbor = map.east_neighbor_of(origin_node);

for(Node n : geometries.nodes()){
bind(n, Distance_labeled_graph_node.class);
}

if(east_neighbor != null){
bind(east_neighbor, Neighbor.class);
}

south_neighbor = map.south_neighborOf(origin_node);

if(south_neighbor != null){
bind(south_neighbor, Neighbor.class);
}
}

/**
* public initialize. It's overloaded so that the public
version doesn't
* have to pass a lot of crap; the initialize method takes
care of
* setting up internal data structures on the first
invocation. On
* recursion we override the defaults
*/
public CalculateShortestPath(Node origin_node, Node
target_node, Geometry geometries, List<Node> path_vector,
Map<Node, Boolean> unvisited_hash, Map<Node, Node>
pathto_hash) {

destination = target_node;

rebind(origin_node, geometries);

// This has to come after rebind is done
if (path_vector == null) {

// This is the fundamental data structure for
Dijkstra's algorithm,
// called
// "Q" in the Wikipedia description. It is a boolean
hash that maps
// a
// node onto false or true according to whether it has
been visited
this.unvisited = new HashMap<Node, Boolean>();

// These initializations are directly from the
description of the
// algorithm
for (Node n : geometries.getNodes()) {
this.unvisited.put(n, Boolean.TRUE);

n.callRole(Distance_labeled_graph_node.class).set_tentative_distance_to(INFINITY);
}


current.callRole(Distance_labeled_graph_node.class).set_tentative_distance_to(0);

this.unvisited.remove(origin_node);

// The path array is kept in the outermost context and
serves to
// store the
// return path. Each recurring context may add
something to the
// array along
// the way. However, because of the nature of the
algorithm,
// individual
// Context instances don't deliver "partial paths" as
partial
// answers.
this.path = new ArrayList<Node>();

// The pathTo map is a local associative array that
remembers the
// arrows between nodes through the array and erases
them if we
// re-label a node with a shorter distance
this.pathTo = new HashMap<Node, Node>();

} else {

this.unvisited = unvisited_hash;
this.path = path_vector;
this.pathTo = pathto_hash;
}

execute();
}

class CurrentIntersection extends Role<Node,
CalculateShortestPath> {

List<Node> unvisited_neighbors() {

//WATCHOUT: moved the access to data from the context,
from outside this method,
//to in inside it, otherwise we are introducing state
to the role
Map<Node, Boolean> unvisited = context.unvisited;
Node south_neighbor = context.south_neighbor;
Node east_neighbor = context.east_neighbor;

List<Node> retval = new ArrayList<Node>();
if (south_neighbor != null) {
Boolean addIt = unvisited.get(south_neighbor);
if (addIt == Boolean.TRUE) { //watch out, addIt
can be null apparently
retval.add(south_neighbor);
}
}
if (east_neighbor != null) {
Boolean addIt = unvisited.get(east_neighbor);
if (addIt == Boolean.TRUE) { //watch out, addIt
can be null apparently
retval.add(east_neighbor);
}

}
return retval;
}
}

/**
* This module serves to provide the methods both for the
east_neighbor and south_neighbor roles
*/
class Neighbor extends Role<Node, CalculateShortestPath> {

boolean relable_node_as(Integer x) {
if (x < (Integer)self.getData("tentative_distance")) {


self.callRole(Distance_labeled_graph_node.class).set_tentative_distance_to(x);
return true;
} else {
return false;
}
}
}

/**
* This is the method that does the work. Called from
initialize
*/
public void execute() {
// Calculate tentative distances of unvisited neighbors
List<Node> unvisited_neighbors =
current.callRole(CurrentIntersection.class).unvisited_neighbors();
if (unvisited_neighbors != null) {
for (Node neighbor : unvisited_neighbors) {

Integer tentativeDistance = (Integer)
current.getData("tentative_distance");
Integer distanceBetween =
map.callRole(CartographyMap.class).distance_between(current,
neighbor);
boolean relable_node_as =
neighbor.callRole(Neighbor.class).relable_node_as(tentativeDistance +
distanceBetween);

if (relable_node_as) {
pathTo.put(neighbor, current);
}
}
}

unvisited.remove(current);

// Are we done?

if (unvisited.size() == 0) {
save_path(this.path);
} else {

// The next current node is the one with the least
distance in the
// unvisited set

Node selection = nearest_unvisited_node_to_target();

// Recur
new CalculateShortestPath(selection, destination, map,
path, unvisited, pathTo);
}
}

Node nearest_unvisited_node_to_target() {

int min = INFINITY;
Node selection = null;

for (Node intersection : unvisited.keySet()) {
if (unvisited.get(intersection)) {

if(intersection.getData("tentative_distance",
Integer.class) <= min) {

min =
intersection.getData("tentative_distance", Integer.class);
selection = intersection;
}
}
}
return selection;
}

/**
* This method does a simple traversal of the data structures
(following
* pathTo) to build the directed traversal vector for the
minimum path
*/
void save_path(List<Node> pathVector) {

Node node = destination;
do {
pathVector.add(node);

node = pathTo.get(node);

} while (node != null);
}

public List<Node> getPath() {
return path;
}

}

////////////////////////////////////////////////////////////////


/**
* This is the main Context for shortest distance calculation
*/
static class CalculateShortestDistance extends Context {

List<Node> path = new ArrayList<Node>();
Geometry map;
Node destination;
Node current;

//MAP ROLE: SEE COMMON CODE NEAR TOP
//DISTANCE LABELED GRAPH NODE: SEE COMMON CODE NEAR TOP

void rebind(Node origin_node, Geometry geometries){
current = origin_node;
destination = geometries.destination();
map = geometries;

bind(map, CartographyMap.class);

for(Node node : map.nodes()){
bind(node, Distance_labeled_graph_node.class);
}
}

public CalculateShortestDistance(Node origin_node, Node
target_node, Geometry geometries) {

rebind(origin_node, geometries);


this.current.callRole(Distance_labeled_graph_node.class).set_tentative_distance_to(0);

this.path = new CalculateShortestPath(this.current,
this.destination, geometries, null, null, null).getPath();

cleanup(); //related to context stacking
}

public int distance() {
int retval = 0;
Node previous_node = null;

List<Node> reversed = new ArrayList<Node>(path);
Collections.reverse(reversed);

for (Node node : reversed) {

if (previous_node == null) {
retval = 0;
} else {
retval +=
this.map.callRole(CartographyMap.class).distance_between(previous_node,
node);
}
previous_node = node;
}
return retval;
}
}

////////////////////////////////////////////////////////////////

static abstract class Geometry extends DomainObject {

List<Node> nodes;
Node root;
Node destination;
Map<Pair, Integer> distances;
Map<Node, Node> next_down_the_street_from = new HashMap<Node,
Node>();
Map<Node, Node> next_along_the_avenue_from = new HashMap<Node,
Node>();

public Node east_neighbor_of(Node a) {
return next_down_the_street_from.get(a);
}

public Node south_neighborOf(Node a) {
return next_along_the_avenue_from.get(a);
}

public Node root() {
return root;
}

public Node destination() {
return destination;
}

public List<Node> nodes() {
return nodes;
}

public Node getRoot() {
return root;
}

public Node getDestination() {
return destination;
}

public List<Node> getNodes() {
return nodes;
}

public Map<Pair, Integer> getDistances() {
return distances;
}
}

////////////////////////////////////////////////////////////////

static class ManhattanGeometry1 extends Geometry {

public ManhattanGeometry1() {
this.nodes = new ArrayList<Node>();
this.distances = new HashMap<Pair, Integer>();

String[] names = { "a", "b", "c", "d", "a", "b", "g", "h",
"i" };

for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
this.nodes.add(new Node(names[(i * 3) + j]));
}
}

// Aliases to help set up the grid. Grid is of Manhattan
form:
//
// a - 2 - b - 3 - c
// | | |
// 1 2 1
// | | |
// d - 1 - e - 1 - f
// | |
// 2 4
// | |
// g - 1 - h - 2 - i
//
Node a = this.nodes.get(0);
root = a;
Node b = this.nodes.get(1);
Node c = this.nodes.get(2);
Node d = this.nodes.get(3);
Node e = this.nodes.get(4);
Node f = this.nodes.get(5);
Node g = this.nodes.get(6);
Node h = this.nodes.get(7);
Node i = this.nodes.get(8);
destination = i;

for (int s = 0; s < 3; s++) {
for (int t = 0; t < 3; t++) {
this.distances.put(new Pair(nodes.get(s),
nodes.get(t)), INFINITY);
}
}

distances.put(new Pair(a, b), 2);
distances.put(new Pair(b, c), 3);
distances.put(new Pair(c, f), 1);
distances.put(new Pair(f, i), 4);
distances.put(new Pair(b, e), 2);
distances.put(new Pair(e, f), 1);
distances.put(new Pair(a, d), 1);
distances.put(new Pair(d, g), 2);
distances.put(new Pair(g, h), 1);
distances.put(new Pair(h, i), 2);
distances.put(new Pair(d, e), 1);

distances = Collections.unmodifiableMap(distances);

next_down_the_street_from.put(a, b);
next_down_the_street_from.put(b, c);
next_down_the_street_from.put(d, e);
next_down_the_street_from.put(e, f);
next_down_the_street_from.put(g, h);
next_down_the_street_from.put(h, i);
next_down_the_street_from = Collections
.unmodifiableMap(next_down_the_street_from);

next_along_the_avenue_from.put(a, d);
next_along_the_avenue_from.put(b, e);
next_along_the_avenue_from.put(c, f);
next_along_the_avenue_from.put(d, g);
next_along_the_avenue_from.put(f, i);

next_along_the_avenue_from = Collections
.unmodifiableMap(next_along_the_avenue_from);
}

}

////////////////////////////////////////////////////////////////

static class ManhattanGeometry2 extends Geometry {

public ManhattanGeometry2() {
this.nodes = new ArrayList<Node>();
this.distances = new HashMap<Pair, Integer>();

String[] names = { "a", "b", "c", "d", "a", "b", "g", "h",
"i", "j", "k" };

for (int j = 0; j < 11; j++) {
nodes.add(new Node(names[j]));
}

// Aliases to help set up the grid. Grid is of Manhattan
form:
//
// a - 2 - b - 3 - c - 1 - j
// | | | |
// 1 2 1 |
// | | | |
// d - 1 - e - 1 - f 1
// | | |
// 2 4 |
// | | |
// g - 1 - h - 2 - i - 2 - k
//
Node a = nodes.get(0);
root = a;
Node b = nodes.get(1);
Node c = nodes.get(2);
Node d = nodes.get(3);
Node e = nodes.get(4);
Node f = nodes.get(5);
Node g = nodes.get(6);
Node h = nodes.get(7);
Node i = nodes.get(8);
Node j = nodes.get(9);
Node k = nodes.get(10);
destination = k;

for (int s = 0; s < 11; s++) {
for (int t = 0; t < 11; t++) {
distances.put(new Pair(nodes.get(s),
nodes.get(t)),
INFINITY);
}
}

distances.put(new Pair(a, b), 2);
distances.put(new Pair(b, c), 3);
distances.put(new Pair(c, f), 1);
distances.put(new Pair(f, i), 4);
distances.put(new Pair(b, e), 2);
distances.put(new Pair(e, f), 1);
distances.put(new Pair(a, d), 1);
distances.put(new Pair(d, g), 2);
distances.put(new Pair(g, h), 1);
distances.put(new Pair(h, i), 2);
distances.put(new Pair(d, e), 1);
distances.put(new Pair(c, j), 1);
distances.put(new Pair(j, k), 1);
distances.put(new Pair(i, k), 2);

distances = Collections.unmodifiableMap(distances);

next_down_the_street_from.put(a, b);
next_down_the_street_from.put(b, c);
next_down_the_street_from.put(c, j);
next_down_the_street_from.put(d, e);
next_down_the_street_from.put(e, f);
next_down_the_street_from.put(g, h);
next_down_the_street_from.put(h, i);
next_down_the_street_from.put(i, k);

next_down_the_street_from = Collections
.unmodifiableMap(next_down_the_street_from);

next_along_the_avenue_from.put(a, d);
next_along_the_avenue_from.put(b, e);
next_along_the_avenue_from.put(c, f);
next_along_the_avenue_from.put(d, g);
next_along_the_avenue_from.put(f, i);
next_along_the_avenue_from.put(j, k);

next_along_the_avenue_from = Collections
.unmodifiableMap(next_along_the_avenue_from);
}

}

////////////////////////////////////////////////////////////////

/** Test drivers */
public static void main(String[] args) {

Geometry geometries = new ManhattanGeometry1();

CalculateShortestPath path = new
CalculateShortestPath(geometries.getRoot(),
geometries.getDestination(), geometries, null, null,
null);

System.out.println("Path is: ");
for (Node node : path.getPath()) {
System.out.println(node.getName());
}

System.out.println("distance is "
+ new CalculateShortestDistance(geometries.getRoot(),
geometries.getDestination(),
geometries).distance());

System.out.println();

geometries = new ManhattanGeometry2();

path = new CalculateShortestPath(geometries.getRoot(),
geometries.getDestination(), geometries, null, null,
null);

System.out.println("Path is: ");
Node last_node = null;
for (Node node : path.getPath()) {
if (last_node != null) {
System.out.print(" - "
+ geometries.distances.get(new Pair(node,
last_node))
+ " - ");
}
System.out.print(node.getName());
last_node = node;
}

System.out.println();
System.out.println("distance is "
+ new CalculateShortestDistance(geometries.getRoot(),
geometries.getDestination(),
geometries).distance());
}

}

---- output -----

Path is:
i
h
g
d
a
distance is 6

Path is:
k - 1 - j - 1 - c - 3 - b - 2 - a
distance is 7


----- framework impl -----

/*
* Copyright (c) 2011 Ant Kutschera
*
* This file is part of Ant Kutschera's blog.
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
* You should have received a copy of the Lesser GNU General Public
License
* along with this software. If not, see <http://www.gnu.org/licenses/
>.
*/
package ch.maxant.dci.util2;


/**
* Here are some rules to consider when implementing roles:<br>
* <br>
* 1) roles must NEVER EVER use "this". Instead they should always
use "self".
* <br><br>
* 2) role methods should NEVER EVER use primative types as parameter
types. e.g. "int" as a parameter will not work.<br>
* use Integer instead. e.g.: <br>
* <br>
* <code>void someRoleMethod(Integer anInt, String someString){...}
</code><br>
*
* @param <S> the type or interface which domain objects playing this
role must be. used to provide the correct type to the
* field "self" which subclasses should use in the methods
they provide. "this" should NEVER BE USED!
* @param <C> the type of the context to which this role belongs.
used to provide the correct type on the field "context" which
* role implementations have access to.
*/
public class Role<S, C> {

/** the object playing this role */
protected S self;

/** the context owning this role */
protected C context;

//not sure this is needed. only if in a role impl, you type
something like
// "this.equals(that)" - but you shouldn't use "this" in a role
impl, only "self"
@Override
public int hashCode() {
return self.hashCode();
}

//not sure this is needed. only if in a role impl, you type
something like
// "this.equals(that)" - but you shouldn't use "this" in a role
impl, only "self"
@Override
public boolean equals(Object obj) {
if(self == null){
if(obj == null){
return true;
}else{
return false;
}
}else{
return self.equals(obj);
}
}

@SuppressWarnings("unchecked")
public Role() {
try {
Context ctx = Helper.getContext();
this.context = (C)ctx;
} catch (SecurityException e) {
throw new RuntimeException("code change required in this
class!", e);
} catch (IllegalArgumentException e) {
throw new RuntimeException("code change required in this
class!", e);
} catch (NoSuchFieldException e) {
throw new RuntimeException("code change required in this
class!", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("code change required in this
class!", e);
}
}

}


/*
* Copyright (c) 2011 Ant Kutschera
*
* This file is part of Ant Kutschera's blog.
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
* You should have received a copy of the Lesser GNU General Public
License
* along with this software. If not, see <http://www.gnu.org/licenses/
>.
*/
package ch.maxant.dci.util2;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import javax.annotation.Resource;

/**
* DCI contexts must inherit from this class
*/
public abstract class Context {

private static final ThreadLocal<Stack<Context>> contextStacks =
new ThreadLocal<Stack<Context>>();
static {
contextStacks.set(new Stack<Context>());
}

/** resources, which will be injected into roles as required. */
private Map<String, Object> resources = new HashMap<String,
Object>();

/** so that we can clean up, we keep a list of domain objects that
are assigned roles */
private Map<DomainObject, Set<Class<? extends Role<?,?>>>>
domainObjects = new HashMap<DomainObject, Set<Class<? extends Role<?,?
>>>>();

public Context() {
contextStacks.get().push(this);
}

/**
* add a resource which can be injected into the role.
* <br><br>
* in the role, there may be a requirement to use say an {@link
EntityManager}
* in order to persist a new part of the domain model. the entity
manager could theoretically
* be passed to the role after its contruction, but the entity
manager has nothing to do
* with the users mental model - its a technical thing. so simply
let it be injected, and available,
* should it be required.
* <br><br>
* to use this, the current implementation looks for fields with
the "name" you pass. any such
* fields in either the role class, or any of its super classes,
which are marked with {@link Resource}
* get injected.
*/
public void addResource(String name, Object resource){
resources.put(name, resource);
}

/**
* assign an object a role. if its already in that role, nothing
happens!
* @param object the object to play the role
* @param roleClass the role to play
*/
public void bind(DomainObject object, Class<? extends Role<?,?>>
roleClass){

Set<Class<? extends Role<?,?>>> roles =
domainObjects.get(object);
if(roles == null){
roles = new HashSet<Class<? extends Role<?,?>>>();
domainObjects.put(object, roles);
}
roles.add(roleClass);
}

/** MUST BE CALLED WHEN A CONTEXT IS FINSHED WITH */
protected void cleanup(){

//cleanup needs to pop contexts, so that the context is the
previous one

Stack<Context> contextStack = contextStacks.get();
contextStack.pop();

resources.clear();
resources = null; //help the GC

if(contextStack.isEmpty()){
//clear all temp data on the object
try {
Field f =
DomainObject.class.getDeclaredField("tempData");
f.setAccessible(true);
for(DomainObject o : domainObjects.keySet()){
@SuppressWarnings("rawtypes")
Map m = (Map)f.get(o);
m.clear();
}
} catch (SecurityException e) {
throw new RuntimeException("code change required in
this class!", e);
} catch (NoSuchFieldException e) {
throw new RuntimeException("code change required in
this class!", e);
} catch (IllegalArgumentException e) {
throw new RuntimeException("code change required in
this class!", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("code change required in
this class!", e);
}
}

domainObjects.clear();
domainObjects = null; //help the GC
}

}



/*
* Copyright (c) 2011 Ant Kutschera
*
* This file is part of Ant Kutschera's blog.
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Lesser GNU General Public License for more details.
* You should have received a copy of the Lesser GNU General Public
License
* along with this software. If not, see <http://www.gnu.org/licenses/
>.
*/
package ch.maxant.dci.util2;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;

import ch.maxant.dci.util2.Role;

/**
* domain classes should extend this
*/
public class DomainObject {

/** key = field name, value = role name */
private Map<String, Object> tempData = new HashMap<String,
Object>();

/** lets you set extra data which you have added to this object,
for the lifetime of oldest parent context. */
public Object getData(String name){
return tempData.get(name);
}

/** lets you set extra data which you have added to this object,
for the lifetime of the context */
@SuppressWarnings("unchecked")
public <T> T getData(String name, Class<T> returnType) {
return (T)getData(name);
}

/** lets you set extra data on this object, for the lifetime of
the context */
public void setData(String name, Object data){
tempData.put(name, data);
}

/** to call a role method on a role, call this method first */
@SuppressWarnings("unchecked")
public <T> T callRole(Class<T> roleClass) {

if(roleClass == null){
throw new NullPointerException("roleClass must be
supplied");
}

if(!Role.class.isAssignableFrom(roleClass)){
throw new IllegalArgumentException("the roleClass
parameter (" + roleClass + ") must be an instance of the class Role");
}

try{
Context context = Helper.getContext();

if(context == null){
throw new IllegalStateException("role methods can only
be called on objects where a valid context exists, and no context
could be found. instantiate a class of type Context before calling
this method.");
}

//
// is it playing that role?
//
if(!isPlayingRole((Class<? extends Role<?,?>>)roleClass)){
throw new IllegalStateException("the object " + this +
" is not currently playing the role " + roleClass + " in the context "
+ context);
}

//
// instantiate the role class
//

@SuppressWarnings("rawtypes")
Constructor constructor = null;
int numParams = 99;
for(Constructor<?> c : roleClass.getDeclaredConstructors())
{
if(c.getParameterTypes() == null){
numParams = 0;
constructor = c;
}else{
if(c.getParameterTypes().length < numParams){
numParams = c.getParameterTypes().length;
constructor = c;
}

}
}

if(constructor == null){
throw new RuntimeException("unable to find a
constructor for role " + roleClass);
}
constructor.setAccessible(true);
Role<?,?> roleInstance = (Role<?, ?>)
constructor.newInstance(new Object[numParams]);

//
// inject self into role
//

Field f = Role.class.getDeclaredField("self");
f.setAccessible(true);
f.set(roleInstance, this);

//
// inject resources into role
//

f = Context.class.getDeclaredField("resources");
f.setAccessible(true);
Map<String, Object> resources = (Map<String, Object>)
f.get(context);
if(resources != null && !resources.isEmpty()){
injectResources(roleInstance, roleInstance.getClass(),
resources);
}

return (T)roleInstance;

} catch (SecurityException e) {
throw new RuntimeException("code change required in this
class!", e);
} catch (NoSuchFieldException e) {
throw new RuntimeException("code change required in this
class!", e);
} catch (IllegalArgumentException e) {
throw new RuntimeException("code change required in this
class!", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("code change required in this
class!", e);
} catch (InstantiationException e) {
throw new RuntimeException("code change required in this
class!", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("code change required in this
class!", e);
}
}

private void injectResources(Object roleInstance,
@SuppressWarnings("rawtypes") Class clazz, Map<String, Object>
resources) {
Field[] declaredFields = clazz.getDeclaredFields();
Field[] otherFields = clazz.getFields();
Field[] allFields = new Field[declaredFields.length +
otherFields.length];
System.arraycopy(declaredFields, 0, allFields, 0,
declaredFields.length);
System.arraycopy(otherFields, 0, allFields,
declaredFields.length, otherFields.length);

for (Field field : allFields) {
Annotation a = field.getAnnotation(Resource.class);
if (a != null) {
String name = field.getName();
Object resource = resources.get(name);
if(resource == null){
//check using the name defined in the annotation
String aName = ((Resource)a).name();
if(aName != null){
name = aName;
resource = resources.get(name);
}
}
if(resource != null){
field.setAccessible(true); //it may be private!
try {
field.set(roleInstance, resource);
} catch (Exception e) {
throw new RuntimeException("unable to set
resource " + name + " in class " + roleInstance.getClass().getName(),
e);
}
}
}
}

// do it recursively, as roles may have super classes!
if(clazz.getSuperclass() != null){
injectResources(roleInstance, clazz.getSuperclass(),
resources);
}

}

public boolean isPlayingRole(Class<? extends Role<?,?>> roleClass)
{

try{
Context ctx = Helper.getContext();
if(ctx == null){
return false;
}

Field f = Context.class.getDeclaredField("domainObjects");
f.setAccessible(true);
@SuppressWarnings("unchecked")
Map<DomainObject, Set<Class<? extends Role<?,?>>>>
domainObjects = (Map<DomainObject, Set<Class<? extends Role<?,?
>>>>)f.get(ctx);
Set<Class<? extends Role<?,?>>> roles =
domainObjects.get(this);
if(roles != null && !roles.isEmpty()){
if(roles.contains(roleClass)){
return true;
}
}
return false;
} catch (SecurityException e) {
throw new RuntimeException("code change required in this
class!", e);
} catch (NoSuchFieldException e) {
throw new RuntimeException("code change required in this
class!", e);
} catch (IllegalArgumentException e) {
throw new RuntimeException("code change required in this
class!", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("code change required in this
class!", e);
}
}

}

James O. Coplien

unread,
Oct 1, 2011, 7:38:45 AM10/1/11
to dci-ev...@googlegroups.com
I like the code generation aspect of this, but I don't like the new syntax. It seems to slide back to class thinking. Each role now has two names: myFlyingCar and plane, in this example. Department of redundancy department...

On Sep 30, 2011, at 11:28 , Ant Kutschera wrote:

It should be as simple as:

   myFlyingPlane.fly();

If you write a compiler to generate byte code which does something
similar to what I did tonight, you'd have DCI in Java.

Bingo.

Wenig, Stefan

unread,
Oct 2, 2011, 12:08:58 PM10/2/11
to dci-ev...@googlegroups.com
I realized there's some dissent between you and Stephan about this. But there might be some useful ideas for a static DCI-compatible type system even if you dislike his concept of identity.

Stefan

________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of James O. Coplien [jcop...@gmail.com]
Sent: Friday, September 30, 2011 19:03
To: dci-ev...@googlegroups.com


Subject: Re: Roles in Eclipse Indigo

On Sep 30, 2011, at 5:20 , Wenig, Stefan wrote:

Wenig, Stefan

unread,
Oct 2, 2011, 12:21:24 PM10/2/11
to dci-ev...@googlegroups.com
It is. Seems very much like C#'s dynamic, but there's a big difference too:

dynamic is a variable type, so whether you're able to access dynamic members depends on the variable type, not on the object type. This has at least two important consequences:
- it works for statically typed objects too, you just don't have to know the static type up-front.
- if you don't use 'dynamic' as a variable type, you still get static type safety for static members (it seems that in Scala, i.e. renaming a static method will reroute all calls to the dynamic interface, which seems wrong to me.

Interesting choice. I wonder what the rationale is.

Stefan

________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of Ant Kutschera [ant.ku...@gmail.com]
Sent: Friday, September 30, 2011 22:21
To: dci-evolution


Subject: Re: Roles in Eclipse Indigo

This is interesting:

http://squirrelsewer.blogspot.com/2011/02/scalas-upcoming-dynamic-capabilities.html

Wenig, Stefan

unread,
Oct 2, 2011, 12:28:14 PM10/2/11
to dci-ev...@googlegroups.com
I think it was Rune who created a working DCI implementation in C# using the dynamic keyword. Why don't you play a bit with it and find out if you like the result before you go all the way into compiler building? It should be _very_ similar to what you want to do with Java. I believe it was over in object-composition.

Stefan

________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of Ant Kutschera [ant.ku...@gmail.com]

Sent: Friday, September 30, 2011 23:20
To: dci-evolution


Subject: Re: Roles in Eclipse Indigo

I spent a few hours creating some really ugly code tonight.

Wenig, Stefan

unread,
Oct 2, 2011, 12:32:31 PM10/2/11
to dci-ev...@googlegroups.com
I think the dynamic keyword in C# is a very useful idea. You can use it to call objects from dynamic languages, read data from XML or other external systems, and even do an occasional dynamic trick within C#. The Scala thing sounds interesting too, but like I said, without knowing the reasoning behind it I prefer the C# version very much. I think it would be a very useful proposition for Java, and much more likely to happen than a full-blown role-aware type system.

But my argument is not that people won't use it, but that people who choose a static language will not want to use it in the heart of their systems.

Stefan

________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of Ant Kutschera [ant.ku...@gmail.com]

Sent: Friday, September 30, 2011 23:30


To: dci-evolution
Subject: Re: Roles in Eclipse Indigo

On Sep 30, 11:46 am, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:

Wenig, Stefan

unread,
Oct 2, 2011, 12:37:04 PM10/2/11
to dci-ev...@googlegroups.com
Sure, there's a lot of interesting combinations. But some just don't make any sense. E.g., you don't want to code an app in some fancy new FullOO language and then write your queries in LINQ, this is all about integrating the query capabilities in your primary language. OTOH, it can make a lot of sense to code the domain in a static language and build your web pages in RoR.

Where would you see such a split in the context of DCI? Your own C#/dynamic samples where very short, so I could be totally wrong. But I'm expecting a DCI app in this style to consist almost exclusively of dynamic calls, so I don't see the advantage of using a static language anymore.

But a static language with a type system that can handle DCI would be very interesting!

Stefan

________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of rune funch [funchs...@gmail.com]
Sent: Saturday, October 01, 2011 07:05
To: dci-ev...@googlegroups.com


Subject: Re: Roles in Eclipse Indigo

> On Sep 30, 11:46 am, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:

Wenig, Stefan

unread,
Oct 2, 2011, 12:48:57 PM10/2/11
to dci-ev...@googlegroups.com
Take re-mix. If I scope role methods within a context, I'm almost there. There are two ways to do this:
a) make roles nested classes (aka inner classes) of the context. Don't like it because it gets large and ugly, even if you use C#'s partial classes to split it in different files
b) create one assembly (similar to JARs) per context. doesn't sound like a bad choice if you assume that contexts get really large

then just make the role interfaces 'private' for a) or 'internal' for b) and you're fine.

(Bad news is that we need to rewrite the assemblies to do this, for technical reasons all interfaces must be 'public' otherwise)

So even a library like re-mix can get pretty close to this goal. For a native DCI language, it would be much easier. Visibility is a well understood concept.

I see a few conceptual challenges for a perfect static DCI language, but I really do think that this one here is actually easy.

Stefan

________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of James O. Coplien [jcop...@gmail.com]
Sent: Saturday, October 01, 2011 13:23


To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo

On Sep 30, 2011, at 4:41 , Ant Kutschera wrote:

Wenig, Stefan

unread,
Oct 2, 2011, 12:51:12 PM10/2/11
to dci-ev...@googlegroups.com
Each role is at runtime bound to an object. So what's the advantage of including the (now redundant) object in the call, when you could just write:
String s = SomeRole.roleMethod(parameter1, parameter2);

________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of Ant Kutschera [ant.ku...@gmail.com]
Sent: Saturday, October 01, 2011 23:30
To: dci-evolution


Subject: Re: Roles in Eclipse Indigo

On Oct 1, 7:36 am, Trygve Reenskaug <tryg...@ifi.uio.no> wrote:

James O. Coplien

unread,
Oct 2, 2011, 1:07:51 PM10/2/11
to dci-ev...@googlegroups.com
+1

Wenig, Stefan

unread,
Oct 2, 2011, 1:15:59 PM10/2/11
to dci-ev...@googlegroups.com
I always understood that from within a context, you talk to roles. So if you use static typing, the type would include the role contract + role methods. (It's still a matter of debate whether the role contract should be explicit, I know.)
So where do you get into the situation where you've got an object reference and are not sure whether it supports some role? Sounds like you want to sideline the notion that role players are always bound to their roles before any role method gets called. Or not?

As for 'null', we've learned to live with it, but it's not perfect.
http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

Stefan

________________________________________
From: dci-ev...@googlegroups.com [dci-ev...@googlegroups.com] on behalf of Ant Kutschera [ant.ku...@gmail.com]

Sent: Saturday, October 01, 2011 23:43


To: dci-evolution
Subject: Re: Roles in Eclipse Indigo

A few weeks ago, I tried to argue that type declarations were

rune funch

unread,
Oct 2, 2011, 1:29:56 PM10/2/11
to dci-ev...@googlegroups.com
Den 02/10/2011 kl. 19.16 skrev "Wenig, Stefan" <stefan...@rubicon.eu>:

> So, for cases where you cannot tell if an object is playing a role or

> Not

If it's bound to a role it is if it's not bound it's not a) a
roleplayer and will therefor not be bound or b) part of the context
and will therefor not be bound. My point is it's possible up front to
say whether an object identified by an identifier in any line of code
is a RolePlayer or not. Or do you see this otherwise?

rune funch

unread,
Oct 2, 2011, 1:48:32 PM10/2/11
to dci-ev...@googlegroups.com
Den 02/10/2011 kl. 18.37 skrev "Wenig, Stefan" <stefan...@rubicon.eu>:

> Sure, there's a lot of interesting combinations. But some just don't make any sense. E.g., you don't want to code an app in some fancy new FullOO language and then write your queries in LINQ,

This I don't understand why wouldn't I want Linq in a fullOO language?

> Where would you see such a split in the context of DCI? Your own C#/dynamic samples where very short, so I could be totally wrong.
> But I'm expecting a DCI app in this style to consist almost exclusively of dynamic calls, so I don't see the advantage of using a static language anymore.

As I recall the example I was simply trying to show that you could
have implicit role contracts in C#. If that's the example you're
talking about you don't need more than one line of dynamically typed
code for each binding operation. What you loose is limited to one area
of the code namely the part where the context picks the RolePlayers.
Since the context is responsible for picking the objects there's low
risk of a type mismatch and since it's in a very limited part of the
code that I in general would expect to have few paths it's also cheap
to have a high path coverage in you testing. The rest of the code ie
non context code and role methods is statically typed

> But a static language with a type system that can handle DCI would be very interesting!

I'd say that Linq is an example of how it could be done

If you had a context with a role method called select then you be able
to decorate any object with of a given type with a locally scoped
method called select.
I'm not saying that you can build DCI on top of Linq (maybe you can
maybe not) I'm saying that the methodology methods are found for the
query translation can be used to build DCI. There's even rules about
resolving name conflicts. The pattern based approach can be used to
both support implicit but verifiable role contracts. In n new type
system I'd prefer to solve them differently though the same approach
can also be used to create a method invocation translation when the
method is invoked on a role and not a local. That translation would
take care of naming conflicts as well (as is the case in Linq where
it's however reversed compared to DCI)

Risto Välimäki

unread,
Oct 2, 2011, 2:41:37 PM10/2/11
to dci-ev...@googlegroups.com


2011/10/2 rune funch <funchs...@gmail.com>

What you loose is limited to one area
of the code namely the part where the context picks the RolePlayers.
Since the context is responsible for picking the objects there's low
risk of a type mismatch and since it's in a very limited part of the
code that I in general would expect to have few paths it's also cheap
to have a high path coverage in you testing. The rest of the code ie
non context code and role methods is statically typed

+1

I was just about to say that too. I think Context and Role code should be in the same file. And when you keep that file relatively small(*) and concise, it's really not a problem, if your Roles are dynamic. After all, you'll likely have some 1...4 Role methods in one Context. I don't see a need for "either statically typed Roles or unit tests" here. Auto-complete would be nice, but one can really live without considering the low number of Role methods, and the fact that those methods are easily located in the very same file.

(*) very small when compared to typical POJO classes that include all the code that you typically would split into 1 Data and multiple Role definitions in DCI.

-Risto

Ant Kutschera

unread,
Oct 2, 2011, 3:28:39 PM10/2/11
to dci-evolution
Quote 1:

On Oct 1, 1:38 pm, James O. Coplien <jcopl...@gmail.com> wrote:
> I like the code generation aspect of this, but I don't like the new syntax. It seems to slide back to class thinking. Each role now has two names: myFlyingCar and plane, in this example. Department of redundancy department...

Quote 2:

On Oct 2, 6:51 pm, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
> Each role is at runtime bound to an object. So what's the advantage of including the (now redundant) object in the call, when you could just write:
> String s = SomeRole.roleMethod(parameter1, parameter2);


Oh dear, I seem to have mis-communicated what the syntax is doing. I
apologise.

Let me try again, from the beginning, using the flying car example
again.

Here is a role definition:

class Plane extends Role<Cart, TravelContext> {
void fly(){
... do something like put engines to full throttle,
take off, fly around using navigation systems, etc.
}
}

That role definition is a trait (an interface with implementation) -
it is a list of methods, together with their implementation. The fact
that I have the word "class" at the top should be ignored. It would
be better if the Java language supported a keyword "role" and I would
use that instead of "class". "Plane" is really a namespace, in that
it tells someone what methods a role player playing the role of a
Plane would have. It has nothing to do with a class, because "Plane"
will never ever be instantiated. The hint to you that this is not a
class, is that the words "extends Role" follow the name. Read it a
few times, and get used to reading it like this: "role definition for
the role plane". I repeat, this is NOT a class definition - it is a
role definition. My language has simply constrained me to use the
wrong keyword. Ruby does that too - it forces programmers to use the
word "module".

(Perhaps we have a new keeper here: DCI languages should have a
keyword which lets the programmer define roles in terms of the methods
they have, e.g. "role")

The context is a class. Here is a context constructor:

TravelContext(Cart myFlyingCar) {
this.myFlyingCar = myFlyingCar;
bind(myFlyingCar, Car.class);
bind(myFlyingCar, Plane.class);
}

Here, I am making a field in the context instance equal to the
parameter which is passed to the constructor (the object which will
become a role player), so that I can use the object later, in the
execute method. I am also casting the object into two roles,
immediately.

After constructing the context, the "execute" method will get called.
A snippet inside the "execute" method might look like this:

myFlyingCar.callRoleMethod(Plane.class).fly();

This line of code should be read like this:

"the object identified by the name myFlyingCar invokes the fly
method found in the Plane namespace"

I previously confused you by writing something else which was
confusing and wrong - sorry!

The syntax looks like that if you want to use DCI in Java today, with
just a library.

More ideally, that would be written something like this:

myFlyingCar#Plane.fly();

Again, "Plane" is simply a namespace, telling the compiler quite
explicitly in which role definition to find the fly method. There is
no object wiht a sticky note on it with the name "Plane". The only
object is the one with the sticky note with the name "myFlyingCar".

It is important to use the namespace, because it stops role method
namespace conflicts. The compiler will never have to choose between a
method on one role and a method on a second role, when an object is
playing two roles. And like Stefan pointed out, the namespace should
not be optional, it should always be present, to avoid potential
bugs. If it is only optional, when is the programmer supposed to know
they should provide it? By the time they find the bug, it is too
late. We are trying to help avoid bugs in the first place.

I hope this is now clear, and we can move on and agree that Java has
become a first class DCI language like Ruby. Well, let's call them
both second class, because first class should be reserved for true DCI
languages of the future!

Ant Kutschera

unread,
Oct 2, 2011, 3:31:04 PM10/2/11
to dci-evolution
On Oct 2, 8:41 pm, Risto Välimäki <risto.valim...@gmail.com> wrote:
> 2011/10/2 rune funch <funchsolt...@gmail.com>
>
> > What you loose is limited to one area
> > of the code namely the part where the context picks the RolePlayers.
> > Since the context is responsible for picking the objects there's low
> > risk of a type mismatch and since it's in a very limited part of the
> > code that I in general would expect to have few paths it's also cheap
> > to have a high path coverage in you testing. The rest of the code ie
> > non context code and role methods is statically typed
>
> +1

Yeah - I was pretty much saying it's a non-issue.

+1

James O. Coplien

unread,
Oct 2, 2011, 5:04:05 PM10/2/11
to dci-ev...@googlegroups.com

On Oct 2, 2011, at 8:41 , Risto Välimäki wrote:

I think Context and Role code should be in the same file.


If they're not, one kind of has to ask: What's the point of using DCI, right?

James O. Coplien

unread,
Oct 2, 2011, 5:04:09 PM10/2/11
to dci-ev...@googlegroups.com
I think the language restrictions support the spirit of the two puzzles you quote. DCI is about run-time thinking; we usually communicate that saying "object thinking." In the past, the sin has been compile-time thinking, and we usually communicate that by saying "class thinking."

To have to think explicitly about things called roles and objects as part of any single mental model violates the way I look at DCI. I want DCI to add role behavior to an object — one set of roles to one object at a time — and to thereafter think only in terms of objects, in terms of their methodless role names. To bring in the name of the type of the methodful role (got that?) doesn't help. It's clutter.

I suppose some software-engineering minded person — the same kind of person who created Hungarian notation — would laud the explicitness. But it's below my type horizon. Most of the time I want to call an integer an integer. If the language forces me to explicitly differentiate between signed-ness and length of integers every time I use one, it's superfluous. Those attributes are indeed part of the type. And I can see the need for explicit qualification to disambiguate method name collisions (horrors! this should not happen within a Context, anyhow — that violates quite a number of Kent Beck's design patterns). Otherwise, it violates Occam's Razor for me.

Trygve Reenskaug

unread,
Oct 3, 2011, 1:16:27 AM10/3/11
to DCI-object-evolution
Example:
    CurrentContext.remap()
The data has changed so there are new role players. Example: Dijkstra algorithm with a loop in the algorithm rather than recursion.

Ant: Please move to the evolution list, I know this discussion confuses newbies.


On 2011.10.02 21:35, Ant Kutschera wrote:
On Oct 2, 6:23 pm, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
  
And there's still the problem with contexts playing roles, you didn't answer that one.
    
Yeah, I'm still thinking about it.

I'll turn it round, and ask for a concrete example of when a context
plays a role.  Not just code, but the use case too please.

I am struggling with the concept of why a context should be a role
player.  I know Trygve and James have also mentioned this in the past.

  

ant.ku...@gmail.com

unread,
Oct 3, 2011, 1:51:33 AM10/3/11
to dci-ev...@googlegroups.com
Hi,

Is that a context playing a role, or just a new instance of the context? In James Ruby code for Dijkstra's algorithm, he is restarting the context by reinstantiating it.  He isn't casting it into a role and calling new injected behaviour on it.

But doesn't matter, in can add the ability for a context to be a role player to my framework, no problem.

Ant

Ant Kutschera

unread,
Oct 3, 2011, 2:29:40 AM10/3/11
to dci-evolution
Same namespace as in something like a Java package: yes.
Same physical file? Not convinced.

I don't mean to argue, but I don't think that's a keeper.

Ant Kutschera

unread,
Oct 3, 2011, 2:34:13 AM10/3/11
to dci-evolution
I'd say you and Feather need to discuss this in more depth, because
you seems to be pulling in opposite directions on this point.

In terms of long term evolution, we can wait for that answer.
In terms of here and now, my toy language and using DCI in Java, I'll
leave role namespaces in.

Risto Välimäki

unread,
Oct 3, 2011, 2:35:02 AM10/3/11
to dci-ev...@googlegroups.com
If Roles are not in the very same file than Context (or even better: in the very same class(or similar structure) as sub classes(or trait or whatever) _not_ visible outside Context), you should have very good IDE support to not lose your Context in Roles and your Roles in your Context.

There might be reasons to separate Context and its Roles (that are integral part of one and just one Context) into different files, but I would be very interesting to see at least one _good_ reason to do that.

-Risto

2011/10/3 Ant Kutschera <ant.ku...@gmail.com>

ant.ku...@gmail.com

unread,
Oct 3, 2011, 2:42:54 AM10/3/11
to dci-ev...@googlegroups.com
Generally, I agree.



----- Reply message -----
From: "Risto Välimäki" <risto.v...@gmail.com>
To: <dci-ev...@googlegroups.com>
Subject: Roles in Eclipse Indigo

rune funch

unread,
Oct 3, 2011, 2:46:27 AM10/3/11
to dci-ev...@googlegroups.com
Den 03/10/2011 kl. 08.29 skrev Ant Kutschera <ant.ku...@gmail.com>:

> Same namespace as in something like a Java package: yes.
> Same physical file? Not convinced.
>
> I don't mean to argue, but I don't think that's a keeper.

When the point is to keep the information in one place how are you
going to argue at all if the information is scattered amongst several
files?

rune funch

unread,
Oct 3, 2011, 3:15:43 AM10/3/11
to dci-ev...@googlegroups.com
Den 03/10/2011 kl. 08.35 skrev "Risto Välimäki" <risto.v...@gmail.com>:

> There might be reasons to separate Context and its Roles (that are integral part of one and just one Context) into different files, but I would be very interesting to see at least one _good_ reason to do that.

To me it's like trying to find an argument to why it might sometimes
be a good idea to declare members outside the class files. Roles are
nothing on their own.
I think it's probably related to whether or not a role is a type or
not. I don't think they are any more a type than a function is and to
some functions are types.

Trygve Reenskaug

unread,
Oct 3, 2011, 3:18:17 AM10/3/11
to dci-ev...@googlegroups.com
Same Context object is used throughout the interaction. The mapping is simply: CurrentContext := self.
A Context object may be stored away from the stack to be reactivated later. (I thought of it for the Observer pattern, but this example collapsed due to the simplicity of the algorithm)

Rune Funch Søltoft

unread,
Oct 3, 2011, 4:05:11 AM10/3/11
to dci-ev...@googlegroups.com


2011/10/3 Ant Kutschera <ant.ku...@gmail.com>

I'd say you and Feather need to discuss this in more depth, because
you seems to be pulling in opposite directions on this point.

In terms of long term evolution, we can wait for that answer.
In terms of here and now, my toy language and using DCI in Java, I'll
leave role namespaces in.

In a simplified world there'e to ways to appreciate code. You can appreciate it for what it does or how it does it. When you write code you can either write it declarative so that the what is clearer or you can write it imperative creating the need for the reader of the code to be able to analyse the code to figure out what it does. I find one of the most important aspects of DCI to be the readability of the code,  including the ability to understand what it does without having to analyse the code.

To me having to distinguish between object and role in the code makes the how visible to the extend where it appears to be more important than the what. I find that a shame and believe that as long as the 'what' is correct you shouldn't have to worry about the 'how'. That's is only if you know there's a bug then the how becomes important.

-Rune

Ant Kutschera

unread,
Oct 3, 2011, 4:14:12 AM10/3/11
to dci-evolution
In my mind, a namespace containing just the context and role impls is
"one place".
I can use keywords to give roles the correct visibility too.

Look at it this way: if the context contains several roles and all
that code results in a file with a thousand lines of code, that's not
good either. Sure the IDE could help, but do you really want to
prohibit people from using vi?

The keeper here is that roles are internal parts of contexts and the
two exist together. Nothing more.

On Oct 3, 8:46 am, rune funch <funchsolt...@gmail.com> wrote:

Risto Välimäki

unread,
Oct 3, 2011, 4:30:33 AM10/3/11
to dci-ev...@googlegroups.com
Hey, that's fun since I would argue just opposite:

Because I don't want to prohibit people from using vi, emacs or even mighty ed, I would not rely on IDE features on connecting Context and Role definitions together. Single Context with all its Roles would be definitely more convenient with plain text editor without special DCI tooling support.

Anyways, Context definition (without Roles) should be quite small, if you are doing really OO code and not playing around with mediator pattern. If one of your Role definitions is loooong (thousands of lines of code), it doesn't help much, if you drop your 5 to 100 LOC Context definition out of the file. If all your Role definitions are that big, you do have either a gigantic Use Case, or something wrong on your architecture.

My guess is that typical "Context with Roles" -definition file will be easily shorter than typical mega POJO class definitions out there.

-Risto  




2011/10/3 Ant Kutschera <ant.ku...@gmail.com>
In my mind, a namespace containing just the context and role impls is

James O. Coplien

unread,
Oct 3, 2011, 4:19:17 AM10/3/11
to dci-ev...@googlegroups.com

On Oct 3, 2011, at 10:14 , Ant Kutschera wrote:

> In my mind, a namespace containing just the context and role impls is
> "one place".


It can be. But in the same sense, anything can be made to be "one place"— but only with tools. Namespaces are not a significant factor unless you have a rich tool empire to physically and contiguously render the namespace content in a single locus of attention.

I think the distinction is logical versus physical. As logical as our brains are, they are also physical, and have the 5 +/- 2 limitation. A logical grouping like namespaces is inadequate: you need a literal, physical grouping.

See Jef Raskin's discussion in his book, "Humane Interfaces" about the notion of a file — he approaches this issue from exactly the opposite end, in such an extreme way that it wraps around to be the same thing...

James O. Coplien

unread,
Oct 3, 2011, 4:21:01 AM10/3/11
to dci-ev...@googlegroups.com

On Oct 3, 2011, at 10:05 , Rune Funch Søltoft wrote:

In a simplified world there'e to ways to appreciate code. You can appreciate it forwhat it does or how it does it. When you write code you can either write it declarative so that the what is clearer or you can write it imperative creating the need for the reader of the code to be able to analyse the code to figure out what it does. I find one of the most important aspects of DCI to be the readability of the code,  including the ability to understand what it does without having to analyse the code.

Do you want to see the assembly code, too?

I think this current fascination with qualification may be a sign that we are looking for aids because we haven't yet made the leap. Early FORTRAN people insisted on seeing "how" the machine compiled their code, too.

Have patience. You'll get over it :-)

Rune Funch Søltoft

unread,
Oct 3, 2011, 4:33:47 AM10/3/11
to dci-ev...@googlegroups.com
In C# you've been able to use multiple files for the same class for years. It's in general highly discouraged because it makes it difficult to navigate the class. Visual studio has had support for navigating partial class definitions all a long. If you have to search for the information it's not at the same location.

C++ has header files and some of the information you need when reading the code of a class is going to be in that file. I don't think it's a weird coincidence that all other curly brace languages have opted for an extract compiler pass to get rid of the header files and thereby have all the information of a class located at the same place.

2011/10/3 Ant Kutschera <ant.ku...@gmail.com>

In my mind, a namespace containing just the context and role impls is
"one place".
Which is like saying that all the information in the books of a library is in the same place, if you go home with the wrong book (read the wrong file) you'd still have to go back to the library to get the right book. How about if you had to have to maps to navigate an unknown city one with all the buildings and another with all the street name. But them in the same binder and you have all the information in  "one place" it still renders the map almost impossible to use.
 
I can use keywords to give roles the correct visibility too.

It's not about accessability but readability. I actually don't see any reason to keep the role and context in the same namespace. In the same file sure but if you're going to violate that then what difference does it do if there's an arbitrary word in the top of the files that match?
 
Look at it this way:  if the context contains several roles and all
that code results in a file with a thousand lines of code, that's not
good either.  Sure the IDE could help, but do you really want to
prohibit people from using vi?
Sure and if there's other design errors it's still going to be bad code but that's not an argument for not doing it right in the first place :)

 
The keeper here is that roles are internal parts of contexts and the
two exist together. Nothing more.
The keeper is that roles and context are part of a hole. If you took out your heart and lung and placed them in a jar you would siece being a human and start being something else, even if all jars were labelled Ant (so for the morbid example but it was the first that sprang to mind). Roles are the heart and lungs of a context. With no roles there's no context just data.
 
-Rune

James O. Coplien

unread,
Oct 3, 2011, 6:14:47 AM10/3/11
to dci-ev...@googlegroups.com
On Oct 3, 2011, at 9:15 , rune funch wrote:

I think it's probably related to whether or not a role is a type or not. I don't think they are any more a type than a function is and to some functions are types.


Rune, I was taken aback earlier when you exclaimed:


On Oct 2, 2011, at 9:05 , rune funch wrote:

I don't find roles to have a type. Let alone a super type.


To me, a (methodful) role is the type of the object as accessed through the corresponding methodless role. Rune, you redeemed yourself later when you said (more or less):


On Oct 3, 2011, at 10:33 , Rune Funch Søltoft wrote (more or less):

With no roles there's no context just bits.


If you think of the concept of type in terms of its origins in mathematics around the semantics of set theory (classes were originally power sets), then a role is perfectly a type. The (methodful) role is the type of the object currently being operated on.

By "type" I do not mean type-checking, which is what too many undergraduate Java programmers learn it to mean. For example, Smalltalk is a much more strongly typed language than C++. C++ has much stronger compile-time type-checking than Smalltalk. Both are true, and if this pair of statements confuse you, you're probably in the type-checking camp rather than in the type camp.

To me, the essence of object-orientation has always been run-time type support. (Trygve once said that there are no types at run time, and I think I know what he means, but I take exception to that statement and here will use the term more in its original sense, for which run-time typing does make sense.) In Restricted OO that's limited to identifying which method to dispatch. In DCI it's bountifully richer, because we've tended to move the powerset semantics of Restricted OO from the class into the role. And it's a much richer type geometry with more opportunity for overlapping sets and more dynamic predicates on set membership at the object level. Yet, as I described in my talk at the Simula Center in Oslo, DCI carefully restricts the type system so it's not just a bunch of unbounded sets that can arbitrarily be combined. The polymorphic branching options are more highly restricted, which results in a much less complex computational model than ad-hoc polymorphism or alomorphism.

One important avenue of research for DCI is to built a type-theoretic basis for it. There will always be academics ready to throw stones, and nothing short of an air-tight type semantics will quiet them. These foundations are also of great use to serious compiler-writers, particularly in areas of code correctness (does the object code do what the source code mandates) and optimization.

I'm not up to it — I think one needs much more foundation in the thinking tools of mathematics than I have. I do have glimmers. I can vaguely see the "hyperset" <role> (that would be the base type of all roles). It has very strict associative properties. Any takers? Unfortunately, communities like this (DCI) tend to attract practical people :-) rather than high academics. But we need a few of them in the wings.

BTW, one reason I feel it is unnecessary to include the (methodful) role name in DCI source code is that the (methodless) role identifier should be carefully enough named to support understanding. All interesting problems in computer science reduce to what's-in-a-name. We don't need types for that; identifiers are enough. Remember Alice and the White Night. That's why "Mega" was bad, and why "CurrentIntersection" is good.

James O. Coplien

unread,
Oct 3, 2011, 6:14:51 AM10/3/11
to dci-ev...@googlegroups.com

On Oct 3, 2011, at 10:33 , Rune Funch Søltoft wrote:

It's not about accessability but readability.


What a deeply thought-out distinction...

James O. Coplien

unread,
Oct 3, 2011, 6:14:54 AM10/3/11
to dci-ev...@googlegroups.com
On principle, I agree.

Can you name me a single programming paradigm that stands today on the success of its toolset, other than pipes and filters, which stand on UN*X?

You can sell tools to a company. It is evil to put them in the critical evolutionary path of an essentially intellectual discipline. If you could obtain a license to write only after having mastered Word on PCs, most key literary works of history would disappear. Thank goodness, we have the pen. And studies show that Word creates worse compositions — not better. They say less in more words. I think the metaphor fits well.

Risto said it best:

On Oct 3, 2011, at 10:30 , Risto Välimäki wrote:

Single Context with all its Roles would be definitely more convenient with plain text editor without special DCI tooling support.



James O. Coplien

unread,
Oct 3, 2011, 6:14:58 AM10/3/11
to dci-ev...@googlegroups.com

On Oct 3, 2011, at 8:34 , Ant Kutschera wrote:

> I'd say you and Feather need to discuss this in more depth, because
> you seems to be pulling in opposite directions on this point.

I don't know what Feather[s?] has to say about DCI.

If you really want to know what's going on, you should also write the name of the class of the object to which the role is bound. Why don't you do that? (I'm seeking an answer in the cognitive space rather than in the technical space.)

Ant Kutschera

unread,
Oct 3, 2011, 7:27:20 AM10/3/11
to dci-evolution
On Oct 3, 10:33 am, Rune Funch Søltoft <r...@asseco.dk> wrote:
> The keeper is that roles and context are part of a whole.

Well, I'm not going to stand in anyone's way - if the group think it
is best to put everything in one file, I will start doing that too.
At the very least, I will see what it's like in practise.

Rune Funch Søltoft

unread,
Oct 3, 2011, 7:32:24 AM10/3/11
to dci-ev...@googlegroups.com


2011/10/3 James O. Coplien <jcop...@gmail.com>

On Oct 3, 2011, at 9:15 , rune funch wrote:

I think it's probably related to whether or not a role is a type or not. I don't think they are any more a type than a function is and to some functions are types.


Rune, I was taken aback earlier when you exclaimed:


On Oct 2, 2011, at 9:05 , rune funch wrote:

I don't find roles to have a type. Let alone a super type.


To me, a (methodful) role is the type of the object as accessed through the corresponding methodless role. Rune, you redeemed yourself later when you said (more or less):


On Oct 3, 2011, at 10:33 , Rune Funch Søltoft wrote (more or less):

With no roles there's no context just bits.


If you think of the concept of type in terms of its origins in mathematics around the semantics of set theory (classes were originally power sets), then a role is perfectly a type. The (methodful) role is the type of the object currently being operated on.

At least I managed in a few tries to convey the underlying thoughts. To me (incorrect as it might be) we have set theory and type theory where the latter can be expressed in terms of the former.
and that being the case I'm sure roles can be expressed based on set theory and I'm sure roles determine the run-time type of RolePlayers. What I'm not so sure of is that the current (general  understanding) of a type suffice to describe it.

As I've hinted in another mail, I have a hunch that the way types are handled in linq query comprehension expressions can be used for this purpose.

a simple linq statement looks like

from item in collection
select item

That's translated (rewritten) to either collection.Select(item=>item) or T.Select(collection,item=>item). That is it's either translated to a instance method invocation on the object identified by the identifier 'collection' or it's an invocation of a static method (doesn't have to be an extension method but usually is).

'from' and 'in' simply helps the parser to realize that it's a query comprehension statement. What I think could be done is to rewrite 

roleName.MethodName(args) into MethodName(RolePlayer, args) or RolePlayer.MethodName(args) where the first is when the name matches the name of a role method and the latter is when the name does not match a tole method name. One of the ClearMud examples I posted actually does just that. It also fits with the model used for dynamic in C#. variables declared with 'dynamic' are strongly typed as dynamic (makes your head in I know) similarly a role name could be strongly typed as a role, meaning it gets special treatment when used (Ie. the above rewrite)

However doing that challenges the notion of roles having a runtime type. They could then been seen simply as a namespace for static methods but static methods that have instance semantics and syntax. (Like Extension methods in C# but with different scoping rules)

-Rune

Ant Kutschera

unread,
Oct 3, 2011, 7:35:55 AM10/3/11
to dci-evolution
On Oct 3, 12:14 pm, "James O. Coplien" <jcopl...@gmail.com> wrote:
> I don't know what Feather[s?] has to say about DCI.

Feather? Wow, my new phone has a mind of it's own.

"Feather" was meant to be "Trygve" :-)

I'd say you and Trygve need to discuss whether a namespace should be
used to qualify the role method you are calling, in more depth,
because
you seem to be pulling in opposite directions on this point.

I say that, based on this quote from a few days ago:

On Oct 2, 11:01 am, Trygve Reenskaug <tryg...@ifi.uio.no> wrote:
> Yes, that's even better. Qualify the name of a role method with its role
> name. Possibly also its Context name. This ties in with my new idea for
> an "ideal" DCI method execution model. (In principle simple, but waiting
> to get on the top of my stack to be written down)

And that was written based on my code listing:

> > String s = domainObject#SomeRole.roleMethod(parameter1, parameter2);

where "SomeRole" was the namespace used in a role definition, where
the set of methods which a role has are defined.

Trygve Reenskaug

unread,
Oct 3, 2011, 10:28:33 AM10/3/11
to dci-ev...@googlegroups.com
Please don't expect me to mean the same for two hours in succession!
I sent a message at 12:35 my time today that reflects my current opinion. On Oct 2, 11:01 am, I thought I would need full qualification if there were name clashes.
Today, I don't think so. self and super will do in my idealized execution model.
Tomorrow is another day.
--Trygve (will also respond to honorary title "Feather")

Wenig, Stefan

unread,
Oct 4, 2011, 11:09:59 AM10/4/11
to dci-ev...@googlegroups.com
I thought contexts are supposed to get huge, like man-months of work? (Assuming that use cases map to contexts more or less 1:1). I believe Jim said that a while ago. So I'm very surprised that it's now being argued that everything should be crammed into a single file!

If a single context grows beyond 1000 lines of code I'd rather have its roles in individual files, packaged in a folder/namespace structure.

Wenig, Stefan

unread,
Oct 4, 2011, 11:18:20 AM10/4/11
to dci-ev...@googlegroups.com
So you're more or less saying that whether a role is a type depends on your definition of type? I'm just one of those abundant practical persons here, but to my compile-time-leaning brain it's pretty clear that roles in a static language should somehow reflect in the type system. Now if you call it a type or not is not that important to me (is a mixin a type? is a trait?), but having a name, some scoping and assignment rules, and all this nice stuff, is essential. It's what lets me realize the advantages of static languages.

Whether a role is considered a type in a dynamic language is probably a much more academic question.

Stefan

On Mo, Okt 03, 2011 at 12:14:47, James O. Coplien wrote:
> Subject: Re: Roles in Eclipse Indigo
>

> I'm not up to it - I think one needs much more foundation in the

Wenig, Stefan

unread,
Oct 4, 2011, 11:47:19 AM10/4/11
to dci-ev...@googlegroups.com

All true. Tooling restrictions aside, I would consider using partial classes for non-trivial nested types though. So instead of

 

class Outer {

  class Inner {…}

  …

}

 

in a single file, you’d get two files:

 

// Outer.cs:

partial class Outer {

  …

}

 

// Outer.Inner.cs:

partial class Outer {

  class Inner {…}

}

 

Still feels like a hack, but at least you don’t have files with 10.000s of lines… and several coders hacking at them concurrently.

 

For contexts and roles, I’d rather use folders and namespaces, but then I don’t get visibility restrictions within a context. And assemblies might be to heavyweight for many contexts.

 

// MyContext/Context.cs:

partial class MyContext {

  private IRole1 role1;

  someFacadeMethod() {…}

}

 

// MyContext/Role1.cs

partial class MyContext {

  private interface IRole1 {…}

  private class Role1 {

     // role methods

  }

}

 

(IRole1 and Role1 could also be split, as could a separate role contract interface if there should be one)

 

This reduces visibility of Role1 methods to a MyContext, and there’s no longer a need to dynamically add/remove methods to/from objects, no name conflicts…  Still, a language with explicit DCI support could do much better.

 

Stefan

 

From: dci-ev...@googlegroups.com [mailto:dci-ev...@googlegroups.com] On Behalf Of Rune Funch Søltoft
Sent: Monday, October 03, 2011 10:34 AM
To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo

 

In C# you've been able to use multiple files for the same class for years. It's in general highly discouraged because it makes it difficult to navigate the class. Visual studio has had support for navigating partial class definitions all a long. If you have to search for the information it's not at the same location.

rune funch

unread,
Oct 4, 2011, 11:48:14 AM10/4/11
to dci-ev...@googlegroups.com
We have several use cases that has take many man years to create that
does not imply that the what-the-system-does is huge. In reality most
of them are less than 50 LOC for the what the system does part

-Rune

Wenig, Stefan

unread,
Oct 4, 2011, 11:52:57 AM10/4/11
to dci-ev...@googlegroups.com
Now this really looks like something a static-typing mind can live with! It's still a bit verbose, but I guess most Java programmers will just be happy they don't have to write anonymous classes for each call ;-)

I hope a get a chance to look at it in more detail soon. How do you think it compares with Qi4j specifically? My take (after a very short look):
+ no name conflicts
+ no compile-time dependencies on a single list of roles per data class
- more verbose call syntax
- callRole will potentially fail for every single call (whereas Qi4j gives you a single type that combines all methods, and you only have to check when binding)

Stefan

> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of Ant Kutschera
> Sent: Sunday, October 02, 2011 11:38 AM
> To: dci-evolution
> Subject: Re: Roles in Eclipse Indigo
>

> I've done it! Java is now fully DCI compliant, and in doing so
> supports Java's type checker as well as full IDE integration.
>
> Quote 1:
>
> On Sep 30, 5:32 pm, "Wenig, Stefan" <stefan.we...@rubicon.eu> wrote:
> > > > Have you solved the name scoping problems in your Java version?
> >
> > > I think so, see my previous post. Or perhaps I don't know which
> > > scoping problem you are talking about. You mean if a role player
> is
> > > playing two roles which have identical methods, which one gets
> called?
> > > Yes, that is solved, see previous comments.
> >
> > Found it, thanks. Problem is, you need to provide an optional
> parameter. If you omit this parameter, a conflict might arise at
> runtime, which is harder to find, plus you'll have to go back to every
> call and add the qualifier. At this point, the code might be compiled
> and out of your hands. (Assuming that composition occurs at deployment
> time or runtime.)
>
> Quote 2:


>
> On Oct 2, 11:01 am, Trygve Reenskaug <tryg...@ifi.uio.no> wrote:
> > Yes, that's even better. Qualify the name of a role method with its
> role
> > name. Possibly also its Context name. This ties in with my new idea
> for
> > an "ideal" DCI method execution model. (In principle simple, but
> waiting
> > to get on the top of my stack to be written down)
> >

> > On 2011.09.30 23:28, Ant Kutschera wrote:
> > > I think the following might be even better:


> > >
> > > String s = domainObject#SomeRole.roleMethod(parameter1,
> parameter2);
>
>

> Stefan & Trygve, you are geniuses! When I read this, my first thought
> was "hey, ok, so we always need to fully qualify the role method we
> are calling".
>
> And in doing that, I have been able to make my little extension to
> Java provide full type safety and IDE integration!
>
> Last night I posted that the ideal role method call might look like
> this:
>
> //Listing 1


> String s = domainObject#SomeRole.roleMethod(parameter1,
> parameter2);
>

> (SomeRole only exists within a given Context, so there is no need to
> add that before the role name - the compiler wouldn't let me anyway,
> if the role was out of scope)
>
> So I was about a quarter way into adding that syntax to the OpenJKD
> compiler, when it struck me. Why not just do it like this:
>
> //Listing 2
> String s =
> myDomainObject.callRole(SomeRole.class).aRoleMethod(parameter1,
> parameter2);
>
> This syntax is a little uglier than my proposal with the "#" symbol,
> but it is far better than the syntax I proposed a few days ago, where
> the method to call is provided using a string. Does anyone have any
> suggestions as to how to make the utility method name "callRole"
> better?
>
> I've been able to update my framework so that I can write exactly that
> shown in listing 2, and it works great. The compiler is able to tell
> me if parameter types are wrong, code-complete offers me all the role
> methods on "SomeRole". The IDE can do refactoring, just like it can
> on any other class. The IDE can jump into role methods when I hit F3,
> just like it can on normal Java methods. It's perfect, from the type
> checking and IDE points of view.
>
> So, below is some test code to show how it all works.
>
> After that, is Dijkstra's Algorithm, implemented with the new
> framework, and again, semantically it is identical to that done in
> Ruby, syntactically it is a little different, but much better than
> what I posted a few days ago, because the compiler and tools help you
> in the same way they help you when you write normal Java code.
>
> Those interested in how I implemented this, check the third section
> below - there are three classes: DomainObject, Context, Role. The
> mechanisms are a little different than that described a few days ago.
> But importantly, context stacking is supported, as well as the ability
> to "unassign" roles when a context is finished with, so that those
> roles go out of scope.
>
> What do you think?
>
>
>
> ----- test code -----
>
> /** a domain class - dumb */
> class Cart extends DomainObject {
> private String name;
> private int numWheels;
> public Cart(String name, int numWheels){
> this.name = name;
> this.numWheels = numWheels;
> }
> }
>
> /** a context */
> class TravelContext extends Context {
>
> Date travelDate;
>
> /** a role */


> class Plane extends Role<Cart, TravelContext> {
> void fly(){

> self.setData("wingsExtended", true);
> log("I'm flying on the " + new SimpleDateFormat("dd
> MMM yyyy").format(context.travelDate) + " man!!");
> }
> }
>
> /** a role */
> class Car extends Role<Cart, TravelContext> {
> void drive(){
> log("Only driving now...");
> }
>
> void aMethodWithParameters(Integer a, Integer b){
> log("aMethodWithParameters called with " + a + " and "
> + b);
> }
>
> int aMethodWithReturnAndParameters(Integer a, Integer b){
> int result = a + b;
> log("aMethodWithReturnAndParameters called with " + a
> + " and " + b + ", returning " + result);
> return result;
> }
>
> int aMethodWithReturnButNoParameters(){
> int result = 69;
> log("aMethodWithReturnButNoParameters called,
> returning 69");
> return result;
> }
>
> void aMethodWithNoReturnAndNoParameters(){
> log("aMethodWithNoReturnAndNoParameters called");
> }
>
> }
>
> /** context constructor and execution
> * @param log */
> TravelContext(Cart myFlyingCar) throws ParseException {
>
> travelDate = new
> SimpleDateFormat("yyyyMMdd").parse("20111002");
>
> //lets check its not already playing the role in this
> context
> assertFalse(myFlyingCar.isPlayingRole(Car.class));
> assertFalse(myFlyingCar.isPlayingRole(Plane.class));
>
> bind(myFlyingCar, Car.class);
> bind(myFlyingCar, Plane.class);
>
> //lets check it is now playing the role in this context
> assertTrue(myFlyingCar.isPlayingRole(Car.class));
> assertTrue(myFlyingCar.isPlayingRole(Plane.class));
>
> log("My cool transport is called: " + myFlyingCar.name + "
> and it has " + myFlyingCar.numWheels + " wheels.");
>
> myFlyingCar.callRole(Car.class).drive();
>
> myFlyingCar.callRole(Plane.class).fly();
>
> log("Are the wings are now extended? " +
> myFlyingCar.getData("wingsExtended"));
>
> myFlyingCar.callRole(Car.class).aMethodWithParameters(5,
> 4);
>
> int n =
> myFlyingCar.callRole(Car.class).aMethodWithReturnAndParameters(6, 7);
> assertTrue(n == 13);
>
> n =
> myFlyingCar.callRole(Car.class).aMethodWithReturnButNoParameters();
> assertTrue(n == 69);
>
>
> myFlyingCar.callRole(Car.class).aMethodWithNoReturnAndNoParameters();
>
> //I can't write a test here to prove there is no object
> schizophrenia,
> //because there is only the single object and no
> wrappers!!
>
> cleanup(); // housekeeping related to context stacking
> }
>
> }
>
> @Test
> public void test() throws ParseException {
>
> log.clear();
>
> Cart myCart = new Cart("betty", 4);
>
> //create and run context in one step
> new TravelContext(myCart);
>
> assertTrue(myCart.getData("wingsExtended") == null); //after
> the context, the field no longer exists on the object
>
> System.out.println(log);
>
> assertEquals(16, log.size());
> int i = 0;
> assertEquals("My cool transport is called: betty and it has 4
> wheels.", log.get(i++));
> assertEquals("\r\n", log.get(i++));
> assertEquals("Only driving now...", log.get(i++));
> assertEquals("\r\n", log.get(i++));
> assertEquals("I'm flying on the 02 Okt 2011 man!!", log.get(i+
> +));
> assertEquals("\r\n", log.get(i++));
> assertEquals("Are the wings are now extended? true", log.get(i+
> +));
> assertEquals("\r\n", log.get(i++));
> assertEquals("aMethodWithParameters called with 5 and 4",
> log.get(i++));
> assertEquals("\r\n", log.get(i++));
> assertEquals("aMethodWithReturnAndParameters called with 6 and
> 7, returning 13", log.get(i++));
> assertEquals("\r\n", log.get(i++));
> assertEquals("aMethodWithReturnButNoParameters called,
> returning 69", log.get(i++));
> assertEquals("\r\n", log.get(i++));
> assertEquals("aMethodWithNoReturnAndNoParameters called",
> log.get(i++));
> assertEquals("\r\n", log.get(i++));
>
> assertFalse(myCart.isPlayingRole(Car.class));
> assertFalse(myCart.isPlayingRole(Plane.class));
> }
>
> }
>
> ----- djikstra solution -----
>
> package three;
> import java.util.ArrayList;
> import java.util.Collections;
> import java.util.HashMap;
> import java.util.List;
> import java.util.Map;
>
> import ch.maxant.dci.util2.Context;
> import ch.maxant.dci.util2.DomainObject;
> import ch.maxant.dci.util2.Role;
>
> /**
> * here, I have removed need to use a type declaration for a merged
> interface.
> * there is no merged interface. I only ever refer to objects by
> their actual data
> * type.
> *
> * Oh, and I have solved the object schizophrenia problem too.
> */
> public class Runner {
>
> ////////////////////////////////////////////////////////////////
>
> static class Pair {
> private Object a;
> private Object b;
>
> public Pair(Object a, Object b) {
> this.a = a;
> this.b = b;
> }
>
> @Override
> public int hashCode() {
> final int prime = 31;
> int result = 1;
> result = prime * result + ((a == null) ? 0 :
> a.hashCode());
> result = prime * result + ((b == null) ? 0 :
> b.hashCode());
> return result;
> }
>
> @Override
> public boolean equals(Object obj) {
> if (this == obj)
> return true;
> if (obj == null)
> return false;
> if (getClass() != obj.getClass())
> return false;
> Pair other = (Pair) obj;
> if (a == null) {
> if (other.a != null)
> return false;
> } else if (!a.equals(other.a))
> return false;
> if (b == null) {
> if (other.b != null)
> return false;
> } else if (!b.equals(other.b))
> return false;
> return true;
> }
>
> @Override
> public String toString() {
> return "Pair [" + a + ", " + b + "]";
> }
>
> }
>
> ////////////////////////////////////////////////////////////////
>
> /**
> * use less than infinity, otherwise some of the calcs go wrong,
> * when we add tentative distances to actual distances and we end
> * up with negative numbers, because weve gone over max INT.
> */
> static final Integer INFINITY = Integer.MAX_VALUE - 10000;
>
> ////////////////////////////////////////////////////////////////
>
> /**
> * "Map" as in cartography rather than Computer Science...
> *
> * Map is technically a role from the DCI perspective. The role
> * in this example is played by an object representing a
> particular
> * Manhattan geometry
> */
> static class CartographyMap extends Role<Geometry, Object> {
>
> Integer distance_between(Node a, Node b) {
> return self.getDistances().get(new Pair(a, b));
> }
>
> // These two functions presume always travelling
> // in a southern or easterly direction
>
> Node next_down_the_street_from(Node node) {
> return self.east_neighbor_of(node);
> }
>
> Node next_along_the_avenue_from(Node node) {
> return self.south_neighborOf(node);
> }
>
> }
>
> ////////////////////////////////////////////////////////////////
>
> /**
> * There are four roles in the algorithm: CurrentIntersection
> (@current)
> * EastNeighbor, which lies DIRECTLY to the east of
> CurrentIntersection
> * (@east_neighbor) SouthernNeighbor, which is DIRECTLy to its
> south
> * (@south_neighbor) Destination, the target node (@destination)
> *
> * We also add a role of Map (@map) as the oracle for the geometry
> *
> * The algorithm is straight from Wikipedia:
> *
> * http://en.wikipedia.org/wiki/Dijkstra's_algorithm
> *
> * and reads directly from the distance method, below
> *
> * (use context type "Object" because this role is found in
> several contexts - we still need to think how to handle duplicate code
> in a better way...)
> */
> static class Distance_labeled_graph_node extends Role<Node,
> Object> {
>
> /*
> * NOTE: This role creates a new data member in the node into
> * which it is injected. An alernative implementation
> would
> * be to use a separate associative array
> */
>
> void set_tentative_distance_to(Integer x) {
> self.setData("tentative_distance", x);
> }
> }
>
> ////////////////////////////////////////////////////////////////
>
> /**
> * Consider street corners on a Manhattan grid. We want to find
> the
> * minimal path from the most northeast city to the most
> * southeast city. Use Dijstra's algorithm
> *
> * Data class
> *
> * Note there is NO NEED to implement hashCode or equals!
> */
> static class Node extends DomainObject {
>
> private String name;
>
> public Node(String name) {
> this.name = name;
> }
>
> public String getName() {
> return name;
> }
>
> /** only done for debugging purposes */
> @Override
> public String toString() {
> return "Node[name=" + name + ", hashCode=" + hashCode() +
> "]";
> }
> }
>
> ////////////////////////////////////////////////////////////////
>
> /**
> * This is the main Context for shortest path calculation
> */
> static class CalculateShortestPath extends Context {
>
> //These are handles to internal housekeeping arrays set up in
> initialize
>
> Map<Node, Boolean> unvisited = new HashMap<Node, Boolean>();
> Map<Node, Node> pathTo;
> Node east_neighbor;
> Node south_neighbor;
> List<Node> path;
> Geometry map;
> Node current;
> Node destination;
>
> // Initialization
>
> void rebind(Node origin_node, Geometry geometries){
> current = origin_node;
> map = geometries;
>
> bind(map, CartographyMap.class);
>
> bind(current, CurrentIntersection.class);
>
> east_neighbor = map.east_neighbor_of(origin_node);
>
> for(Node n : geometries.nodes()){
> bind(n, Distance_labeled_graph_node.class);
> }
>
> if(east_neighbor != null){
> bind(east_neighbor, Neighbor.class);
> }
>
> south_neighbor = map.south_neighborOf(origin_node);
>
> if(south_neighbor != null){
> bind(south_neighbor, Neighbor.class);
> }
> }
>
> /**
> * public initialize. It's overloaded so that the public
> version doesn't
> * have to pass a lot of crap; the initialize method takes
> care of
> * setting up internal data structures on the first
> invocation. On
> * recursion we override the defaults
> */
> public CalculateShortestPath(Node origin_node, Node
> target_node, Geometry geometries, List<Node> path_vector,
> Map<Node, Boolean> unvisited_hash, Map<Node, Node>
> pathto_hash) {
>
> destination = target_node;
>
> rebind(origin_node, geometries);
>
> // This has to come after rebind is done
> if (path_vector == null) {
>
> // This is the fundamental data structure for
> Dijkstra's algorithm,
> // called
> // "Q" in the Wikipedia description. It is a boolean
> hash that maps
> // a
> // node onto false or true according to whether it has
> been visited
> this.unvisited = new HashMap<Node, Boolean>();
>
> // These initializations are directly from the
> description of the
> // algorithm
> for (Node n : geometries.getNodes()) {
> this.unvisited.put(n, Boolean.TRUE);
>
> n.callRole(Distance_labeled_graph_node.class).set_tentative_distance_to
> (INFINITY);
> }
>
>
> current.callRole(Distance_labeled_graph_node.class).set_tentative_dista
> nce_to(0);
>
> this.unvisited.remove(origin_node);
>
> // The path array is kept in the outermost context and
> serves to
> // store the
> // return path. Each recurring context may add
> something to the
> // array along
> // the way. However, because of the nature of the
> algorithm,
> // individual
> // Context instances don't deliver "partial paths" as
> partial
> // answers.
> this.path = new ArrayList<Node>();
>
> // The pathTo map is a local associative array that
> remembers the
> // arrows between nodes through the array and erases
> them if we
> // re-label a node with a shorter distance
> this.pathTo = new HashMap<Node, Node>();
>
> } else {
>
> this.unvisited = unvisited_hash;
> this.path = path_vector;
> this.pathTo = pathto_hash;
> }
>
> execute();
> }
>
> class CurrentIntersection extends Role<Node,
> CalculateShortestPath> {
>
> List<Node> unvisited_neighbors() {
>
> //WATCHOUT: moved the access to data from the context,
> from outside this method,
> //to in inside it, otherwise we are introducing state
> to the role
> Map<Node, Boolean> unvisited = context.unvisited;
> Node south_neighbor = context.south_neighbor;
> Node east_neighbor = context.east_neighbor;
>
> List<Node> retval = new ArrayList<Node>();
> if (south_neighbor != null) {
> Boolean addIt = unvisited.get(south_neighbor);
> if (addIt == Boolean.TRUE) { //watch out, addIt
> can be null apparently
> retval.add(south_neighbor);
> }
> }
> if (east_neighbor != null) {
> Boolean addIt = unvisited.get(east_neighbor);
> if (addIt == Boolean.TRUE) { //watch out, addIt
> can be null apparently
> retval.add(east_neighbor);
> }
>
> }
> return retval;
> }
> }
>
> /**
> * This module serves to provide the methods both for the
> east_neighbor and south_neighbor roles
> */
> class Neighbor extends Role<Node, CalculateShortestPath> {
>
> boolean relable_node_as(Integer x) {
> if (x < (Integer)self.getData("tentative_distance")) {
>
>
> self.callRole(Distance_labeled_graph_node.class).set_tentative_distance
> _to(x);
> return true;
> } else {
> return false;
> }
> }
> }
>
> /**
> * This is the method that does the work. Called from
> initialize
> */
> public void execute() {
> // Calculate tentative distances of unvisited neighbors
> List<Node> unvisited_neighbors =
> current.callRole(CurrentIntersection.class).unvisited_neighbors();
> if (unvisited_neighbors != null) {
> for (Node neighbor : unvisited_neighbors) {
>
> Integer tentativeDistance = (Integer)
> current.getData("tentative_distance");
> Integer distanceBetween =
> map.callRole(CartographyMap.class).distance_between(current,
> neighbor);
> boolean relable_node_as =
> neighbor.callRole(Neighbor.class).relable_node_as(tentativeDistance +
> distanceBetween);
>
> if (relable_node_as) {
> pathTo.put(neighbor, current);
> }
> }
> }
>
> unvisited.remove(current);
>
> // Are we done?
>
> if (unvisited.size() == 0) {
> save_path(this.path);
> } else {
>
> // The next current node is the one with the least
> distance in the
> // unvisited set
>
> Node selection = nearest_unvisited_node_to_target();
>
> // Recur
> new CalculateShortestPath(selection, destination, map,
> path, unvisited, pathTo);
> }
> }
>
> Node nearest_unvisited_node_to_target() {
>
> int min = INFINITY;
> Node selection = null;
>
> for (Node intersection : unvisited.keySet()) {
> if (unvisited.get(intersection)) {
>
> if(intersection.getData("tentative_distance",
> Integer.class) <= min) {
>
> min =
> intersection.getData("tentative_distance", Integer.class);
> selection = intersection;
> }
> }
> }
> return selection;
> }
>
> /**
> * This method does a simple traversal of the data structures
> (following
> * pathTo) to build the directed traversal vector for the
> minimum path
> */
> void save_path(List<Node> pathVector) {
>
> Node node = destination;
> do {
> pathVector.add(node);
>
> node = pathTo.get(node);
>
> } while (node != null);
> }
>
> public List<Node> getPath() {
> return path;
> }
>
> }
>
> ////////////////////////////////////////////////////////////////
>
>
> /**
> * This is the main Context for shortest distance calculation
> */
> static class CalculateShortestDistance extends Context {
>
> List<Node> path = new ArrayList<Node>();
> Geometry map;
> Node destination;
> Node current;
>
> //MAP ROLE: SEE COMMON CODE NEAR TOP
> //DISTANCE LABELED GRAPH NODE: SEE COMMON CODE NEAR TOP
>
> void rebind(Node origin_node, Geometry geometries){
> current = origin_node;
> destination = geometries.destination();
> map = geometries;
>
> bind(map, CartographyMap.class);
>
> for(Node node : map.nodes()){
> bind(node, Distance_labeled_graph_node.class);
> }
> }
>
> public CalculateShortestDistance(Node origin_node, Node
> target_node, Geometry geometries) {
>
> rebind(origin_node, geometries);
>
>
> this.current.callRole(Distance_labeled_graph_node.class).set_tentative_
> distance_to(0);
>
> this.path = new CalculateShortestPath(this.current,
> this.destination, geometries, null, null, null).getPath();
>
> cleanup(); //related to context stacking
> }
>
> public int distance() {
> int retval = 0;
> Node previous_node = null;
>
> List<Node> reversed = new ArrayList<Node>(path);
> Collections.reverse(reversed);
>
> for (Node node : reversed) {
>
> if (previous_node == null) {
> retval = 0;
> } else {
> retval +=
> this.map.callRole(CartographyMap.class).distance_between(previous_node,
> node);
> }
> previous_node = node;
> }
> return retval;
> }
> }
>
> ////////////////////////////////////////////////////////////////
>
> static abstract class Geometry extends DomainObject {
>
> List<Node> nodes;
> Node root;
> Node destination;
> Map<Pair, Integer> distances;
> Map<Node, Node> next_down_the_street_from = new HashMap<Node,
> Node>();
> Map<Node, Node> next_along_the_avenue_from = new HashMap<Node,
> Node>();
>
> public Node east_neighbor_of(Node a) {
> return next_down_the_street_from.get(a);
> }
>
> public Node south_neighborOf(Node a) {
> return next_along_the_avenue_from.get(a);
> }
>
> public Node root() {
> return root;
> }
>
> public Node destination() {
> return destination;
> }
>
> public List<Node> nodes() {
> return nodes;
> }
>
> public Node getRoot() {
> return root;
> }
>
> public Node getDestination() {
> return destination;
> }
>
> public List<Node> getNodes() {
> return nodes;
> }
>
> public Map<Pair, Integer> getDistances() {
> return distances;
> }
> }
>
> ////////////////////////////////////////////////////////////////
>
> static class ManhattanGeometry1 extends Geometry {
>
> public ManhattanGeometry1() {
> this.nodes = new ArrayList<Node>();
> this.distances = new HashMap<Pair, Integer>();
>
> String[] names = { "a", "b", "c", "d", "a", "b", "g", "h",
> "i" };
>
> for (int i = 0; i < 3; i++) {
> for (int j = 0; j < 3; j++) {
> this.nodes.add(new Node(names[(i * 3) + j]));
> }
> }
>
> // Aliases to help set up the grid. Grid is of Manhattan
> form:
> //
> // a - 2 - b - 3 - c
> // | | |
> // 1 2 1
> // | | |
> // d - 1 - e - 1 - f
> // | |
> // 2 4
> // | |
> // g - 1 - h - 2 - i
> //
> Node a = this.nodes.get(0);
> root = a;
> Node b = this.nodes.get(1);
> Node c = this.nodes.get(2);
> Node d = this.nodes.get(3);
> Node e = this.nodes.get(4);
> Node f = this.nodes.get(5);
> Node g = this.nodes.get(6);
> Node h = this.nodes.get(7);
> Node i = this.nodes.get(8);
> destination = i;
>
> for (int s = 0; s < 3; s++) {
> for (int t = 0; t < 3; t++) {
> this.distances.put(new Pair(nodes.get(s),
> nodes.get(t)), INFINITY);
> }
> }
>
> distances.put(new Pair(a, b), 2);
> distances.put(new Pair(b, c), 3);
> distances.put(new Pair(c, f), 1);
> distances.put(new Pair(f, i), 4);
> distances.put(new Pair(b, e), 2);
> distances.put(new Pair(e, f), 1);
> distances.put(new Pair(a, d), 1);
> distances.put(new Pair(d, g), 2);
> distances.put(new Pair(g, h), 1);
> distances.put(new Pair(h, i), 2);
> distances.put(new Pair(d, e), 1);
>
> distances = Collections.unmodifiableMap(distances);
>
> next_down_the_street_from.put(a, b);
> next_down_the_street_from.put(b, c);
> next_down_the_street_from.put(d, e);
> next_down_the_street_from.put(e, f);
> next_down_the_street_from.put(g, h);
> next_down_the_street_from.put(h, i);
> next_down_the_street_from = Collections
> .unmodifiableMap(next_down_the_street_from);
>
> next_along_the_avenue_from.put(a, d);
> next_along_the_avenue_from.put(b, e);
> next_along_the_avenue_from.put(c, f);
> next_along_the_avenue_from.put(d, g);
> next_along_the_avenue_from.put(f, i);
>
> next_along_the_avenue_from = Collections
> .unmodifiableMap(next_along_the_avenue_from);
> }
>
> }
>
> ////////////////////////////////////////////////////////////////
>
> static class ManhattanGeometry2 extends Geometry {
>
> public ManhattanGeometry2() {
> this.nodes = new ArrayList<Node>();
> this.distances = new HashMap<Pair, Integer>();
>
> String[] names = { "a", "b", "c", "d", "a", "b", "g", "h",
> "i", "j", "k" };
>
> for (int j = 0; j < 11; j++) {
> nodes.add(new Node(names[j]));
> }
>
> // Aliases to help set up the grid. Grid is of Manhattan
> form:
> //
> // a - 2 - b - 3 - c - 1 - j
> // | | | |
> // 1 2 1 |
> // | | | |
> // d - 1 - e - 1 - f 1
> // | | |
> // 2 4 |
> // | | |
> // g - 1 - h - 2 - i - 2 - k
> //
> Node a = nodes.get(0);
> root = a;
> Node b = nodes.get(1);
> Node c = nodes.get(2);
> Node d = nodes.get(3);
> Node e = nodes.get(4);
> Node f = nodes.get(5);
> Node g = nodes.get(6);
> Node h = nodes.get(7);
> Node i = nodes.get(8);
> Node j = nodes.get(9);
> Node k = nodes.get(10);
> destination = k;
>
> for (int s = 0; s < 11; s++) {
> for (int t = 0; t < 11; t++) {
> distances.put(new Pair(nodes.get(s),
> nodes.get(t)),
> INFINITY);
> }
> }
>
> distances.put(new Pair(a, b), 2);
> distances.put(new Pair(b, c), 3);
> distances.put(new Pair(c, f), 1);
> distances.put(new Pair(f, i), 4);
> distances.put(new Pair(b, e), 2);
> distances.put(new Pair(e, f), 1);
> distances.put(new Pair(a, d), 1);
> distances.put(new Pair(d, g), 2);
> distances.put(new Pair(g, h), 1);
> distances.put(new Pair(h, i), 2);
> distances.put(new Pair(d, e), 1);
> distances.put(new Pair(c, j), 1);
> distances.put(new Pair(j, k), 1);
> distances.put(new Pair(i, k), 2);
>
> distances = Collections.unmodifiableMap(distances);
>
> next_down_the_street_from.put(a, b);
> next_down_the_street_from.put(b, c);
> next_down_the_street_from.put(c, j);
> next_down_the_street_from.put(d, e);
> next_down_the_street_from.put(e, f);
> next_down_the_street_from.put(g, h);
> next_down_the_street_from.put(h, i);
> next_down_the_street_from.put(i, k);
>
> next_down_the_street_from = Collections
> .unmodifiableMap(next_down_the_street_from);
>
> next_along_the_avenue_from.put(a, d);
> next_along_the_avenue_from.put(b, e);
> next_along_the_avenue_from.put(c, f);
> next_along_the_avenue_from.put(d, g);
> next_along_the_avenue_from.put(f, i);
> next_along_the_avenue_from.put(j, k);
>
> next_along_the_avenue_from = Collections
> .unmodifiableMap(next_along_the_avenue_from);
> }
>
> }
>
> ////////////////////////////////////////////////////////////////
>
> /** Test drivers */
> public static void main(String[] args) {
>
> Geometry geometries = new ManhattanGeometry1();
>
> CalculateShortestPath path = new
> CalculateShortestPath(geometries.getRoot(),
> geometries.getDestination(), geometries, null, null,
> null);
>
> System.out.println("Path is: ");
> for (Node node : path.getPath()) {
> System.out.println(node.getName());
> }
>
> System.out.println("distance is "
> + new CalculateShortestDistance(geometries.getRoot(),
> geometries.getDestination(),
> geometries).distance());
>
> System.out.println();
>
> geometries = new ManhattanGeometry2();
>
> path = new CalculateShortestPath(geometries.getRoot(),
> geometries.getDestination(), geometries, null, null,
> null);
>
> System.out.println("Path is: ");
> Node last_node = null;
> for (Node node : path.getPath()) {
> if (last_node != null) {
> System.out.print(" - "
> + geometries.distances.get(new Pair(node,
> last_node))
> + " - ");
> }
> System.out.print(node.getName());
> last_node = node;
> }
>
> System.out.println();
> System.out.println("distance is "
> + new CalculateShortestDistance(geometries.getRoot(),
> geometries.getDestination(),
> geometries).distance());
> }
>
> }
>
> ---- output -----
>
> Path is:
> i
> h
> g
> d
> a
> distance is 6
>
> Path is:
> k - 1 - j - 1 - c - 3 - b - 2 - a
> distance is 7
>
>
> ----- framework impl -----
>
> /*
> * Copyright (c) 2011 Ant Kutschera
> *
> * This file is part of Ant Kutschera's blog.
> *
> * This is free software: you can redistribute it and/or modify
> * it under the terms of the Lesser GNU General Public License as
> published by
> * the Free Software Foundation, either version 3 of the License, or
> * (at your option) any later version.
> *
> * This software is distributed in the hope that it will be useful,
> * but WITHOUT ANY WARRANTY; without even the implied warranty of
> * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> * Lesser GNU General Public License for more details.
> * You should have received a copy of the Lesser GNU General Public
> License
> * along with this software. If not, see <http://www.gnu.org/licenses/
> >.
> */
> package ch.maxant.dci.util2;
>
>
> /**
> * Here are some rules to consider when implementing roles:<br>
> * <br>
> * 1) roles must NEVER EVER use "this". Instead they should always
> use "self".
> * <br><br>
> * 2) role methods should NEVER EVER use primative types as parameter
> types. e.g. "int" as a parameter will not work.<br>
> * use Integer instead. e.g.: <br>
> * <br>
> * <code>void someRoleMethod(Integer anInt, String someString){...}
> </code><br>
> *
> * @param <S> the type or interface which domain objects playing this
> role must be. used to provide the correct type to the
> * field "self" which subclasses should use in the methods
> they provide. "this" should NEVER BE USED!
> * @param <C> the type of the context to which this role belongs.
> used to provide the correct type on the field "context" which
> * role implementations have access to.
> */
> public class Role<S, C> {
>
> /** the object playing this role */
> protected S self;
>
> /** the context owning this role */
> protected C context;
>
> //not sure this is needed. only if in a role impl, you type
> something like
> // "this.equals(that)" - but you shouldn't use "this" in a role
> impl, only "self"
> @Override
> public int hashCode() {
> return self.hashCode();
> }
>
> //not sure this is needed. only if in a role impl, you type
> something like
> // "this.equals(that)" - but you shouldn't use "this" in a role
> impl, only "self"
> @Override
> public boolean equals(Object obj) {
> if(self == null){
> if(obj == null){
> return true;
> }else{
> return false;
> }
> }else{
> return self.equals(obj);
> }
> }
>
> @SuppressWarnings("unchecked")
> public Role() {
> try {
> Context ctx = Helper.getContext();
> this.context = (C)ctx;
> } catch (SecurityException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalArgumentException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (NoSuchFieldException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalAccessException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> }
> }
>
> }
>
>
> /*
> * Copyright (c) 2011 Ant Kutschera
> *
> * This file is part of Ant Kutschera's blog.
> *
> * This is free software: you can redistribute it and/or modify
> * it under the terms of the Lesser GNU General Public License as
> published by
> * the Free Software Foundation, either version 3 of the License, or
> * (at your option) any later version.
> *
> * This software is distributed in the hope that it will be useful,
> * but WITHOUT ANY WARRANTY; without even the implied warranty of
> * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> * Lesser GNU General Public License for more details.
> * You should have received a copy of the Lesser GNU General Public
> License
> * along with this software. If not, see <http://www.gnu.org/licenses/
> >.
> */
> package ch.maxant.dci.util2;
>
> import java.lang.reflect.Field;
> import java.util.HashMap;
> import java.util.HashSet;
> import java.util.Map;
> import java.util.Set;
> import java.util.Stack;
>
> import javax.annotation.Resource;
>
> /**
> * DCI contexts must inherit from this class
> */
> public abstract class Context {
>
> private static final ThreadLocal<Stack<Context>> contextStacks =
> new ThreadLocal<Stack<Context>>();
> static {
> contextStacks.set(new Stack<Context>());
> }
>
> /** resources, which will be injected into roles as required. */
> private Map<String, Object> resources = new HashMap<String,
> Object>();
>
> /** so that we can clean up, we keep a list of domain objects that
> are assigned roles */
> private Map<DomainObject, Set<Class<? extends Role<?,?>>>>
> domainObjects = new HashMap<DomainObject, Set<Class<? extends Role<?,?
> >>>>();
>
> public Context() {
> contextStacks.get().push(this);
> }
>
> /**
> * add a resource which can be injected into the role.
> * <br><br>
> * in the role, there may be a requirement to use say an {@link
> EntityManager}
> * in order to persist a new part of the domain model. the entity
> manager could theoretically
> * be passed to the role after its contruction, but the entity
> manager has nothing to do
> * with the users mental model - its a technical thing. so simply
> let it be injected, and available,
> * should it be required.
> * <br><br>
> * to use this, the current implementation looks for fields with
> the "name" you pass. any such
> * fields in either the role class, or any of its super classes,
> which are marked with {@link Resource}
> * get injected.
> */
> public void addResource(String name, Object resource){
> resources.put(name, resource);
> }
>
> /**
> * assign an object a role. if its already in that role, nothing
> happens!
> * @param object the object to play the role
> * @param roleClass the role to play
> */
> public void bind(DomainObject object, Class<? extends Role<?,?>>
> roleClass){
>
> Set<Class<? extends Role<?,?>>> roles =
> domainObjects.get(object);
> if(roles == null){
> roles = new HashSet<Class<? extends Role<?,?>>>();
> domainObjects.put(object, roles);
> }
> roles.add(roleClass);
> }
>
> /** MUST BE CALLED WHEN A CONTEXT IS FINSHED WITH */
> protected void cleanup(){
>
> //cleanup needs to pop contexts, so that the context is the
> previous one
>
> Stack<Context> contextStack = contextStacks.get();
> contextStack.pop();
>
> resources.clear();
> resources = null; //help the GC
>
> if(contextStack.isEmpty()){
> //clear all temp data on the object
> try {
> Field f =
> DomainObject.class.getDeclaredField("tempData");
> f.setAccessible(true);
> for(DomainObject o : domainObjects.keySet()){
> @SuppressWarnings("rawtypes")
> Map m = (Map)f.get(o);
> m.clear();
> }
> } catch (SecurityException e) {
> throw new RuntimeException("code change required in
> this class!", e);
> } catch (NoSuchFieldException e) {
> throw new RuntimeException("code change required in
> this class!", e);
> } catch (IllegalArgumentException e) {
> throw new RuntimeException("code change required in
> this class!", e);
> } catch (IllegalAccessException e) {
> throw new RuntimeException("code change required in
> this class!", e);
> }
> }
>
> domainObjects.clear();
> domainObjects = null; //help the GC
> }
>
> }
>
>
>
> /*
> * Copyright (c) 2011 Ant Kutschera
> *
> * This file is part of Ant Kutschera's blog.
> *
> * This is free software: you can redistribute it and/or modify
> * it under the terms of the Lesser GNU General Public License as
> published by
> * the Free Software Foundation, either version 3 of the License, or
> * (at your option) any later version.
> *
> * This software is distributed in the hope that it will be useful,
> * but WITHOUT ANY WARRANTY; without even the implied warranty of
> * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> * Lesser GNU General Public License for more details.
> * You should have received a copy of the Lesser GNU General Public
> License
> * along with this software. If not, see <http://www.gnu.org/licenses/
> >.
> */
> package ch.maxant.dci.util2;
>
> import java.lang.annotation.Annotation;
> import java.lang.reflect.Constructor;
> import java.lang.reflect.Field;
> import java.lang.reflect.InvocationTargetException;
> import java.util.HashMap;
> import java.util.Map;
> import java.util.Set;
>
> import javax.annotation.Resource;
>
> import ch.maxant.dci.util2.Role;
>
> /**
> * domain classes should extend this
> */
> public class DomainObject {
>
> /** key = field name, value = role name */
> private Map<String, Object> tempData = new HashMap<String,
> Object>();
>
> /** lets you set extra data which you have added to this object,
> for the lifetime of oldest parent context. */
> public Object getData(String name){
> return tempData.get(name);
> }
>
> /** lets you set extra data which you have added to this object,
> for the lifetime of the context */
> @SuppressWarnings("unchecked")
> public <T> T getData(String name, Class<T> returnType) {
> return (T)getData(name);
> }
>
> /** lets you set extra data on this object, for the lifetime of
> the context */
> public void setData(String name, Object data){
> tempData.put(name, data);
> }
>
> /** to call a role method on a role, call this method first */
> @SuppressWarnings("unchecked")
> public <T> T callRole(Class<T> roleClass) {
>
> if(roleClass == null){
> throw new NullPointerException("roleClass must be
> supplied");
> }
>
> if(!Role.class.isAssignableFrom(roleClass)){
> throw new IllegalArgumentException("the roleClass
> parameter (" + roleClass + ") must be an instance of the class Role");
> }
>
> try{
> Context context = Helper.getContext();
>
> if(context == null){
> throw new IllegalStateException("role methods can only
> be called on objects where a valid context exists, and no context
> could be found. instantiate a class of type Context before calling
> this method.");
> }
>
> //
> // is it playing that role?
> //
> if(!isPlayingRole((Class<? extends Role<?,?>>)roleClass)){
> throw new IllegalStateException("the object " + this +
> " is not currently playing the role " + roleClass + " in the context "
> + context);
> }
>
> //
> // instantiate the role class
> //
>
> @SuppressWarnings("rawtypes")
> Constructor constructor = null;
> int numParams = 99;
> for(Constructor<?> c : roleClass.getDeclaredConstructors())
> {
> if(c.getParameterTypes() == null){
> numParams = 0;
> constructor = c;
> }else{
> if(c.getParameterTypes().length < numParams){
> numParams = c.getParameterTypes().length;
> constructor = c;
> }
>
> }
> }
>
> if(constructor == null){
> throw new RuntimeException("unable to find a
> constructor for role " + roleClass);
> }
> constructor.setAccessible(true);
> Role<?,?> roleInstance = (Role<?, ?>)
> constructor.newInstance(new Object[numParams]);
>
> //
> // inject self into role
> //
>
> Field f = Role.class.getDeclaredField("self");
> f.setAccessible(true);
> f.set(roleInstance, this);
>
> //
> // inject resources into role
> //
>
> f = Context.class.getDeclaredField("resources");
> f.setAccessible(true);
> Map<String, Object> resources = (Map<String, Object>)
> f.get(context);
> if(resources != null && !resources.isEmpty()){
> injectResources(roleInstance, roleInstance.getClass(),
> resources);
> }
>
> return (T)roleInstance;
>
> } catch (SecurityException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (NoSuchFieldException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalArgumentException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalAccessException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (InstantiationException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (InvocationTargetException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> }
> }
>
> private void injectResources(Object roleInstance,
> @SuppressWarnings("rawtypes") Class clazz, Map<String, Object>
> resources) {
> Field[] declaredFields = clazz.getDeclaredFields();
> Field[] otherFields = clazz.getFields();
> Field[] allFields = new Field[declaredFields.length +
> otherFields.length];
> System.arraycopy(declaredFields, 0, allFields, 0,
> declaredFields.length);
> System.arraycopy(otherFields, 0, allFields,
> declaredFields.length, otherFields.length);
>
> for (Field field : allFields) {
> Annotation a = field.getAnnotation(Resource.class);
> if (a != null) {
> String name = field.getName();
> Object resource = resources.get(name);
> if(resource == null){
> //check using the name defined in the annotation
> String aName = ((Resource)a).name();
> if(aName != null){
> name = aName;
> resource = resources.get(name);
> }
> }
> if(resource != null){
> field.setAccessible(true); //it may be private!
> try {
> field.set(roleInstance, resource);
> } catch (Exception e) {
> throw new RuntimeException("unable to set
> resource " + name + " in class " + roleInstance.getClass().getName(),
> e);
> }
> }
> }
> }
>
> // do it recursively, as roles may have super classes!
> if(clazz.getSuperclass() != null){
> injectResources(roleInstance, clazz.getSuperclass(),
> resources);
> }
>
> }
>
> public boolean isPlayingRole(Class<? extends Role<?,?>> roleClass)
> {
>
> try{
> Context ctx = Helper.getContext();
> if(ctx == null){
> return false;
> }
>
> Field f = Context.class.getDeclaredField("domainObjects");
> f.setAccessible(true);
> @SuppressWarnings("unchecked")
> Map<DomainObject, Set<Class<? extends Role<?,?>>>>
> domainObjects = (Map<DomainObject, Set<Class<? extends Role<?,?
> >>>>)f.get(ctx);
> Set<Class<? extends Role<?,?>>> roles =
> domainObjects.get(this);
> if(roles != null && !roles.isEmpty()){
> if(roles.contains(roleClass)){
> return true;
> }
> }
> return false;
> } catch (SecurityException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (NoSuchFieldException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalArgumentException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> } catch (IllegalAccessException e) {
> throw new RuntimeException("code change required in this
> class!", e);
> }
> }
>
> }

Ant Kutschera

unread,
Oct 4, 2011, 11:55:50 AM10/4/11
to dci-evolution
I've been reading some interesting stuff.

Java 7 byte code contains an instruction for invoking dynamic
methods. It's part of JSR-292 which is all about closures, lambda,
and integration with dynamic languages.

While Java 7 only contains part of it, it also includes a new
reflection API, cut at a different, seemingly lower level.

And Java 8, expected next year will provide the finished JSR, so that
invoking dynamic methods will be possible.

There is a patch for the OpenJDK out there, which lets you do a lot of
this stuff today.

I even read one blog where someone was talking about writing code like
this:

myBook.findByProductCodeAndCountry(123, Locale.DE);

That method does not exist in the "Book" class, of which myBook is an
instance.

It's basically crude DCI :-)

It will be interesting to see how DCI compliant it is, once they fully
release it. It doesn't currently look like they have considered roles
or contexts, context stacks or scopes. Let's wait an see... All
exciting, nonetheless.

ant.ku...@gmail.com

unread,
Oct 4, 2011, 11:57:04 AM10/4/11
to dci-ev...@googlegroups.com

Careful: Trygve just a few days ago, reminded us that contexts can be used at much lower levels than use cases.  That means while a use case might take man-months to implement, individual contexts won't necessarily.  Think in terms of nested contexts, then it becomes easier to see why the people here are interested in keeping roles and contexts in one file.


----- Reply message -----
From: "rune funch" <funchs...@gmail.com>
To: "dci-ev...@googlegroups.com" <dci-ev...@googlegroups.com>
Subject: Roles in Eclipse Indigo
Date: Tue, Oct 4, 2011 17:48


We have several use cases that has take many man years to create that
does not imply that the what-the-system-does is huge. In reality most
of them are less than 50 LOC for the what the system does part

-Rune

Den 04/10/2011 kl. 17.10 skrev "Wenig, Stefan" <stefan...@rubicon.eu>:

> I thought contexts are supposed to get huge, like man-months of work? (Assuming that use cases map to contexts more or less 1:1). I believe Jim said that a while ago. So I'm very surprised that it's now being argued that everything should be crammed into a single file!
>
> If a single context grows beyond 1000 lines of code I'd rather have its roles in individual files, packaged in a folder/namespace structure.
>
>> -----Original Message-----
>> From: dci-ev...@googlegroups.com [mailto:dci-
>> evol...@googlegroups.com] On Behalf Of Ant Kutschera
>> Sent: Monday, October 03, 2011 1:27 PM
>> To: dci-evolution
>> Subject: Re: Roles in Eclipse Indigo
>>

Wenig, Stefan

unread,
Oct 4, 2011, 11:57:53 AM10/4/11
to dci-ev...@googlegroups.com
So your systems don't do much, do they? ;-)

But seriously, if you only have 50 LOC, why bother with DCI at all? None of this is visible from the outside, and from the inside, the complexity of 50 LOC is manageable no matter how you do it. I was thinking these samples are just for explanation, but contexts are expected to grow much larger in real life. Am I wrong here?

Stefan

Wenig, Stefan

unread,
Oct 4, 2011, 12:01:00 PM10/4/11
to dci-ev...@googlegroups.com

Makes sense too. I guess it would be best to leave this decision to the programmer. Better decided on a by-case basis. Losing the capability to put large roles in separate files would just… suck.

 

Stefan

ant.ku...@gmail.com

unread,
Oct 4, 2011, 12:04:50 PM10/4/11
to dci-ev...@googlegroups.com
Just to be pernickerty Stefan, a role contract is the interface which domain object are required to have in order to play a role.  IRole is the role interface, which is totally different.


----- Reply message -----
From: "Wenig, Stefan" <stefan...@rubicon.eu>
To: "dci-ev...@googlegroups.com" <dci-ev...@googlegroups.com>
Subject: Roles in Eclipse Indigo

Stefan

 

From: dci-ev...@googlegroups.com [mailto:dci-ev...@googlegroups.com] On Behalf Of Rune Funch Søltoft
Sent: Monday, October 03, 2011 10:34 AM
To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo

 

In C# you've been able to use multiple files for the same class for years. It's in general highly discouraged because it makes it difficult to navigate the class. Visual studio has had support for navigating partial class definitions all a long. If you have to search for the information it's not at the same location.

Wenig, Stefan

unread,
Oct 4, 2011, 12:06:54 PM10/4/11
to dci-ev...@googlegroups.com
I'd again recommend that you play around with Runes 'dynamic' based C# version. It's possible there to do this today, but your (more verbose) Java version keeps more attributes of static typing and therefore fits better into the Java picture IMHO. Also, the 'dynamic' code has the same old problem of name clashes and visibility unless you do some serious run-time dispatching. Without having read the JSR, I don't think it solves it (because a) I don't see how, and b) I don't think they cared, since they use it just for interoperability with dynamic languages that don't have these features to start with).

I really think your callRole-approach is both better if you'd use it today, and more interesting if you want to explore possible language improvements to Java.

Stefan

> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of Ant Kutschera

> Sent: Tuesday, October 04, 2011 5:56 PM
> To: dci-evolution
> Subject: Re: Roles in Eclipse Indigo
>

rune funch

unread,
Oct 4, 2011, 12:11:07 PM10/4/11
to dci-ev...@googlegroups.com
Firstly I don't think our project is standard in the size of use case
implementation I was simply trying to say that just because something
takes a long time to create doesn't mean it's big

The project I was on before this current had around 20 use cases was
written in 65k Lines of c++ code and was being reduced when I left.
Parts of it was pretty complex most was rather straight forward. Quite
a fraction of the code was low level hardware drivers and graphic
rendering and I think 25% of the code had anything to do with what the
system does.

When I left the project had been on going for 10 years with an average
of around 10 people on the software team. Admittedly wasn't the
highest performing team I've been on but again LOC is not a function
of time. We actually reduce the count from 88k to 65k during the two
years I was there _while_ adding three major use cases.

-Rune

ant.ku...@gmail.com

unread,
Oct 4, 2011, 12:11:48 PM10/4/11
to dci-ev...@googlegroups.com
I only looked at versions of qi4j when you were still required to define an interface containing all roles and the domain object interface up front before role casting, and i think outside of contexts, ie when it didn't seem very mature compared to what we expect now.  I think I once read that that is no longer required.

I'm really interested in seeing Dijkstra's algorithm in qi4j, but as yet no one has done it.  That would be a great way to compare the two.




----- Reply message -----
From: "Wenig, Stefan" <stefan...@rubicon.eu>
To: "dci-ev...@googlegroups.com" <dci-ev...@googlegroups.com>
Subject: Roles in Eclipse Indigo
Date: Tue, Oct 4, 2011 17:52


Now this really looks like something a static-typing mind can live with! It's still a bit verbose, but I guess most Java programmers will just be happy they don't have to write anonymous classes for each call ;-)

I hope a get a chance to look at it in more detail soon. How do you think it compares with Qi4j specifically? My take (after a very short look):
+ no name conflicts
+ no compile-time dependencies on a single list of roles per data class
- more verbose call syntax
- callRole will potentially fail for every single call (whereas Qi4j gives you a single type that combines all methods, and you only have to check when binding)

Stefan

> -----Original Message-----
> From: dci-ev...@googlegroups.com [mailto:dci-
> evol...@googlegroups.com] On Behalf Of Ant Kutschera
> Sent: Sunday, October 02, 2011 11:38 AM
> To: dci-evolution
> Subject: Re: Roles in Eclipse Indigo
>

Wenig, Stefan

unread,
Oct 4, 2011, 12:13:36 PM10/4/11
to dci-ev...@googlegroups.com

Thanks, I just learned a new word! But just to be pernickety, there’s just one ‘r ‘ in it ;-)

 

(see? I used it in a sentence!)

 

BTW, I was talking about two different interfaces, IRole1 and an optional role contract interface. (You might even need a third one that derives from both, so you can call an object’s role player methods and role methods from a single strongly-typed variable. I think I once called them IRole1Methods, IRole1Contract, and IRole1 for the combination of both. The role player implicitly or explicitly implements IRole1Contract, the role mixin implements IRole1Methods, and IRole1 just derives from both. I guess that’s just what happens if you try and get creative in statically typed languages ;-))

 

Stefan

ant.ku...@gmail.com

unread,
Oct 4, 2011, 12:16:17 PM10/4/11
to dci-ev...@googlegroups.com
I hope management got bonuses based on LOCAL :-)




----- Reply message -----
From: "rune funch" <funchs...@gmail.com>
To: "dci-ev...@googlegroups.com" <dci-ev...@googlegroups.com>
Subject: Roles in Eclipse Indigo
Date: Tue, Oct 4, 2011 18:11


Firstly I don't think our project is standard in the size of use case
implementation I was simply trying to say that just because something
takes a long time to create doesn't mean it's big

The project I was on before this current had around 20 use cases was
written in 65k Lines of c++ code and was being reduced when I left.
Parts of it was pretty complex most was rather straight forward. Quite
a fraction of the code was low level hardware drivers and graphic
rendering and I think 25% of the code had anything to do with what the
system does.

When I left the project had been on going for 10 years with an average
of around 10 people on the software team. Admittedly wasn't the
highest performing team I've been on but again LOC is not a function
of time. We actually reduce the count from 88k to 65k during the two
years I was there _while_ adding three major use cases.

-Rune

Den 04/10/2011 kl. 17.57 skrev "Wenig, Stefan" <stefan...@rubicon.eu>:

> So your systems don't do much, do they? ;-)
>
> But seriously, if you only have 50 LOC, why bother with DCI at all? None of this is visible from the outside, and from the inside, the complexity of 50 LOC is manageable no matter how you do it. I was thinking these samples are just for explanation, but contexts are expected to grow much larger in real life. Am I wrong here?
>
> Stefan
>
>> -----Original Message-----
>> From: dci-ev...@googlegroups.com [mailto:dci-
>> evol...@googlegroups.com] On Behalf Of rune funch
>> Sent: Tuesday, October 04, 2011 5:48 PM
>> To: dci-ev...@googlegroups.com
>> Subject: Re: Roles in Eclipse Indigo
>>
>> We have several use cases that has take many man years to create that
>> does not imply that the what-the-system-does is huge. In reality most
>> of them are less than 50 LOC for the what the system does part
>>
>> -Rune
>>
>> Den 04/10/2011 kl. 17.10 skrev "Wenig, Stefan"
>> <stefan...@rubicon.eu>:
>>
>>> I thought contexts are supposed to get huge, like man-months of work?
>> (Assuming that use cases map to contexts more or less 1:1). I believe
>> Jim said that a while ago. So I'm very surprised that it's now being
>> argued that everything should be crammed into a single file!
>>>
>>> If a single context grows beyond 1000 lines of code I'd rather have
>> its roles in individual files, packaged in a folder/namespace
>> structure.
>>>
>>>> -----Original Message-----
>>>> From: dci-ev...@googlegroups.com [mailto:dci-
>>>> evol...@googlegroups.com] On Behalf Of Ant Kutschera
>>>> Sent: Monday, October 03, 2011 1:27 PM
>>>> To: dci-evolution
>>>> Subject: Re: Roles in Eclipse Indigo
>>>>

Wenig, Stefan

unread,
Oct 4, 2011, 12:17:30 PM10/4/11
to dci-ev...@googlegroups.com

So would I. And I still didn’t get around to code it up using re-mix (guess the Dijkstra thing scares me a bit too ;-))

 

Unfortunately, I’m beginning to think that Qi4j can’t really work around the name conflict problem, because unlike the CLR, the JVM won’t let you implement 2 interface methods of the same name. That would be a killer. Can anybody confirm this?

 

Stefan

ant.ku...@gmail.com

unread,
Oct 4, 2011, 12:20:55 PM10/4/11
to dci-ev...@googlegroups.com
Damn, I wrote it with one r and then corrected it :-(

All those role interfaces sound like my sci framework which used dynamic proxies.  It wasn't as good as what I have now.


----- Reply message -----
From: "Wenig, Stefan" <stefan...@rubicon.eu>
To: "dci-ev...@googlegroups.com" <dci-ev...@googlegroups.com>
Subject: Roles in Eclipse Indigo
Date: Tue, Oct 4, 2011 18:13

Thanks, I just learned a new word! But just to be pernickety, there’s just one ‘r ‘ in it ;-)

 

(see? I used it in a sentence!)

 

BTW, I was talking about two different interfaces, IRole1 and an optional role contract interface. (You might even need a third one that derives from both, so you can call an object’s role player methods and role methods from a single strongly-typed variable. I think I once called them IRole1Methods, IRole1Contract, and IRole1 for the combination of both. The role player implicitly or explicitly implements IRole1Contract, the role mixin implements IRole1Methods, and IRole1 just derives from both. I guess that’s just what happens if you try and get creative in statically typed languages ;-))

 

Stefan

 

From: dci-ev...@googlegroups.com [mailto:dci-ev...@googlegroups.com] On Behalf Of ant.ku...@gmail.com
Sent: Tuesday, October 04, 2011 6:05 PM
To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo

 

Just to be pernickerty Stefan, a role contract is the interface which domain object are required to have in order to play a role.  IRole is the role interface, which is totally different.

----- Reply message -----
From: "Wenig, Stefan" <stefan...@rubicon.eu>
To: "dci-ev...@googlegroups.com" <dci-ev...@googlegroups.com>
Subject: Roles in Eclipse Indigo

Stefan

 

From: dci-ev...@googlegroups.com [mailto:dci-ev...@googlegroups.com] On Behalf Of Rune Funch Søltoft
Sent: Monday, October 03, 2011 10:34 AM
To: dci-ev...@googlegroups.com
Subject: Re: Roles in Eclipse Indigo

 

In C# you've been able to use multiple files for the same class for years. It's in general highly discouraged because it makes it difficult to navigate the class. Visual studio has had support for navigating partial class definitions all a long. If you have to search for the information it's not at the same location.

It is loading more messages.
0 new messages