
Bookmarks:

Preface
The Lilly classes make tool building with Lilith
easy. They define a simple interface and default actions for the client and
the user code (Lilim) on the tree nodes for initialization, data distribution, and result collection.
The LillyLilim Class
In Lilith in general, the user code, called Lilim, handles the action
at each node of the tree. In the Lilly classes, a special Lilim, called
LillyLilim, provides an easy way for the user code to obtain a special
type of functionality from Lilith.
The LillyLilim class is automatically instantiated on each node of the tree.
It is responsible for:
Unseen by the user, the LillyLilim class also ensures that the methods are called in the proper order and that data is passed around the tree properly. The user gains these actions automatically through the inheretance of LillyLilim; this requires no knowledge or intervention on the part of the user.
The LillyInterface and default behavior is described below.
| Lilly Interface | PURPOSE | DEFAULT BEHAVIOR |
|---|---|---|
| void initOnTree(String[] args, MO fromClient) | performs initial actions on the tree. Receives as arguments an array containing the command line parameters and an MO sent from the client (the return value from LillyClient.initOnClient())for any initialization to be done on that node. All nodes in the tree will receive the same paramters. | nothing |
| MO[] distributeOnTree(MO m, int[] numDesc) | processes data to be sent down the tree. Receives as arguments an MO from its parent in the tree and an integer array containing the value of the total number of descendents of each child of this node. Returns an array of MO's, the first for itself and the rest for its children. The first MO in this array will become the argument to onTree(), and the rest will be arguments to the childrens' distributeOnTree() methods. | returns an MO array where each MO is a copy of the MO received in the arguments |
| MO onTree( MO m) | performs action on this node. Receives the first MO in the return array from this node's distributeOnTree(). Returns an MO that will be the first argument in the array to this node's collateOnTree() | returns the MO it received as an argument |
| MO collateOnTree(MO[] m) | processes data being returned up the tree. Receives as arguments an MO array where the first element is that returned from this node's onTree() and the rest are the returns from the childrens' collateOnTree(). Returns an MO that will be in the argument MO array of the parent's collateOnTree(). | returns an MO containing all the MOs in the argument array |
| Hashtable getSystemHandle() | a utility method that returns a hashtable useful for storing infomation. This method is not meant to be overridden. By default, this hashtable contains infomation about this node's position in the tree. | N/A |
The user's class derived from LillyLilim is automatically distributed and instantiated on each node. Its methods are then called automatically. initOnTree() is called only once, prior to the real action to be performed to handle any initial actions to be performed on that node. The others are called in a looping sequence until the client stops the loop. Each time through the loop, distributeOnTree() manipulates and distributes data down the tree, onTree() performs action on the node, and collateOnTree() manipulates and returns data back up the tree. Data is sent to and returned from methods in the form of Message Objects, aka MOs. The MO is a general purpose data rack that can hold a list of data objects.
Use of the LillyLilim is illustrated in the case of the distributed sort, distributeOnTree() receives the list of numbers to be sorted, separates the list into pieces for itself and its children to sort, and returns these lists. onTree() is responsible for sorting the list of numbers for that node and returning the sorted list, and collateOnTree() takes the sorted lists from oneself and one's children and merge sorts them together. The relevant pseudo-code follows:
In the distributed sort, initOnTree() is not used
and performs its default action of nothing, but a possible example
of its usage in another context would be to unpack a file send down from
the client to be instantiated on each node.
Note that MOs for the node itself
to handle are returned from distributeOnTree() and onTree()
and passed as parameters to onTree() and collateOnTree(). This is in order to
minimize the number of methods needing to be overriden in the case the
default behavior is desired. For example, if the data to be distributed
need not be manipulated down the tree, but needs to be saved for the
action of onTree(), then distributeOnTree() need not be overridden
to save the data because it is provided as an argument to
onTree(). Similiarly, if the data is not going to be changed in onTree(),
then onTree() need not be overridden to save the data since it
is provided as an argument to collateOnTree().
The Lilly Client Class
Unseen by the user, the LillyClient class also ensures that
the methods are called in the proper order and that data is sent to and
retrieved from the root properly. The user gains these actions
automatically through the inheretance of LillyClient;
this requires no
knowledge or intervention on the part of the user.
The LillyClientInterface and default behavior is described below.
The user starts LillyClient on the client. The methods
are called automatically. checkCommandLine() is called
to check the validity of the command line. If the method returns
false, the client exits. Otherwise the rest of the the LillyClient's actions
progrees. initOnClient() is called to
handle any initial actions on the client. The return value of
initOnClient(), the command line parameters, and the user's LillyLilim
are automatically packaged up and sent to the tree. The other methods
are called in a loop until the client decides to stop the loop.
distributeToTree()
calculates an MO to be sent to the root of the
tree, reapOnClient()
handles the return values from the collation on
the tree, and stopLoop()
is used to determine when to end the loop.
For example in the distributed sort,
distributeToTree() calculates a set of random numbers to be sorted
and returns this list. reapOnClient()
displays the sorted list. The relevant pseudo-code follows:
In this case, initOnClient() is not used
and performs its default action of nothing, but a possible example of its
usage in another context would be to pack up a local file to be sent to
each node of the tree only once, rather than each time through the loop,
as would be the case if it were sent through distributeToTree().
Note how the LillyClient and LillyLilim methods work together. This is
illustrated in color in the interfaces diagram.
LillyClient.initOnClient() provides an argument for LillyLilim.initOnTree().
LillyClient.distributeToTree() provides the argument for the
root LillyLilim.distributeToTree(). LillyClient.reapOnClient() handles the
collected results from the tree provided by the return from the root
LillyLilim.collateOnTree().
Data Passing in Lilith - the Message Object
MOs also carry with them an id field called an MOUUID used for identification of the MO. The MOUUID is in the form of a
user-defined String. MOUUIDs are set/returned using
set/getMOUUID.
public class SorterLilly extends
LillyLilim{
public MO[] distributeOnTree(MO tmpMO){
tmpMO.pullInt()
MO[] all_piecesMO
all_piecesMO[i].pushInt(int)
return all_piecesMO;
public MO OnTree(MO myMO){
myMO.pullInt() and places them into myArray
sort(myArray); /* sort own piece using own sort method */
MO my_pieceMO
my_pieceMO.pushInt(int)
return my_pieceMO;
public MO collateOnTree(MO[] allMO){
allMO
final array = mergeSort(allArrays); /* merge sort all sorted arrays */
MO combined_pieceMO
combined_pieceMO.pushInt(int)
return combined_pieceMO;
The user starts up the LillyClient to interact with the root node
of the tree. It is responsible for:
The LillyClientInterface class provides a few well-defined interfaces for
defining these events. The LillyClient class implements
LillyClientInterface, providing default methods for the interfaces.
The user extends the LillyClient class, overriding the
methods to suit his own purposes.
LillyClient Interface
PURPOSE
DEFAULT BEHAVIOR
MO
initOnClient()
performs initial actions on the client. Returns an MO containing
the results of those actions. This MO will be used in an initial
distribution of information down the tree and is provided to the
tree nodes as an argument to LillyLilim.initOnTree().
returns an empty MO
MO
distributeToTree(MO m)
calculates an MO to be sent to the root node to be distributed
to the tree. The return value is sent to the root node as the argument to
LillyLilim.distributeOnTree().
(This MO may be further processed by
LillyLilim.distributeOnTree() as it is sent down the tree.)
The argument is an MO - in the first iteration it is an empty MO; in
subsequent iterations it is the MO returned from the previous
iteration via reapOnClient().
returns an empty MO
void reapOnClient(MO m)
takes the resulting MO from the results collection on the tree
(the return value from the root's
distributeToTree() in the next iteration of the loop.
displays the Objects in the MO as strings
boolean stopLoop(MO m)
determines whether to stop iterating. Takes MO returned from
reapOnClient()
returns true, so default action is just one iteraction
boolean checkCommandLine(String[] argv)
takes command line arguments to check validity. If it returns false,
the client exits.
returns true
String[] getParameterArgs()
utility method to return the paramters from the command line arguments.
not intended to be overridden.
returns the parameters from the command line
public class SorterLillyClient extends
LillyClient{
public MO distributeToTree(MO reapedMO){
getParameterArgs()
MO all_numsMO
all_numsMO.pushInt(int)
return all_numsMO;
public void reapOnClient(MO reapedMO){
int nitems = ret.pullInt();
for (int i = 0; i < nitems; i++)
System.out.println(ret.pullInt());
MO Method
ACTION
void pushXXX(yyy)
adds an itemobject yyy of type XXX to the MO
XXX pullXXX()
removes an itemobject of type XXX from the MO
XXX peekXXX(m)
returns the value of the itemobject of type XXX at position m (integer) from the MO without removing it from the MO
Object hashedPut(String, yyy)
adds object yyy to be specified in the hash table interface by the String value returning the previous object
Object hashedGet(String)
returns a reference to the value of the object specified by the String value when placed in using the hash table interface, hashedPut(). This method does not remove the object from the MO.
Object hashedRemove(String)
returns a reference to the value of the object specified by the String value when placed in using the hash table interface, hashedPut(). This method removes the object from the MO
boolean hashedIsEmpty()
returns true if there are no hashed items in this MO
Enumeration hashedKeys()
returns an object that allows the user to enumerate over all the keys in the hash table
void setMOUUID(String)
sets the MOUUID to the String value
String getMOUUID()
returns the MOUUID value of this MO
Communications are handled through the sending of Message Objects, MOs. The MO is a general purpose data rack that can hold a list of data objects. It is capable of marshaling this data into a byte stream, unmarshaling it from a byte stream, and recreating the data in a new MO.
Each data item consists of a length, type, and the actual data. Data is placed into and removed from the MO through a well-defined set of calls pertaining to the primitive data types such as
push/pull/peekInt(), push/pull/peekString(), as well as push/pull/peekMO().
Push places an object into the MO, pull removes it from the MO, and peek returns the value without removing it from the MO.
There is also a hash table interface which allows the users to assign a label to each item.
This interface can be used to provide random access to internal MO data structures.
The methods are
hashedPut(), hashedGet(), and
hashedRemove(), and they support the Java wrapped types corresponding to the primitive types supported by MO.
The total set of user-related calls for assembling/disassembling the MO is defined above.