next_inactive up previous


Demeter/Java
Laboratory Guide









 
Walter Hürsch
Karl Lieberherr
Cristina Videira Lopes
Ignacio Silva-Lepe
Cun Xiao
 
with Modifications by Johan

IMPORTANT NOTICE

DISCLAIMER OF WARRANTY

In no event shall Northeastern University (NEU) be liable to any party for direct, indirect, special, incidental, or consequential damages arising out of the use of this software and its documentation, even if NEU has been advised of the possibility of such damage.

NEU specifically disclaims any warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The software provided hereunder is on an ``as is'' basis, and NEU has no obligation to provide maintenance, support, updates, enhancements, or modifications.

COPYRIGHT NOTICE

Copyright © 1996 - 1997 Northeastern University, Boston, MA 02115-9959. (SOFTWARE = Demeter/Java and associated documents.) All rights reserved. Copyright in SOFTWARE is owned by Northeastern University. Any person is hereby authorized to view, copy, print, run, modify (Java source is provided), and distribute SOFTWARE (without fee) subject to the following conditions:

  1. SOFTWARE is not sold commercially. Generated code may be sold commercially, except code generated when SOFTWARE is compiled with SOFTWARE.

  2. Any copy of the SOFTWARE or portion thereof must include this copyright notice.

  3. Successful use of the SOFTWARE in commercial and research projects is reported to demeter@ccs.neu.edu with a brief description of the project and possibly with feedback on how to improve the tools. The reports will be posted in the Demeter interest file .

Note that any product, process or technology described in the SOFTWARE may be the subject of other Intellectual Property rights reserved by Northeastern University and are not licensed hereunder.

The Demeter Tools are developed at Northeastern University, Boston, in the College of Computer Science, by the Demeter Research Group with support from DARPA, NSF, IBM, Mettler Toledo, Xerox PARC, Citibank, SAIC and Mitsubishi.

THIS SOFTWARE IS PROVIDED ``AS IS'' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.

NORTHEASTERN UNIVERSITY MAY MAKE IMPROVEMENTS AND/OR CHANGES IN THE SOFTWARE AT ANY TIME.

Summary: You can use the current Demeter/Java to produce products and sell them but you cannot sell Demeter/Java or its derivative works. But you may distribute them for free. You report back your successful uses which will allow us to attract further funding for our project. For further information on Intellectual Property matters regarding Demeter contact lieber@ccs.neu.edu.

See: http://www.ccs.neu.edu/research/demeter/sources/DemeterJava/NUDCopyright.html for any changes or updates to this copyright notice.

DemJava

brought to you by the efforts of

Kenneth R. Fox
Lars Thomas Hansen
Geoff Hulten
Karl Lieberherr
Doug Orleans
Johan Ovlinger
Patankar Kedar P
Boaz Patt-Shamir
Binoy Samuel

based on work on the Demeter C++ system by

Walter Hürsch
Karl Lieberherr
Ignacio Silva-Lepe
Cun Xiao

INTRODUCTION

Welcome to Demeter/Java $^{\scriptstyle TM}$, the adaptive object-oriented software development tool. Demeter/Java is designed to help you build high-quality object-oriented software in a productive and timely manner.

This Laboratory Guide is for newcomers to Demeter/Java$\,$ and shows you how easy it is to use Demeter/Java$\,$ by guiding you through a sample application. But first, a few words about Demeter/Java$\,$ and this Guide.

The Demeter System/Java

The Demeter System/Java$\;$ is an object-oriented software development method that allows you to develop adaptive programs. The Demeter/Java tools make it easier for you to create new programs and to maintain and modify the existing ones. The Demeter/Java features include:


The main characteristic of adaptive programs is that they define object behavior without specifying the detailed structure of the objects. Therefore, programs are less dependent on a specific class structure and thus become more adaptive to changes and evolution.

Supported platforms

Demeter System/Javais written in its own technology and runs in any Java 1.1 environment. Suns JDK 1.1 compiler is used to compile Demeter System/Java, and apart from minor differences in the implementation of Java's standard libraries, Microsoft's beta 1.1 compiliant J++ compiler can recompile Demeter System/Javatoo. Both Microsoft's and Sun's VM run Demeter System/Javawithout modification.

What you will learn in this Guide

Over the course of this Guide, you will learn how to:


What you should know before starting

This Guide assumes that you are running Demeter System/Javaon a Unix environment that you have some degree familiarity with. In particular, you should know how to echo and set up environment variables in the shell you're using. Throughout this Guide, we assume sh is being used, but this is by no means mandatory as long as you know the equivalent commands in the particular shell of your system. Also, we assume you know how to use some text editor (emacs, vi, or any other).

Although it is not required that you have any knowledge of adaptive software, it is strongly advised that you know the basics of object-oriented programming as well as the basics of the Java programming language.

Conventions used in this Guide

The following conventions are used throughout this document:


--------------------------
All instructions that you should follow are boxed like this.
--------------------------

ClassNames are supposedly written in non-serif type (in HTML, they appear to be italic), while <partnames> are written like they appear in the .cd file. MethodNames are also written in a special font.

Type represents text as it appears onscreen or in a program, possibly with the ``>'' sign as a prompt. It is also used for anything you must type literally.

GETTING STARTED

To follow the Laboratory Guide, you need to set up your environment and then copy the laboratory files into your directory.

Setting up the Demeter System/Java

This manual assumes that your system has Demeter System/Javaversion 0.7.1 installed. Verify this by running demeterj with the -version option.


