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:
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
Welcome to Demeter/Java
, 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 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.
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.
Over the course of this Guide, you will learn how to:
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.
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.
To follow the Laboratory Guide, you need to set up your environment and then copy the laboratory files into your directory.
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 .
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.
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).
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.
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.
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.
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).
Any meaningful Demeter/Java application consists of one file
.cd
and at least one
file
.beh 1.
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:
--------------------------
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.
![]() |
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.
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.
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!
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.
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.
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.
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.
Regarding the original class dictionary, the changes are the following:
<
staff>
, which contains a list of the staff persons.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
--------------------------
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.
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" )
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.
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.
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.
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.
Find the method named show_operations in LibrarySystem.beh.
--------------------------
Include class ActiveUsers in the set of targets for the traversal.
--------------------------
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
--------------------------
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.
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.
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.
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