--------------------------
> demeterj -version
--------------------------

If demeterj was not found or is too old, you'll have to install a newer version. Typically, this involves downloading and extracting a jar file. The LabGuide's home page (http://www.ccs.neu.edu/home/johan/labguide.html) has links to the demeterj installation page that details .

Running the Demeter System/Javaat NEU

Most people expected to read this guide will be working through it at a NEU computer. They will have had Demeter System/Javaset up for them, and all they need to do is to follow the instructions in http://www.ccs.neu.edu/research/demeter/DemeterJava/use/DemJavaUse.html to make sure everything is working right. Be careful! Only Solaris machines currently support the version of Java needed to run Demeter System/Java. Solaris machines are all named after star systems.

Setting up the laboratory program

Throughout most of this Guide we will use a simple Demeter/Java program which we call Library System. This program is a simplified version of a real library system, consisting of book-objects and user-objects - the data of the system - and operation-objects which implement a set of operations over the books and users.

In order to continue the Laboratory you need to copy the Library System files into some directory of yours. Obtain and install the system by following the instructions in the LabGuide web site (http://www.ccs.neu.edu/research/demeter/DemeterJava/use/lguide/html/labguide.html).

The Demeter/Javaphilosophy

All programmers who have written large programs in imperative languages like C, Java, or SmallTalk will have noticed that when the program reaches a certain size, the interactions between different parts of the program become difficult to keep track of. Localized changes (like, f.ex. the layout of a datastructure) have global effects as parts of the program that had made assumptions about the datastructure's layout (or location) now no longer work.

The Demeter programming paradigm, which Demeter/Javawas designed to support, claims that only by limiting the non-local assumptions an object makes can large programs become manageable. The Law of Demeter states that an object may only directly access it's own parts.

Should an object wish to access a non local data-structure, it must be retrieved from a neighbor via an accessor function (that in turn may have to ask a neighbor of the neighbor for the information). Since classes make no assumptions about non-local datastructures, should that datastructure be moved to another location, only the previous and new locations must be modified. All other classes can stay the same.

Programming with Traveling Circuses

Rather than limit itself to object access, Demeter/Javaproposes a new programming paradigm- that of visitor and traversals. A visitor encapsulates a specific behavior- the canonical example is counting the number of elements in a list that match some criteria. Rather than specify where data is to be found, that visitor only specifies what actions are to be performed when those datastructures are found. Sticking with the counting visitor, it might say that just when we reach an Item, (in demeter terminology, before an Item), we should check if it matches our criteria, and if-so, increment a counter that is local to this behaviour. In demeterj, we would write:

before Item (@ 
  if (host.matches(somecritera)) this.counter++; 
@)

The Item object that we have arrived at is called the host, and the visitor is referred to as this. The visitor is carried around the program's object graph by a traversal. Traversals are controlled by Strategies, that say in which classes the traversal may start, where the visitor wants to go, and which special considerations are to be respected along the way.

In demeter terminology, the combination of a traversal with a visitor is called an adaptive method. The two can be visualized as a traveling circus- the truck driver knows little of what the performers do; he just knows which towns need to be passed through in order to get to the performer's destinations, and which towns should be avoided at all costs. Conversely, the performers care nothing about the geographical locations of towns, concentrating on what should be performed in each town.

RUNNING THE LIBRARY SYSTEM PROGRAM

The current program is a very simple Library System; it works over simple definitions for books and users of the system, and initially the supported operations are limited to:

Throughout the laboratory guide you will modify and enhance this program. For now, let's just analyze the existing code and obtain the executable file.

Understanding the source code

All your hacking will be in the src subdirectory. You should cd to it now.


--------------------------
Do a listing of the current directory:
> ls
--------------------------

The program consists of 4 source files, 1 init file read by the application, and 1 project file. The project file is a recent addition to the Demeter/Java system, and replaces Makefiles and the make utilty, which are poorly supported on non-Unix systems. The project file specifies the dependencies between different parts of the application, and tells Demeter/Java the details about how we want the source files processed.

In a later section of this guide you will learn how to obtain and customize a project file; for the Library System, the files is already available and ready to be used.

Notice that there are 3 types of extensions on the files in the directory (ignore file LibInit, which is just an initializing file read in by the application).

.prj
: is the project file we just mentioned.
.cd
: contains a Demeter/Java class dictionary.
.beh
: contains Demeter/Java traversals, visitors, adaptive methods, and possibly verbatim Java code.

Any meaningful Demeter/Java application consists of one $<$file$>$.cd and at least one $<$file$>$.beh 1.

Class Dictionaries

Class dictionaries define the objects' structure; in this case, LibrarySystem.cd contains the class dictionary for the library system.

Three types of classes are available to the programmer:


Construction
classes are non-abstract classes that [as far as Demeter/Javais concerned] have no subclasses. Construction classes may have any number of parts, i.e. instance variables.
Alternation
classes are abstract superclasses of one or more classes. Alternation classes may have any number of subclasses, and any number of parts. Since these parts are available to all the subclasses, they are called common parts.
Repetition
classes represent lists of objects of a given class.


--------------------------
Take a look at LibrarySystem.cd:
> more LibrarySystem.cd
--------------------------

Lines of comments start with the Java comment token ``//''. Whatever is not commented defines the structure of classes. Note that LibrarySystem.cd consists of several entries with the generic form:

classname def-sign definition . (Notice the ``.'' at the end!!!)

For example,

Book = "book:" Title "by" Author Publisher Year Subject "ISBN" ISBN CopyList.
...
Phone :  HomePhone | WorkPhone | Fax 
         *common* <area_code> Integer "-" <number> Integer .

Books are construction classes, and can thus be instantiated. They contain one Title-part, one Author-part, one Publisher-part, etc. The syntactic element "book:" is used to define the grammar for Book-objects, but this will only be needed in a later section; for the time being we will ignore all syntactic elements in LibrarySystem.cd.

The Phone class is an alternation class. It has as subclasses HomePhone, WorkPhone, and Fax. All these classes have access to the parts <area_code> and <number>, both of which will be of the class Integer.

Figure 1: Graphical representation of the Library System class dictionary. To keep down clutter, most edges have not been labeled, and edges to classes not defined in the .cd file (Like String) have been omitted.
\begin{figure}\par\centerline{\epsfxsize=5in\epsfbox{lib-cd.ps}}\par\end{figure}

Graphically, the Library System class dictionary is represented as in figure 1. For more information about class dictionaries, please consult the Demeter/Java Users Guide and The Book.

Back to Visitors and Traversals

Let us understand a simple adaptive method. In User.beh, lines 81..85, get_quota() is defined as:

User {
  Integer get_quota() 
    to-stop Status
    { before Status (@ return_val = host.get_quota(); @) }
}

Let us look at the different parts of the definition:

User {

This indicates that the method is attached to class User. Any traversal strategy defined by an adaptive methods on User will have this class as a starting point.

   Integer get_quota()

We are defining an adaptive method called get_quota() that returns an Integer.

     to-stop Status

The traversal strategy should go from User to Status, stopping once it reaches its destination. Refer back to fig 1. Do you see that only User and Status are in the traversal? If we didn't stop once we've reached Status, we would go through ItemList, Item, Copy, User and arrive back at Status. Since this is a cycle, the traversal would never terminate.

     { before Status (@ return_val = host.get_quota(); @) }
}

We end the adaptive pattern by specifying a visitor. We could also have defined the visitor outside the method and referred to it by name. We take that approach in other adaptive methods in the program.

The visitor just has just one wrapper: Upon arriving at ( before) our destination, Status, we store its <quota> part in <return_val> of the visitor. All visitors defined inside an adaptive method automatically have return value part, the type for which is deduced from the signature of the method. The <return_val> becomes the result returned by the adaptive method.

Other adaptive methods

Let us look at another adaptive method. When the program is run, the user is presented with a menu of options. This menu is generated by show_operations.


--------------------------
View LibrarySystem.beh and find the definition of show_operations. (Hint: look at line 87).
--------------------------

Its behavior would seem to make it the responsibility of the LibrarySystem-object, but its implementation certainly will involve all Operation-objects of the system, since, according to the encapsulation principle, only these objects know their own <op_code>s. Therefore, the set of collaborating classes is identified through the traversal specification
from LibrarySystem
to {Exit, ShowBooks, SearchBook, ShowUsers, SearchUser}
.

Compare this to the Strategy on line 89. You might wonder what happened to the from LibrarySystem clause. Because the traversal is Strategy is defined in the scope of LibrarySystem we infer that this is the source. Look at figure 1 and try to identify the subgraph defined by this traversal specification; for this class dictionary and this propagation pattern, the set of collaborating classes is {LibrarySystem, OpList,Operation_List, Operation, Exit, ShowBooks, SearchBook, ShowUsers, SearchUser}. The classes CheckIn, CheckOut and Invalid are not included in the traversal specification, because we don't want these operations to be shown as an option of the menu. For now, the system only implements four operations, namely the ones mentioned in the beginning of this section. Later, you will write the code for checking books in and out.

Look at show_operations again. Note that some code wrappers are defined as before while others are defined as after. The difference is when the wrapper called. before wrappers are called immediately upon arriving at an object, before we deal with subclasses or parts. Conversely, after wrappers are called upon (i.e. after) having completed all subtraversals of this object. Hence, the output of the method will be:

 Possible operations are:
 1 - List of books
 2 - Search book(s)
 3 - List of users
 4 - Search user(s)
 9 - Exit

 Enter your choice:

Look back at figure 1 and show_operations, and see in which order the wrappers must have been called in order to get this result.

Throughout this guide we will explain other propagation patterns of the Library System program. For more information about propagation patterns please consult the Users Guide and The Book.

Finally, let's look at main.


--------------------------
Go to line 14 of LibrarySystem.beh.
--------------------------

The main method is called by the operating system upon running the program. It instantiates one LibrarySystem-object along with its BookList-object, UserList, and OpList-object by parsing them from the LibInit file. It then tries to run the main loop.

Enough with explanations, let's run the program!

Obtaining the executable file

In order to generate the executable file, you simply need to type one command


--------------------------
> demeterj
--------------------------

This command will do all the necessary steps, using the project file to control the necessary steps, and will take a few minutes, depending on how fast your machine is. Typically, the ``compiler'' can take a long time to run the first time around.

Once the compilation is finished, take a look at the current directory. Notice that the Demeter/Java tools created a new directory gen full of several new files. For each class in the LibrarySystem.cd file, a corresponding Java was created containing the Java methods corresponding to the propagation patterns. For a complete description of these new files, please consult the Users Guide.

Running the program

The demeterj script takes care of setting up the classpath and invoking the java runtime system. To run the program, all you need to type is


--------------------------
> demeterj test
--------------------------

You can play a little with the program by testing the several options. Note that the search of books and users is case sensitive, and works with partial matching of words.

Alternately, you can run the code manually, by setting up the CLASSPATH to include both the gen/classes directory and the rt.jar demeterj runtime (this runtime lives in /proj/demsys/demjava/ on CCS computers. Then you can invoke your main class like any other java program. However, the rest of the LabGuide assumes you'll use demeterj to both compile and run your application.

MODIFYING THE STRUCTURE OF OBJECTS

In this section, you will modify the structure of some objects and introduce new objects in the Library System. Basically, the system will be enhanced by including information about staff personnel, i.e. the persons responsible for managing the system and perform some privileged operations.

Understanding Users and Staff

In the original class dictionary, illustrated by Figure 1, User-objects were the only person objects of the system. We now want to include Staff-objects in order to be able to refine the operation of the library; in particular, only staff should be allowed to list and search information about the users of the library. The refinement of the functionality will be done in the next section. For now, you will simply modify the class organization.

The new class dictionary graph is illustrated by Figure 2. We only show the subgraph which suffered modifications; all the rest remains unchanged.

Figure 2: The new class dictionary (sub)graph for the Library System.
\begin{figure}\par\centerline{\epsfxsize=5.5in\epsfbox{person-cd.ps}}\par\end{figure}

Regarding the original class dictionary, the changes are the following:


Modifying the class dictionary file

Let's modify the class dictionary. Edit the file LibrarySystem.cd.


--------------------------
> editor LibrarySystem.cd
Add a new part, <staff> StaffList, to the LibrarySystem class definition.
--------------------------

The definition of the LibrarySystem class should now look like:

LibrarySystem = <books> BookList <users> UserList <staff> StaffList
<ops> OpList .

Note that the order of the parts makes a difference for the grammar generated by Demeter/Java, so be sure that your definition has the parts in the same order.

Find the definition of class User (User = "user:" $<$uid$>$ ...).


--------------------------
Delete all parts of class User except the first (uid) and the second (status).
--------------------------

The definition of class User should now look like:

User = "user:" <uid> UID Status .

Following the definition of class User, define the new class Staff, and also add the StaffList class and a class defining the secret StaffCode.


--------------------------
Type:
Staff = "staff:" $<$sid_code$>$ StaffCode .
(don't forget the ``.'' at the end!)
StaffList = List(Staff).
StaffCode = <code> Integer.
--------------------------

Following the definition of class Staff, define the new class Person.


--------------------------
Type:
Person : User | Staff *common* Name Address Phones .
(don't forget the ``.'' at the end!)
--------------------------

Find the definition of class Operation (Operation : Exit |...)


--------------------------
Delete the alternatives ShowUsers, SearchUser and CheckBook, and add the alternative PrivOperation.
--------------------------

The definition of class Operation should now look like:

Operation : Exit | ShowBooks | SearchBook | PrivOperation | Invalid
*common* <op_code> OpCode <op_str> String.

Following the definition of class Operation, define the new class PrivOperation. For reasons of bug workarounds, we need to have a wrapper class as well to insulate it from the Operation alternation class. Luckily, this is really easy:


--------------------------
Type:

PrivOperation = PrivOperationWrapper.
PrivOperationWrapper : ShowUsers | SearchUser | CheckBook .
(don't forget the ``.''s at the end!)
--------------------------

Save the file LibrarySystem.cd.

Updating LibInit

Now you need to update some parts of the code in order to adapt to the new class organization. As you are about to see, although the class structure has changed drastically, the number of modifications in the code (traversals and .beh files) is minimal.

Since we read in the initial state from LibInit, all we need to do is to create some Staff objects; for simplicity reasons, let's instantiate only one. This is done in file LibInit, so edit this file.


--------------------------
Goto just after the clause defining the users, line 71, and add:

(
staff: 4321 
 "Your Name" "goes here" 
 address: 432 "Piccadilly Circus" # 71 
 city:  "Dar-es-Salam" 
 state: "Canada" 10678 
 phones: 
  fax 617 - 7654321
)

--------------------------

Save the file LibInit.

Regenerating the executable file

In order to regenerate the executables, you simply need to type:


--------------------------
> demeterj
--------------------------

At this phase, you may get errors if you made any typos. If so, edit LibrarySystem.cd again, and make sure you typed exactly what this Guide tells you to type. type demeterj to try again. You might consider demeterj clean; demeterj, which removes all the old files and starts from scratch.

When the program has been recompiled, run it again by typing:


--------------------------
> java LibrarySystem
--------------------------

or


--------------------------
> demeterj test
--------------------------

The behavior of the Library System should be exactly the same as before, since the modifications in this section were made on the structure of the classes and not on their functionality.

How the changes affected (or not) the propagation patterns

The previous changes affected the structure of class User: many of its parts which were immediate are now inherited from the new class Person. Therefore, some methods could have also been affected by the changes on the structure. Let's take a look at two interesting propagation patterns involving the class User.

Look at file User.beh, where method search_user is defined. The traversal goes from class LibrarySystem to classes User, Name, and Address, but not entering Status. The visitor has three wrappers and an initializer. At class User, we store the user object in the visitor, so we can add it to a 'found' list if it matches our criteria. These matches are tried both in classes Name and Address. The traversal is exactly the same whether we use the original class dictionary (Figure 1) or the new one (Figure 2). The code in the wrapper is also the same for both class structures, since all the parts are available through the accessor functions get_xxx(). Therefore, this propagation pattern remains unchanged.

In the same file, look at where operation printuser is defined. In this case, the traversal (prntusertrv) goes from (amongst others) class UserList to class Address and the phone classes; It carries a PrintUserV visitor with wrappers defined for several of these classes. Concerning the code, no changes are necessary, since all the referenced parts are available through the accessor functions get_xxx(), both in the original class dictionary and in the new one. Let's concentrate our attention to the traversal specification, which may be somewhat more difficult to understand. First look at the original class dictionary (Figure 1) and try to identify the classes involved in this traversal; they are: LibrarySystem, UserList, User_List, User, Address, Phones, HomePhone, WorkPhone and Fax. Now look at the modified class dictionary (Figure 2) and try to identify the classes involved in the traversal (Even though this may be difficult- we only shoe the changed parts, after all). Note that there is a knowledge path from User to Address and to the phone classes that goes through the inheritance edge between class User and class Person. Therefore, the traversal specification is consistent also for this class structure, and no changes are necessary.

For more information about knowledge paths, please consult the Users Guide and The Book.

ENHANCING THE PROGRAM

In this section you will refine the execution of the operations on the library by checking the staff access code when a privileged operation is invoked. For example, when the human operator chooses option 3 (Show Users) the Library System asks for the access code; then the execution of that operation proceeds normally if and only if the access code is correct.

For this, you will enhance the method execute, defined for Operations. You will also write a new method check_staffcode which checks if the input from the human operator matches a staff code.

Modifying the method execute

Edit the file LibrarySystem.beh. The method execute specifies a visitor with code for the execution of each of the possible operations on the Library System. We want to include an additional wrapper for class PrivOperation so that it will trap the invocation of any privileged operation. By inspecting the class dictionary, we see that all the privileged operations are subclasses of PrivOperation.


--------------------------
In LibrarySystem.beh, find the adaptive method execute and add the following wrapper:

around PrivOperation 
  (@ 
   String scstr = LibrarySystem.readline(
                    "\n This is a privileged operation."+
                    "\n Please enter staff code: ");
   StaffCode sc = StaffCode.parse(scstr); 
   if (get_ls().check_staffcode(sc))
     subtraversal.apply();
   else 
     System.out.println(" Access Denied");
   @)

--------------------------

Save the file.

Notice that the around wrapper is attached to the class PrivOperation which is in the path defined by the traversal specification, before the target classes ShowUsers, SearchUser and CheckBook. The fact that we programmed it as an around wrapper determines that this code is executed before the execution of the wrappers at classes ShowUsers, SearchUser and CheckBook. Furthermore, around wrappers control if and when the traversal goes deeper, by calling subtraversal.apply(). If the around wrapper never calls subtraversal.apply() then none of PrivOperation's subclasses or parts will be traversed. Hence an around wrapper on PrivOperation will allow the program to determine whether the user is allowed to perform the operation and if so apply the subtraversal or whether to just return without traversing to the class representing the privileged operation.

Writing the new method check_staffcode

Now you need to write check_staffcode, the function you just referenced in the previous wrapper. First, however, we need to be able to test StaffCodes for equality:


--------------------------
Append to the end of LibrarySystem.beh:

StaffCode {
  public boolean equals(Object thing) (@ 
    return (thing instanceof StaffCode) && 
          get_code().equals(((StaffCode) thing).get_code());
  @) 
}

--------------------------

Armed with this definition, we can write our check_staffcode operation.


--------------------------
Append this to LibrarySystem.beh:

LibrarySystem {
   public boolean check_staffcode(StaffCode staffcode) 
     to Staff      
     { 
       init (@ return_val = false; @)
       before Staff (@ return_val = return_val || 
            staffcode.equals(host.get_sid_code()); @) 
     }
}

--------------------------

Save.

We traverse the list of Staff and for each Staff-object the <sid_code> is compared with the given code (see the wrapper at class Staff). We could return as soon as we find a match, but to keep matters simple, we or together the results- as long as at least one Staff object has a matching code, then we allow the operation to continue.

Running the modified program

Now we are able to regenerate the executable file. By now, you should know what command you use to do this... of course, demeterj. If errors are reported, check your typing, or try demeterj clean; demeterj.

Once the compilation is finished, you may test your modified program by typing java LibrarySystem or demeterj test. Do you know the secret password for privileged operations? Hint: check the code you wrote for instantiation of the Staff-object in LibInit.

MORE ON TRAVERSALS AND VISITORS

When you run the Library System and choose option 4 (Search user(s)), the program doesn't give you any information about the status of the user, i.e. how many books (s)he has checked out. In this section you will modify the program in order to display this information.

Modifying execute

We start by looking at the implementation of execute, so scroll to where it is defined (in LibrarySystem.beh).


--------------------------
Find the place where the wrapper for SearchUser is defined in the method execute. At the end of this wrapper (just before ``@)''), insert:

     if (userl.get_list().size() > 0) 
     while (true) {
       String ustr = LibrarySystem.readline(
                        "\n If you want to see the status,"+
                        " enter user id., 0 to end: ");
       UID uid = UID.parse(ustr);
       if (uid.get_n().intValue() == 0) break;
       User u = userl.search_userid(uid);
       if (u == null) System.out.println(" User id non existent.");
       else u.print_status(); 
     }

--------------------------

Save.

The code you just added in calls two new methods, namely search_userid and print_status(). search_userid searches a list of users for a user with a specific <uid>, and returns this user (or null). print_status is applied to a User-object, and prints the information about the books this user has checked out.

Writing search_userid

Go to the dummy definition of search_userid in User.beh. There are two such definitions (defined on different classes): the first is just a wrapper that calls the second. Go to the second one. You will see the header for operation search_userid along with statement that return a new User object.


--------------------------
Replace the dummy definition with the following:

UserList { 
   User search_userid(UID key) 
     to-stop User 
     { before User (@ 
          if (host.get_uid().equals(key)) return_val = host; 
       @) 
     } 
}

--------------------------

Save.

This operation introduces the to-stop traversal directive. Look at the class graph. We wish to go from UserList to all the User objects in that list. However, once we have reached such a User object, we can reach other Users via its Status and Copy parts. To avoid this, we tell the traversal not to go through any parts of a target. Other than that, search_userid is very simple. At class User the <uid> is compared with the key given by the human operator; if there is a match, the ``host'' User-object is remembered in the visitor. We could program the traversal to stop once we have found a match, but since the <uid>s are assumed to be unique, we omit that to gain clarity at the expense of efficiency.

Writing print_status

Continue editing User.beh. Find the dummy definition of print_status.


--------------------------
Replace the dummy definitions for print_status with

User { 
 void print_status() 
   to-stop {Book, Copy}
   {
     before User 
      (@ System.out.println(" List of books checked out by user "
         +host.get_uid());
       @)
     before Book 
      (@ System.out.print("\n");
         host.printbook();
       @)
     before Copy 
      (@ System.out.println(" Copy #"+host.get_copyno()); @) 
   }
}

--------------------------

Save the files.

This traversal is defined as going from User to Book and Copy (see Figure 1 again). The to-stop directive is used here to avoid the undesirable loop from User to User. The visitor it carries is very simple. All it does is: for a user, print out the checked out books as well as the copy number of each of those books. The method printbook, invoked on books in the Book wrapper, is defined for both Books and BookLists. A search in Book.beh should uncover both definitions.

Running the program

Regenerate the Library System. If you get errors, recheck everything you've typed. Then run the program and test option 4, so that you can see the status of the users. You will notice that none of the users has any books checked out (yet!). In the next sections, you will write the code for checking books in and out.

CHECKING BOOKS IN AND OUT

Most of the code for checking books in and out is already included in the system. All that you have to do is to fill in the missing code fragments to link the parts together.

Understanding the existing code

View the behavior file LibrarySysytem.bet; find the execute operation.


--------------------------
Analyze the code wrappers for classes CheckBook, CheckOut and CheckIn. The code wrapper for superclass CheckBook is common to both subclasses CheckOut and CheckIn, and is executed ``before'' the code for the subclasses. Make sure you understand the general idea.
--------------------------

Visitor variables

Notice how the wrappers for CheckIn and CheckOut use the ItemList <il> which was built in the CheckBook wrapper. It is available for use in the other wrappers because it is a visitor variable. The first line in the definition of the visitor for execute is defines a visitor variable <il>, of type ItemList. Thus any changes made to it in CheckBook will be seen by code executed later on, such as the wrappers for CheckIn and CheckOut.

Let us see another use of visitor variables. Find the definition of printbook and the corresponding named visitor PrintBooksV in Book.beh. Notice the use of <avail>. The visitor is simple in its intent: for a list of books, it prints out information about the books. The visitor variable <avail> is used to determine whether a book is available or not, i.e. if the number of copies that haven't been checked out is greater than zero. The way we determine this is by counting the number of copies that have no borrower (this is done in the before Copy wrapper).

Transportation of objects in the visitor is a very important part of adaptive software. For more information, please consult the Users Guide and The Book.

Bits of code that are missing

Let's concentrate again on operations check in and check out. In order to activate the existing code, there are a couple of minor modifications that you have to do.

Edit the behavior file LibrarySystem.beh.


--------------------------
Go to the definition of show_operations. Extend the traversal definition to go also to classes CheckIn and CheckOut.
--------------------------

Finally, we need to add the operations to the LibInit file, so they appear in the operation menu. The options are already included in the file, but have been commented out.


--------------------------
Uncomment the two lines (one for CheckIn, the other for CheckOut) in last section of LibInit, so it reads:

(
Invalid 0   "Invalid"
ShowBooks 1  "Show Books"
SearchBook 2 "Search Books"
ShowUsers 3  "Show Users"
SearchUser 4 "Search Users"
CheckOut 5   "Check Out"
CheckIn 6    "Check In"
Exit 9       "Exit"
)

--------------------------



Running the program

Regenerate the Library System. If you get errors, recheck everything you've typed. Then run the program and test options 5 and 6, so that you can check books in and out.

WRITING A NEW OPERATION

Let's introduce one last operation in the Library System which will display some information about all users that have borrowed some books. We call these users ``active users''. In particular, we want to know the first and last names and the home telephone of all active users. You will need to write all the code for this new operation, since we didn't include any code related to it in the base program.

Defining a new operation class

According to the design of our Library System, we should have one class per supported operation. So, we need a new class ActiveUsers, which we choose to be a privileged operation.

Edit the file LibrarySystem.cd.


--------------------------
Find the definition of class PrivOperationWrapper, and add a new alternative class ActiveUsers. The result should look like:

PrivOperationWrapper : ShowUsers | SearchUser | CheckBook | ActiveUsers.
--------------------------

Where would you place class ActiveUsers if we'd chosen it to be a non-privileged operation?


--------------------------
Then define class ActiveUsers:

ActiveUsers = "ActiveUsers" .
--------------------------

Save the file LibrarySystem.cd.

Defining the op_code

Edit LibInit.


--------------------------
Go to the last section of LibInit, where the other opcodes are defined. Add a line, in-between CheckIn and Exit (anywhere in that section will do):

ActiveUsers 7 "Show Active Users"
--------------------------

Save the file.

Making the operation visible

Find the method named show_operations in LibrarySystem.beh.


--------------------------
Include class ActiveUsers in the set of targets for the traversal.
--------------------------

Updating execute

Now find the method execute


--------------------------
Include class ActiveUsers in the set of targets for the traversal.
--------------------------

If we don't add ActiveUsers to the set of targets of the traversal, we will never perform the operation, as the execute method will never reach the following wrapper; the method execute needs a wrapper for class ActiveUsers:


--------------------------
Add the following wrapper to it:

before ActiveUsers (@ get_ls().print_active_users(); @)
--------------------------

print_active_users is the new method that you will need to define, and that will actually do the job.

Writing print_active_users

Continue editing the file LibrarySystem.beh. For organizational reasons, let's write this new method after the method named check_staffcode. Go to that position in the file.

As a first approach to print_active_users, type the following code:


--------------------------

LibrarySystem { 
  public void print_active_users() 
    to { HomePhone, Address} 
    { 
      // These are the visitor's wrapper
      before User 
       (@ System.out.println(" User: "+host.get_fname()+
                             " "+host.get_lname()); 
        @)
      before HomePhone
       (@ System.out.print(" Tel Home: ");
          host.show(); 
        @)
      before Address 
       (@ System.out.println(" City: "+host.get_city()); 
        @)
    } 
}

--------------------------

The wrappers above do exactly what we want, i.e., they print out the first and last names as well as the home telephone and the city of the user. However, the traversal (to {HomePhone,Address}) is not correct for the purpose of this operation.

Look at the class dictionary graph (Figures 1 and 2) and identify all the paths defined by print_active_users' specification. From class LibrarySystem to classes HomePhone and Address there are at least two obvious paths, one that goes through class UserList and other that goes through class BookList. Furthermore, all paths include cycles such as {User, Status, CheckedOutList, CheckedOut, Copy, User} (what other cycle do you find?). We need to redefine the traversal specification in order to get only the path(s) that we want.

Controlling the traversal

For the purpose of printing out the active users, there are at least two different but correct ways to solve the problem. We are going to show you only one; the second one is left as exercise.


--------------------------
Redefine the traversal specification from to { HomePhone, Address} to:

 through BookList 
 bypassing -> User, *, Status 
 to {HomePhone, Address}

--------------------------

Now is a good time to save the file.

The subgraph defined by this traversal is shown in Figure 3. In order to get the information about the active users, we access the borrower of each copy of the books in the library.

Figure 3: Subgraph defined by the traversal.
\begin{figure}\vspace*{5mm}
\centerline{\epsfxsize=5in\epsfbox{trav-cd.ps}}\par\end{figure}

Excluding repeated users

There is one problem with the method print_active_users as it is now: if a user has checked out several books, the information about this user will be printed out several times. Feel free to verify this- the system should be in a demeterjable state. We can solve this problem in this operation by having the visitor maintain a list of users already seen. Every user it visits is added to the list, and thus it knows which users have already been handled, and thus need not be printed.

Find the definition of print_active_users in LibrarySystem.beh. We need to add a UserList part to its visitor section.


--------------------------
Add a verbatim section just before the first wrapper (before User) of print_active_users:

(@ UserList seen = UserList.parse("()"); @)
--------------------------

We also need to modify the before wrapper for User to update and use this variable. Currently, it just prints out the first and last name of the User, but we want it to check if the User has been seen before, and only if this is false continue, printing the User's names, address, and phone. In order to achieve this, we change the wrapper type from being a before wrapper to an around wrapper. Around wrappers allow us to control when (and how often) a class's subparts are traversed. Hence, if we choose to not call subtraversal.apply() the subparts of User will not be traversed, and the phone and address will not printed.

You should be confused now.

Alert readers should be looking at the classgraphs in figs 2 and 3 and noting that neither Address nor Phones are subparts of User but rather Person, and concluding that not applying the subtraversal should only affect traversal of Status and the user id.

The reason why our approach works is the principle of flattening. Whilst *common* parts are indeed stored where they are declared (ie on Alternation classes, also known as abstract superclasses, represented by flat hexagons with thick incomming arrows in our graph notation), in this case the class Person, they are treated as if they were duplicated for both User and Staff. This principle greatly simplifies reasoning about adaptive programs, and is quite useful (as this example shows).


--------------------------
Change print_active_users wrapper for User to read:

 around User 
  (@ 
    if (!seen.get_list().contains(host)) {
      seen.get_list().addElement(host); 
      System.out.println(" User: "+host.get_fname()+
                         " "+host.get_lname()); 
      subtraversal.apply();
    }
  @)

--------------------------

Repetition classes, like the class User_List 2, have several predefined methods; among them contains, which returns true if a list contains the specified element. Other predefined methods are addElement, size, and the elements, which gives DemeterJ lists the enumeration interface.

Running the program

Save and regenerate the Library System. If you get errors, recheck everything you've typed. Then run the program and test option 7. Make sure you check out some books before you test it, though, otherwise the list of active users will be empty.

UNDERSTANDING OBJECTS AS SENTENCES

In this section we will focus on an important part of Demeter/Java which is related with the instantiation of objects. Any object-oriented application needs objects to execute. In our Library System, we instantiate all of the initial objects (books, users and staff) by reading in the file LibInit.

The Demeter System/Java provides automatically generated parsing facilities for instantiating objects from their textual representation. With a bit of thought, these representations can be made to resemble English phrases, thus earning the name ``sentences''. Objects can be instantiated by parsing sentences. Conversely, existing objects in the application can be printed out in sentence form. All classes have a static method parse defined on them, overloaded to accept a String- or InputStream- valued argument. To print an object, the automatically generated PrintVisitor is traversed over the part of the class graph to be printed. Together, they provide a very adaptive and robust representation of objects: as long as the grammar stays the same, the same representation can be used for different class graphs.

Grammar

When we first analyzed the file LibrarySystem.cd we told you to ignore the syntactic elements such as "book" or "ISBN". These syntactic elements are included in the class definitions precisely for parsing and printing objects as sentences.


--------------------------
Take a look at LibrarySystem.cd:
> more LibrarySystem.cd
--------------------------

In order to use sentences, we need to follow the grammar. For example, the following sentence is a valid sentence for a Book-object:

book: "GNU Emacs Manual" by "Richard Stallman"
"Free Software Foundation" 1992 "Text editors" ISBN 882114019
(copy 1 copy 2)

First of all, let's clarify the use of the double quotes (" "). The syntactic elements in the class dictionary are used in sentences without the double quotes - for example "book:" in the class dictionary is simply written as book: in the sentence. The double quotes are used whenever there is an object of the Demeter/Java terminal class String - for example, according to the class dictionary, the Title-object contains a String-object, so in the sentence we will have "GNU Emacs Manual" as the title.

The sentences are constructed following the definitions in the class dictionary, from left to right and in a depth first order. The syntactic element book: comes before any other parts of the Book-object, since it is the first element to be found according to the class dictionary; then follows the Title-object, which doesn't include any syntactic element and is simply a terminal object String; after the title, comes the Author-object, and according to the class dictionary Author-objects start with the syntactic element by followed by a terminal object String, etc. Note that the CopyList-object is surrounded by parenthesis - this is specified in the definition of template class List(S) at the end of the file.

There are some rules and restrictions that you must observe when defining grammar in class dictionaries. For more information about this topic, please consult the Users Manual and The Book.

Printing the object

To complete the explanation about objects and sentences, let's print out the LibrarySystem-object after we've read it in.


--------------------------
Type the following code immediately after the line which parses the LibrarySystem object from LibInit

theSystem.universal_trv0(new PrintVisitor(System.out));

--------------------------

We use two new facilities; the printing visitor, and the universal traversal. The universal traversal, with the slightly unintuitive name universal_trv0 goes to every reachable part. At every class, the PrintVisitor prints a description of the class on the OutputStream provided; in our example, System.out.

Running the program

Regenerate the Library System. Run it by typing:
> java LibrarySystem
or
> demeterj test

You will be presented with the information read in from LibInit, and then the LibrarySystem continues as normal.

WRITING A PROGRAM FROM SCRATCH

Throughout this Guide you have used a base program that was already prepared for you. However, when you write your own Demeter/Java applications, there are a couple of things that you need to know and which were not covered by the Lab program. In this section, we will explain the pre-arrangements for any Demeter/Java application.

Create a new directory and change into it, for example:


--------------------------
> mkdir dem-test
> cd dem-test

--------------------------

Writing the class dictionary

Make new .beh, .cd, and .prj files by typing:


--------------------------
> demeterj new

--------------------------

This will create four files in the current directory. Edit the file called program.cd. Replace its contents with:


--------------------------
> editor program.cd
Then type:

A = B.
B : C | D .
C = "c-object" .
D = "d-object" .

--------------------------

Save the file.

Writing a behavior file.

Edit the file called program.beh.


--------------------------
> editor program.beh
Then replace the sample definition there with:

A {
  public static void main(String[] args) (@ 
    try {
      A a = A.parse(System.in); 
      a.universal_trv0(new TraceVisitor(System.out)); 
    } catch (ParseException e) {
      System.out.println("Could not parse stdin");
    }
  @)
}

--------------------------

Save it. This test program simply parses an object and prints out a trace of a traverse over it; we don't need to write any propagation patterns. Save the file program.beh.

Now we just need to tweak the generated program.prj a little:


--------------------------
edit program.prj. We need to let demeterj know which class holds the main function, so change MAIN = Main to MAIN = A.
--------------------------

Save the file program.prj.

Generating and running the application

From here on, it's exactly as you've learned with the Library System application. Generate the executable program by typing:


--------------------------
> demeterj
--------------------------

According to the code in program.beh, we'll try to parse our object from the standard input. demeterj test will redirect the file program.input to the standard input. Let's make program.input. Fire up an editor:


--------------------------
> editor program.input
Then type, for example,

c-object
--------------------------

Save the file.

Now run the application by typing:


--------------------------
> demeterj test

or equivalently (if your $CLASSPATH is still set)

> java A < program.input


--------------------------

If no error is reported, you have written your first demeter application. Congratulations, you've completed the LabGuide. Go buy yourself a treat.

About this document ...

Demeter/Java
Laboratory Guide

This document was generated using the LaTeX2HTML translator Version 2K.1beta (1.61)

Copyright © 1993, 1994, 1995, 1996, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999, Ross Moore, Mathematics Department, Macquarie University, Sydney.

The command line arguments were:
latex2html -split 2 Mainhtml.tex

The translation was initiated by Johan Ovlinger on 2002-02-12



next_inactive up previous
Johan Ovlinger 2002-02-12