Close
About
FAQ
Home
Collections
Login
USC Login
Register
0
Selected
Invert selection
Deselect all
Deselect all
Click here to refresh results
Click here to refresh results
USC
/
Digital Library
/
University of Southern California Dissertations and Theses
/
00001.tif
(USC Thesis Other)
00001.tif
PDF
Download
Share
Open document
Flip pages
Contact Us
Contact Us
Copy asset link
Request this asset
Transcript (if available)
Content
OBJECT PROCESS MODELING AND THE PROCESS PROGRAMMING LANGUAGE GALOIS by Yasuhiro Sugiyama A Dissertation Presented to the FACULTY OF THE GRADUATE SCHOOL UNIVERSITY OF SOUTHERN CALIFORNIA In Partial Fulfillment of the Requirements for the Degree DOCTOR OF PHILOSOPHY (Computer Science) April 1990 Copyright 1990 Yasuhiro Sugiyama U M I Number: DP22808 All rights reserved INFORMATION TO ALL USERS The quality of this reproduction is d ep en d en t upon th e quality of the copy subm itted. In the unlikely event that the author did not sen d a com plete m anuscript and th ere are m issing pag es, th e se will be noted. Also, if m aterial had to be rem oved, a note will indicate the deletion. Dissertation Publishing UMI DP22808 Published by P roQ uest LLC (2014). Copyright in th e Dissertation held by the Author. Microform Edition © P roQ uest LLC. All rights reserved. This work is protected against unauthorized copying under Title 17, United S ta tes C ode P roQ uest LLC. 789 E ast Eisenhow er Parkw ay P.O. Box 1346 Ann Arbor, Ml 4 8 1 0 6 -1 3 4 6 UNIVERSITY OF SOUTHERN CALIFORNIA THE GRADUATE SCHOOL UNIVERSITY PARK LOS ANGELES, CAUFORNIA 90089 This dissertation, w ritten by under the direction of h is . Dissertation Committee, and approved b y all its members, has been presented to and accepted by The Graduate School, in partial fulfillm ent of re quirem ents for the degree of hp. CpS h S997 Y asuhiro Sugiyama D O C TO R OF PHILOSOPH Y Dean of Graduate Studies Date DISSERTATION COMMITTEE Chairperson For Jiirom l, N aoki, and my parents ftichiko and Tasayoshl Acknowledgements It is my great pleasure to thank Ellis Horowitz, my advisor, for his continuing and enthusiastic support, advice, and encouragement during my graduate years. I would like to thank him again for reading the early draft of this dissertation and providing meaningful suggestions on it. I also would like to thank the other members of my committee - Dean Jacobs and Jean-Luc Gaudiot for their helpful comments on this dissertation. Table of Contents 1. Introduction................................................................................................1 1.1. Software Process...........................................................................................4 1.2. Software Process M odel.......................................................................6 1.3. Software Process Modeling........................................................................10 1.4. About This D issertation......................................................................14 2. Related Works........................................................................................ 17 2.1. Software Process M odels...........................................................................17 2.1.1. Stagew ise M odel..........................................................................17 2.1.2. The W aterfall M odel..................................................................19 2.1.3. The Alternate Lifecycle Models........................................................22 2.1.4. The Spiral M odel.............................................................................. 23 2.2. Process Modeling Approaches.......................................................... 25 2.2.1. Algorithmic Process Modeling......................................................... 25 2.2.2. Behavioral Approach.........................................................................27 2.2.3. Combining Algorithmic and Behavioral Approach........................ 29 2.3. Resource Management................................................................................ 31 2.3.1. Software Configuration Management..............................................31 2.3.2. Software Engineering Databases.............................................. 38 2.4. Object-Oriented Programming Languages...........................................40 2.4.1. Objects................................ 40 2.4.2. Object Creation - Instantiation and Prototyping..............................42 2.4.3. Object Classification - Classes and Prototypes.......................... 44 iv 2.4.4. Information Sharing - Inheritance and Delegation.........................46 2.4.5. Encapsulated Inheritance.......................................................... 48 2.4.6. U n ificatio n ..................................................................................... 49 2.5. C++ : A Quick Review......................................................... 55 2.5.1. C lass..................................................................................................55 2.5.2. Encapsulated Inheritance in C + + .................................................... 60 2.5.3. Friend.................................................................................................61 2.5.4. Concurrency in C + + ........................................................................ 63 3 . O bject Process M odeling........................................................................... 68 3.1. Object Process Modeling............................................................................68 3.2. Galois : A Process Programming Language............................................ 72 3.3. OPM: A Process Modeling Environment.................................................. 76 3.3.1. Graphical Description of Software Processes................................ 76 3.3.2. Cooperative working.........................................................................78 3.3.3. Resource Management......................................................................79 3.3.4. Working Environment......................................................................80 3.3.5. Working in the OPM Environment..................................................83 4 . The Galois L anguage...................................................................................86 4.1. An Overview of G alois..................................................................................86 4.1.1. Metaclasses........................................................................................ 87 4.1.2. D eriv atio n ....................................................................................... 89 4.1.3. C oncurrency................................................................................... 92 4.1.4. Rule-based Features..........................................................................95 4.2. G alois and C++........................................................................... 96 v 4.3. Class............................................................................................................97 4.3.1. Objects and Classes..........................................................................97 4.3.2. Defining Simple Classes (Part I).......................................... 100 4.3.3. Initializing Objects...........................................................................104 4.4. Metaclass...................................................................................................106 4.4.1. Defining Metaclasses (Part I).................................................106 4.4.2. Defining Simple Classes (Part II)..........................................110 4.4.3. Metaclass Hierarchy........................................................................113 4.4.4. Defining Metaclasses (Part II)...............................................115 4.4.5. Initializing C lasses....................................................................116 4.4.6. Refining C onstructors.............................................................. 118 4.4.7. Managing Instances........................................................................ 123 4.4.8. Managing Instances using Binary Trees....................................... 126 4.4.9. Refining Operations........................................................................ 131 4.4.10. Default Function..............................................................................134 4.4.11. Subclasses of Metaclasses.............................................................. 137 4.5. Derived Class............................................................................................143 4.5.1. D eriv atio n ..................................................................................... 143 4.5.2. Defining Derived Classes................................................................145 4.5.3. Deriving Objects..............................................................................148 4.5.4. Identifying Instantiation with Derivation................................149 4.5.5. Information Sharing........................................................................ 151 4.5.6. Deriving Classes..............................................................................153 4.5.6.1. Deriving classes from objects................................................154 vi 4.5.6.2. Deriving Classes from Classes........................................ 160 4.5.6.3. Deriving Objects from Classes.................................................166 4.6. Class Hierarchies in Galois.................................................................... 168 4.6.1. Derived Classes and Subclasses....................................................168 4.6.1.1. Delegation and Inheritance...............................................168 4.6.1.2. Class Refinement and Object Refinements............................ 172 4.6.1.3. Evolution and Metamorphosis................................................ 173 4.6.2. Derived Classes and Metaclasses.................................................. 174 4.6.3. Metaclasses and Subclasses...........................................................177 4.7. Concurrency in Galois..............................................................................180 4.7.1. Function Objects..............................................................................180 4.7.2. Concurrent Function Invocations.................................................. 182 4.7.3. Scheduling of Concurrent Function Invocations.....................187 4.7.4. Asynchronous Function Calls................................................ 192 4.7.5. Background operations..................................................................200 4.8. Rule-based F eatures.......................................................................... 202 4.8.1. Preconditions and Postconditions.................................................202 4.8.2. Preprocesses and Postprocesses....................................................204 4.8.3. Classical Concurrency Problems.................................................. 208 5. Cooperative Working..........................................................................213 5.1. Cooperative Working...............................................................................213 5.2. Managing Processes................................................................................ 215 5.2.1. Managing Multiple Users............................................................... 215 5.2.2. Managing Multiple Processes........................................................ 221 vii 5.3. Modeling Shared Resources...................................................................230 5.3.1. Representing Shared Resources....................................................231 5.3.2. Coordinating Shared Resources.................................................... 243 5.3.3. Accessing Shared Resources..........................................................249 5.3.4. Scheduling Shared Resources................................................ 262 5.3.4.1. FIFO scheduling................................................................... 263 5.3.4.2. Priority Scheduling...............................................................266 5.3.4.3. Scheduling with Background Operations........................... 273 5.3.5. Monitoring the Access to Shared Resources................................278 6. Resource Management..........................................................................287 6.1. M odeling Version Control..............................................................287 6.1.1. A Version Control Mechanism.......................................................287 6.1.2. Implementing the Version Control Mechanism............................292 6.1.3. A ccessing V ersions.................................................................. 303 6.1.4. Selecting Versions...........................................................................312 6.2. Modeling Software Manufacture........................................................... 315 6.2.1. Representing Manufacturing Relationships............................316 6.2.2. Coordinating Software Manufacture.............................................322 7. Conclusions...........................................................................................329 R efer en ces..........................................................................................332 List of Figures Figure 2.1: The Stagewise M odel..........................................................................18 Figure 2.2: "Do It Twice" step in the Waterfall M odel.........................................20 Figure 2.3: The W aterfall Model.................................. 21 Figure 2.4: The Spiral Model .................................................................................24 Figure 2.5: Osterweil's sample process program ..................................................26 Figure 2.6: Definition of the Process Model in Behavioral Approach.................28 Figure 2.7: Behavioral Description of the Spiral Model........................................29 Figure 2.8: Instantiation...........................................................................................43 Figure 2.9: Prototyping...........................................................................................44 Figure 2.10: Inheritance and Delegation ............................. 47 Figure 2.11: Parallel Class Hierarchy in Smalltalk-80............................................52 Figure 2.12: Metaclass Hierarchy in Smalltalk-80..........................................55 Figure 2.13: Comparison of Concurrent C and A da...............................................64 Figure 3.1: Object-Oriented Software Development Environment.......................69 Figure 3.2: Comparison of Process Modeling Approaches..................................71 Figure 3.3: Debugging Process..............................................................................77 Figure 3.4: Refinement of QA confirms the correction.........................................78 Figure 3.6: W orking on Bug process............................................................ 80 Figure 3.7: A Sample Process Window.................................................. 81 Figure 3.8: Overview of OPM .......................................................................84 Figure 4.1: Managing Multiple Debugging Processes.......................................... 88 Figure 4.2: Comparison of Derivation, Instantiation, and Prototyping........... 90 ix Figure 4.3: Compile D erivation .....................................................................92 Figure 4.4: Scheduling Shared Resources.............................................................94 Figure 4.5: Defining Simple Classes....................................................................101 Figure 4.6: Defining Metaclasses......................................................................... 108 Figure 4.7: Relationship of Instance Classes and Sample class.........................I l l Figure 4.8: Classes as O bjects.......................................................................113 Figure 4.9: Sam ple-Class-Of H ierarchy........................................................114 Figure 4.10: Instance-of Hierarchy involving University.................................... 116 Figure 4.11: Managing Instances using a Binary Tree.......................................... 129 Figure 4.12: Defining A Metaclass of Another Metaclass.................................... 140 Figure 4.13: Refinement of Constructors....................................................... 141 Figure 4.14: Derivation............................................................................................ 144 Figure 4.15: Derivation Sequence...........................................................................145 Figure 4.16: D eriving O bjects...........................................................................149 Figure 4.17: Identifying Derivation and Instantiation............................................150 Figure 4.18: Delegation............................................................................................ 152 Figure 4.19: Types of Derivation.............................................................................153 Figure 4.20: Defining Metaclasses from a Simple Class............................... 155 Figure 4.21: Relationship Among Instances involved in Metaclass Derivation.. 160 Figure 4.22: Deriving A metaclass..........................................................................162 Figure 4.23: Recursive D erivation................................................................... 163 Figure 4.24: Non-Recursive Derivation.................................................................165 Figure 4.25: Deriving Simple Classes from a Metaclass............................... 167 Figure 4.26: Derived Classes and Subclasses............................................... 170 x Figure 4.27: Class Refinement and Object Refinement........................................ 172 Figure 4.28: Evolution and Metamorphosis...........................................................174 Figure 4.29: Fat Class Approach............................................................................ 175 Figure 4.30: Thin Class Approach......................................................................... 176 Figure 4.31: Template construction and Object Metamorphosis ................177 Figure 4.32: Superclass and Metaclass .........................................................178 Figure 4.33: Evolution and Template construction .............................................. 179 Figure 4.34: A Function Class and Instances........................................................ 180 Figure 4.35: Execution of a Member Function............................................. 181 Figure 4.36: Function State Transition Diagram.................................................... 182 Figure 4.37: Function Templates without Scheduling Capability................... 183 Figure 4.38: Creation of Instances of Multiple Function Templates................184 Figure 4.39: Creation of Multiple Instances of Function Templates................186 Figure 4.40: Function Templates with Scheduling Capability............................. 188 Figure 4.41: FIFO Scheduling of Functions using Linked List.......................189 Figure 4.42: Synchronous Function Invocation.................................................... 192 Figure 4.43: Refined Function State Transition Diagram......................................193 Figure 4.44: Asynchronous Function Invocation...........................................195 Figure 4.45: Timing Diagram of A Spooler Object................................................197 Figure 4.46: Execution of Fifo Functions..............................................................200 Figure 5.1: Keeping Track of Process Instances................................................. 222 Figure 5.2: Process List..........................................................................................223 Figure 5.3: Flat Coordination of Processes using Static Members............... 229 Figure 5.4: Hierarchical Coordination of Processes using A Metaclass............230 xi Figure 5.5: Sharing of programmers....................................................................231 Figure 5.6: Representation of Resource Sharing using Subclasses in C++....235 Figure 5.7: Representation of Resource Sharing in G alois................................236 Figure 5.8: Coordinating Shared Resource in Galois..................................240 Figure 5.9: Representing Group of Objects using Subclasses..................... 245 Figure 5.10: Multiple Relationships Among Programmers........................... 248 Figure 5.11: Synchronous Access Control.............................................................250 Figure 5.12: Mutual Exclusive Access to a Shared Resource.............................. 251 Figure 5.13: Synchronization Mechanism in Resource R .................................... 255 Figure 5.14: Implementation of Resource R Reusing An Existing Class............259 Figure 5.15: Scheduling based upon Priority........................................................ 266 Figure 5.16: Priority Queue of Function Children........................................ 271 Figure 6.1: A Sample Version Tree.......................................................................288 Figure 6.2: Branches in a Version T ree................................................................289 Figure 6.3: Generation of a Version Tree.............................................................290 Figure 6.4: Conflicting Version Generation........................................................ 291 Figure 6.5: Hierarchical versions......................................................................... 293 Figure 6.6: Version List......................................................................................... 302 Figure 6.7: Creating a New Revision.......................................................... 304 Figure 6.8: State Transition of Resources................................................... 307 Figure 6.9: Reserve() and Get() operations..........................................................309 Figure 6.10: Selecting Versions..............................................................................315 Figure 6.11: Manufacturing Relationship among Classes.................................... 317 Figure 6.12: Manufacturing Relationship among Objects.................................... 318 xii Figure 6.13: Modeling A Compilation Process Figure 6.14: Modeling A Link Operation Abstract This Ph.D. dissertation presents a software process modeling strategy called object process modeling, and a process programming language called Galois. In object process modeling, each software process is modeled as an object which encapsulates operations around a collection of resources required to accomplish a specific software development task. Each software process provides a working environment in which software engineers are allowed to work in order to pursue the designated task by invoking the operations provided by the process. At the same time, the process keeps watching the activities performed by the software engineers, so that they always stay on the right track. I am developing an object process modeling environment called OPM which j provides environmental support for object process modeling. In OPM, software I process models are designed in Galois and are executed by the Galois runtim e?^’ system. OPM itself is implemented in Galois, and therefore it is considered as a software process running on top of the Galois runtime system. I have developed the new process programming language G alois because conventional programming languages are inadequate for implementing software processes, including OPM. In particular, to implement process coordination, software manufacturing, and resource sharing, special programming language features are highly desirable. Galois is an object-oriented language which is a superset of C++, but is different from Smalltalk or C++ in several important ways. xiv First, Galois treats classes as typed objects which can be produced by instantiating their metaclasses. Typed classes are used to coordinate multiple process instances of a single process model. Second, G alois provides a new object creation mechanism called derivation. Derivation is used to model software manufacturing, the evolution of software processes, and shared resources. Third, operations in Galois are also typed. The type of operations determines the scheduling algorithm of multiple invocation of the operations. Finally, in Galois, operations can be associated with a precondition and/or a postcondition so that the operations are carried out only when the conditions are met. I have implemented major features of OPM using Galois, and it turned out that OPM and many software process models can be far more effectively implemented in Galois, rather than using conventional programming languages. xv Chapter Introduction This Ph.D. dissertation presents a software process modeling strategy called object process modeling, and an object-oriented process programming language called Galois. In object process modeling, each software process is modeled as an object which encapsulates operations around a collection of resources required to accom plish a specific software development task. Each software process provides a working environment in which software engineers are allowed to work in order to pursue the designated task by invoking the operations provided by the process. At the same time, a software process keeps watching the activities performed by the software engineers in the process, so that they always stay on the right track. Object process modeling provides a software development environment in which multiple software engineers are working concurrently in their own working envi ronments to pursue their own goals. Each working environment may consist of a different set of resources and operations depending on the task to be performed within it. Fundamentally, working environments are concurrent and asynchronous entities, but they can coordinate their activities, if necessary, by communicating to each other. Consequently, the software development environment, as a whole, is a collection of small and heterogeneous working environments which are working together by coordinating their activities. In object process modeling, resources involved in software processes are also modeled as objects. Resources include computer files, computer hardware, and human resources. Each resource provides a collection of operations through which software processes and/or software engineers are allowed to manipulate the re source. The operations include those for software manufacturing [14], version and configuration management [7], synchronous access control to resources, and re source allocation. Galois is an object-oriented programming language which is specifically designed for describing software processes. Galois manipulates software components as well as data objects, and carries out software manufacturing operations, including compilation and link operations, as well as arithmetic and string operations. Currently, I am developing an object process modeling environment called OPM which provides environmental support for object process modeling. In OPM, soft ware process models are designed in Galois and are executed by the Galois runtime system. OPM itself is implemented in Galois, and therefore it is considered as a software process running on the top of the Galois runtime system. Software process programs written in Galois encode the knowledge of software development experts which is essential to accomplish software development tasks into a machine readable form, and software engineers in the software process are allowed to get 2 assistance from the processes which have the equivalent knowledge as the software j development experts. i I have developed the new process programming language Galois because conven tional programming languages are inadequate for implementing OPM and software processes in OPM. In particular, to implement process coordination, software man ufacturing, and resource sharing, special programming language features are highly desirable. Syntactically, Galois is an object-oriented language which is a superset of C++ [93], but is significantly different from Smalltalk [40] or C++ in several im portant ways. First, classes in Galois are typed objects. Classes are instances of their metaclasses which define the behavior of the classes as objects. Classes in Galois can be pro duced by instantiating metaclasses, while metaclasses in Smalltalk are not instan- tiable. As a result, classes are first class objects which are capable of coordinating their multiple instances. Metaclasses are used to coordinate and monitor the multiple process instances of a single process model. Second, Galois provides a new object-creation mechanism called derivation which is a generalization of the traditional instantiation model. In the derivation model, although every object belongs to its own class, it is not created from the class. Derivation allows objects to be created from some other existing objects by trans formational operations. Derivation is used to model software manufacturing, the 3 evolution of software processes, and resources which are accessed by multiple pro cesses simultaneously. Third, operations in Galois are also typed objects. Galois provides a concurrent environment in which multiple objects are performing their tasks simultaneously. But each object is essentially a sequential process which executes one of its opera tions at a time. Therefore, when an object receives two or more requests to execute its operations, the object need to schedule these requests. The type (class) of an op eration determines the scheduling algorithm of multiple invocation of the operation. Typed operations are used to implement the scheduling of resources shared by sev eral processes or programmers. Fourth, in Galois, operations can be associated with a precondition and/or a post condition so that the operations are carried out only when the conditions are met. Galois also allows us to specify preprocesses and postprocesses of each operation which determine the order of execution of the operations. Preprocesses are used to achieve backward chaining of operations, while postprocesses are used to achieve forward chaining of operations. 1.1. Software Process A software process is a collection of activities that will be carried out over time to produce a software system. An activity is a basic unit of work and it minimally contains a name, some attributes such as a start date and a duration, and some re 4 sources. Often the activities are ordered in some way, but not necessarily. If an ordering is available, then we can talk about an activity's predecessors and succes sors. When a process is being carried out, typically more than one activity is going on at the same time. The result of an activity may be a product or simply a confir mation that it has concluded. An activity can invoke other processes. In fact, there is no logical reason to distinguish between an activity and a process, so in the future I will use these terms interchangeably. One of the key elements of a software process is that it involves and manipulates several resources. One of the major resources is a computer file which will be pro duced and manipulated by the activities, but resources are not limited to computer files. They may be software personnel who produce the software system, a design document, a manual, or computer hardware. Some resources may be physically lo cated closely, while others are distributed. Some resource may be exclusively used by a single process, while others may be shared among several processes with or without a coordination mechanism. Some resources may have several versions, while others might be immutable. The origin of the study on software process is quite old. Earliest efforts, such as [9], were made more than thirty years ago. Despite its old origin, the intensive study of the software process has not been made for a long time. Traditionally, the research of software engineering has been focused on developing "vehicles" which are used to carry out the activities in some hypothetical software process. The "vehicles" will include programming tools, requirement analysis tools, testing tools 5 and software development environments which are integrated sets of tools. Although many vehicles to carry out the activities have been invented, the software process itself has not been investigated for a long time. Recently, in 1980's, the importance of the research on the software process has been increasingly recognized. Several workshops [82] [106] [31] [103] [2] have been devoted to the discussion of the modeling of software process. Early workshops [82][106][31] were focused on formal models of the software process, while recent workshops [103] [2] were devoted to the discussion on enactability of software pro cess models. 1.2. Software Process Model A software process model is a static and formal description of software processes. A software process model serves as a template from which a collection of software processes, called its instances, are created. A process model will determine several characteristics of its instances which will include: • activities included in the process • the way of coordinating these activities, e.g. ordering of activities, conditions of starting and terminating activities, and so on. • the way in which each activity is carried out • the description of resources to be consumed by the executing activities • the description of products to be produced by the execution of the activities 6 The stagewise model [9] is one of the earliest attempts to define a formal model for software development, and the waterfall model [86] is a classical lifecycle model which is still widespread in the current software industry. In these models, the software process is defined to be a collection of ordered steps (or sometimes called stages or phases) which need to be executed in order to produce a software system, such as system requirements step, analysis step, coding step, testing step, and so on, plus documents produced by these steps. As the research on software engineering evolves, quite a few advanced software development methods have been invented. As a result, the necessity of guidelines to conduct a diversified software project which involves these new programming methods has been increasingly recognized. Each newly developed method will need to be carried out in a specific software process. One of such attempts is the devel opment of alternate lifecycle models which accommodate a specific programming method. Such examples will include the Pamas Information-hiding approach [79], the two-leg model [58], and evolutionary development model [65]. Another approach to accommodate various programming paradigms can be seen in the spiral model [11]. While the alternate lifecycle models mentioned above have tried to be more specific to each programming paradigm, the spiral model tends to be generic so that it can accommodate all the available development approaches. The spiral model represents the software process as an iteration of small cycles which incrementally develop the software system. Each cycle explicitly includes multiple software development paradigms, such as specification-oriented, prototype- 7 oriented, automatic transformation-oriented approaches, and so on. The particular combination of these paradigms is determined by the risk analysis performed in each cycle. As a result, the model is a mixture of (portions of) existing paradigms, rather than a fixed collection of phases based upon a specific paradigm. More de tails of these software process models will be reviewed in Chapter 2. More primitive examples of software process models include procedure manuals of various software development companies and a makefile which is an input to the MAKE utility [33]. Many software development companies prepare their own writ ten procedure manuals which outline how software systems need to be built in their institution. A makefile states the interdependencies among software objects to be produced during the development of a software system, and how these objects need to be built from other objects. A makefile is executed by the MAKE utility to produce the software objects specified in it. The executability is one of the signifi cant differences of a makefile from other software process models introduced here. Software process models have received so much attention from the software engi neering community, because the use of a software process model gives us the fol lowing advantages. The first advantage of designing a software process model is in its capability of serving as guidelines or a recipe to conduct software development processes productively. It is generally true that the productivity of the development of a software system notably depends on the way in which the software system is manufactured. Thus, it is obviously desirable to devise a superior plan or a recipe to carry out the software process before it starts, rather than conducting the project on 8 the fly. A software process model will serve, and have served, as such guidelines or a recipe to conduct software development projects. By designing a superior software process model in advance and conducting the software project along the model, the software project will be able to obtain the maximum productivity. By conducting a software project based on a software process model, another ben efit can be obtained. A software process model can serve as common media for the communication between software engineers involved in a software project. The manager of the project can push programmers by just pointing out the steps in their process model need to be finished as soon as possible in order to catch up the schedule. Programmers can talk to other programmers in terms of their process model in order to coordinate their tasks. i The second advantage of designing a software process model is its capability of j making the knowledge and information on software processes to be reusable. The j knowledge and technique to conduct a software development project productively cannot be easily obtained and only a few software experts can invent them through an intensive research and an expensive experience. Therefore, such knowledge and technique obtained for a specific software project should not be abandoned as the project terminates. They should be preserved and passed on to other projects for being reused. A software process model will provide a media for reusing knowl edge and technique on software processes. Some software process models might be designed based on the formal study of software processes by software researchers, while others might be obtained from the practical experience of conducting actual 9 ! i _________________________________________________________ j software development projects by software practitioners. In both cases, the knowl edge embedded within the models by the software researchers and practitioners will be available for being reused by everybody. The third advantage of software process models is their abstraction capability. A software process model is capable of representing a range of software processes in a generic and generalized way by abstracting away non-essential differences be tween its instances. Thus a software process model will provide a media for formal izing a range of software processes, and will serve as a criterion for evaluating a range of software processes. 1.3. Software Process Modeling As the evolution history of software process models shows, software process models are required to be both more generic and more specific. But no single model can be generic and specific enough to accommodate all kinds of software processes. This is mainly because existing models are just the description of a "sample pro cess" and do not have an explicit capability of defining new software processes. A software process model provides a predefined and fixed "alphabet," consisting of development phases and kinds of documentation to be produced by phases, which can be used to describe software processes, and only these alphabets can be used. This limitation is a critical disadvantage of existing software process models. Since the alphabet is fixed, it can be used to describe only a limited set of software pro- 10 cesses. This argument naturally leads us to the necessity of a language for describ- | ing process models. Recently, software process modeling has received attention from software engineer ing communities. The main idea here is to allow software personnel to design and describe their own software process models by providing a language for designing and describing software processes models, rather than providing a fixed software process model and forcing software engineers to use it. The software process modeling approach is superior to the conventional approach which provides a fixed software process model, because: • The software process modeling approach allows us to design a new software process model which is suitable for describing a specific software process. Thus, no one will complain about the limitation and the lack of flexibility of a specific software process model. I • The granularity of the model can be selected based upon the specific need of each software project. No one will complain about the lack of the power of the software process model to describe the details of a specific software pro cess. • Existing software process models can be re-described in a single form of rep resentation. Thus we can compare and reason about different software models effectively even if they are originally not comparable. I I 11 I The software process modeling approach consists of the following two key ele ments: • a metamodel: a process modeling strategy • a process modeling language: a notation for describing software process models The first element is a software process modeling strategy or a metamodel which will serve as guidelines to design a software process model. And the second element is a process modeling language which is a notation to describe software process mod els. Here, I stress that process modeling languages should be considered separately from metamodels of software processes. A process modeling language can specifi cally support a specific metamodel, but it is not necessary. Object process modeling which is presented in this dissertation is an instance of the software process modeling approach. Object process modeling consists of the software process metamodel called the object process model which is based upon object-oriented programming techniques [12][23][27][66], and the object-oriented process programming language Galois. It is only a short time since the first attempt to define a formal metamodel for de scribing software processes was made by Osterweil [77]. He proposed that soft ware processes should be specified by means of rigorous algorithmic descriptions, and these process encodings should themselves be view as items of software. But, Osterweil's approach has been criticized [28] [59]. The argument against 12 Osterweil's approach is mainly that no algorithm of an interesting software process can be prescribed completely in advance. Another effort was made by Williams [107]. He proposed that processes should be described in behavioral descriptions, rather than algorithmic prescriptions. Behavioral descriptions of software processes focus on the effects which software development activities produce, rather than the specific algorithms to obtain the effect. The details of these approaches will be dis cussed in Chapter 2. On the other hand, the history of process modeling languages is quite long. Quite a few attempts to describe software processes using various kinds of notations have been made. Many software development companies have developed their own pro cedure manuals in natural languages, such as English, German, and Japanese, although natural languages are neither formal nor rigorous enough to describe soft ware processes. Attempts to describe software processes using graphical notations, such as a network chart and the Petri net, also have been made. PERT [105] allows us to describe interdependencies among tasks performed during the software devel opment using a network chart, called a PERT chart. Attempts to use the Petri net for describing software process models can be seen in [63][64]. It is also possible to describe software processes using a conventional programming language. The use of conventional programming languages for describing software processes was proposed by Osterweil [77]. 13 1.4. About This Dissertation In this chapter, I gave an overview of basic concepts, such as software processes, process models, and process modeling. The remaining chapters of this dissertation are organized as follows. In Chapter 2 ,1 will review several related works including existing process models, such as the waterfall model and the spiral model; existing process modeling strate gies, such as algorithmic process modeling proposed by Osterweil and the behav ioral process modeling proposed by Williams. I will also review resource manage ment capabilities in existing software development systems and models for resource management, because resource management is one of the important capabilities to be performed by software processes. The focus will be put on version and configu ration management. I will also review several existing object oriented languages which provides a basis for discussing Galois. I will also give a quick review of the C++ language. The review will be focused on the class hierarchy in C++, the in heritance mechanism which differs from many other languages, and concurrency in C++. In Chapter 3, I will present the object process modeling approach upon which Galois is based. Chapter 3 also includes an overview of the process modeling envi ronment OPM which supports the process programming using Galois. By an envi ronment we mean a user interface through which process templates are designed, instantiated, and tracked as they execute. In this chapter, the way OPM uses con 14 ventional CASE tool graphical notations for describing process templates, and the way OPM supports model instantiation and cooperative work among a team of de velopers, and the way OPM provides a working environment in which a software engineer can work to accomplish his tasks, will be discussed. In Chapter 4 ,1 will present the new language Galois. I will start with a brief intro duction to Galois, and will present the major innovations of Galois including: • metaclasses and metaclass hierarchy • derivations, derived classes, and derived class hierarchy • typed functions and concurrency • rule-based extension Chapters 5 and 6 will present the process programming in Galois. I will focus on how each capability included in Galois is used for implementing OPM and software processes in OPM. In Chapter 5, I will present the way of cooperative working using Galois including: • how to represent shared resource using Galois • how to control the concurrent access to the shared resource in Galois • how to coordinate cooperative activities • how to archive chaining of activities • how to keep track of the execution of activities In Chapter 6 ,1 will present resource management using Galois including: • how to represent software manufacture in Galois 15 • how to implement the version and configuration control using Galois These two chapters will clarify the motivation for designing new features found in Galois, and will prove the usefulness of these new features. This chapter will also include the study of how existing programming languages fail to implement the ca pabilities of process programs listed above. And Chapter 7 will conclude this dis sertation. 16 Chapter 2 Related Works 2.1. Software Process Models 2.1.1. Stagewise Model One of the earliest efforts to give a formal representation of the software process using a software process model, which is called the stagewise model, can be seen in [9]. Figure 2.1 shows a pictorial representation of the stagewise model. In the stagewise model, the software process is described as successive nine phases which are outlined below. Each phase is described in terms of documents which will be produced by the phase. (1) An operational plan which will determine the design requirement for the complete system will be jointly prepared by the computer systems engineers and the end users of the system. (2) An operational specifications which specify the functionality of the system will be determined. (3) A program specifications which outline the implementation of the compo nent subprograms will be produced. 17 Design ■ m i ' •■Hi' Coding Shakedown Operational Plan System Evaluation Coding Specifications Program Specifications Machine Specifications Operational Specifications Parameter Testing (Specifications) Assembly Testing (Specifications) Figure 2.1: The Stagewise Model [9] (4) A coding specifications which define the detail of each component subpro grams will be prepared. (5) In the coding phase, each component subprogram will be implemented in a programming language. (6) In the parameter testing phase, each subprogram will be tested against the parameter testing specifications. (7) In the assembly testing phase, the program will be gradually assembled and tested based on the assembly testing specifications. (8) In the shakedown phase, the completely assembled program will be tested in the operational environment. 18 (9) The program will be ready for operation and evaluation. The picture includes two kinds of arrows which indicates the flow of documents. Solid arrows indicate the flow of design documents. For instance, the program specifications phase has three incoming solid arrows. This indicates that the pro gram specifications are prepared based on the operational plan, the machine specifi cations, and the operational specifications. On the other hand, dashed arrows indi cate the flow of test data. For instance, the parameter testing is guided by the coding specifications instead of the coded program. A set of test specifications will be pre pared, based on the coding specifications, in parallel with the coding phase, and the parameter testing will be conducted by this test specification. It is an astonishment that such a complete life cycle model has been defined in such early days, although the model itself is still in its infancy. Benington recognized the existence of the loop of some successive phases. He pointed out, in the description of the coding phase, that "detailed coding uncovers inconsistencies that requires the revisions in the coding specification (and occasionally in the operational specifica tions)." But this recognition of the necessity of loop has not been reflected in the stagewise model itself. 2.1.2. The Waterfall Model One of the major improvements to the stagewise model has been achieved in the waterfall model [86], Royce recognized the existence of the feedback loops across non-consecutive phases, and proposed several guidelines to confine the loops to 19 Preliminary DesjcjiT^^ _ Analysis Program Design Coding Testing I Testing Coding Operations Analysis System Requirements Software Requirements Program Design Preliminary Program Design Usage Figure 2.2: "Do It Twice" step in the Waterfall Model [86] successive phases in order to minimize the redesign. Royce also analyzed that the predecessor models are very risky because "the testing phase which occurs at the end of the development cycle is the first event for which timing, storage, in put/output transfer, etc., are experienced as distinguished from analyzed. These phenomena are not precisely analyzable. ... If these phenomena fail to satisfy the various external constraints, then invariably a major redesign is required." One of the major enhancements of the waterfall model over its predecessors is the introduction of the prototyping approach. The prototyping approach in the waterfall 20 Prelim inary Design Analysis Program D esign Testing | U sa g e System R equirem ents Softw are R equirem ent System R equirem ents G eneration Preliminary Program Design Analysis Program Design Preliminary Softw are Review m D ocum ent No.1 Softw are R equirem ents C oding Docum ent h o.2 Testing Prelim inary D esign Critical Softw are Review O perations Docum ent No.3 Interface D esign Softw are A cceptance Review Document No.4 Final Design D ocum ent No.4 Final Design D ocum ent No.5 Test Plan D ocum ent No Operating Instructions Figure 2.3: The Waterfall Model [86] model is achieved by the insertion of the preliminary program design phase and the "do it twice" phase. Royce emphasized the necessity of the preliminary program design phase which will come after the requirement document is completed and before the analysis phase begins. The preliminary design phase will be begun with program designers, not analysts or programmers. The idea here is that both analysis and program de signers sufficiently commit themselves to the program design. 21 Royce also suggested that "the version finally delivered to the customer for opera tional deployment is actually the second version insofar as critical design/operation areas are concerned." This can be carried out by "do it twice" step illustrated in Figure 2.2. Another enhancement is the inclusion of review phases which involve the cus tomers. The waterfall model includes three major software reviews: preliminary software review, critical software review, and final software acceptance review. This makes it possible to detect problems which were originated in the early phases, rather than waiting for the testing phase which comes later. Royce also pointed out the importance of the formal involvement of the customers in the reviews. This makes the customers' early commitment to the development possible before the fi nal delivery. As a result, the whole waterfall model can be illustrated as in Figure 2.3. 2.1.3. The Alternate Lifecycle Models Although its widespread use, quite a few difficulties of the waterfall model have been pointed out, and several alternative models have been invented. Boehm sum marized the difficulties of the waterfall model in [11]. • The waterfall model does not adequately address concerns of developing pro gram families and organizing software to accommodate changes. The Pamas Information-hiding approach [79] does an excellent job of addressing these concerns. 22 • The waterfall model assumes a relatively uniform progression of elaboration steps. The two-leg model [58] features separate processes of abstraction until a formal specification is achieved, followed by a set of formal deductive "reification" steps to proceed through design and into code. • The waterfall model does not accommodate the sort of evolutionary develop ment made possible by rapid prototyping capabilities and fourth-generation languages. Several evolutionary development models [65] and mixed models [39] have been advanced to address this approach. • The waterfall model does not address the possible future modes of software development associated with automatic programming capabilities, program transformation capabilities, and "knowledge-based software assistant" capa bilities. The automation paradigm [8] provides an alternative life-cycle model and conceptual framework for incorporating these capabilities. 2.1.4. The Spiral Model The spiral model represents the software process as an iteration of small cycles which incrementally develop the software system. Each cycle explicitly includes multiple software development paradigms, such as specification-oriented, prototype oriented, automatic transformation-oriented approaches, and so on. The particular combination of these paradigms is determined by the risk analysis performed in each cycle. As a result the model is a mixture of (portions of) existing paradigms, rather than a fixed collection of phases based upon a specific paradigm. Each cycle also includes the planning step for the next cycle based on a review of the current cycle. As a result, the cycles in the process are also developed incrementally. 23 C um ulative Cost Progress Through S tep s D eterm ine O bjectives, Alternatives, C onstraints Evaluate Alternatives: Identify, Resolve Risks Risk Analysis Risk Analysis Risk Analysis O perational Prototype , Proto* ! T ypel Prototype2 \ Prototype3 Com m itm ent P artition Rqts. Plan Lite CyclePlan C oncept of O peration Softw are Rqts Softw are Product D esign D etailed D esign D evelopm ent Plan R equirem ents V alidation C o d e Unit Test D esign Validation and Verification Integration and Test Plan Next P h a se s Integration and Test Im plem en-; A ccePta n ca Test tation Develop, Verify Product Figure 2.4: The Spiral Model [11] Figure 2.4 illustrates the spiral model. Each cycle in the spiral model consists of the following four steps: (1) Determine Objectives, Alternatives, Constraints: Each cycle will begin with identifying objectives of the elaboration to be performed in the cycle, such as performance, functionality, user-friendliness, and so on. At the same time, the alternate ways for the elaboration and the constrains imposed on the elabo ration are identified. 24 (2) Evaluate Alternatives and Identify, Resolve Risks: In the second step, the alternatives are evaluated with respect to the objectives and the constraints. In an ideal case, the most appropriate one will be selected among the alternatives, but frequently this is impossible because of the existence of some uncertainty. In such a case, several risk-resolution techniques, such as prototyping, simu lation, analytic modeling, etc., or combinations of these techniques will be applied. (3) Develop, Verify Product-. The (portion of the) software system will be de veloped by the way selected in the previous step. (4) Plan Next Phases: Each cycle will complete with a review of the current cy cle and the planning for the next cycle. The planning will include a partition of the product into increments for successive development. 2.2. Process Modeling Approaches In this section, I will review two software process modeling approaches: the algo rithmic model by Osterweil [77] and the behavioral model by Williams [107]. 2.2.1. Algorithmic Process Modeling In [76] Osterweil proposed that "software engineering processes should be speci fied by means of rigorous algorithmic descriptions", and these "process encodings should themselves be viewed as items of software." Figure 2.5 shows a sample process program presented in [77]. 25 Function All_Fn_Perf_OK(executable, tests); declare executable executable_code, tests testset, case, numcases integer, result derived__result; All_Fn_Perf_OK := True; For case ;= 1 to numcases derive(executable, tests[case].input_data, result) if -resultOK(result, testcase [case] . re<g_output) then All_Fn_Perf_OK := False; exit ; end if end loop; end All_Fn_Perf_OK; Figure 2.5: Osterweil's sample process program [77] This program represents a process which tests an executable program using test cases passed as parameters. The program begins with the declaration of variables executable, tests, case, numcases, and result. They belong to types executable_code, integer, testset, and derived_result. These types are defined somewhere else. The for-loop is the heart of the testing process, specifying the iterative execution of the testcases in a testset array and the comparison of the results with expected behavior. APPL/A [43] is a first prototype process programming language that was developed as part of the Arcadia project [98]. APPL/A is a superset of Ada [1] that enables the explicit definition of relations among software objects. Several attempts to write software process programs in APPL/A have been reported [78]. For instance, REBUS [100] is a series of prototype process programs written in APPL/A for de veloping requirements specification. 26 j Osterweil's algorithmic process modeling approach has been criticized [59] [28]. In I [28], Curtis argues that such a rigorous algorithmic description of software pro cesses is inappropriate because "there is tremendous variability both in the means of executing processes and in the results." If a process model does not represent the processes that control the largest share of the variability in software development, then it is not helpful in boosting productivity and quality. Furthermore, he argues that the examples of process programs given by Osterweil are so trivial that "even inexperienced software engineers know how to perform." Such a process program "is not likely to not assist software engineers in performing their tasks with greater efficiency or accuracy unless their problem was not know ing what to do next." 2.2.2. Behavioral Approach Williams proposed a process modeling approach to describe software software pro cesses in behavioral descriptions [107]. It puts emphasis on the effects which soft ware development activities produce, rather than the specific algorithms to obtain the effect Fundamentally, this approach is based on the formalization of the rule-based ap proach [48] which has been implemented in several systems including Genesis [84] and Marvel [51]. In these systems, an activity is associated with preconditions and post conditions which are described in terms of the state of the environment. The condition might be the existence of some document and/or the status of the docu- 27 Software Process Model = { activity} activity = ( {precondition}, action, {postcondition}, {m essage}) Figure 2.6: Definition of the Process Model in Behavioral Approach [107] ment such as a source program has been compiled or not. The preconditions must be true before the associated activity can be performed. The postconditions become true when the activity is completed. Preconditions and postconditions establish in terconnection among software development activities. A survey on interconnection models of software development activities can be seen in [80]. The use of preconditions and postconditions are similar to Hoare's assertion [44] where a program segment is associated with its preconditions and postconditions, but the interpretation of the postcondition is significantly different. In Hoare's assertion, the postconditions are verified in order to prove the correctness of the program segment, while here postconditions are asserted rather than verified. Figure 2.6 shows the definition of the software process model proposed in [107]. A software process model is described as a set of activities. The behavior of each activity is encoded as a tuple consisting of a set of preconditions, action, a set of postconditions, and a set of messages. A software process model is described in terms of these activities using the constrained expression formalism [6] which in cludes the following operators: concatenation, repetition(*), and shuffle operation (A). The concatenation represents the sequential execution of activities, while the shuffle operation represents the concurrent execution of the operations. 28 ((determine_objective determine_alternative determine_constraints) (evaluate_alternatives identify_risks resolve_risks) (develop_product verify_product) (plan_next_phase))* Figure 2.7: Behavioral Description of the Spiral Model [107] Figure 2.7 shows, as an example, the behavioral description of the spiral model. The spiral model is represented as an iteration of four phases, which are enclosed in parenthesis, using the repetition operator(*). Each phase is represented as a se quence of corresponding subphases using the concatenation operator. For instance, the first phase is represented as a sequential execution of three subphases determine_objective, determine_alternative, and determine_constraints. 2.2.3. Combining Algorithmic and Behavioral Approach The behavioral description is an extreme case in attempting to represent software processes which will be located in the opposite side of the algorithmic process modeling. The behavioral description of a process does not refer to how the process is carried out at all, while the algorithmic description fully describes the way of car rying out the process. But it is needless to say that these extremely biased represen tations, that is the algorithmic description and the behavioral description, are not appropriate. The algorithmic description of a process in a machine readable form is necessary because the environment must know the algorithm in order to help the programmers accomplish the task. But rigid description of a process, such that the environment can completely navigate the programmers, will be impossible, and such a navigation without any freedom will make the programmers uncomfortable. 29 Thus the third approach which accommodates these two extreme approaches is re quired. The necessity of the third approach can be inferred from the following trend in the Arcadia project. For instance, in the Arcadia project, a language called P4 [41] was developed to be parallel and complementary to the APPL/A language. P4 is an ex tension of Prolog which supports types, inheritance, and parallelism. Using P4, each process program is a set of rules for satisfying goals in terms of other goals. Other attempts in the Arcadia project include the use of. a formal specification lan guage for describing and understanding software processes. An attempt to give a formal specification of REBUS in a specification language, called PLEASE [99], was reported in [100]. PLEASE is an Ada based specification language that sup ports predicate logic annotations. Recently, significant questions about the proper relationship between software de velopment environment and the software process have been raised [81] [73]. The object process model which I develop in this dissertation is a solution to the ques tions which incorporates the algorithmic and behavioral approaches. The object process modeling approach allows us to describe the algorithm of software pro cesses to be carried out in a software development environment and it still leaves plenty of freedom to programmers in the environment. 30 2.3. Resource Management 2.3.1. Software Configuration Management This section will give a quick review of current technologies of version and config uration management [95] [102]. (1) SCCS and RCS SCCS (Source Code Control System) [85] and RCS (Revision Control System) [101] are version control tools which have established a foundation of the current version and configuration control systems. The capabilities of SCCS and RCS have been incorporated into various advanced systems such as DSEE [56] and Gypsy [24], which I will cover later. Furthermore, although SCCS and RCS are UNIX tools, similar tools that run on various operating systems have been developed. For instance, SCS (Source Control System) [69] is a version control system that runs under IBM VM/SP with CMS. Both SCCS and RCS are designed to maintain multiple versions of text files, mainly source programs, which will form a tree structure. SCCS and RCS use a delta mechanism to save the storage space to keep slightly different versions. The mechanism used in SCCS is called the forward delta. In the forward delta, the old est version is kept as it is and newer versions are kept as difference to it predeces sor. On the other hand, the mechanism used in RCS is called the backward delta. In the backward delta, the newest version is kept as it is and older versions are kept as 31 differences to their successors. Both the forward delta and backward delta mecha nisms take the difference of versions line by line. Recently several other algorithms [74] to take deltas have been invented. They take the difference of versions word by word rather than line by line. As a result, some improvement has been achieved, but the major performance has been unchanged. Both SCCS and RCS implement the reserve/release mechanism in order to avoid simultaneous update of a single version by two or more programmers. When a user needs to modify a version, he first executes the reserve operation which will make a copy of the version on which he can work. Successive requests to reserve the same version will be denied until the first programmer releases the version. This will guarantee the mutual exclusive access to the version. When the programmer fin ishes the modification to the version, he will release the version. Then the modified copy will be saved as a new version along with a brief description of the version, and it allows other programmers to reserve the version. (2) Make Make [33] is a configuration management tool which maintains the dependency among components involved in generating a system. Make accepts a makefile as a description of the system to be built which includes the dependency information among components as well as manufacturing operations to be used to build the system. The only dependency relationship which is supported by Make is using the time stamp of files. In a makefile, a file can be associated with names of files which i 32 i « it depends on. If the time stamp of the file is older than those of its ancestor files, Make will execute the operation associated with the dependency rule. Make is quite useful for keeping the consistency among objects which are being transformationally produced, such as a source file, its object program, and its exe cutable program. Whenever the source program is modified, Make will keep both the object program and the executable program to be fresh, that is, the modified source program is recompiled and a new object and executable program will be generated. (3) DSEE DSEE (Domain Software Engineering Environment) [56] [57] is a software devel opment environment which runs on Apollo workstations. The most significant contribution of DSEE is the way in which DSEE integrates both version manage ment and configuration management into a single stage. In early days, the version control and the configuration control were supported by different tools, such as SCCS and Make. As a result, there was no mechanism provided in order to coop erate a version description into a configuration description. DSEE configuration manager accepts a system model which describes the depen dency among components involved in building a software system, as well as the manufacturing rule to build the system. A system model is generic rather than in cluding the version specification to be used to build a specific software system. This makes a contrast to the Cedar system model [55] in which specific versions of 33 files are specified. Instead, a configuration thread will specify a specific binding of generic component names in system models to versions to be used. As a result, a combination of a system model and a configuration thread will determine a specific set of versions to be used to build a specific sy stem. The configuration manager is allowed to directly access the versions of each com ponent. There is no need to explicitly check-out the necessary versions before using them. In a configuration thread, it is possible to bind a component name to a spe cific version. Assume a component Tree is shared by several systems. A configu ration thread might say: under X for Tree [2 4] under Y for Tree [23] for Tree [REV9] A configuration thread will be interpreted from the top to the bottom. This specifies that (1) use the version 24 of component Tree when building system x (2) use the version 23 of component T ree when building system y (3) use the version tagged with REV9 of component Tree when building other systems But a configuration thread also allows us to specify more dynamic rules to select versions. A configuration thread may say: for ?* -when_reserved -use_options -debug for ?* [REV5] -when_exists for ?* [] 34 This specifies that, for all the components, (1) if the component is reserved, use the reserved copy with a debug option (2) if a version tagged with rev5 exists, then use it. (3) otherwise, use the latest version. These dynamic references to components are resolved when the system is asked to build a specific system. Then a bounded configuration thread will be created which explicitly specifies the version to be used for each component. After the build op eration, the bounded configuration thread will be associated with the derived object and will be used to the historical identification purpose of the derived object. (4) Gypsy More advanced approach to the version management can be seen in Gypsy [24]. Gypsy implements its version management mechanism by extending the capability of directories in its file system, rather than providing a separate system component for the version management. Related versions are stored in a directory, and the di rectory provides the operations which are necessary for the version management. As a result, Gypsy obtained the following advantages over its predecessor systems. First, Gypsy naturally integrates the version naming with the file naming. Fundamentally, each version is identified by appending the branch name and the version name which the version belongs to. For instance, the version 5 on the branch B3 of the file /usr/sugiyama/tree. c can be referred by /usr/sugiyama/tree.C0B305 Although the branch names and the version names are given by the system, users are allowed to give a mnemonic name to their versions. Each directory is allowed to designate a default version. Then the directory name without a version selector will denote the default version. Gypsy also allows us to select a version by specifying a selection rule. According to [24], the only rule supported by Gypsy is latest. The following will denote the latest version in the branch B3. /usr/sugiyama/tree.c0B301atest Gypsy also allows us to specify a predicate, which is described in terms of proper ties of versions, in order to obtain a subset of versions. The example below will list all the tested versions in branch B3. Is /usr/sugiyama/tree.c@B3@*[status=tested] A second advantage is the transparency of the version management. Gypsy per forms the delta compression like its predecessors. But the reconstruction of com pressed versions is carried out transparently from the users. Whenever a version is accessed, it will be automatically reconstructed if it is compressed. As a result, ap plication programs and the users need not worry about if the version is compressed or not. Gypsy also provides a reconstruction cache in order to minimize the cost of the transparent reconstruction. Frequently used versions are stored in the cache in the decompressed format, and ready to use without reconstruction. 36 (5) Formal model for software configuration management Borison [14] introduced a graph based model to represent manufacturing pro cesses. Borison's manufacturing graphs fundamentally represent the dependency among software objects involved in manufacturing processes. She defines a con figuration as a directed acyclic graph whose nodes are components and manufactur ing steps. Components are not limited to the objects conventionally considered as part of a configuration. A component is any software artifact that has a concrete rep resentation and that can affect the result of the manufacturing process if replaced by another artifact with another value. The model uses difference predicates to discrim inate between changes that are significant and those that are not. Difference predi cates determine which steps in the manufacturing process need to be carried out when incorporating a given set of changes. The model proposed by Heimbigner and Krane in [42] is based upon the transfor mational model. The model starts with an incomplete graph specifying a software object to be constructed. And a sequence of transformation is applied to this graph to transform it into a graph representing a complete specification for the production of the software product. The final graph closely corresponds to Borison’ s manufac turing graphs. This product graph may be executed to produce the actual product. Traditionally, version control mechanisms are hardcoded within the environment which provide them. Zdonik [108] proposed a more generic strategy to define a version control mechanism in the framework of object-oriented approach. Objects 37 are defined by their types and each type will define a version control mechanism. As a result, different types could provide different version control mechanisms and all the objects of that types will subject to the specific version control mechanism defined by the types. 2 .3 .2 . Softw are E ngineering D atabases Software objects involved in software development are highly structured and are highly inter-related to each other. The inadequacies of the file system and existing database systems have been pointed out [72]. Quite a few attempts to use database systems instead of, or in addition to, the file system have been made. These ap proaches can be divided into two groups. Systems in the first category focus on the structured representation of the internal structure of software objects using databases. In this case, the granularity of infor mation stored in databases is very small. OMEGA [61] uses the relational database system INGRES [92] to store information of source programs in a structured form. The information obtained by parsing a source program will be stored in INGRES, and later users are allowed to interact with the database to obtain the information about the program. The C Information Abstractor [22] takes the similar approach. The second approach, which I am mostly interested in here, focuses on represent ing relationship among objects rather than their internal structure, such as version and configuration relationship. Early attempts have been made by augmenting the expressive power of the file system. SVCE, System Version Control Environment 38 [52], Cedar [55], and SAGA [26] put a new user interface on the top of the file system to manage the complex relations and structure of the software objects. NuMIL [71] and DSEE [57] still keep the documentation in the file system and use a database system as a manager of the administrative information, such as attributes and relations associated with them. NuMIL uses INGRES, while DSEE uses D3M. More recent and advanced approaches start with designing their own data model and database systems to manage inter-related software objects. PCTE [34] [18] is based upon the Entity-Relationship model [21]. In PCTE, data is held as objects in an object management system (OMS) which is both a filestore and a database at the same time. Objects in the OMS are typed. Object types form a type hierarchy with a subtype inheriting the attributes and links defined in the supertype. A relationship is defined as a bidirectional link between two objects. Both objects and links may have attributes. Attributes are also typed, such as integer, boolean, date or string. DAMOKLES [30] also uses the Entity-Relationship approach [21] to represent the relationship among software objects. DAMOKLES extends it by allowing compos ite objects and objects with multiple versions. DAMOKLES' data definition lan guage provides a language construct which defines objects with versions. As a re sult, objects defined by the language construct subject to the version control mech anism hardcoded within the DAMOKLES database. Cactis [47] focuses on the ability to manage derived attributes of software objects. The value of a derived attribute is computed based upon the value of attributes in the 39 same object or some other objects. The values of attributes flow from objects to other objects through relationships defined between them. Cactis fundamentally uses the Entity-Relationship approach to define objects and relationship among them, but is considered as an object-oriented database in the sense that it supports typed objects, and allow objects to have local behavior in the form of procedures which define the rules for updating the values of derived attributes. Cactis empha sizes a space and time efficiency of automatic updating of values in response to changes to the values upon which they depend. APPL/A [43] was one of the early clients of Cactis which uses Cactis to manage relationships. / 2.4. Object-Oriented Programming Languages 2.4.1. Objects Historically, the notion of objects was originated from the study of the abstract data types. Languages such as Simula [29], Clu [62], and Ada [13], which are some times considered predecessors of object-oriented languages, allow a user to define types that behave in nearly the same way as built-in types. Such a type is often called an abstract data type, because it refers to a set of objects which is defined by an implementation independent specification. Objects are created as instances of abstract data types. Each object is allowed to keep its internal state. The internal state may vary from time to time, but the identity of the object remains unchanged even if the internal state is changed. The internal state is invisible from the outside of the object. 40 Instead, a set of operations is provided to manipulate the internal state. The abstract data type determines the possible internal states of its instances and their operations. An abstract data type is usually implemented by a collection of local variables, called instance variables and a collection of procedures, called methods, to manipu late the instance variables. The internal state of the object is determined by the ob jects which are stored in the instance variables. Thus, an object can be considered as a collection of instance variables associated with a collection of methods. The power of abstraction is one of the main features of object-oriented program ming languages. We can define our own data types and operations on objects of these types, and we can write a program in terms of objects and operations of these data types which we have defined. When we use a programming language which is lacking of the abstraction capability, we must write a program in terms of objects belonging to the small number of data types which are predefined by the language. Then, obviously, the program will become complex and hard to read. In other words, object-oriented languages allow us to increase our alphabet and vocabulary which can be used to write programs, while conventional programming languages provide only a fixed alphabet. When we talk about object-oriented programming languages, there are several addi tional mechanisms to be considered: (1) Object Creation (2) Object Classification 41 (3) Information Sharing In the following sections, I will discuss these issues. 2.4.2. Object Creation - Instantiation and Prototyping As far as object creation mechanism is concerned, the existing object-oriented pro gramming languages can be divided into two groups. The first group includes Smalltalk [40], Flavors [67], C++ [94], which are based upon the instantiation approach. In these languages, every object is created from a special kind of object, called a class. Objects which are created from a class are called instances of the class. A class is associated with an abstract and generic tem plate of objects. Instances of a class will be created from the abstract template asso ciated with the class. For instance, in Figure 2.8, three objects Tom, Bob, and John are created from class student. The student class is associated with a template specifying that the instances will include two instance variables name and major. As a result, each instance has its own instance variables name and major according to the template. When an object is instantiated, the internal data structure and the behavior of the object are determined by the class of the object But the initialization of the object is left to the creator of the object. The value of object is usually determined by argu ments supplied to the constructor of the object. In other words, objects are consid ered to be a "frame" or a "slot" which can be filled by a value in a fixed range. 42 instance Tom class student template name major name major (john ( math} name major instance John Figure 2.8: Instantiation instance Bob oo Q math} name major Classes will determine this frame structure of objects. An instantiation creates a "frame" or a "slot," but the inside of the "frame" or "slot" is left to be blank. The second group includes Actor [3], Director, Orbit, Object-Lisp [32], Self, KRS, ThingLab [15]. They are based on the prototyping approach [16]. The prototyping approach removes the difference of classes and instances. New objects can be pro duced from other existing objects, called prototypes, by specifying differences against the prototype. Any object can be a prototype. Thus we do not need classes any more. For instance, as Figure 2.9 illustrates, object Tom is a prototype, and object John is created from object Tom. Object John has one additional instance variable degree in addition to the two variables in object Tom. Once John is cre ated, John can serve as a prototype. Then, Bob is created from John. In prototypical object approach, new objects are created based upon its prototype object, without defining a class as a template. As a result, both the "frames" and 43 I i 1 prototype Tom object John object Bob ( T o m ) ( m a th ) name major ( S ) fi^ ) name major degree r^(m 5h) name major degree Figure 2.9: Prototyping their values of a new object are determined by the prototype on which the object is based. Also notice that in class/instance based languages, classes are created using a proto typing like mechanism. Each class is a subclass of its superclass, and the class is defined by specifying the difference against the superclass, rather than instantiating some template. In addition to these models, several hybrid models of instantiation and prototyping, including [91], have been proposed. Stein introduced a model [91] in which objects can be created by either instantiation or prototyping. Objects created from classes can serve as prototypes and new objects can be created based upon these proto types. Furthermore, prototypes can be promoted to classes when necessary. 2.4.3. Object Classification - Classes and Prototypes The second issue to be considered here is the concept for object organization [104]. Classes and prototypes are two models for object organization. The model of classes and instances leads to a strict categorization of objects. In this model, every object is supposed to be an instance of a unique class, and all the in 44 stances of a single class have the same structure. When we create a new object, first we need to create a new class, and second we create a new object by instantiating the class. We cannot create objects directly. As a result, every object is strictly cate gorized by its class, and the categorization must be done before programming. On the other hand, the model of prototypes does not require any categorization of objects in advance. Objects are created from similar objects just by specifying dif ferences. The prototype model uses similarity rather than classification. Strict categorization has advantages and disadvantages. First, proper categorization helps to understand the problem domain at hand and, as a result, will result in more modular and better designed programs. Second, categorization enables type-check ing by language processors. Type-checking will decrease run-time type errors and will make programs more efficient, that is, enable code optimization. C++ is a good example which provides a strong type-checking mechanism. Unfortunately it is not true that all the languages which take the class-instance approach are type-check able. For instance, Smalltalk is not type-checkable, because variables in Smalltalk are not typed. Several attempts to make Smalltalk type-checkable have been made. These extensions can be seen in [97] [17] [50]. On the other hand, one of the biggest disadvantages of strict categorization is that categorization cannot always be known in advance. This is especially true in areas in which programming itself is a part of experimentation in order to learn about the 45 problem domain. In these cases, programming in a language with strict categoriza tion will make life difficult. As a consequence, both principles may be useful in a language, and a language which supports both of them might be desirable. Such a hybrid language might be used in the following way. In the early stages of programming, when the problem domain is still unclear, prototyping will be used, and in later stages, when the problem domain becomes clear, the program shifts to a class-instance organization. 2.4.4. Information Sharing - Inheritance and Delegation As far as information sharing mechanism is concerned, two different implementa tions are conceivable: inheritance and delegation [60]. In the inheritance model, all the shared information, such as instance variables, methods, and external interface of objects, is copied among objects, while in the delegation model requests to shared information are forwarded to the objects which own the information and processed there, rather than copying the shared informa tion. Delegation transfers the control while inheritance moves the data. As a result, objects in inheritance model tend to be fat because they carry locally defined in stance variables and methods in addition to inherited instance variables and meth ods. On the other hand, objects in delegation model tend to be thin, because they carry only the locally defined instance variables and methods. 46 object Tom object John object Tom object John name name major major request Tom name name request major (a) Inheritance (b) Delegation Figure 2.10: Inheritance and Delegation Figure 2.10 illustrates the difference between inheritance and delegation. Consider the case when we create a new object John which is supposed to share the informa tion with an existing object Tom. Assume object Tom has two instance variables: name and major. In the inheritance model, object John will get its own copies of these instance variables. In Figure 2.10 (a), arrows indicate the flow of informa tion. On the other hand, in the delegation model, John will not get its own copies of these instance variables. Instead, requests to access these variables will be for warded to object Tom. In Figure 2.10 (b), arrows indicate the flow of requests. Most of the class/instance based languages such as Smalltalk and Flavors take in heritance approach. But inheritance is not the inherent mechanism of class/instance based languages. Some prototypical object systems such as Object-Lisp and Self provide inheritance like mechanisms [20][70]. In Object-Lisp, each object is in a binding environment consisting of nested frames. The innermost frame contains the I object's own bindings. The next frame contains the bindings of the object's proto- j 47 type. The next frame contains the bindings of the prototype's prototype, and so on. The outer most frame contains the bindings for global variables. Whenever a look up to an instance variable occurs, the frames are searched from innermost to outer most. 2.4.5. Encapsulated Inheritance One of the key features of object oriented languages is encapsulation. Encapsulation gives class designers freedom to change the internal representation of the class without changing the external interface, the way of being used, of the class, be cause users of objects of the class are allowed to access objects of the class only through the operation provided by the class. But we must notice that inheritance and encapsulation are two opposing techniques [89] [90], although they are the most important and powerful techniques in object- oriented programming. Encapsulation hides the internal details of objects by pre venting an object from being manipulated except via its defined operations, while inheritance exposes the internal details of objects to other objects by allowing two or more objects to share a single implementation. Most of object-oriented pro gramming languages, such as Smalltalk, Flavors, allow a class to access inherited instance variables from its superclass. Members hidden from the outside can be made accessible from the outside of the class by simply defining a subclass of the class. Thus, the designer of the superclass cannot have the freedom to change its implementation. i i I 48 One approach to fix this problem is called encapsulated inheritance [89]. Several languages, which include C++ [94], CommomObjects [88], and Trellis/Owl [87], take this approach. In the encapsulated inheritance model, where access to inherited instance variables is needed, it should be provided in the form of operations. A class inherits instance variables and methods from its superclasses, but methods in the class are not allowed to directly access the instance variables inherited from the superclasses. The methods must access the instance variables which are inherited from the superclass via methods which are also inherited from the superclass. Thus, the instance variables and methods accessing them are inherited together, and cannot be separated. This approach preserves the encapsulation principle, and still allows inheritance. Encapsulated inheritance mechanism in C++ will be discussed in the later section of this chapter. 2.4.6. Unification The notion of unification was introduced by Smalltalk-80 in [40] which claims that classes are also objects. Since a class is an object, it is an instance of another class. Classes whose instances are themselves classes are called metaclasses. There exist several languages, including Smalltalk-80 [40], ObjVlisp [25], and Common Lisp Object System [10], which support metaclasses. Each class in Smalltalk-80 is associated with its own metaclass. A metaclass will define the internal data structure and the behavior of the associated class as its in stance. But a metaclass does not serve as a generic template to instantiate multiple 49 ! classes. Rather, each class is uniquely associated with its own metaclass, and the internal structure and the behavior of the class is uniquely determined by the asso ciated metaclass. There exist one-to-one correspondence between classes and meta classes, and it is not allowed to associated two or more classes with a single meta class. Metaclasses in Smalltalk-80 are not first class objects. Whenever a new class is de fined, its associated metaclass is automatically defined without explicit declaration. Smalltalk-80 does not provide any language construct to define a metaclass inde pendently. Each metaclass is defined as a part of the class which the metaclass is associated with. Below is a sample definition of a metaclass in Smalltalk-80 which defines circles in a two dimensional space. class name Circle Superclass Object instance variable names originX originY radius class variable names ScaleFactor class methods instance creation new I newCircle | newCircle <— super new. newCircle radius: Ox: 0 y: 0. T newCircle radius: r x: x y: y I newCircle | newCircle <— super new. newCircle radius: r x: x y: y. T newCircle class initialization scaleFactor: newScaleFactor ScaleFactor <— newScaleFactor instance methods inquiries 50 area I actualRadius | actualRadius <— radius * ScaleFactor. T actualRadius * actualRadius * Float pi private radius: r x: x y: y originX <— x . originY <— y. radius <— r A class definition may include a part entitled "class methods” which includes the definition of methods added by the metaclass, as well as a part entitled "class vari able names" which includes the list of instance variables added by the metaclass. Instance variables and methods of metaclasses are called class variables and class methods respectively. In the example above, one class variable scaleFactor and three class methods new, radius:, and scaleFactor: are added by the metaclass of the class Circle. Notice that metaclasses in Smalltalk-80 are not allowed to have their own names. Metaclasses are identified by sending a unary message class to the corresponding class. For instance, the metaclass of a class Circle will be identified by Circle class. Like other classes, each metaclass is associated with its superclass and inherits from the superclass. But metaclasses are not allowed to designate their own superclasses. The metaclass of the superclass of a class is automatically designated as the super class of the metaclass of the class. As a result, metaclasses and classes form parallel superclass hierarchies as illustrated in Figure 2.11. 51 superclass superclass superclass of metaclass of metaclass of metaclass of superclass superclass superclass class Object metaclass Sphere class class Sphere metaclass Class class Circle metaclass Circle class Figure 2.11: Parallel Class Hierarchy in Smalltalk-80 In this case, the superclass of the metaclass Circle class is the metaclass of the class Object. The class Object is the most fundamental class in Smalltalk-80. The class Object is, directly or indirectly, the superclass of all the classes in Smalltalk- 80. The class object determines the fundamental nature of all objects. The meta class of Object is called Class. All metaclasses in Smalltalk-80 are subclasses of the metaclass Class. The metaclass Class determines the fundamental behavior of all classes, including the generic new operation which creates a new instance of classes. Each metaclass will inherit this new operation, and may refine it so that the instances are properly initialized. In the example above, two instance creation operations: new and radius: are de fined by the metaclass Circle class. The new operation, which does not take any argument, first invokes the new operation inherited from the superclass Class in j order to obtain a new instance, and the new instance is stored in a temporary vari- i j able newCircle : I I newCircle <— super new. 52 Notice that newCircle is not initialized at this point The initialization is performed in the next step : newCircle radius: Ox: 0 y: 0. The initialization of newCircle is performed by sending it a message. The method for instance initialization is defined in the instance methods part of the class Circle. This is because, class methods do not have access to the instance vari ables. And finally, new returns the properly initialized instance : T newCircle The instance creation operation radius: which takes three arguments is similarly implemented. Three arguments to radius: specify the radius and the origin of the circle to be created. In order to understand the subclass hierarchy of metaclasses, consider the example below which shows class sphere. The class Sphere is a subclass of the class Circle which I defined above. class name Sphere Superclass Circle instance variable names originZ class variable names class methods instance creation new I newSphere | newSphere e- super new. newSphere originZ: 0. T newSphere radius: r x: x y: y z: z 53 I newSphere I newSphere <— super radius: r x: x y: y. newSphere originZ: z. T newSphere instance methods inquiries volume I actualRadius | actualRadius <— radius * ScaleFactor. T 4/3*actualRadius*actualRadius*actualRadius*Float pi private ociginZ: z originZ <— z. The class Sphere describes spheres in a three dimensional space. In the instance creation method new, a new instance is created by: newSphere f-super new. But notice that the new operation is inherited from the metaclass Circle class which is the superclass of the metaclass Sphere class. As a result, the inherited instance variables, such as originx and originY, are already initialized by the in herited new operation. So we just need to initialize the local instance variable originZ by: newSphere originZ: 0. Further restriction of metaclasses in Smalltalk-80 is that Smalltalk-80 allows only a limited level of metaclass hierarchy. In Smalltalk-80, every metaclass is considered to be an instance of the special class Metaclass. Furthermore, Metaclass is an instance of itself, because it is also a metaclass. As a result, only two levels of metaclass hierarchy are allowed. Figure 2.12 illustrates a sample metaclass hierar chy in Smalltalk-80 including the class Circle. 54 metaclass of jnetaclass of metaclass of superclass superclass superclass metaclass of metaclass of metaclass of superclass superclass class Circle class Object metaclass Metaclass metaclass Circle class class Sphere metaclass Class metaclass Sphere class Figure 2.12: Metaclass Hierarchy in Smalltalk-80 2.5. C++ : A Quick Review 2.5.1. Class In [94] Stroustrup summarized the major features of C++ added beyond the C lan guage. Such features include: (1) argument type checking (2) inline functions (3) scoped and typed constants (4) functions taking varying number of arguments (5) classes Among the new features, the most significant improvement of C++ beyond its pre decessor is the support of classes. Classes are user-defined types. Programmers can define their own types that can be used in a similar way as built-in types. Mathematical types such as rational and complex, or primitive and frequently used 55 data structures such as stack and queue are common examples. Below is a defini tion of class stack which implements stacks of integers. class stack { int element[100]; int top; // index to top of stack public: stack() { top = 0; . }; // constructor int push(int newElement); // push a new element int pop(int *topElement); // pop from the top } A class in C++ is essentially a record type which is associated with a set of opera tions allowed on the record type. A class consists of several members and opera tions which access the members. Operations available on a class are sometimes called member functions. The stack class has two members: elements and top, and three operations: stack (), push (), and pop (). The p u b l i c : declaration in a class definition divides the members and the operations into two groups. Members and operations which appear after the p u b l i c : declaration are called public, while those appear before the p u b l i c : declaration are called private. If no p u b l i c : declaration appears, all members and operations are considered private. Public members and operations are accessible from both the outside and the inside of the class, while private members and operations are only accessible from the in side of the class. In this case, element and top are private members and they can be accessed from only the inside of the stack. On the other hand, stack (), push (), and pop () are public operations, and they can be accessed from both the outside and the inside of the class. Users of stack who reside in the outside of the 56 stack class cannot access the private members. They are only allowed to access the private members through public operations defined by the class. The stack class implements a stack structure using an array of integers: int element[100]; element is a linear array of integers which holds integers in the stack, top keeps the index to the integer on the top of the stack. Since element and top are private, they are not accessible from the outside of the class. Accessing to these members are carried out through public operations defined on the class, such as pop () and push (). The operation which has the same name as the class is called the constructor of the class. The role of the constructor is the initialization of instances of the class, stack () will initialize new stacks by assigning zero (0) to the member top, so that every stack is initially empty, push () and pop () implement the standard stack op erations. push () inserts a new integer to the top of the stack, and pop () removes the integer from the top of the stack and returns it. The implementation of operations can be specified either in the class or the outside of the class. The implementation of push () and'pop () can be seen below, push () and pop () are implemented in the outside of the class definition, while the con structor stack () is directly implemented in the class. int stack::push(int newElement) { if(top>=100) 57 return(-1); else { element[top++] = newElement; return(0) ; } } int stack::pop(int *topElement) { i£(top<=0) return(-1); else { *topElement = element[— top]; return(0) ; } } The name of functions which are implemented on the outside of classes are prefixed by a class name separated by double colons This specifies that the functions are operations of the specified class, push () and pop () are prefixed by stack, such as stack: : push and stack: : pop, because they are operations of the stack class. Implementation of push () and pop () is straightforward, push () takes one argu ment newElement which is an integer to be placed on the top of the stack, and push () returns an int: int stack::push(int newElement) When the stack is full, that is to p is equal or greater than 1 00, no action is taken, pop () simply returns -1. When the stack is not full, the new integer is placed on the top of the stack and to p , which is the index to the element on the top of the stack, is incremented. element[top++] = newElement; pop () also takes one argument, and it returns an int: int stack::pop(int *topElement) 58 When the stack is empty, push () simply returns -1. When the stack is not empty, pop () first decrement the value of top, and removes the integer from the top of the stack and places it to the argument topElement: *topElement = element[— top]; Below is a sample main program which involves the class stack. main () { int x; stack myStack; 11... if(myStack.push(50)) printf("stack overflow\n"); 11... if(myStack.pop(&x)) printf("stack underflow\n"); else printf("%d\n", x) ; I / ... } In the beginning, x and myStack , a new int and a new stack, are declared. Operations of objects are referred using the dot notation, my stack, pu s h (5 0) will push a new int 50 to the top of myStack. If myStack is full, an error message will be displayed, pop () will be accessed in a similar way. myStack (&x) will receive an int from the top of myStack into x. If myStack is already empty, an error mes sage will be displayed. 59 2.5.2. Encapsulated Inheritance in C++ A class can be a subclass of another class. Subclasses inherit members and opera tions from superclasses. class streetAddress { string street; int apartment; public: // ... void print () { printf("%s", street); if(apartment != 0) printf("# %d\n", apartment); else printf("\n"); } } class address : public streetAddress { string city; string state; int z ip; public: // ... void print () { streetAddress::print() ; printf("%s, %s %d\n", city, state, zip); } } Class streetAddress consists of two private members: street and apartment. It also provides an operation print () which outputs the street address and the apart ment number if the apartment number is supplied. Class address is a subclass of the streetAddress class. It has three locally de fined members city, state, and zip. As a subclass of the streetAddress class, the address class inherits all the members of streetAddress. As a result, 60 address consists of five members: street, apartment, city, state, and zip. Operations are also inherited. In this case, although the print () function is inher ited from streetAddress, it is overwritten by a locally defined function with the same name. The way of inheritance in C++, called encapsulated inheritance, is a little bit differ ent from other object oriented programming languages as I mentioned earlier. In C++, each subclass is allowed to access only the public members of its superclass. In other words, access to the private members of the superclass must be carried out via public operations provided by the superclass. In the example above, although address inherits street and apartment from streetAddress, address cannot directly access these members because they are private members of streetAddress. Consequently, print () function of address cannot directly print streetAddress and apartment. Instead, it invokes the print () function of the superclass streetAddress, referred as streetAddress : : print () , in order to print street and apartment. 2.5.3. Friend In some cases, it is desirable to be able to override the encapsulation and encapsu lated inheritance. Functions which involves two or more classes might be required. If all the involved classes totally hide their members, such operations cannot be de fined [75]. Or direct access to the members is required to improve the runtime effi ciency or some other reasons. 61 C++ provides a mechanism, called friend, to override the encapsulation and encap sulated inheritance. A class may designate other classes and/or functions to be its friend classes and!ox friend functions. Friend classes and friend functions are al lowed to directly access all the members and operations even if they are private in the class which designate them to be friends. A class may designate its subclasses to be friend classes. Then the subclass can directly access the private members of the class. Below shows classes which represents matrices in various sizes. matrix3by4 rep resents 3 by 4 matrices, matrix4by2 represents 4 by 2 matrices, and matrix3by2 represents 3 by 2 matrices. Consider the multiplication of 3 by 4 matrices and 4 by 2 matrices. The multiplication of a 3 by 4 matrix and a 4 by 2 matrix will result in a 3 by 2 matrix. The friend function * denotes the multiplication of 3 by 4 matrices and 4 by 2 matrices. class matrix3by4 { float x [3][4]; friend matrix3by4 operator+(matrix3by4, matrix3by4); friend matrix3by4 operator-(matrix3by4, matrix3by4); //_... friend matrix3by2 operator*(matrix3by4, matrix4by2); } ; class matrix4by2 { float x [4][2]; friend matrix4by2 operator+(matrix4by2, matrix4by2); friend matrix4by2 operator-(matrix4by2, matrix4by2); II ... friend matrix3by2 operator*(matrix3by4, matrix4by2 ); }; class matrix3by2 { float x [3][2]; 62 friend matrix3by2 operator+(matrix3by2, matrix3by2); friend matrix3by2 operator-(matrix3by2, matrix3by2); 11 ... friend matrix3by2 operator*(matrix3by4, matrix4by2 ); } ; matrix3by2 operator*(matrix3by4 m, matrix4by2 n) { int i, j, k; matrix3by2 r; for(i=0; i<3; i++) for(j=0; j<2; j++) r.x[i][j] = 0; for(k=0; k<4; k++) r.x[i][j] += m.x[i][k] *n.x[k][j]; return r; } The multiplication * takes a matrix3by4 and a matrix4by2 as its arguments, and returns a matrix3by2. The multiplication * is designated as a friend of all the classes involved in the operation, but it is not a member function of any of these three classes. If it is not a friend of matrix3by 4, matrix4by 2, or matrix3by 2, it cannot access the elements of these matrices. In such a case, the operation cannot be defined unless alternate ways of accessing private members of the classes are pro vided. 2.5.4. C oncurrency in C++ Concurrent C [36] is an extension of the C programming language [53][54] that supports parallel programming facilities. Concurrent C also accepts class definitions in C++ as a compile time option. The resulting language is called Concurrent C++ [37]. Originally, Concurrent C and C++ were developed separately. As a result, two different extensions of the C languages were produced. Concurrent C supports parallel programming, while C++ provides data abstraction capabilities based upon 63 Ada Concurrent C task process rendezvous transaction entry declaration transaction declaration entry call transaction call access types pointers records structures Figure 2.13: Comparison of Concurrent C and Ada[38] the object-oriented approach. But, since data abstraction capabilities are also useful for writing parallel programs, the developers of Concurrent C decided to integrate C++ into Concurrent C. As a result, Concurrent C++, which provides both parallel programming facilities and data abstraction capabilities, was produced. Concurrency in Concurrent C is based upon the rendezvous concept [38]. Unit of concurrency in Concurrent C is called a process. Two processes interact by means of the following three steps: (i) synchronization, (ii) information exchange , and fi nally (iii) continuation of their individual activities. In the rendezvous model, the concepts of synchronization and communication between processes are unified. A process can call a transaction of another process to achieve rendezvous. The calling process sends information to the called process using transaction arguments, and gets information back as the transaction result like function invocation. Ada [1] is one of the most popular programming languages which are based upon the ren dezvous model. As a result, the parallel programming facilities in Concurrent C are veiy comparable with those of Ada. The comparison of Ada and Concurrent C++ is given in the Figure 2.13. 64 Below is a sample producer-consumer problem written in Concurrent C taken from [38]. The producer process repeatedly (i) reads a character from the standard input; and (ii) sends it to the consumer process. On the other hand, the produce process repeatedly (i) receives the character sent by the producer process; and (ii) converts it to the upper case; and (iii) writes the converted character to the standard output. /* specification of consumer process */ process spec consumer() { trans void send(int c); } ; /* specification of producer process */ process spec producer) process consumer cons); /* Implementation of consumer process */ process body consumer() { int ch; f o r ( ; ; ) { accept send(c) // accept transaction call { ch = c; } if(ch == EOF) break; islower(ch) ? putchar(toupper(ch)) : putchar(ch); } } /* Implementation of producer process */ process body producer(cons) { int c ; while((c = getchar() ) != EOF) cons.send(c); // transaction call cons.send(EOF); } main() { process consumer q; q = create consumer(); create producer(q) ; 1 65 Like Ada, each process is defined by a type (or specification ) and a body (or im plementation). In the specification part of the consumer process, a transaction send () which takes a single argument is declared, while the producer process does not provide any transaction. In the implementation part, the producer process invokes the transaction send () of the consumer process to send a character to the consumer process. The called process consumer accepts the transaction call with the accept statement. Concurrent C also supports the select statement, like Ada. The select state ment allows a process to wait for several alternate events: such as an accept statement, a delay statement, and a terminate statement. Each alternative can be associated with a guard, which is a Boolean expression. An alternative is said to be open if the associated guard is satisfied. The select statement chooses one of the open alternatives non-deterministically at a time, and executes it. Below is an implementation of the bounded-buffer problem in Concurrent C, taken from [35]. The buffer process provides two transactions: put () and get (). The bounded buffer, defined by the process buffer, accepts either put () or get () transaction at a time. A put () transaction call can be accepted only when the buffer is not full, while a get () transaction call can be accepted only when the buffer is not empty. When a get () transaction call is accepted, it returns the information to the caller with the t return statement. process spec buffer(int max) { trans void put(int c); 66 trans int get(); } ; process body buffer(max) { int *buf /* circular buffer of size max */ int n = 0; /* number of characters in buffer */ int in = 0; /* index to next empty slot */ int out =0; /* index of next character */ char *malloc(); buf = (int*) malloc(max*sizeof(int)); f o r (;/) select { (n < max): accept put(c) buf[in] = c; in = (in + 1) % max; n++; o r (n > 0) : accept get() treturn buf[out]; out = (out + 1) % max; n — ; o r terminate; } } 67 Chapter 3 Object Process Modeling 3 .1. Object Process Modeling In Chapter 2 ,1 have discussed the two basic process modeling approaches: algo rithmic and behavioral approaches. Both approaches define software processes as active entities, although they use different strategies of representing processes. One of the big deficiencies of these approaches is that it is very hard to accommodate the roles of human being within software processes. Most software processes involve quite a few human interaction, while some of them might be fully automatic. We need to be able to naturally embed the roles of human being in software processes. I consider software processes as objects of human interaction, rather than simply ac tive entities. The software process metamodel, that is the software process modeling strategy, presented in this dissertation is based upon object-oriented programming techniques [12j[23][27][66], and is called object process model. In the object process model, each software process is modeled as an object which encapsulates operations 68 working environment Remote Site U n ° □ □ o o local operations working environment □ 1 ° 0° o working environment □ ^ o local operations resources / programmer a □ □ o □□ o o local operations resources S ' \ OO o o o operations programmer b operations resources programmer resource working environment resource working environment resource Z Local Site Figure 3.1: Object-Oriented Software Development Environment around a collection of resources required to accomplish a specific task. Each soft ware process provides a working environment in which software engineers can ac tually work in order to pursue the designated task by invoking the operations pro vided by the process. At the same time, a software process keeps watching the ac tivities performed by software engineers in the process, so that they always stay on the right track. The object process modeling approach provides a software development environ ment in which multiple software engineers are working concurrently in their own working environments to pursue their own goals, as illustrated in Figure 3.1. Each working environment may consist of a different set of resources and operations de 69 pending on the task to be performed within it. Consequently, the software devel opment environment, as a whole, is a collection of small and heterogeneous work ing environments. Engineers may be either tightly coupled or loosely coupled, that is, all the programmers might be working in a single location, or they might be dis tributed to several locations. All the software engineers are working concurrently and asynchronously within their own working environment, but they are coordinat ing their activities, if necessary, through their processes, that is, working environ ments. The synchronization can be achieved by direct communication between the processes, by using shared resources, and/or by some other methods. Resources manufactured by software processes are also described in terms of ob jects. All resources are typed, and the type of a resource determines the possible operations on the resource. Thus, the software engineers need not worry about im plementation details of operations, such as which compilers need to be used to compile which source programs. Furthermore, resources in the object process modeling environment will inherently provide a set of operations for managing themselves, such as version control [7] and synchronous access control. The ver sion control mechanism keeps the modification history of resources, while the syn chronous access control coordinates the simultaneous access to a single resource by two or more processes. Therefore, software engineers are not required to maintain resources using separate tools, such as SCCS [85]. Figure 3.2 shows a comparison between the object process model and two existing models which I reviewed in Chapter 2 .1 consider the object-oriented approach to be 70 Metamode] Modeling Approach Process Modeling Languages System s Algorithmic Model algorithmic description of software processes Conventional Programming Language Arcadia [98] Behavioral Model behavioral description of software processes Constraint expression formalism Marvel [51] Genesis [84] Object Process Model environmental description of software processes Object-Oriented Programming Language OPM [96] Figure 3.2: Comparison of Process Modeling Approaches superior to other approaches for modeling software processes. This is mainly be cause of the following reasons. First in the object process modeling, the description of software processes is envi ronmental, rather than algorithmic or behavioral. Each software process just pro vides primitive operations required to accomplish a task, and some rules to con strain the execution of these operations. Therefore, process programmers need not to invent the complete algorithm to conduct the process in advance, and the users of the process are given plenty of freedom to execute the primitive operations in their own way. Second, activities performed by human beings is not totally independent from soft ware processes. Software processes provide environments in which human beings can work, and human beings pursue their goals by interacting with software pro 71 cesses. Therefore, the roles of cooperating human beings can be naturally embed ded within each software process. Furthermore, the degree of human interaction with software processes can vary de pending on the nature of the software processes. Some software process may heav ily interact with human being, while other software processes may be highly auto mated without human interaction. 3.2. Galois : A Process Programming Language This section will discuss (i) why I have chosen a programming language for im plementing process models and (ii) why the language is object-oriented ? Software process models can be described using several notations. English is one of the no tations to describe process models. Graphical notations were frequently used to de scribe traditional software process models. We have already seen several examples of graphical description of traditional software process models in Chapter 2. A Pert chart is another example of a notation for describing software process models. A Pert chart describes the predecessor and successor relationship among tasks, but in this case, only the superficial aspect of the software process, such as starting date and completion date, can be described in terms of this pictorial representation. The internal information on the process, such as how to carry out each process, cannot be described. 72 In the object process modeling, an programming language Galois is used for the uniform description of software process models. This is because programming lan guages are superior to other process modeling languages in the following aspects: (i) a programming language enables far more complete and rigorous description of software processes compared with other notations for describing software processes. Thus it is a more suitable medium for the communication purpose. (ii) both the act of creating the descriptions and the act of reading and interpret ing process models should be comfortable for software professionals. In this respect, a programming language is a most suitable medium. (iii) a software process model written in a programming language can be di rectly executed by a computer. Therefore, a software process model is neither a static entity, nor a static description, anymore. An executing software pro cess model can interact with software engineers and can provide support to them. Thus this leads to an approach to designing working environments in which software engineers can actually work. The third point is the clearest advantage of programming languages against other notations. It is clear that an environment, which has embedded knowledge of the software process model along which an on-going project is carried out, can provide superior support for the project. In traditional approaches, the knowledge of a spe cific software process model is hardcoded within an environment, and the environ ment provides support based on that knowledge. Thus a particular environment as sumes a specific process model. 73 In the object process modeling approach, software engineers are allowed to pro gram their environments. Each environment is a simple virtual machine which exe cutes process programs. In this approach, software objects are thought of as in stances of types. Software tools are thought of as operations which transform soft ware objects. Humans are accorded certain well-defined roles in creating and trans forming objects, too. Executing process programs will define the behavior of the environment and give the knowledge on supporting software personnel working in the environment. The second question to be answered here is why Galois is an object-oriented lan guage. I call Galois an object-oriented process programming language because it has the following features. First, Galois provides an encapsulation mechanism [89] which allows us to define a new object by collecting operations around data. This allows us to define a working environment which collects software manufacturing operations around software objects to be produced. It also provides data hiding mechanism which protects data within an object from being accessed by other objects. Second, Galois provides strong data abstraction capabilities. One of advantages of the process modeling approach is that it allows us to define our own alphabet which we can use to write a new software process. Software development involves quite a few types of objects. Computer files, such as source files and executable files, are 74 typical examples of objects. Further, source files can be divided into quite a few subtypes depending on several characteristics of each file, such as a programming language being used, a language compiler to be used, and/or compilation options to be used, and so on. Further, objects are not limited to computer files. Software per sonnel will be objects which need to be manipulated by software processes and have a complex classification scheme. Some programmer is a full-time worker, while another programmer is a part-time worker. Someone is an experienced soft ware engineer, while another is a novice programmer. Thus it is quite essential that the notation to be used to describe software processes must have a powerful typing system. Object-oriented languages satisfy this requirement. Finally, Galois provides sophisticated information sharing mechanisms. An infor mation sharing mechanism is another important feature of process programming languages. Resources in the software development environment share several items of information, such as behavior and attributes. For example, several source pro grams need to be compiled with the same compilation operation, or several libraries are maintained using the same version control mechanism. Two or more object files may share several attributes because they are compiled from a single source file with different compilation options. Two or more software projects may share sev eral attributes because they are conducted by the same project leader, and so on. Galois provides a more sophisticated information mechanism than inheritance and delegation [60] found in existing object-oriented programming languages. 75 3.3. OPM: A Process Modeling Environment This section is an attempt to give an overview of OPM [96], a process modeling environment which supports the development and the execution of process pro grams written in Galois. By an environment I mean a user interface through which process models are designed, instantiated, and tracked as they execute. OPM itself is written in Galois. The four novel features of OPM that I want to stress here are: (i) the way OPM uses conventional CASE tool graphical notations [83][68] [49] for describing process models, and (ii) the way OPM supports model instantiation and cooperative work among a team of developers, and (iii) the way OPM manipulates resources involved in software processes, in cluding version and configuration management and resource scheduling, and (iv) the way OPM provides a working environment in which a programmer can work to accomplish his tasks. 3 .3 .1 . Graphical Description of Software Processes In the OPM environment, process models are originally written using a graphical notation, and will be translated into Galois at a later stage. Figure 3.3 contains a de scription of how debugging takes place within some hypothetical organization. A database of bug reports is collected and there is an available pool of programmers to 76 debugging process done ? [ assign bug ] ^ f working j__JQA confirms the) ^ f to programmersI I on bug J I correction I I submit report & code ^ to manager^ write report Figure 3.3: Debugging Process work on them. Each instantiation of debugging process will assign a bug report to a programmer for fixing. When the programmer is done, he submits his fix to the QA (quality assurance) group who confirm that his fix is correct. If not, it is returned for further work. If so, then a report is written and submitted to the manager. From this example I can conclude that the use of a graphical notation is suitable for describing this software process. Rectangles are used to represent resources and ovals are used to represent processes. Arrows indicate process flow and they may carry data. Other examples I have done also indicate that a wide variety of processes can be adequately described by this natural notation. To refine further the debugging example, I assume that when the programmer submits the corrected code to the quality assurance group, a new process is started. That process is shown in Figure 3.4. From this elaboration I conclude that process models may have lower levels which contain process models. Therefore I see that a process model should be thought of as a hierarchical object. 77 QA confirms the correction bug modified testing report source code team f gather all } f £ I materials J I ft assign someone from testing team produce a new executable code devise test cases verify code ) yes or no Figure 3.4: Refinement of QA confirms the correction 3.3.2. Cooperative working Another observation from this example is that there may be several people all fol lowing this process description at the same time. Therefore there need to view the process model as a template and talk about its instantiations. When the template of Figure 3.3 is instantiated I interpret this to mean that a new bug has been assigned to a programmer for repair. Observe that when a single programmer is selected to fix a single bug, that instantiation of the debugging process may give rise to multi ple instantiations of the working on bug subprocess. Therefore I see that when a process is executing, subprocess templates may be instantiated multiple times. The process modeling environment must be capable of monitoring all instances and displaying them in a useful fashion. In Figure 3.5 you see that five instantiations of the debugging process were initiated. One is done and the other four are still active. There are a total of two programmers involved and a variety of other information is available. OPM supports and monitors all of these active instantiations and keeps a history of those that have been completed. From the programmer's point of view, 78 process name attribute: start resource: programmer status resource: bug name result: source modified debugging 1/1/89 ellis done x1 main.c, ask.c debugging 1/11/89 tarry active x2 cen.c, ask.c debugging 1/12/89 ellis active x3 denote.c debugging 1/13/89 larry active x3 main.c, denote.c debugging 1/14/89 ellis active x3 denote.c Figure 3.5: Sample Table Showing How Instantiations are Tracked each active instantiation is shown as an icon on his terminal. By opening each icon, he is placed in a new working environment, similar in concept to a new UNIX shell. This environment may offer him a variety of operations not normally avail able. In addition, active processes may need to communicate with each other, for example if more than one person is involved in fixing a particular bug. 3.3.3. Resource Management One of the key elements of a process is that it manipulates resources. A resource can be: a person, a computer, a manual, a computer file. At this stage I am not in terested with exactly what a process does to the resource. My key area of concern is the management of these resources. A resource has a name and it may have a ca pacity, location, cost, versions, etc. When a process requires a resource, the pro cess requests it from the resource manager. If the resource is busy, the process is delayed. Requests for a resource could normally be handled on a fifo basis, but it is clear that it should be possible to override this with another allocation rule. If a pro cess owns a resource for too long a period, then the resource manager can request its return. The resource manager initially owns all of the data objects. It implements 79 working on bug process sample.o sample sample.c sample.h Link Test Compile Edit Figure 3.6: Working on Bug process a check-out/check-in policy as resources are required by processes. The process designer must specify the attributes and operations on the data objects. 3.3.4. Working Environment In OPM, a process will provide a working environment in which programmers can work to perform designated tasks, rather than prescribing the task and carrying it out along with the prescription. A process allows programmers to: • collect necessary resources • collect necessary activities • specify certain constraint on the execution of activities. A process will also: • navigate activities to be performed by a human • execute activities asked by a human • execute some activities automatically when certain conditions are met 80 Samplel sample.c sample.h m m Reserve Reserve Edit Edit Compile Release Release ' lin k ......... Sam ple==.;::-'....'.:.- Link S R itm ti sample.c sample.h Run Save Rel Reserve Save Rel Save Exe Edit Save Exe Compile Release Figure 3.7: A Sample Process Window As an example, consider the working on bug process introduced above. Figure 3.6 is a pictorial representation of the process. An instantiated process is iconified on programmer's workstation. When a process opens up, it shows all the available re sources and activities upon the resources. Operations defined in the process and re sources are accessible through pull-down menus associated with the window and the resource icons respectively. Figure 3.7 shows a process window of the work ing on bug process. (1) Collecting Resources In current environments, programmers usually establish their working environment by • creating a working directory and copying necessary files into it. • writing their own shell scripts in order to customize tools provided by the en vironment to their needs. • setting up environment variables 81 In OPM, processes will perform these tasks instead of programmers. Programmers are not required to take the physical organization of data store into consideration. Processes can collect any resources into it regardless of the location where they are stored. Programmers in a process can work in the process as if the process is a working directory and all the necessary resources are collected into it. (2) Navigation Each operation can be associated with a precondition which will be defined in Galois. Each operation can be invoked only when the associated preconditions are met. Otherwise, the execution of the operation will cause a runtime error, or might be delayed. For instance, we can associate with a precondition with the Link op eration so that the Link operation can be invoked only after the compilation of the source program has been completed. This will guarantee that the object program obtained from the newest source program is always used in the Link operation. Preconditions have tight relationship with the process window. When the precondi tion of an operation is not met, the operation will be shaded in the menu in the pro cess window. Thus the preconditions will visually navigate the users. In the exam ple above, originally, Edit and Compile operations are shaded, while the Reserve operation is not. This means that the Reserve operation is ready to be executed. When the Reserve operation is executed, the Compile and Edit operation will be come active. 82 (3) Chaining Operations also can be associated with a preprocess. When some preprocess is as sociated with an operation, even if the precondition is not m e t, a run time error will not happen. Instead, the associated preprocess will be executed before the operation is started. Below is an example. The Compile operation is defined to be a prepro cess of the Link operation, while the Link operation is designated as a preprocess of the Run operation. Then, if the Run operation is invoked when both the Compile and the Link operation have not been finished, the Run operation will automatically invoke the Link operation before it starts, and furthermore, the Link operation will invoke the Compile operation before it starts. When the Compile operation com pletes, the Link operation will start and the Run operation will start only after the Link operation finishes. Postprocesses will establish the forward chaining, while preprocesses will establish the backward chaining of processes. For instance, it is possible to designate the Link operation as a postprocess of the Compile operation. Then whenever the Compile operation is finished, the Link operation is automatically started following the completion of the Compile operation. 3.3.5. Working in the OPM Environment An overview of the system architecture of OPM is shown in Figure 3.8.1 conceive of the system being used in the following way. First, a process designer creates the process model. This is done using the hierarchical graphical notation, like data flow 83 workstation P graphical view G E ) process A instantiatioi waiting allocation resource resource resource process manager resource manager process editor process B object base workstation Q Figure 3.8: Overview of OPM diagrams, shown in the examples with support from a specially designed graphical process editor. All process descriptions are kept in the object database. Second, the process designer defines all of the data objects that will be used by this process. For each data object he must define its attributes and operations. Access to these are also through the object database. Third, the process designer must define the semantics of primitive process nodes using the process programming language Galois. When a programmer is ready to start a process he informs OPM and the requested process is instantiated for him. He receives an instance of the process, which can be iconified on his workstation, but remains active even though he may log out. Further instantiations of subprocesses by the same programmer for the same pro cess are tracked by the process manager and are also shown on the programmer's workstation. The resource manager handles requests for resources according to the 84 rules established by the process designer. The execution of an instantiated process on each workstation is carried out by the Galois' run-time system. 85 Chapter 4 The Galois Language 4.1. An Overview of Galois In this chapter, I will present the object-oriented process programming language Galois which I specifically designed for managing software processes and re sources involved in software processes. I have developed the new process pro gramming language Galois because conventional programming languages are inad equate for describing software processes. In particular, to implement process coor dination, software manufacturing, and resource sharing, special programming lan guage features are highly desirable. This section will give an overview of the major innovations invented in Galois which I want to stress in this dissertation. Galois includes the following four major features: « Metaclasses • Derived classes • Concurrency • Rule-based features 86 4.1.1. Metaclasses The first innovation included in Galois is support for metaclasses. As I reviewed in Chapter 2, the notion of unification was introduced by Smalltalk-80 in [40] which claims that classes are also objects. Although Smalltalk claims the unification, metaclasses in Smalltalk have the following limitations which make classes different from objects: (i) metaclasses are not instantiable. Classes are always handcrafted, and are not produced by instantiating their metaclasses. This is one of the critical differences between classes and objects which are created by instantiating their classes; (ii) classes and their metaclasses have one-to-one correspondence, while a class and its instances have one-to-many correspondence. As a result, a metaclass does not serve as a generic template of classes and there is no simple way to create classes with the same structure, while a class serves as a generic template of its in stances and objects with the same structure can be easily produced by instantiating a single class; (iii) metaclasses are not allowed to have their own metaclasses. They must be an instance of a class called Met a class. As a result, only a fixed level of metaclass hierarchy is allowed. Galois removes these restrictions from metaclasses. Metaclasses in Galois are in stantiable, thus, classes can be produced by instantiating a metaclass. As a result, a metaclass and its instance classes have one-to-many correspondence. Furthermore, metaclasses can also be created from their metaclasses. As a result, an arbitrary level of metaclass hierarchy is allowed. Metaclasses in Galois will be discussed in Section 4.4 87 Debugging process metaclass instance instance Debugging' irocess mode! (class) j ^Dcbugging^ process mode] ^ (class) y instance instance Process' Fix Bug C J Process Fix Bug A Process Fix Bug B module X module Y Figure 4.1: Managing Multiple Debugging Processes The notion of metaclasses has been motivated by the necessity of managing multiple software processes. In Galois, process models are defined as classes, and software processes are created by instantiating the process models. Then, each class, as an object, serves as a coordinator or a manager of its instances, and the behavior of the process model is determined by the metaclass of the process model. For instance, assume we are fixing multiple bugs found in some module, say module X. As illus trated in Figure 4.1, given a process model for debugging, multiple processes are instantiated from the process model, say a debugging process for fixing bug A, a debugging process for fixing bug B, and a debugging process for fixing bug C. Then, the debugging process model serves as a manager of these three debugging processes, and keeps track of activities performed in these processes. 88 Since a process model is an instance of its metaclass, its replicas can be easily pro duced by instantiating the metaclass. Then, each replica serves as a manager of its own process instances, while the metaclass serves as a higher-level manager of these process instance managers. The behavior of the metaclass as the higher-level manager is determined by the metaclass of the metaclass. Return to the debugging process example. Usually a software system consists of two or more modules, and debugging processes of these modules are often on-going simultaneously. Then, replicas of the debugging process model are created for these modules, and each replica will serve as a manager of the debugging process for the corresponding module. Furthermore, the metaclass serves as a manager of these debugging pro cess managers for the modules. In this way, processes can be hierarchically orga nized and coordinated. Details of the coordination of multiple processes will be dis cussed in Section 5.2. 4.1.2. Derivation The second innovation included in Galois is support for derivation. As I reviewed in Chapter 2, existing object-oriented languages can be divided into two groups as far as their object creation mechanisms are concerned: instantiation model and pro totyping model. But Galois uses neither of these models directly. Galois provides a third object-creation mechanism called derivation which is a gen eralization of the instantiation model. In the derivation model, although objects be long to their own classes, called derived classes, objects are not created from the 89 Approach Categorization Object Creation Information Sharing Language Derivation Yes (by classes) created from a concrete object by transformation Inheritance and Delegation Galois Instantiation Yes (by classes) created from an abstract description of objects Inheritance Smalltalk [40] C++ [93] Flavors [67] Prototyping No created from a similar object Delegation Actor [3] Inheritance ObjectLisp Figure 4.2: Comparison of Derivation, Instantiation, and Prototyping classes. Derivation allows objects to be created from some other existing objects, called parent objects, by transformational operations. Figure 4.2 summarizes the differences among three object creation mechanisms: derivation, instantiation, and prototyping. Galois is a strictly class/instance based language, like instantiation model. All objects uniquely belong to their classes which determine the internal structure and behavior of the objects. Although objects are not created from their classes, objects cannot be created without defining classes to which the objects are supposed to belong. First, classes are defined, and then instances of the classes are derived from some existing objects. As a consequence, Galois results in a strict categorization of objects, like traditional object-oriented languages with the instantiation model. Information sharing mechanism of Galois is based upon both inheritance and delegation. Inheritance is used for information 90 sharing among classes, while delegation is used for sharing information among objects. Derived classes in Galois will be discussed in Section 4.5 The notion of derivation was inspired from the production process of various kinds of entities and creatures in the world. A typical example of such production pro cesses is the "production" of human beings. Every human being belongs to the type "human." Although the type "human" determines the fundamental nature of each human being, he or she is not bom from that type. Every human being is bom from his or her parents who belong to the same type "human," and many characteristics of children are determined by their parents. Another example is the manufacturing [14] of software objects. Although software objects can be considered to belong to their own classes, they are not created from the classes. They are created from some other existing software objects by trans formational processes. Figure 4.3 illustrates a typical software manufacturing pro cess. An object program, say Tree.obj, which belongs to its class objectProgram is manufactured from a source program, say Tree .c, which be longs to its class sourceProgram by some compilation operation. But the object program is not manufactured from its class. As these examples show, the object production mechanism based upon derivation rather than instantiation resembles the production mechanisms found in the natural world. It is suitable for modeling these processes in the various fields. 91 derived class class class sourceProgram " llf objectProgram instance of compile derivation instance of Tree.obj V , delegation Figure 4.3: Compile Derivation Another important aspect of derivation is that derived objects are allowed to share information with their parent objects using delegation [60]. When a derived object receives a request to perform an operation which cannot be processed by the object itself, the request is forwarded to its parent object and is processed there. This al lows us to consider the parent object as a part of the derived object. For instance, when an object program Tree. ob j receives a request to edit the corresponding source file, the request cannot be processed by the object program itself, but the re quest is forwarded to the parent object Tree. c and processed there. This will free us from tracking up and down the manufacturing sequences during software devel opment. Derived objects are also used to model shared resources. Modeling shared resources using derivation will be discussed in Section 5.3, and modeling software manufacturing using derivation will be discussed in Section 6.2. 4.1.3. Concurrency , Galois provides a concurrent environment in which multiple objects co-exist and perform their tasks simultaneously. Objects in Galois are essentially sequential pro cesses like monitors [19][45]. Each object executes only one operation at a time. 92 Monitors are useful when we need a high level language construct to express con currency. But unfortunately, monitors provide only a fixed scheduling algorithm of their operations. If more complex or customized scheduling algorithm is required, we must implement it using lower level primitives such as condition variables and/or semaphors. Unlike monitors, objects in Galois allow us to define scheduling algorithms of their operations using high level language constructs. In Galois, op erations are typed, and the types (classes) of operations determine the scheduling algorithm of multiple invocation of the operations. Furthermore, Galois makes objects to be active entities. Traditionally objects are passive entities which are activated only when they receive a request to perform their operations from the outside. This is because objects are originated from ab stract data types. On the other hand, there exist several languages which implement active entities, including CSP [46], Ada [1][13], and Concurrent C++ [37][35]. For instance, Ada provides a program construct called a task which is an active en tity. Although Ada provides abstract data types called packages, unfortunately, Ada tasks are not abstract data types. Ada clearly distinguishes passive data, packages, and active entities, tasks. Similarly, Concurrent C++ provides a programming con struct called a process which is an active entity and corresponds to an Ada task. Concurrent C++ was produced by combining the data abstraction capabilities of C++ and the parallel programming capabilities of Concurrent C [36]. As a result, although Concurrent C++ supports both concurrency and data abstraction, it sup ports the data abstraction capabilities and the parallel programming capabilities with different language construct: processes and classes. 93 Programmer Check-out Request > Programmer Check-out . Request Resource Manager Programmer Check-out Request Programmer i . Check-out Request Shared Resource Figure 4.4: Scheduling Shared Resources Galois makes objects active, rather than introducing another program construct for active entities. Objects in Galois can be either passive or active. Like traditional ob jects, objects in Galois will perform their operation upon receiving requests from the outside. In addition, objects in Galois are allowed to keep doing their own tasks even if they do not receive any requests from the outside. The concurrency in Galois will be discussed in Section 4.7. The notion of typed operations has been motivated by the necessity of scheduling the execution of operations on a resource shared by several processes or program mers. Operations which cause modification of a resource should not be executed simultaneously by two or more programmers in order to keep the integrity of the re source. Therefore, if the resource receives multiple requests to execute such opera tions from different programmers simultaneously, as illustrated in Figure 4.4, only one request should be accepted and the other requests need to be delayed. Galois allows the delayed requests to be processed later based upon the scheduling algo 94 rithm associated with each operation. In the OPM environment, the scheduling ca pability is also used to implement the scheduling of processes shared by several programmers. Active objects are used to implement daemon processes which keep watching the change of state. These scheduling issues will be discussed in Section 5.3.4. 4.1.4. Rule-based Features In Galois, member functions of a class can be associated with a precondition and/or a postcondition. Preconditions and postconditions guarantee that the operations are carried out only when the conditions are met. Preconditions and postconditions also implicitly establish interconnection among member functions. The interconnection relationship established by preconditions and postconditions will guarantee that all the functions are carried out in the desired order. Galois also allows us to explicitly specify interconnection among operations. Galois allows us to specify preprocesses and postprocesses of each operation. An opera tion which is designated as a preprocess of some operation must be finished before the operation starts. Preconditions and postconditions are used as a trigger to in voke preprocesses and postprocesses. Thus, chaining of operations can be estab lished. The rule-based features in Galois will be discussed in Section 4.8. In the OPM environment, member functions are implemented as pull-down menus, as we saw in Chapter 3. Preconditions are used to determine when menus need to be active. Preprocesses are used to achieve backward chaining of operations which 95 can be commonly seen in software manufacturing processes such as chaining of compilation and link operations, while postconditions are used to achieve forward chaining of operations. 4.2. Galois and C++ Although major features in Galois, discussed in the previous section, are language independent, their syntax has been chosen to be compatible with C++[93]. This is mainly because of the following reasons. First, in process programming environ ments, we need to develop two kinds of programs: target application programs and process programs to build the target application programs. If these programs need to be written in totally different languages, it will result in unessential complexity and will produce a chaos. To avoid such excessive and unessential complexity, it is desirable to be able to write both application programs and process programs in a single language, or at least similar languages. OPM is going to be implemented on the UNIX system. Most UNIX programmers are familiar with the C language [53] [54], and many software systems have been and are going to be built using the C language. Therefore, I have chosen the C language as the basis of my new pro cess programming language. Second, C++ is the most widely accepted language which is an object-oriented ex tension of C compared with other extensions, such as Objective-C [27] and COP [5]. I decided to use the syntax of C++ in Galois rather than designing Galois from 96 scratch so that programmers familiar with C++ can migrate into the Galois' world from C++. Finally, when a new language is designed as a superset of an existing language, it is desirable that the new features to be added do not interfere with existing language constructs. C++ is suitable for this purpose compared with other bigger and more complex language such as Ada, because C++ provides relatively simple and primi tive language constructs. For instance, since Galois has extensive class hierarchies, it is desirable that the class hierarchies in Galois do not conflict with that of C++. Fortunately, C++ has only the subclass hierarchy, and I could introduce meta classes and derived classes without interference. 4.3. Class 4.3.1. Objects and Classes In Galois, every object is associated with a class. Objects associated with a class are called instances of the class. The object declaration in Galois will associate objects with their classes. For instance, the declaration below specifies that three objects x, y, and z are instances of class int, and two objects Tom and John are instances of Class worker. int x, y, z; worker Tom, John; 97 Each class includes a description of its instances. The description determines the internal data structure as well as the behavior of its instances. The internal structure refers to the member objects included in the objects, and the behavior refers to the operations available on the instances. The description of instances included in a class is unique. The internal structure and the behavior of all the instances of a sin gle class are determined by the single object description. As a result, all the in stances will carry the same internal structure and the behavior. In Galois, classes are also objects in their turns. This is one of the significant dif ferences between C++ and Galois. In C++, classes are simply descriptions of in stances, but not objects. Classes are not allowed to carry their own members and operations. In Galois, since classes themselves are objects, they may have their own members and operations. Each class is associated with its own class which determines the structure and behavior of the class as an object. Classes whose in stances are themselves classes are called metaclasses, while classes which are not metaclasses are called simple classes. Classes which are instances of a metaclass are called instance classes of the metaclass. Each metaclass includes a class description which determines the internal structure and the behavior of its instance classes. One of the typical usages of a class as an object is that each class serves as a collec tion of its instances. Since all the instances of a single class carry the same internal structure and behavior, each class naturally represents the homogeneous collection of all the instances of the class. A class actually denotes a set of all the instances, 98 rather than a simple collection of the instances. This is because, all the instances have different object identities even though their values might be equal. For in stance, in the example above, the values of x and y are two different instances of class i n t with different object identities, but they might have equal values, say 5. Notice that Galois does not provide the universal notion of equality of values of objects. The equality of the values of objects is determined by the equality operator, usually denoted as =, defined by the class of the objects, while every object is given a unique object identity upon its creation. As a set of instances, each class play s the following two roles. The first role is that each class serves as a manager of its instances. Historically, classes have provided operations to create and destroy instances. In addition, a class will keep track of the information on its instances, such as: (i) how many instances are currently created; (ii) whether a specific object belongs to the class or not, (iii) when an instance was created and destroyed, and so forth. The second role of classes as an object is that each class serves as a representative of its instances. Each class enables us to con sider the set of its instances as a single object, and allows us to discuss the charac teristics common to all the instances. Or it may perform operations which involve all the instances. But notice that classes are not simply sets, although classes can serve as sets of their instances. Several object oriented programming languages, including Smalltalk-80 [40] and COP [5], include a set class as one of its primitive classes. A set will be characterized by insert, remove, and membership operations. The mem 99 bership to a set will be established by an execution of the insert operation, and the membership will be deleted when the remove operation is executed. Thus a set al lows members to be inserted and deleted dynamically like classes. But classes differ from sets in the following aspects. First, a class is instantiable. Members of a class can be created by directly instantiat ing the class. Then, the created object is automatically identified as a member of the class. On the other hand, a set is not instantiable. Usually a set is defined as a col lection of objects of another class. Thus, to obtain a member of a set, first we need to instantiate the class which members belong to, and then insert the created object into the set. Second, the membership relationship between a class and its instances is subject to the type-checking capability of the language processor. On the other hand, the membership relationship between a set and its members is established by executing the specific operation. Thus, it is not subject to the type-checking capability of the language processor. In other words, the establishment and the maintenance of the relationship is totally left to the human. 4.3.2. Defining Simple Classes (Part I) Galois provides two ways of defining simple classes, as illustrated in Figure 4.5. The first way is to handcraft a simple class from scratch or as a subclass of an exist ing simple class, as we define classes in C++. The second way is to define a new simple class as an instance of a metaclass, as we define a new object as an instance 100 instances handcrafted simple class ’ " ° 0 l ' - o (a) A Handcrafted Simple Class instances instance simple class metaclass (b) An Instance Class Of A Metaclass Figure 4.5: Defining Simple Classes of a class in C++. In order to define a new simple class in the second way, the metaclass needs to be defined first. So, in this section I will discuss the first way of defining simple classes, and the second way will be discussed in the later section after introducing the way of defining metaclasses. You will see a skeleton of simple classes below. The simple class name is option ally followed by its superclass name separated by a colon (:). And then the descrip tion of the internal structure and the behavior of the simple class, enclosed with braces { }, follows the superclass name. Each simple class is a collection of mem bers and operations working on the members. Operations of simple classes are sometimes called member functions of the simple classes. I will use these two terms interchangeably. class simpleClassName [: superclassName ] { class definition body } ; 101 The first example is person which is a simple class and is defined from scratch. It consists of three members: name, address, and dateOfBirth, as well as a public operation print (). The print () operation is implemented so that it prints out name, address, and dateOfBirth of each person object. class person { string name; string address; date dateOfBirth; public: void print(); person:iprint() { // implementation of print() of person printf("name : %s\n", name); // print name printf("address ; %s\n", address); // print address dateOfBirth.print(); // print date of birth The second example is worker which is also a simple class like person. But worker is defined as a subclass of person. The subclass name worker is followed by the superclass name person separated by a colon (:). The worker class consists of two members: company and title, as well as two public member functions: new Job () and print () . In addition to these members and operations which are lo cally defined, the class worker inherits all the members from the class person, be cause worker is a subclass of person. As a result, the class worker has five mem bers and two locally defined member functions. Although the print () operation is inherited from the superclass person, it is overridden by the print () operation of worker. class worker : public person { string company; // worker's company name string title; // worker's title 102 public: void newJob (string, string); void print(); // override inherited operation }; void worker::newJob(string newCompany, string newTitle) { company = newCompany; title = newTitle; }; void worker::print () { // implementation of print () of worker person::print(); // invoke print() of person to // print out inherited members printf("company : %s\n", company); // print company name printf("title : %s\n", title); // print title } These operations are implemented straightforwardly. The operation newJob () takes two arguments: newCompany and newTitle. The operation newJob () assigns newCompany to the member company and newTitle to the member title respec tively. The print < ) operation first invokes the inherited print () operation to print out the inherited members, and then prints out its local members. As I mentioned earlier, every class needs to be associated with a metaclass. But the simple class definition introduced above does not specify such association between simple classes and their metaclasses. When a simple class is handcrafted, the newly created simple class is assumed to be an instance of a special metaclass called metaclass. Every handcrafted simple classes are instances of metaclass. Furthermore, Galois assumes that every class is a subclass of another class. When a simple class is handcrafted from scratch, that is no superclass name is specified, the simple class is considered a subclass of the predefined simple class called class. The class class itself is a subclass of the class class, and the class class 103 is an instance of the metaclass metaclass. The classes metaclass and class will be discussed in later sections. 4 .3 .3 . Initializing Objects Initialization of instances of a simple class is performed by constructors of the sim ple class. Each simple class may have a constructor. A constructor is an operation which has the same name as the simple class, and is not allowed to return any ob jects. The constructor of a simple class is invoked whenever a new instance of the simple class is created. A simple class may have two or more constructors with dif ferent function prototypes. When two or more constructors are available on a single class, the constructor to be executed will be determined by the arguments supplied to the object creation statement. class person { string name; string address; date dateOfBirth; public: void print(); person(string, string, date); // constructor person::person(string n, string a, date dob) { name = n; address = a; dateOfBirth = dob; class worker : public person { string company; // worker's company name string title; // worker’s title public: void newJob(string, string); void print (); worker(string, string, date, string, string); 104 // constructor }; worker::worker(string n, string a, date dob, string c, string t) : (n, a, dob) /* arguments to constructor person */ { company = c; t itle = t; } The example above shows refinements of the class person and its subclass worker obtained by adding constructors. The constructor person o of the class person takes three arguments: n, a, and dob which determine the value of three members of the instance to be created. The constructor simply assigns the three arguments to the corresponding members. The constructor worker () of the class worker takes five arguments: n, a, dob, c, and t. The first three arguments: n, a, and dob are used to initialize the members inherited from the superclass person, while the remaining two arguments: c and t are used to initialize the locally defined members: company and title respectively. But the constructor of a subclass is not allowed to directly initialize the inherited private members. Instead, it will invoke the constructor of the superclass in order to t initialize the inherited members. Arguments to be supplied to the constructor of the superclass are specified in the definition of the constructor of the subclass: (n, a, dob) In the example above, the first three arguments are directly supplied to the construc tor of the superclass person. You may specify the superclass name with the argu ment list, such as: 105 person(n, a, dob) When a constructor takes some arguments, all the arguments must be supplied when we create objects. person Jack("Jack Horton","765 E 3rd St","1-31-45"); person Dennis("Dennis Weber","1234 W Beverly Dr","5-25-50"); worker Tim("Tim Berens","3025 W 31st St”,"3-10-60", "JPL", "analyst"); worker Kent("Kent Quirk","10 S 104th St","9-30-61", "TRW","programmer"); In the example above, two instances: Jack and Dennis of the class person as well as two instances Tim and Kent of the class worker are created. Since the construc tors of person and worker take three and five arguments respectively, appropriate arguments are supplied. Jack is an instance of the person class whose name is "Jack Horton", his address is "7 65 e 3rd s t" , and his date of birth is " 1-31- 45". Tim is an instance of the worker class whose name is "Tim Berens", his ad dress is "3025 w 3lst s t" , his date of birth is "3-10-60", he is working for a company called " jpl", and his job title is "analyst". 4.4. Metaclass 4 .4 .1 . Defining Metaclasses (Part I) As I mentioned earlier, there are two ways of defining simple classes. The first way, which I already described in the previous section is to handcraft simple classes. The second way, which I promised to discuss later, is to define simple 106 classes as instances of metaclasses. Similarly, there are also two ways of defining metaclasses. The first way, which is illustrated in Figure 4.6 (a), is to handcraft metaclasses, and the second way is to define a metaclass as an instance of another metaclass as illustrates in Figure 4.6 (b). In this section, I will present the way of handcrafting metaclasses. And then in the following sections, I will present the way of creating simple classes and metaclasses as instances of metaclasses. The definition of a metaclass starts with defining or selecting a sample class. Every metaclass is uniquely associated with a class called its sample class. The sample class is essential to make the instance classes of the metaclass to be themselves classes. The sample class determines the structure and the behavior of the instances of the instance classes of the metaclass being defined. All the instance classes of the metaclass are replicas of the sample class. But the sample class itself is not an in stance class of the metaclass. Below is a skeleton of a metaclass. The metaclass name is followed by a sample class name separated by of. A new sample class may be defined in order to define a new metaclass, but it is not necessary. An existing class might be selected as a sample class of the metaclass being defined. And then, the description of the inter nal structure and the behavior of instance classes, enclosed with braces { }, fol lows the sample class name. Like simple classes, each metaclass is a collection of members and operations working on the members. metaclass metaclassName of sampledassName { metaclass definition body } 107 handcrafted instance metaclass ** of instances o (a) A Handcrafted Metaclass handcrafted instance instance simple class metaclass ^ of of instances o (b) An Instance Metaclass of A Handcrafted Metaclass Figure 4.6: Defining Metaclasses Below you will see the definition of metaclass Department, as an example. Each instance of the metaclass Department represents a department of some institution. The metaclass Department has four members: departmentName, chairman, mainof f ice, and numberOf student s which denote the name of the department, the name of the department chairman, the department's main office, and the number of students enrolled in the department respectively. metaclass Department of student { string departmentName; string chairman; string mainOffice; int numberOfStudents; } ; Since Department is a metaclass, instances of the metaclass Department are classes. The instance classes of the metaclass Department have an ability to create 108 objects defined by the sample class student. The definition of the sample class student is given below: class student { float GPA; // student's GPA public: string name; // student name string studentID; // student identification number int unit; // number of units taken student(string n, string id, float g, int u) { // constructor name = n; studentID = id; GPA = g; unit = u; void newGPA(float x) { GPA = x; }; float getGPAO { return GPA; }; }; The student class represents students enrolled in the departments represented by the class Department. The student class defines three public members: name, studentID, and unit, and one private member: gpa. As a result, instances of the instance classes of Department will carry these four members, name, studentID, and gpa denote the name, the identification number, and the GPA of each student object, unit denotes the number of units currently taken by the student. The student class has a constructor student () which takes four arguments. The con structor initializes the four local members based upon the value passed by the argu ments. In addition to the constructor, the class student defines two member func tions: newGPA (), getGPA (). These operations are provided for manipulating the private member: gpa from the outside of the class. getGPA () returns the current 109 value of the member gpa, while newGPA () assigns a new value passed by the ar gument to the member gpa. 4 .4 .2 . Defining Simple Classes (Part II) In C++, although each object is defined as an instance of a class, classes must be handcrafted. In Galois, classes as well as objects can be defined as instances of other classes. Once a metaclass is defined, its instance classes can be defined in the same way as we define objects. In the example below, two simple classes cs and ee are defined as instance classes of the metaclass Department. As classes, cs and ee have an equivalent ability with the sample class student of the metaclass Department. As a result, both cs and ee are classes which define the four members: name, studentID, unit and gpa. Figure 4.7 illustrates the relationship among cs, ee, and student as classes. Department CS, EE; // CS and EE are classes CS Tom("Tom Cook", "5534", 3.5, 8); CS John("John Fullman", "1168", 3.2, 12); CS Bob("Bob Hudson", "8875", 2.8, 6); EE Sam("Sam Jackson", "2268", 2.5, 6); EE George("George Smith", "6318", 2.0, 8); Three objects Tom, John, and Bob are defined to be instances of the class cs, while two objects Sam and George are defined as instances of the class ee. The internal structure and behavior of Tom, John, Bob, Sam, and George are determined by the sample class student Of the metaclass Department. Accordingly, Tom, John, Bob, Sam, and George carry four members: name, studentID, unit and GPA. But 110 metaclass Department • I I I - instance of instance of sample class cs class EE equivalent equivalent name name studentID unit unit GPA GPA instance of Tom John Bob Sam George class of class student | name~~ studentID unit GPA Figure 4.7: Relationship of Instance Classes and Sample class notice that although the internal structure and the behavior of Tom, John, Bob, Sam, and George are determined by the sample class student, they are not instances of the class student. Tom, John, and Bob are instances of the cs class, and sam and George are instance of the ee class. One of the advantages of defining a metaclass is that any number of classes with the same internal structure can be easily produced from a single metaclass. In the ex ample above, two classes: cs and ee which have the same structure are produced from the metaclass Department. If we simply view the class Department as a template of classes, the above definition is equivalent to the following definitions. Here, the same definition must be repeated twice. The use of the metaclass Department allows us to avoid such duplication. I l l class CS { float GPA; // student's GPA public: string name; // student name string studentID; // student identification number int unit; // number of units taken CS (string n, string id, float g, int u) ; // constructor void newGPA(float x) ; float getGPAO; class EE { float GPA; // student's GPA public: string name; // student name string studentID; // student identification number int unit; // number of units taken EE (string n, string id, float g, int u) ; // constructor void newGPA(float x) ; float getGPAO; } ; But notice that instance classes of a metaclass are not simply templates of objects. Instance classes of a metaclass are themselves objects which carry their own mem bers and operations defined by the metaclass. In our example, as Figure 4.8 illus trates, when cs and ee are defined as instances of the metaclass Department, they carry four members: departmentName, chairman, mainOff ice, and numberof students which are defined by the metaclass Department. But when cs and ee are handcrafted as right above, they do not carry these members. Also notice that the sample class student itself is not an instance class of the metaclass Department. Therefore, the class student does not carry these members. 112 metaclass Department | dep'artmentMaine''™! | chairman j I mainOffice I numberOfStudents j" •ill- instance of | computer science] | F.R.Carlson | SAL200 ' ^ jW class cs X instance of , X . sample class of | electrical engineering J.M.Mendel | SAL300 | 700 class student no members !! class E E Figure 4.8: Classes as Objects 4 .4 .3 . Metaclass Hierarchy Every metaclass is also a class, and it can be a sample class of other metaclasses. Therefore, metaclasses whose sample classes are also metaclasses can be defined. In the example below, you will see metaclass University whose sample class is the metaclass Department. metaclass University of Department { string schoolName; string president; date established; } Each instance class of the metaclass Department is at once an object as well as a class. As an object, each instance class of the metaclass university represents some university. Each instance class of the metaclass University carries three 113 ..... class class class class Department class class class class U n iv ersity class class Student N°“ S : 0— 0 A is the sample class of B Figure 4.9: Sample-Class-Of Hierarchy members: schooiName, president, and established denoting the name of the university, the name of the university president, and the established date of the uni versity respectively. As a class, each instance class of the metaclass university has the equivalent capabilities as the sample class Department. Since Department is also a metaclass, we obtained a two-level sample-class-of hierarchy. Student is the sample class of Department, and Department is the sample class of university. Although the sample-class-of hierarchy established by the metaclass university is two level, the sample-class-of hierarchy can be extended to any level by defining a metaclass whose sample class is a existing metaclass. The metaclass University can be a sample class of another metaclass, and again the metaclass which is the sample class of university can be a sample class of another metaclass. Furthermore, a single metaclass can be sample classes of two or more metaclasses. As a result, the sample-class-of hierarchy will form a tree structure as illustrated in Figure 4.9. 114 4 .4 .4 . Defining Metaclasses (Part II) As classes can be created as instances of metaclasses, metaclasses can be created as instances of metaclasses whose sample classes are metaclasses. University USC, UCLA; // USC and UCLA are metaclasses USC UscCS, UscEE; // simple classes UCLA UclaCS, UclaEE; // simple classes UscCS Tom(...), John(...), Bob(...); // objects UclaEE Sam(...), George (...); // objects For instance, in the example above, two metaclasses use and ucla are defined first as instance classes of the metaclass university, use and ucla are metaclasses which carry the same ability as the metaclass Department which is the sample class of university. And then, instance classes UscCS, uscee, udacs, UclaEE are defined. Usccs and Uscee are instances of use, while udacs and UclaEE are in stances of ucla. These are simple classes which have the same ability as the sample class student. Finally, objects Tom, John, Bob are defined as instances of usccs, and objects Sam and George are defined as instances of UclaEE. Figure 4.10 illustrates the instance-of hierarchy established by the declaration above. In addition to the sample-class-of hierarchies, metaclasses whose sample classes are also metaclasses establish multiple-level instance-of hierarchies which are also trees. Every instance-of hierarchy starts with the predefined metaclass metaclass. Every class is directly or indirectly an instance class of metaclass, metaclass itself is an instance of itself. 115 sample class sample instance metaclass of metaclass metaclass University metaclass Department class """of class Student instance of class UscCS John metaclass use class UscEE instance of metaclass UCLA instance of class OclaCS class UclaEE instance Figure 4.10: Instance-of Hierarchy involving university 4 .4 .5 . Initializing Classes Classes are also objects. Therefore, initialization of a class is also performed by a constructor of the metaclass. Below is a refinement of the metaclass Department obtained by adding a constructor. metaclass Department of student { string departmentName; string chairman; string mainOffice; int numberOfStudents; public: Department(string, string, string); } // constructor Department::Department(string dName, string cName, string oName) { = dName; = cName; = oName; =0; // initially no student exists depa rtme nt Name chairman established numberOfStudents 116 The constructor Department () is defined so that it properly initializes four mem bers of each instance class. The constructor takes three arguments: dName, cName, and oName which specify the value of three members: departmentName, chairman, and mainoffice respectively, while the fourth m em ber numberOf students is initialized to 0 (zero) without referencing any arguments indicating that initially no student exists in each department. Department CS("computer science","F.R.Carlson","SAL200"); Department EE("electrical engineering","J.M.Mendel","SAL300"); The above shows a way of initializing the classes cs and ee. cs is initialized so that departmentName is "computer science", chairman is "F . R. Carlson", and mainoffice is "SAL200" respectively, while ee is initialized so that departmentName is "electrical engineering", chairman is "J.M.Mendel", and mainoffice is "SAL300" respectively. The constructor of Department also initializes numberOf Students of cs and ee to zero because no instance of these instance classes initially exists. But, in order to keep track of the correct number of the instances, numberOf students must be in cremented whenever a new instance is created, and numberOf students must be decremented whenever an existing instance is deleted. Then, one question will arise. Which class should implement these increment and decrement operations ? One conceivable answer to this question is that the constructor and the destructor of the sample class student may carry out the increment and decrement operations re spectively. This is reasonable because the constructor is executed whenever new 117 instances are created, while the destructor is executed whenever instances are deleted. But unfortunately, the constructor and the destructor of student have no access to numberOf Students because numberOf Students is a private member of Department. If numberOf students is defined within the sample class student, instead of the metaclass Department, the constructor and the destructor of student can have ac cess to it. But then, every instance of instance classes of Department has its own copy of numberOf students. Therefore, numberOf students no longer keeps the correct number of instances. Generally speaking, metaclasses must be able to describe activities which need to be performed associated with the creation or the deletion of instances of the instance classes. The next section will describe this capability of Galois. 4.4.6. R efining C onstructors Galois allows metaclasses to refine constructors and destructors of their instance classes. By refinement, constructors and destructors can get access to the members of the metaclass, because the refining operations are defined within the metaclass. metaclass Department of student { string departmentName; string chairman; string mainOffice; int numberOfStudents; // number of instances public: Department(string, string, string); student::student(string, string, float, int); 118 // refining constructor student::~student() ; // refining destructor } The example shown above includes the refinement of the constructor and the de structor of the instance classes of Department so that the instance classes can keep the number of their instances. These refining constructor and the destructor are de noted as student: : student () and student: : “Student () respectively. Generally speaking, each refining constructor is denoted by prefixing the refined constructor name with the sample class name: sampleClassName::sampleClassName(argumentList) Each refining constructor is defined as if it refines the constructor of the sample class. But, actually, it does not refine the constructor of the sample class. It refines the constructor of the instance classes of the metaclass. Similarly, each refining destructor is denoted by prefixing the refined destructor name with the sample class name: sampleClassName::-sampleClassName() // refining destructor Each refining destructor is also defined as if it refines the destructor of sample class. But it refines the destructor of the instance classes of the metaclass. 119 Below is the definition of the refining constructor and the destructor of Department. The refining constructor increments numberOfstudents, while the refining destructor decrements numberOfstudents. Department::student::student(string n, string id, float g, int u) : (n, id, g, u) /* passed to the refined constructor */ { numberOfStudents++; // increment number of students // in department } Department::student::~student() : () { numberOfstudents— ; // decrement number of students // in department } As the constructor of the instance classes of Department, the refining constructor student:: student () must properly initialize the private members of the instance classes which are replicas of the sample class student. But notice that member functions of a metaclass are not allowed to access the private members of its sample class unless the sample class designates the metaclass as its friend class. To overcome this problem, refining constructors are allowed to invoke the refined constructors in order to properly initialize the private members of the instance classes. Here, the same notation introduced in the initialization of subclasses is used. Although refining constructors are not allowed to explicitly invoke the refined constructors, the arguments to be supplied to the refined constructor can be speci fied. When an argument list to be supplied to the refined constructor is specified, 120 the refined constructor is executed first and then the refining constructor is executed second whenever a new instance of an instance class of the metaclass is created. In the example above, the refining constructor takes four arguments, and all the ar guments are simply passed to the refined constructor: (n, id, g, u) When a new instance of an instance class of the metaclass Department is created, the refined constructor is executed first. The refined constructor will initialize the members of the new instance. And then the refining constructor simply increments the value Of numberOfstudents. Similarly, refining destructors may invoke the refined destructors. Although de structors do not take any argument, the refining destructor may specify an empty argument list to indicate that it invokes the refined destructor: 0 When an empty argument list is specified, the refining destructor is executed first, and then the refined destructor is executed second. In the example above, whenever an instance of an instance class of the metaclass Department is deleted, first numberOfstudents is decremented, and then the re fined destructor is invoked in order to perform the clean-up process. As a result, 121 numberOfstudents of each instance class always holds the correct number of the instances. A refining constructor does not override the refined constructor unless they have the exactly same function prototype. In the example above, the refined constructor and the refining constructor have exactly the same function prototype. They take four arguments which belong to string, string, float and int respectively. Therefore, the refined constructor is overridden by the refining constructor. But if they have different function prototypes, both the refining operator and the refined operator are available. For instance, Department may refine the constructor student () so that it takes an extra argument: student::student(string, string, float, int, string /*new*/ ); Then the refining constructor does not override the refined constructor. Consequently, two different constructors are available on a single class. Generally speaking, when a refining constructor has different function prototype v from the refined constructor, two different constructors are available on a single class at the same time. And the constructor which is actually invoked is determined by the classes of the arguments which are supplied to the initialization statement. But we must notice that when the refined constructor is invoked, several local vari ables of the metaclass may not be properly initialized. For instance, in the case above, numberOfstudents will not be properly incremented. Therefore, it is desir 122 able that refining constructors have the same function prototype as the refined con structors. On the other hand, refined destructors are always overridden by the refining de structors, because destructors do not take any arguments. 4 .4 .7 . Managing Instances As I mentioned earlier, one of the uses of metaclasses is to define a set of objects. In this section, I will discuss metaclasses focusing on the capability of defining classes which are collections of their instances. You will see the definition of metaclass Maxset below. The purpose of defining Maxset is to see how a set of integers with the maximum operator can be defined using a metaclass. The class int is selected as the sample class of MaxSet. This indicates that instance classes of Maxset are sets of integers. Maxset provides maximum () operator which returns the maximum value in each set. metaclass MaxSet of int { public: int maximum(); } ; You will see a program fragment which involves Maxset below. Two classes setA and setB are defined as instance classes of Maxset. Objects x, y, and z are defined as instances of the class setA, while object p, q, r, and s are defined as instances of the class setB. As a result, setA denotes the collection of three integers x, y, 123 and z, while setB denotes the set of four integers p, q, r, and s. Then, maxA gets the maximum value among x, y, and z, while maxB gets the maximum value among p, q, r, and s . MaxSet setA, setB; // creating set objects setA x, y, z; // members of setA setB p, q, r, s; // members of setB int maxA, maxB; // ------ maxA = setA.maximum(); // maximum of x, y, and z maxB = setB.maximum(); // maximum of p, q, r, and s The maximum () operation is defined as follows: int MaxSet::maximum() { int *max, *t; max = any(); for(t in this) if(*max < *t) return *max; } The operation any () is one of the fundamental set operations which are available on every class. Galois assumes that every metaclass is directly or indirectly a subclass of the metaclass metaclass, and the operation any () is predefined in the class metaclass. As a result, MaxSet inherits the any () operation from metaclass. The any () operation selects an arbitrary instance of the class on which it is invoked and returns the address of the selected instance. The for statement is also one of the fundamental set operations defined on metaclass. The general form of the for statement is as follows: for(pointerToInstance in pointerToClass) 124 // max = this->any() max = t; The for statement enumerates all the instances of the class pointed by pointerTociass by returning pointers to the instances: pointerToinstance. In the above case, for(t in this) will enumerate all the instances of the instance class of Maxset on which the maximum () operation is invoked. Like C++, this is the self-reference variable of Galois. The self-reference variable this points to the object on which the opera tion, that includes the self-reference variable this, is invoked. As a result the maximum () operator will select the biggest integer in instance classes of Maxset, and returns the pointer to the biggest integer. When a class is defined as an instance of a metaclass, like setA which is an in stance class of the metaclass Maxset, the class can provide set operations which are defined by the metaclass, such as the maximum () operation above. But when a class is handcrafted, we cannot define such set operations on the class, because hand crafted classes are associated with no specific metaclass. But remember that every handcrafted class is an instance of the metaclass metaclass. As a result, the fun damental set operations defined by metaclass are available on every handcrafted classes. Therefore, every class in Galois is able to carry the minimal capabilities of managing the collection of its instances. 125 4 .4 .8 . Managing Instances using Binary Trees Although every class provides the fundamental operations for managing their in stances, it is sometimes desirable to be able to implement more sophisticated in stance management operations. For instance, some class may use a linked list in order to sort its instances. Or, another class may use a binary tree in order to im plement an efficient search operation. In addition to the fundamental operations for managing the instances which are provided by metaclass, Galois allows users to define their own instance management operations. In this section, I will introduce a way of managing instances using a binary tree. metaclass Department of student { string departmentName; string chairman; string mainOffice; int numberOfstudents; binaryTree studentList; // binary tree of students // sorted by GPA public: void print (void) ; // print out student list sorted by GPA Department(string, string, string); // class constructor student::student(string, string, float, int); // instance constructor student::-student() ; // instance destructor } ; The example above shows a refinement of the class Department. In this refine ment, each instance class of the metaclass Department keeps track of its instances using a binary tree called studentList. Nodes in studentList and instances of the instance class of Department have one to one correspondence, and each node in studentList keeps a pointer to the corresponding instance of the instance class 126 of Department. Nodes in studentList are sorted based upon the GPA of the cor responding instances which the nodes point to. Department provides two local operations: print < ) and the constructor Department (). print () generates a list of the students sorted by their GPA. In addition, Department refines two operations of its instance classes, student:: student () refines the constructor, and student:: -student () refines the destructor. Department::student::student(string n, string id, float g, int u) : (n, id, g, u) { studentList.insert(student); // add to studentList numberOfStudents++; } Department::student::-student() : () { studentList.remove(student); // remove from studentList numberOfstudents— ; } The implementation of the refining constructor and the destructor is shown above. Whenever a new instance of an instance class of Department is created, a new node which points to the new instance must be inserted into studentList of the instance class. The insertion operation is performed by invoking the insert () op eration of studentList. The pointer to the new instance to be inserted into studentList is specified by the argument. In the refinement of an operation of an instance class, the sample class name is the pointer to the instance on which the re fining operation is invoked. In this case, student points to the instance which in / 127 vokes the constructor, that is, the newly created instance. Similarly, the destructor is refined so that whenever some instance of the instance class of Department is deleted, the corresponding node in studentList is removed. Below is a sample instantiation sequence of the class Department. First, a class cs is created, and then its several instances are created. The instances are inserted into the binary tree studentList whenever they are created. Department CS("computer science","George Bush","SAL 200"); CS Tom("Tom", "1937", 3.5, 10); CS Bob("Bob", "2439", 3.7, 8) ; CS John("John", "6437", 3.2, 12); CS FredC’Fred", "3775", 3.6, 8) ; // . . . Figure 4.11 illustrates the binary tree which is constructed by the instantiation se quence above. The root node of the binary tree points to T om because T om is the first instance of the class cs. The second instance Bob is pointed from the right son of the root node, because Bob's gpa is greater than Tom 's gpa. The third instance John is pointed from the left son of the root node of the binary tree, because John's gpa is smaller than Tom 's gpa. Similarly, the fourth instance Fred is pointed from the left son of the right son of the root node of the binary tree, because Fred's gpa is greater than Tom 's gpa but is smaller than Bob's gpa. Below is the definition of the class binaryTree which I used in the class Department. binaryTree provides five operations: insert (), remove (), max (), print (), and its constructor, insert () will create a new node in the binary tree, 128 class CS computer science departmentN ame chaorman mainOffice George Bush | SAL 200 Tom numberOfstudents name studentList 1937 | studentID theStudent unit left right 3.5 GPA Bob John Bob theStudent theStudent 2439 6437 | left right left right 3.2 theStudent S left right 1 nilII nil Fred tneStudent - s left right Fred 3775 3.6 Figure 4.11: Managing Instances using a Binary Tree which points to the instance of student specified by the argument, remove () will remove the node, which points to the instance of student specified by the argu ment, from the binary tree, max < ) returns the pointer to the instance of the student class which has the maximum value of gpa. print () generates a list of instances of student sorted by the value of gpa. The algorithm for the remove () operation adopted here is taken from [4] class binaryTree { student* theStudent; 129 binaryTree *right, *left; public: void insert(student*); void remove(student*) ; student* max(); void print (); binaryTree() { // constructor theStudent = NIL; left = right = NIL; } } ; void binaryTree::print(void) { if (theStudent) { left->print() ; theStudent->print(); right->print(); } } void binaryTreeinsert(student* ptr) { if(!theStudent) { theStudent=ptr; left = new binaryTree; right = new binaryTree; } else { if(ptr->getGPA() < theStudent->getGPA() ) { left->insert(ptr); } else { right->insert(ptr); } } } student* binaryTree::max() { if(!right->theStudent) return(theStudent); else return(right->max() ) ; } void binaryTree::remove(student* ptr) { binaryTree *leftson, *rightson; student* p; if(theStudent) { if(ptr==theStudent) { if( !left->theStudent && !right->theStudent) /* leaf node */ { theStudent=NIL; 130 delete(left); delete(right); }else if(left->theStudent && !right->theStudent) /* only left son */ { leftson = left; theStudent = leftson->theStudent; left = leftson->left; right = leftson->right; delete (leftson); Jelse if(!left->theStudent && right->theStudent) /* only right son */ { rightson = right; theStudent = rightson->theStudent; left = rightson->left; right = rightson->right; delete(rightson); } else /* two sons */ { p = left->max(); /* find max of left subtree */ left->remove(p); theStudent = p; } } else if(ptr->getGPA() < theStudent->getGPA()) left->remove(ptr); else if(ptr->getGPA() > theStudent->getGPA()) right->remove(ptr); } } 4 .4 .9 . Refining Operations In the previous section, I introduced a way of managing instances of a class using a binary tree. The constructor and the destructor of the sample class were refined so that they take care of the insertion and deletion of nodes, denoting newly created instances and deleted instances, from the binary tree. But notice that it is not only the constructor and the destructor which needs refinement. Whenever GPA of each student is changed, studentList must be re-sorted in order to keep the correct order of students. In addition to refinement of constructors and destructors, meta classes in Galois are allowed to refine the public operations of their instance classes. Below you will see the small improvement of the metaclass Department. 131 The class Department refines newGPA () SO that it restructures studentList whenever GPA of some student is changed. metaclass Department of student { string departmentName; string chairman; string mainOffice; int numberOfstudents; binaryTree studentList; // binary tree of students // sorted by GPA public: void print (void) ; // print out student list sorted by GPA Department(string, string, string); // class constructor student::student(string, string, float, int); // instance constructor student::~student(); // instance destructor student::newGPA(float x) ; // refinement of newGPA Generally speaking, refining operations are denoted by prefixing the refined opera tion names with the sample class name: sampleClassName::refinedOperationName(argumentList) In the example above, the refinement of newGPA () is denoted as: student::newGPA(float x); The implementation of the refinement of newGPA () is shown below. Like refine ments of constructors and destructors, the refining operations of regular operations are defined as if they refine the operations of the sample class. But, actually, they do not refine the operations of the sample class. They refine the operations of the instance classes of the metaclass. Department::student::newGPA(float x) { 132 studentList.remove(student); student::newGPA(x); studentList.insert(student); // remove from studentList // invoke the original // reinsert to studentList The refinement of newGPA () takes a single argument which denotes the new GPA of a student. The refinement of newGPA () first removes the node, which points to the instance being changed, from studentList. Then it changes the GPA of the student. Notice that, unlike constructors and destructors, the refinement of a regular operation is allowed to directly invoke the refined operation. newGPA () in the student class is directly invoked from the refinement, rather than simply specify ing an argument list. Then finally, the refinement re-inserts the instance into studentList. At this moment, the instance already has the new value of its GPA. Therefore, studentList will be properly sorted based upon the new GPA. One issue I have to comment here is the change to the external interface of functions by refinement. Metaclasses are allowed to refine the implementation of the public member functions of its instances, as well as the external interface of the member functions. And if the external interface is changed, both the refining and refined op erations are available. For instance, Department may refine getGPAO so that it returns the GPA of each student through an argument, such as: void getGPA(float* theGPA) Then the instance classes of Department provide two getGPA () functions at the same time. One of them takes no argument, while the other takes one argument. But notice that metaclasses are allowed to include members which belong to classes in 133 volving the sample classes of the metaclasses. And the manipulation of the in stances of the instance classes of the metaclass is described in terms of the sample class. For instance, Department includes studentList which belongs to the class binaryTree. The class binaryTree is defined in terms of student which is the sample class of Department, but not an instance class of Department. Several op erations of binaryTree invoke getGPA () function of student. But, since the op erations are described in terms of the sample class student, they invoke getGPA () without taking any argument. As a result, the operations of binaryTree invoke the unrefined getGPA (), rather than the refining getGPA (). Therefore, if the refining functions are expected to be invoked, the refinement should not change the external interface of the functions. 4.4.10. Default Function In Galois, class names are overloaded to refer to either a class or the pointer to a specific instance of the class. MilkPrice AtLucky, AtVons, AtAlphaBeta, AtRalphs; II... printf("%s\n", AtLucky.company); printf C^dNn", AtLucky .price) ; II ... printf(”%s\n", MilkPrice->company); printf("%d\n", MilkPrice->price); Consider the example above. Assume MilkPrice is the name of a class. In the first line, MilkPrice is used to define its instances: AtLucky, AtVons, AtAlphaBeta, and AtRalphs. These instances hold the price of milk at these supermarkets. This is the regular use of the class name referring to a class. These instances may have sev 134 eral public members, such as: company and price which are the name of manufac turer and the price of milk sold at each supermarket, respectively: class MilkPrice { //. . . public: string company; int price; / / - - - } ; The first two printf statements, which involve AtLucky: printf("%s\n", AtLucky.company); printf("%d\n”, AtLucky.price); will print out the value of these members of the object AtLucky. On the other hand, in the last two printf statements: printf("%s\n", MilkPrice->company); printf ("%cL\n", MilkPrice->price) ; the class name MilkPrice is used instead of the names of instances. When a class name is used as a pointer, it refers to a specific instance of the class. Each class may have a member function called the default function which determines the instance to be chosen. The instance chosen is called the default instance. In the example above, MilkPrice->price might be the highest price, the lowest price, or the median price among the supermarkets. It depends on the definition of the default function of the class MilkPrice. 135 Below is the definition of metaclass FoodPrice. MilkPrice is an instance class of FoodPrice. FoodPrice defines the default function to be used by MilkPrice. In this case, the default function returns the pointer to the instance which has the low est price. metaclass FoodPrice of atEachMarket { public: atEachMarket *default() { // default function atEachMarket *lowest, *t; lowest = any(); for(t in this) if (lowest->price > t->price) lowest = t; return lowest; } } ; class atEachMarket { public: string company; int price; }; Although class names are overloaded, pointers to classes are not overloaded. Given a pointer to a class, the default function must be explicitly specified in order to ac cess the default instance. FoodPrice *EggPrice; II. .. printf("%s\n”, EggPrice->default()->company); printf("%d\n”, EggPrice->default()->price); For instance, assume EggPrice is a pointer to a class which is an instance of the metaclass FoodPrice. In this case, EggPrice->company is illegal because EggPrice points to an instance class of FoodPrice and the instance class does not cany member company. Instead, 136 EggPrice->default O will return to the pointer to the default instance and EggPrice->default()->company refers to the member company of the default instance. 4.4.11. Subclasses of Metaclasses Like other classes, metaclasses are subclasses of other classes. Metaclass can be superclasses or subclasses of other metaclasses, but simple classes cannot be su perclasses or subclasses of metaclasses. A metaclass can be a subclass of another metaclass only when the sample class of the former metaclass is a subclass of the sample class of the latter metaclass. class car { string purchaser; // name of purchaser date datePurchased; // date purchased public: car(string p, date dp) { purchaser=p; datePurchased=dp; }; metaclass carSales of car { string shopLocation; string shopManager; int numberOfCars; // Number of cars sold public: carSales(string si, string sm) { shopLocation = si; shopManager = sm; numberOfCars = 0; car::car(string p, date dp) : car(p, dp) { numberOfCars++; 137 } } ; The classes defined above will keep the records of cars sold at some car shop. Class car has two members: purchaser and datePurchased which keep funda mental information of cars purchased. Metaclass carSales has three members: shopLocation, shopManager, and numberOfCars which denote the location of the car shop, the shop manager's name, and the number of cars sold at this location respectively. The metaclass carSales is implemented using the same technique de veloped for the metaclass Department before. numberOfCars is incremented by car:: car () in carSales which is the refinement of the constructor car () of the sample class car. As a result, it always keeps the correct number of cars sold. The car shop sells not only passenger cars but also trucks. Class passengercar, which is a subclass of car, is defined in order to keep specific information of pas senger cars purchased. You will see the definition of the class passengercar be low. The class passengercar includes three local members: numberOfDoors, numberOf Cylinders, and engineSize which indicate the number of doors, the number of cylinders, and the engine displacement of the passenger car purchased respectively. class passengercar : car { int numberOfDoors; int numberOfCylinders; int engineSize; public: passengerCar(string p, date dp, int nod, int noc, int es) : car(p, dp) { numberOfDoors=nod; numberOfCylinders=noc; engineSize=es; 138 } } Accordingly, a metaclass whose sample class is passengercar needs to be defined in order to keep track of passenger cars purchased. You will see the definition of metaclass passengerCarSales below. passengerCarSales is a metaclass whose sample class is passengercar. The metaclass passengerCarSales is defined to be a subclass of the metaclass carSales. This is completely legal, because the sample class passengercar of the metaclass passengerCarSales is a subclass of the sample class car of the metaclass carSales. The commutative relationship among classes is illustrated in Figure 4.12. metaclass passengerCarSales : carSales of passengerCar { int TwoDoorCar; // Number of sold two door cars int FourDoorCar; // Number of sold four door cars public: passengerCarSales(string si, string sm) : carSales(si, sm) { TwoDoorCar = 0; FourDoorCar = 0; } passengercar::passengerCar(string p, date dp, string make, int nod, int noc, int es) : passengercar(p, dp, nod, noc, es) { if(nod==2) TwoDoorCar++; else if(nod==4) FourDoorCar++; } }; The constructor of the metaclass passengerCarSales: passengerCarSales(string si, string sm); first invokes the constructor of its superclass, that is carSales (), by passing ar guments: 139 subclass sample class of sample class of ubclass metaclass carSales class car metaclass passengerCarSales class passengercar Figure 4.12: Defining A Metaclass of Another Metaclass I I carSales(si, sm) to initialize the inherited members from the superclass carSales. And then, the constructor passengerCarSales () initializes the local members: TwoDoorCar and FourDoorCar tO 0 (zero). Furthermore, the metaclass passengerCarSales refines the constructor passengercar () of its sample class passengercar. Figure 4.13 illustrates the re lationship among constructors of the related classes. The constructor car () of the class car is refined by car: :car () in the metaclass carSales, and the constructor passengercar () of the class passengercar is refined by passengercar: :passengerCar () in the metaclass passengerCarSales. When an instance of an instance class of passengerCarSales is created, the refin ing constructor passengercar: :passengerCar() in the class passengerCarSales is invoked, because it refines the constructor passengercar () of the sample class passengercar. The refining constructor passengercar: :passengerCar () will update the local members: TwoDoorCar and FourDoorCar based upon the instances to be created. But notice that before 140 class car metaclass carSales ►j purchaser I datePurchasedJ | shopLocation I shopMapager | - f - 1 numberOfCars r refining car::car() inherit from (subclass of) mhent from (subclass of) nod | numberOfDoors TwoDoorCar numberQfCylinders FourDoorCar 1 engineSize | passengerCarQ refining class passengerCar passengerCar: :passengerCar() Metaclass passengerCarSales Figure 4.13: Refinement of Constructors updating these members, passengercar: :passengercar () invokes the refined constructor passengercar () in the class passengercar. As I mentioned earlier, when a refining constructor is invoked, first it invokes the refined constructor if necessary, and then executes itself. The initialization of the local members is de layed until the execution of the refined constructor passengercar {) completes. The argument list supplied to passengercar () is specified as: passengercar(p, dp, nod, noc, es) Since the class passengercar is a subclass of the class car, the constructor passengercar () needs to invoke the constructor car!) of the superclass car to initialize the inherited members: purchaser and datePurchased. But notice that the class car is the sample class of the metaclass carSales which is a superclass of the metaclass passengerCarSales, and the constructor car () is already refined by 141 car:: car () in the metaclass carSales. Therefore, in order to increment the mem ber numberOfCars, the constructor passengercar () must invoke the refining constructor car:: car () in the metaclass carSales, rather than directly invoking the constructor car () of the class car. In this case, this is guaranteed because the refining constructor car: :car () in the metaclass carSales has the same external interface as the refined constructor car () in the class car. If the refinement changes the external interface of the constructor car (), the unrefined constructor will be invoked. Then the member numberOfCars of carSales will not be prop erly incremented. When car: : car () is invoked by passengercar (), it invokes car () before in crementing numberOfCars, because car:: car () refines car <). The argument list to be supplied to car () is defined as: car(p, dp) car() initializes the members: purchaser and datePurchased based upon the ar guments supplied. When car () finishes, car: : car () resumes and it increments the member: numberOfCars. Furthermore, when car: : car o finishes, passengercar () resumes. It initializes the members: numberOfDoors, numberOf Cylinders, and engineSize based upon the arguments supplied. When passengercar () finishes, finally passengerCar: :passengerCar () resumes. It increments the members: TwoDoorCar or FourDoorCar based upon the argument nod, and at this point the complete initialization finishes. 142 4.5. Derived Class 4 .5 .1 . D erivation In Galois, objects are created by derivation, rather than instantiation. When an ob ject is created by a derivation, the object is created based upon some existing con crete object, called the parent object, rather than a class which is an abstract repre sentation of concrete objects. The object being created is called a child of the parent object. The internal values of the child object are determined by the internal values of the parent object. Both the child object and its parent object belong to their own classes. The class of the parent object is called the parent class, while the class of the child object is called the derived class of the parent class. Figure 4.14 illustrates a typical derivation. The object being created is associated with two objects: its parent object and its class. The class determines the "frame" of the object, while the parent object determines the "fillings" of the "frame" defined by the class. The "fillings" could be obtained by simply copying some values from the parent object, or some computation might be carried out in order to obtain the "filling" based upon the value of the parent object. The "filling" also can be deter mined by arguments supplied to the constructor of the object. In Galois, every object is derived from its parent object. Given an object, its parent object is uniquely determined, while two or more objects may share their parent object. The derivation sequence starts from the system object called o b jec t, as il- 143 parent class derived class 1 .1 1 1 1 l> instance of instance of derived from parent object 1 1 1 1 3 object being created Figure 4.14: Derivation lustrated in Figure 4.15. All objects are derived from object by a finite number of derivation, object itself is a child of object. Objects which are directly derived from the object object are called primitive objects. The object object itself is also considered a primitive object. Similarly, in Galois, every class is a derived class of its parent class. Given a class, its parent class is uniquely determined, while two or more classes may share their parent class. All the objects of a class are derived from objects of the parent class of the class. It is not allowed to derive objects of a class from objects belonging to classes other than the parent class. The class which object belongs to is called class. The derived-class-of hierarchy among classes starts from the class class. All classes are directly or indirectly derived classes of the class c la s s . The class class itself is a derived class of class. The classes of primitive objects are called primitive classes. The parent class of all primitive classes is the class class. The 144 derived derived derived derived class ^class class — class class — class class — class class c la s s of A of B of c of D instance T instance ■ instance T instance ■ instance T of \ of = of j of i of | o b ject Y * * - ' I instance V"*-;----finstance V ^ - -finstance finstance ’ ' derived V J derived V J derived V J derived' from from from from Figure 4.15: Derivation Sequence class class itself is considered a primitive class. In Figure 4.15, the class a is a primitive class, and instances of the class a are primitive objects. 4.5.2. Defining Derived Classes Below is the general form of the class definition in Galois. The name of derived class which is being defined is preceded by its parent class name. parentClassName derivedClassName { class definition body } Below you will see a sample derived classes student Account. The derived class studentAccount has three members: tuition, paid, and balance which denote the amount of the student's tuition, the amount already paid by the student, and the remaining balance to be paid by the student respectively. The parent class of studentAccount is the primitive class student which I defined in Section 4.4.1. But for convenience, the definition of the student class is repeated here. The par ent class student includes four members: name, studentID, GPA and unit which denote the student's name, the student's identification, the student's GPA, 145 and the number of units which the student is currently taking respectively. Notice that the parent class student itself is a derived class of the class class. class student { // parent class float GPA; // student's GPA public: string name; // student name string studentID; // student identification number int unit; // number of units taken student(string n, string id, float g, int u); // constructor void newGPA(float x) { GPA = x; } float getGPA() { return GPA; } }; student studentAccount { // derived class public: float tuition; float paid; // amount paid float balance; // current balance / / ... studentAccount() { // constructor tuition = unit * 350.0 ; // "unit" is in student paid = 0.0; balance = tuition; void studentAccount::settlement(float amount) { paid += amount; balance -= amount; // } ; The derived class studentAccount defines two operations: the constructor student Account () and settlement () as given above. The constructor studentAccount () initializes the student's account based upon the number of units taken by the student. First, the member tuition, the amount of the tuition to be paid by the student, is computed by multiplying the number of units taken by the student: unit and $350 which is the cost per a single unit: 146 tu itio n = unit * 350.0; Then, the member paid, the amount already paid by the student, is initialized to $0 (zero) and the member balance, the amount to be paid by the student, is initialized to be equal to tuition. The student owes the full amount of the tuition, because no payment has been received. The operation settlement o updates the student's account when the student makes a payment. The implementation of the operation settlement () is straight forward. The operation settlement () takes one argument: amount which denotes the amount being paid by the student. The total amount already paid by the student: paid is recomputed by adding amount to paid, and the new balance: balance is Computed by Subtracting amount from balance. As this example shows, members of a derived class can be properly initialized based upon the value of the members of its parent class even if the constructors do not take any arguments. Each derived class is allowed to access the public members and operations of its parent class as if they are its own members and operations. In the example above, although unit is a member of the student class, the derived class studentAccount is allowed to access unit as if it is a member of the studentAccount class. 147 4 .5 .3 . D eriving O bjects The declaration and initialization of derived objects take the following form: derivedClassName derivedObjectName = parentObjectName.constructor(argumentList); The constructor is prefixed by the name of the parent object from which the derived object is being derived. This is essential to indicate the parent object of each derived object. Consider the following derivation: student Tom = object.student("Tom Logan","254-12-5534”,3.5, 8); student Jack = object.student("Jack Hudson","387-96-2841",2.8,6); II... studentAccount TomsAccount = Tom.studentAccount(); studentAccount JacksAccount = Jack.studentAccount(); // ... First, two objects Tom and Jack, which are instances of the class student, are derived, and then, two objects TomsAccount and JacksAccount, which are in stances of the class studentAccount, are derived. The constructor in each state ment is prefixed by an object which is the parent object of the object being derived. Remember that the class student, which Tom and Jack belong to, is a primitive class. Therefore, Tom and Jack are derived from the object object. On the other hand, TomsAccount and JacksAccount are derived from the parent objects: Tom and Jack respectively, which are instances of the parent class student. Figure 4.16 illustrates the derivation producing TomsAccount and JacksAccount. First, Tom and Jack are initialized so that they carry 8 units and 6 units respectively. Then the member tuition of TomsAccount and JacksAccount is computed based 148 parent class derived class student studentAccount name tuition | studentID | tuition = unit x 350 paid unit | balance ] GPA derivation | 2000 | 800 TomsAccount Tom derivation JacksAccount Jack Figure 4.16: Deriving Objects upon the value of unit of their parent objects. Consequently, the member tuition of the object TomsAccount gets 2800 by multiplying 350 by 8, while the member tuition of the object JacksAccount gets 2100 by multiplying 350 by 6. ! i 4.5.4. Identifying Instantiation with Derivation j l I Galois allows us to consider instantiation in C++ as a special case of Galois' j derivation. In Galois, every object must be created by derivation and instantiation is not allowed. On the other hand, in C++ objects are created by instantiation from their classes. But Galois identifies objects in C++, which are created from classes by instantiation as illustrated in Figure 4.17 (b), with primitive objects in Galois, which are derived from the object o b jec t as illustrated in Figure 4.17 (a). As a re- 149 ! I i class class class primitive ^ of class instance of f ^derivation / X ^object J — — — ►+ instance! instance of derivation instance] instantiation instance C++ class (a) Derivation from object (b) Instantiation in C++ Figure 4.17: Identifying Derivation and Instantiation suit, instantiation is identified as a special case of derivation which derives objects from the object object. When an object is derived from object, i.e. the object to be created is primitive, the parent object name can be suppressed, because there is no ambiguity of select ing the parent object: PrimitiveClassName PrimitiveObjectName = constructor!...); This is equivalent to the regular form: PrimitiveClassName PrimitiveObjectName = object.constructor(...); The abbreviated form above is identical to the object declaration statement of C++. For instance, the derivation producing Tom and Jack from object presented in the previous section is identical to the following abbreviated form of derivation. 150 student Tom = student("Tom Logan”,"254-12-5534", 3.5, 8); student Jack. = student ("Jack Hudson", "387-96-2841", 2.8, 6) ; The abbreviated form allows us to consider as if Tom and Jack are created from the class student by instantiation, although they are created from object by deriva tion. More generally speaking, the notation above allows us to consider as if derivation includes instantiation as its special case, and primitive objects in Galois are created by instantiation rather than derivation. Similarly, in Galois, every class must be a derived class of some other class, while C++ does not have the concept of derived classes. But Galois identifies classes in C++ with primitive classes in Galois which are derived classes of the class c la ss . As we have already seen, the definition of a primitive class is a special case of the definition of derived classes. In the definition of primitive classes, the class name is preceded by class: class className { // primitive class declaration class definition body } Notice that this is identical to the class declaration in C++. As a result, Galois al lows us to naturally identify classes in C++ with primitive classes in Galois which are derived classes of the class c la ss. 4 .5 .5 . Inform ation Sharing Since a derived class is not a subclass, a derived class does not inherit from its par ent class. But, given a derived object, all the public operations and members of its 151 Tom.name Tom TomsAccount ........ . i i f flame ------------- | unit ------------- —j unit I o ta | | tuition | paid I balance ... : ....:.............i T oms Account.name Figure 4.18: Delegation parent object are accessible through the derived object as if the derived object has such members. In the example of the previous section, the object TomsAccount can be considered as if it has members: studentID, name, and unit of its parent object Tom, as illustrated in Figure 4.18. For instance, TomsAccount. name and TomsAccount. studentID will refer to name and studentID of the parent object Tom respectively. But gpa is not available on TomsAccount because gpa is a private member of Tom. Notice that the members of the parent object are not copied into the derived object. Rather, when requests to access the members of the parent object are given to the derived object, the requests are forwarded to the parent object and are processed there. Such mechanisms are called delegation. Delegation is useful when two or more objects need to share the same information. In the example above, studentAccount needs name and studentID in order to identify each account. But the information is already defined in student. In such a case, by defining 152 Parent Object Derived Object Note object object discussed in the previous section object class class class (1) recursive (2) non-recursive class object Figure 4.19: Types of Derivation studentAccount to be a derived class of student, the duplication of the informa tion can be avoided. Also notice that in Galois classes are, at the same time, subclasses and derived classes of some other classes. Information sharing mechanism in the subclass hier archy is inheritance, while derived classes use delegation for sharing information. As a result, Galois includes both inheritance and delegation in a single language. 4 .5 .6 . D eriving C lasses In the previous sections, I discussed derivation involving only objects. But as I mentioned earlier, classes are also objects. So the question asking if a class can be involved in derivation or not will arise. The answer is "yes." In Galois, derived classes of a metaclass can be defined, and these derived classes can be also meta classes. In this section, I will discuss derivation involving not only objects but also classes. Figure 4.19 lists the possible combinations of types of parent objects and 153 derived objects involved in derivation. In Galois, all the possible combinations listed here are permitted. The first type of derivation, which I discussed in the previous section, produces simple objects from other simple objects. In the following three sections, I will dis cuss the remaining three types of derivation. 4.5.6.1. Deriving classes from objects The second type of derivation produces classes from objects. In order to define such derivation, we need to define metaclasses which are derived classes of simple classes. class department { // parent class // . . . public: string name; string chairman; string mainOffice; department(string n, string c, string m) { // constructor name = n; chairman = c; mainOffice = m; }; } ; department departmentStudents of student { // derived class int numberOfStudents; public: departmentStudents() { numberOfStudents = 0; }; student::student(string n, string id, float g, int u) : (n, id, g, u) { numberOfStudents++; } ; student::-student() : () { numbe rO f S t udent s — ; 154 }; } ; department departmentEmployees of employee! // derived class int numberOfEmployees; public: departmentEmployees!) { numberOfEmployees =0; }; employee::employee(string n, string e, string s) : (n, e, s) { numberOfEmployees++; } ; employee::~employee() : () { numberOfEmployees— ; } ; In the example above, two m etaclasses departmentEmployees and department students are defined as derived classes of class department which is class department metaclass departmentEmpolyees metaclass ] departmentStudents name chairman mainOffice denved class of denved class of instance of numberQfEmployees | numberOfStudents mstance sample class of sample class of mstance of mstance of class student denved from equivalent *----- * 7 instanc class " — Equivalent' name name instancXjqu^alent studentID employee ID supervisor instance of mstanc class instance of class employee mstance /instance! (instancA © mstance of V / v j Figure 4.20: Defining Metaclasses from a Simple Class 155 a simple class. Figure 4.20 illustrates the relationship among classes involved in the definition above. The parent class is department which represents departments of some institution, such as computer science department and business administration department. The department has three members: name, chairman, and mainOffice which include the name of the department, the name of the department chairman, and the location of the departm ent's m ain office respectively. departmentEmployees and department Students are classes which denote, re spectively, the sets of employees and the set of students of each department which are instances of the department class. The sample Class Of the metaclass departmentStudents is the class student which I have used many places. The sample class of the metaclass departmentEmployees is employee which is defined below. The class employee has three members: name, empioyeeiD, supervisor which denote the employee's name, the employee's identification number, and the name of the employee's su pervisor respectively. This indicates that each instance of the departmentEmployees class is a collection of objects which have these three members. class employee { public: string name; string employeeID; string supervisor; employee(string n, string id, string s) { // constructor name = n; empioyeeiD = id; supervisor = s; } }; 156 The metaclass departmentEmployees has a member numberOfEmployees which keeps the number of employees in each department. Initially, numberOfEmployees is set to 0 by the constructor departmentEmployees (). The metaclass departmentEmployees refines the constructor employee () and the destructor -employee < ) of its sample class employee. Whenever a new object is created from an instance class of the metaclass departmentEmployees, the instance class incre ments the value of its member numberOfEmployees. On the other hand, whenever an existing instance of an instance class of departmentEmployees is deleted, the instance class decrements the value of its member numberOfEmployees. As a re sult, the member numberOfEmployees of each instance class of the metaclass departmentEmployees always holds the correct number of existing instances of the instance class. Similarly, the metaclass departmentStudents has a member numberOf Students which keeps the num ber of students in each departm ent. Initially, numberOf Students is set to 0 by the constructor departmentStudents (). The metaclass departmentStudents refines the constructor student () and the de structor -student () of its sample class student. Whenever a new object is cre ated from an instance class of the metaclass departmentStudents, the instance class increments the value of its member numberOf students. On the other hand, whenever an existing instance of an instance class of departmentStudents is deleted, the instance class decrements the value of its member numberOf students. As a result, the member numberOfEmployees of each instance class of the meta 157 class departmentStudents always holds the correct number of existing instances of the instance class. Remember, in Section 4.4.1,1 defined the class Department as a metaclass whose sample class is the class student. Therefore, instance classes of the Department class were semantically overloaded. As an object, each instance class of the Department class denoted a department of some institution. As a class, each in stance class of the Department class was a replica of the sample class student, and denoted the set of its instances, that is, the collection of students in the depart ment. But here, the class department is defined as a simple class which denotes depart ments of some institution, and two derived classes: departmentEmployees and departmentStudents are defined as derived classes of the simple class department. Instance classes of the derived classes: departmentStudents and departmentEmployees denote the collections of students and employees of the de partments respectively. Here, the class department is not semantically overloaded. And, the information about each department, such as the department name and the name of the chairman, is shared by two derived classes. Below is a sample derivation sequence involving the classes defined above. Two departments csci and buad are defined as instances of the department class, csci denotes the computer science department, and buad denotes the business adminis tration department in some institution. They are properly initialized so that they keep 158 their name, the chairman's name, and the location of the main office. Figure 4.21 illustrates the relationship among the instances and their classes. department CSCI = department("computer science", "F.R.Carlson", "SAL200"); department BUAD = department("business administration", "J.D.Steele", "BRI104"); departmentEmployees CSCIemployees = CSCI.departmentEmployees(); departmentEmployees BUADemployees = BUAD.departmentEmployees(); CSCIemployees Tom("Tom", "1257", "Chris"); CSCIemployees John("John", "2058", "Chris"); BUADemployees Jack("Jack", "8835", "Sam"); BUADemployees Bob("Bob", "0358", "Sam"); CSCIemployees which denotes the set of employees of the computer science de partment is defined as an object which is derived from c s c i. Sim ilarly, BUADemployees which denotes the set of employees of the business administration department is defined as an object which is derived from buad. Several instances of CSCIemployees and BUADemployees are defined. As a result, CSCIemployees is the set consisting Tom, John, and so on, while BUADemployees is the set consisting of Jack, Bob, and so on. Since departmentEmployees is a derived class of department, instances of departmentEmployees have access to public members of department. Furthermore, users of an instance of departmentEmployees are allowed to access the public members of the corresponding parent object of department, as if they are public members of the instance of departmentEmployees. For example, since CSCIemployees is a derived object c s c i, users of CSCIemployees are allowed to access the public members of c sc i: name, chairman, and mainof f ice, as if they 159 class derived class sample j class department class of departmentEmpolyees class of I employee instance of computer science F.R.Carlson SAL200 BUAD business administration J.D .Steele BRI104 7 \ instance of instance of :/n class CSC/empolyees 'equivalent/ as classes ' I derived from^ 1 ^ 10 employees instance of John L I s s l.... ,...1 | 2058 | | 1257 f | 1 Chris 1 Chris | John Tom class BUADempolyees 15 employees derived from instance of Jack Bob Figure 4.21: Relationship Among Instances involved in Metaclass Derivation are public members of CSCIemployees. Although name, chairman, and mainOffice are not members Of CSCIemployees, CSCIemployees . name, CSCIemployees . chairman, and CSCIemployees .mainOffice refer to "computer science", "F.R.Carlson", and "SAL200" respectively. 4.5.6.2. D eriving C lasses from Classes The third type of derivation produces classes from other classes. In order to define such derivation, we need to define metaclasses which are derived classes of other metaclasses. Each class will define a set of objects. Thus, a class derived from an other class will define a set of object which is derived from another set of objects. 160 The question here is if there exists special relationship among elements of these two sets. Galois allows two types of derivation between classes, that is, sets of objects. (a) R ecursive D erivation In the first case, when a class is derived from another class, the instances of the child class are also derived from the instances of the parent class. Such a derivation is called a recursive derivation. departmentStudents deptLibUsers of libUser { public: deptLibUsers() ; // constructor }; student libUser { checkout checkedout[5] ; } ; class checkout { string bookname; date duedate; Boolean overdue; }; Metaclass deptLibUsers will represent the set of each department's library users. The sample class of deptLibUsers is libUser. libUser includes a member checkedout which is an array of checkout, checkout is a class representing the information of books checked-out by each user. The member checkedout consists of five checkout elements. This indicates that each users are allowed to check out at most five books at a time. The key issue here is that the sample class libUser of deptLibUsers is a derived class of student which is the sample class Of departmentStudents, while 161 derived class of sample class of sample class of derived class of student libUser deptLibUsers departmentStudents Figure 4.22: Deriving A metaclass deptLibUsers is a derived class of departmentStudents, as illustrated in Figure 4.22. In such a case, recursive derivation can be defined. In this case, each instance of deptLibUsers is a collection of objects which are derived from the instances of the corresponding instance of departmentStudents. As a result, all the students of each department will be automatically the users of their department’ s library. Below is a sample derivation sequence. departmentStudents CSCIstudents; CSCIstudents Ed("Ed", "3357", 3.0, 12); CSCIstudents Bob("Bob", "0687", 3.1, 8); CSCIstudents Jane("Jane", "5003", 2.9, 6); deptLibUsers CSCIlibUsers=CSCI.deptLibUsers(); CSCIstudents is an instance class of the metaclass departmentStudents. Three objects Ed, Bob and Jane are defined as students of the computer science depart ment. When csciiibUsers which is an instance of deptLibUsers is derived from cscistudents, all the instances of the csciiibUsers are also derived from the instances of the cscistudents, although they are not explicitly declared. The rela tionship among these instances is illustrated in Figure 4.23. 162 derived class of instance of instance of derived from * (child of) instance of instance of derived from (child of) derived from (child of) derived from (child of) Bob’ Jane Jane' departmentStudents deptLibUsers CSCIstudents CSCIlibUser Figure 4.23: Recursive Derivation In this case, a child class and its parent class have always the same number of in stances, and the instances have one-to-one correspondence with child-of (or derived-from) relationship. Whenever a new instance of the parent class is created, the corresponding instance of the child class will be automatically created. In the example above, when a new instance of cscistudents is created, its child which is an instance of csciiibUsers is created. Thus, whenever a new student enrolls in the computer science department, he or she will be automatically entitled as a user of the department's library. Such a relationship is defined in the constructor of deptLibUsers. deptLibUsers::deptLibUsers() : libUser() { // constructor II... } 163 The constructor of deptLibUsers is associated with a construction statement of its sample class libUser. Whenever a new instance class of deptLibUsers is created, the associated construction statement of the sample class is automatically executed for all the instances of the parent class of the newly created class. (b) Non-Recursive Derivation In the second case, as illustrated in Figure 4.24, although a child class which is a set is created from its parent class which is also a set, the instances of the child classes are created independently of the instances of the parent class. Such a deriva tion is called a non-recursive derivation. departmentStudents deptTerminals of terminal { int expectedNumberOfTerminals; int numberOfTerminals; public: deptTerminals() { expectedNumberOfTerminals = numberOfStudents / 2; } } ; class terminal { name terminalType; / / - - - } ; The class deptTerminals represents the set of computer terminals available on each departm ent. deptTerminals is defined to be a derived class of departmentStudents, and has two members: expectedNumberOf Terminals and numberOf Terminals. expectedNumberOf Terminals denotes the number of ter minals which each department is supposed to have, and numberOfTerminais de notes the number of terminals which each department actually has. The constructor 164 derived departmentStudents deptTerminals class of instance of instance of derived from * (child of) CSCIstudents CSCIterminals instance of instance of SUN1 SUN2 Bob SUN4 SUN5 Jane no relationship SUN3 Figure 4.24: Non-Recursive Derivation deptTerminals will compute expectedNumberOf Terminals based upon the number of students: numberOf students in each department. In this case, one ter minal is supposed to be available for every two students. Although both departmentStudents and deptTerminals represent sets and they have specific relationship as sets, there is no specific relationship among elements of the sets. Although the expected number of terminals is computed based upon the number of students, there is no specific relationship between students and termi nals. 165 I ] I _______ I 4.5.6.3. Deriving Objects from Classes The fourth type of derivation produces simple objects from classes. In order to de fine such derivation, we need to define simple derived classes whose parent classes are metaclasses. deptTerminals deptTerminalRoom { int area; int numberOfDesks; public: deptTerminalRoom() { numberOfDesks = expectedNumberOfTerminals; area = numberOfDesks * 4; //. . • } deptTerminals deptPowerSupply { int capacity; public: deptPowerSupply() { capacity = expectedNumberOfTerminals * 2; I I . . . } } ; Two classes deptTerminalRoom and deptPowerSupply are defined above. Both classes are simple classes and are derived classes of the metaclass deptTerminals which I defined in the previous section, as illustrated in Figure 4.25. The class deptTerminalRoom will describe computer terminal rooms of departments. deptTerminalRoom is defined to be a derived class of deptTerminals, because several attributes of deptTerminalRoom need to be determined based upon the at tribute of deptTerminals. For instance, each department terminal room needs enough number of desks to accommodate all the terminals available in that room. And the area of the room is determined by the number of desks which the room is 166 class deptTerminalRoom derived class of area class deptTerminals | numberOfDesks ~] expectedNumberOfTerminals numberOfTerminals class deptPowerSupply derived from instance of capacity instance of instance of CSCIterminals derived from, CSCIterminalRoom derived from instance of SUN1 CSCIpowerSupply SUN3 SUN2 Figure 4.25: Deriving Simple Classes from a Metaclass housing. numberOfDesks is equal to expectedNumberOfTerminals and area is computed by multiplying numberOfDesks and 4 which is the area occupied by a single desk. Similarly, the class deptPowerSupply represents the power supply of computer terminal rooms of departments. deptPowerSupply is defined to be a derived class of deptTerminals, because several attributes of deptPowerSupply are determined based upon the attribute of deptTerminals. For instance, the capacity of the power supply for a terminal room is mainly determined by the number of terminal available in the terminal room. Thus capacity of deptPowerSupply is obtained by multi plying expectedNumberOfTerminals and 2 which is the maximum power con sumption of a single terminal. 167 4.6. Class Hierarchies in Galois In the preceding sections, I introduced metaclasses and derived classes in Galois. In addition, Galois already has superclasses which are inherited from C++. As a re sult, Galois includes three orthogonal class hierarchies in a single language. This section will give a comparative study of these class hierarchies. 4.6.1. Derived Classes and Subclasses Derived classes and subclasses are similar language constructs. Notice that in some literatures [93][94] the term "derived class" is used to refer to the term "subclass." But Galois distinguishes these two terms. 4.6.1.1. Delegation and Inheritance One of the main differences between derived classes and subclasses can be seen in their mechanism of sharing information. Galois is a language which includes both inheritance and delegation. Delegation is used to share information between objects and their parent objects, while inheritance is used to share information between classes and their superclasses. To understand the difference between subclasses and derived classes, consider the following two classes: checkingAccount and savingAccount. Given a class customer which has two members: name and address, checkingAccount is de fined to be a subclass of the class customer, while savingAccount is defined to 168 be a d erived class of the class customer. Both checkingAccount and savingAccount have the same members: accountNumber and balance. class customer { public: string name; string address; customer(string n, string a) { name = n; address = a; } } ; class checkingAccount : public customer { public: string accountNumber; float balance; checkingAccount(string n, string a, string an) : (n, a) { accountNumber = an; balance = 0; } } ; customer savingAccount { public: string accountNumber; float balance; savingAccount(string an) { accountNumber = an; balance = 0; } }; At a first glance, the subclass checkingAccount and the derived class savingAccount might give us very similar impression. Both the derived class checkingAccount and the subclass savingAccount behave as if they carry all the members defined in the class customer. But their ways of implementing the infor mation sharing mechanism are totally different. Figure 4.26 illustrates the difference between subclasses and derived classes. Since checkingAccount is a subclass of customer, all the instances of 169 original class customer subclass checkingAccount inheritance derived class savingAccount instaice of Boston delegation instance of instance of [jack "j [Chicago rPh $1800| 0295 | $3520 $5000 derived object derived object address name number| |balanee| object object Figure 4,26: Derived Classes and Subclasses checkingAccount have their own copy of the members of the superclass customer: name and address. When tWO Or more instances of checkingAccount are created, the value of name and address in each instances can be different. Furthermore, instances of checkingAccount are totally independent of instances o f the superclass customer. On the other hand, since savingAccount is a derived class of customer, instances of savingAccount do not carry its own copies of members of the parent class customer. And each instance of savingAccount is derived from a specific in stance of customer. All the requests to the members of customer are forwarded to 170 ^ the parent object which actually holds the information, and are processed there rather than in the derived objects. When two or more instances of savingAccount are derived from the same parent object, the value of name and address is same for all the instances. As a result, members in the parent object are shared by all the derived objects. The difference between checkingAccount and savingAccount also can be seen in their constructors. Since checkingAccount inherits all the members of customer, the constructor of checkingAccount takes arguments: n and a which are required to initialize the inherited members, in addition to an argument: an which is required to initialize its local member. But the constructor of savingAccount takes only one argument which is used to initialize its local member. Inheritance sometimes results in the difficulty of keeping the consistency among objects. Since information is shared by duplication, multiple copies of the identical information may co-exist. When one copy is modified, other copies may not be modified. Then it will result in inconsistency. In the example above, when one customer has two checking accounts, his name and address are kept twice in two checkingAccount objects. Then if his address is changed, the value of the member address of both objects need to be updated. On the other hand, in case of savingAccount, even if one customer has two saving accounts, his name and ad dress are stored only once in a single parent object. Thus only the parent object need to be updated. And the modification in the parent object is automatically re flected to all the savingAccount objects derived from it. 171 derived class parent class instance of instance of refinement of superclass subclass instance instance of ..m - (a) Class Refinement (b) Object Refinement Figure 4.27: Class Refinement and Object Refinement Inheritance is useful in the case when such consistency need not be maintained. On the other hand, delegation does not suffer from such consistency problems, because delegation is implemented using the forwarding of requests. 4.6.1.2. Class Refinement and Object Refinements The second difference between derived classes and subclasses is that a derived class defines refinement of individual instances of the parent class, while a subclass de fines refinement of the superclass itself, not instances of the superclass. As Figure 4.27 (a) illustrates, objects created from a subclass are always "new." All instances of the subclass are created independently from instances of the superclass. It is not allowed to "convert" existing objects of the superclass to those of the sub class. 172 On the other hand, as Figure 4.27 (b) illustrates, instances of a derived class cannot be created independently from instances of the parent class. Each instance of a derived class is a refinement of some instance of the parent class. Derived objects are always created from specific instances of the parent class. 4.6.1.3. Evolution and Metamorphosis The third difference between derived classes and subclasses is that subclasses are used to define similar objects, while derived classes allow us to define totally dif ferent objects. Instances of a subclass are similar to those of the superclass in the sense that the instances of the subclass include all the members which are also in cluded in the instances of the superclass. On the other hand, although members of parent objects are accessible from their child objects, the child objects do not include their own copy of the members of the parent objects. Accordingly, instances of the derived class are totally different from those of the parent class. Both subclasses and derived classes provide a way of representing refinement, but they are different in essence. Subclasses represent evolution, while derived classes represent metamorphosis or transformation. Figure 4.28 illustrates the orthogonal ity of evolution and metamorphosis. Each column represents the evolution history of classes. Each class is a subclass of the class right above in the same column. On 173 ■ m i " ■M il' • I II ' ■ III' ■■III" ■ • I I I ' •M il' I ' •Mil • n i l ' class class Notes: • M il.... derived class of subclass of Evolution primitive classes Metamorphosis Figure 4.28: Evolution and Metamorphosis the other hand, each row represents the metamorphosis or transformation history of classes. Each class is a derived class of the class on the left in the same row. Primitive classes, denoted as white boxes in Figure 4.28, are derived classes of the class class. As a result, every class is obtained by a finite derivation sequence from the class class. Classes which do not explicitly specify their superclasses are considered subclasses of the class class. Therefore, the superclass-of and derived- class-of hierarchies are starting from the class class. 4.6.2. Derived Classes and Metaelasses One of the main differences of derived classes and metaclasses is derivation and in stantiation. Metaclasses tend to be fat when the metaclass hierarchy is deep. On the other hand, derived classes tend to be thin even if the derived class hierarchy is deep. 174 instance instance instance instance object1 class A class B class C template of template of template of template of template of template of template of template of template of template of class D Figure 4.29: Fat Class Approach Suppose we have a three-level metaclass hierarchy as illustrated in Figure 4.29. Class d is a metaclass of class c, class c is a metaclass of class b, and class b is a metaclass of class a. In order to create an instance x of class a, class a needs to in clude a template to create x. Class a, in its turn, is an instance of class b. Then the class b needs to include a template to create class a. A s a result, the class b will in clude templates for both class a and object x, because the class a includes the tem plate for the object x. Similarly, the class b is an instance of class c. Then the class c needs to include three templates for the object x as well as classes a and b. i Finally, the class c.is an instance of class d. Then the class d needs to include four templates for the object x as well as classes a, b, and c. As a result, deeper meta class hierarchy results in fatter classes. On the other hand, derived classes result in thin classes. Suppose we have a three- level derived class hierarchy as illustrated in Figure 4.30. Class x is a derived class of class a, the class a is a derived class of class b, and the class b is a derived class of class c. Given an object c which is an instance of the class c, an object b which 175 class C class B class A class X instance of instance of instance of instance of objectV — derived/obiecA ^derived f objectN^derived from from from template of template of template of template of Figure 4.30: Thin Class Approach is an instance of the class b can be derived from the object c. Similarly, object a which is an instance of the class a can be derived from the object b, and so forth. Although the information available on each object increases when the derived class hierarchy becomes deep, classes themselves do not become fat. Figure 4.31 illustrates the orthogonality of the metaclass hierarchy and the derived class hierarchy. In the figure, boxes denote classes including metaclasses, while circles denote non-class objects. The metaclass hierarchy represents the construc tion history of abstract templates, while the derived class hierarchy represents the transformational process of creating concrete objects. In Figure 4.31, each column represents the construction history of abstract templates. Each class, denoted by a box, is the template of the class right below in the same column. On the other hand, each row represents the metamorphosis or transformation history of classes. Each class is a derived class of the class on the left in the same row. Primitive objects, denoted as a white circle in Figure 4.31, are directly derived from object. Therefore, every object is derived from object by a finite number of 176 handcrafted classes primitive classes^ t' •■HI “ ■ H I ' ■ III Notes: •nil....... Derived class of metaclass metaciass -ill-. ■ 'ill Construction class class A b stra ct Templates Derived from •<nl...... simple k classes -iil" l- 'iil- f • ■ ill Instance of * ■ Construction of Concrete Objects Figure 4.31: Template construction and Object Metamorphosis derivation. Simple primitive classes are derived classes of the class class, while non-simple primitive classes are derived classes of the metaclass metaclass. Every object is an instance of its class. Especially, the object object is the instance of the class class. Every class is an instance of its metaclass. Handcrafted classes and metaclasses are considered to be instances of the metaclass metaclass. 4.6.3. Metaclasses and Subclasses In Galois, a class is at once a subclass of its superclass and an instance class of its metaclass. And the metaclass and the superclass of a class determine different aspects of the class. Classes themselves are objects, while at the same time they are templates of their instances. The behavior and the structure of a class as an object are determined by its metaclass, while the characteristics of the object as a template are determined by its superclass. For instance, as illustrated in Figure 4.32, assume 177 metaclass class A superclass operation P operation X definition of operation X definition of operation P definition of operation X instance Figure 4.32: Superclass and Metaclass class a defines operation x of its instances, while operation p is available on the class a. Then the definition of the operation x may be inherited from the superclass of the class a, while the operation p is defined in the metaclass of the class a. As I mentioned earlier, every class is an instance of its metaclass, and when a class is handcrafted, the class is considered to be an instance class of the metaclass metaclass. As a result, the operations defined by the metaclass metaclass are available on every classes. The metaclass metaclass defines the common behavior of every class. The operations defined in the metaclass metaclass include the fun damental set operations such as any (), and the operation to create a new instance new. These operations are available on every class. On the other hand, every class is a subclass of another class, and when a class does not explicitly designate its superclass, it is considered as a subclass of the class 178 Construction of Abstract Templates Evolution Figure 4.33: Evolution and Template construction class if it is a simple class, or a subclass of the metaclass metaclass if it is a metaclass. As a result, every metaclass inherits the members and operations defined in the metaclass metaciass, while every simple class inherits the members and op erations defined in the class c la ss . Consequently, the instance-of relationship and the subclass-of relationship are orthogonal relationships as illustrated in Figure 4.33. Remember that a metaclass can be a subclass of only a metaclass. A metaclass can not be a subclass of a simple class, and a simple class cannot be a subclass of a metaclass. This is because of the following reasons. If a simple class is a subclass of a metaclass, the simple class will inherit the members and operations defined in the metaclass metaclass. Especially, it will inherit the new operation. Then the new operation will be available on instances of the simple class, although the in stances of the simple class are not classes. Similarly, if a metaclass is a subclass of 179 metaclass metaciass Notes; ••■ ■ It......... subclass of class class Instance of function class int add(int x, int y) { retum(x+y); } ^instance of instance o f , add(5, 10) function object add(20, function object Figure 4.34: A Function Class and Instances a simple class, the metaciass will inherit the members and operations defined in the class c la s s , rather than the metaciass m etaciass. Especially, the metaciass does not inherit the new operation. Then, the instance classes of the metaciass will not be capable of creating instances although they are classes. 4.7. Concurrency in Galois 4 .7 .1 . F unction O bjects In Galois, the definition and the execution of functions are described in terms of objects. Each function definition is a class, called a function class or a function template. Each instance of a function class corresponds to an invocation of the function class. When a function class is invoked, an instance of the function class is created. Multiple invocations of a single function class will result in the creation of multiple instances of the function class, as illustrated in Figure 4.34. .180 Request stack object function template push () function template pop 0 instance of function template top() Figure 4.35: Execution of a Member Function Given an object, the creation of instances of its member functions and the execution of the instances are coordinated by the object itself. When an object receives a re quest to execute one of its member functions, first the object creates an instance of the requested function template. Then the object will dispatch the execution of the instance of the function template. When the instance completes the execution, the object will delete the instance. As Figure 4.35 illustrates, when a sta ck object receives a request to execute its pop function, the sta ck object will create an instance of the function template pop. Then the sta c k object will start the execution of the instance of the function template pop. When the execution of the instance of the function template pop completes, the sta ck object will remove the instance. 181 4.7.2. Concurrent Function Invocations Although Galois provides a concurrent environment in which multiple objects can work simultaneously, each object is essentially a sequential process. Each object will carry out one of its operations at a time. If an object receives a new request to carry out one of its operations when it is busy, that is, when it is already executing some other operation, the new request must be delayed. Therefore, a scheduling mechanism to coordinate multiple invocation requests is necessary. When a new request arrives at an object which is currently busy, the object will create an instance of the newly requested function template. As a result, instances of two or more functions co-exist. But the object does not start the execution of the new function instance. Instead, the object put the new function instance into the ready state which indicates that the function instance is ready to execute, but is not executed immediately because some other instance is being executed. Figure 4.36 illustrates the state transition diagram of function instances. Function instances can be in one of the following states: executing state, and ready state. The executing state indicates that the function instance is currently running. Function instances in the ready state are waiting for the completion of other function in invoked Ready Executing Terminated constructor run() destructor Figure 4.36: Function State Transition Diagram 182 sample class of instance of push() Function FunctionClass instance of /\ f function^r funCtion^ V n s ta n c e J ^ instaDCeJ Figure 4.37: Function Templates without Scheduling Capability stances which are currently in the executing state. Within a single object, only one function instance can be in the executing state at a time. Other function instances will be put into the ready state and will be delayed until the executing instance fin ishes. When the current executing instance finishes, the object will choose one of the ready instances, and start the execution of the selected function instance. The selection will be made non-deterministically. In Galois, every function template is considered as an instance class of a predefined metaciass, called FunctionClass, as illustrated in Figure 4.37. The sample class of the metaciass FunctionClass is called Function. The class Function defines operations which change the state of function instances. The skeleton of Function is given below: class Function { functionState state; // ready or executing public: Function(); // invoked -> ready : constructor runO; // ready -> executing 183 Request function template top() function template pop () function template push() ready instance ready instance stack object instance ( executing | 1 instance I Figure 4.38: Creation of Instances of Multiple Function Templates ~Function(); // executing -> terminated : destructor } When a function is invoked, the constructor is executed. The constructor will put the instance into the ready state. When a ready instance starts execution, the run () operation is automatically invoked, run () operation puts the instance into the exe cuting state. When a running instance terminates, the destructor is invoked. The destructor will remove the terminated instance. Figure 4.38 illustrates the case when a stack object receives requests to execute its push () and top () function while it is executing the pop () function. The stack object creates instances of the push () and top () function templates, but the func tion instances do not start and are waiting for the completion of the executing in stance of the pop () function. When the instance of the pop () function terminates, the stack object will remove the terminated instance, and will select one of the ready instances non-deterministically and start it. 184 Notice that the creation of instances of a function template is carried out as an inter rupt Whenever an object receives a request to execute one of its operations, a func tion instance of the operation is created immediately, even if the object is busy. But the created function instance is put into the ready state if the object is busy. It is also possible to create two or more instances of a single function template at the same time. This will happen when an object receives a request to invoke the func tion which already has instances. The existing function instances might be currently executing, or might be in the ready state. When such a new request arrives at the object, the same process which I mentioned above will be taken. The object will create an instance of the newly requested function template, but the object does not start the execution of the new function instance. Instead, the object put the new in stance into the ready state. In this case, it is also true that only one instance is in the executing state at a time. The other instances are waiting for the completion of the executing instance. Figure 4.39 illustrates the case when the stack object in Figure 4.38 receives a second request to execute its push () function while it is executing the pop () func tion and the push () and top () functions already have instances which are in the ready state. Then, a new instance of the push () function template is created, but it does not start. When the executing instance of the pop () function terminates, the stack object will remove the terminated instance, and will select one of the ready instances and start it 185 Request stack object function template push() function template P°P() function template top ( ) ready instance instance of executing ] instance J G ready instance instance newly created instance ; Figure 4.39: Creation of Multiple Instances of Function Templates In this case, the object will choose the function instance to be executed next in the following manner. First, the object will choose a function template among those which have one or more ready instances non-deterministically. Then the object will determine the instance of the selected function template to be executed by the default function of the function template. Since function templates are classes, we can de fine their default functions. If no default function is specified, an arbitrary instance is selected non-deterministically. The next section will describe the way of scheduling function execution by default functions. In Galois, like C++, function templates which do not belong to any class can be defined. The creation of instances of such function templates will be maintained by the Galois' runtime system, instead of any specific object. 186 Although functions are also classes, subclasses and derived classes of function classes are not allowed. Only a metaclass of a function class can be defined. The use of metaclasses of function classes will be discussed in the next section. 4.7.3. Scheduling of Concurrent Function Invocations When two or more requests to execute a member function arrive at an object, only the first request will be carried out and the remaining requests are delayed. When the first request completes, the request to be processed next is determined by the de fault function of each requested function. As a result, the scheduling of multiple function invocation is possible. As I mentioned earlier, every function template is an instance of the metaclass FunctionClass. But FunctionClass itself does not define any scheduling capa bility. As a result, function templates which are instances of the class FunctionClass do not carry any scheduling capability. In order to define some scheduling mechanism in function templates, we first define a new metaclass whose sample class is the class Function. The metaclass defines the scheduling mecha nism which the function templates need to carry. Then the function templates are defined as instances of the newly defined metaclass, rather than the class FunctionClass. The function templates which are instances of the metaclass are equivalent to the class Function except that the scheduling mechanism defined in the metaclass is available on all the function template, as illustrated in Figure 4.40. 187 metaclass defining scheduling capability FunctionClass 4 V instance of sample class of ^ A ____ pop ( ) instance of push() Function / ^ \ | ) ( function^ y ^instancey ^ i n s t a n c e J Figure 4.40: Function Templates with Scheduling Capability The following class stack, as an example, provides the FIFO scheduling of the multiple invocations of its member functions. The class stack has three member function templates: push (), pop (), and top (). The declarations of these functions are preceded by FifoFunction which is a metaclass. This indicates that these function templates are instances of the metaclass FifoFunction, rather than FunctionClass. class stack { / / ... public: FifoFunction int push<int); FifoFunction int pop{); FifoFunction int top(); The definition of FifoFunction can be seen below. The metaclass FifoFunction designates the class Function as its sample class. » metaclass FifoFunction of Function { FunctionQueue theQueue; 188 public: Function::Function() : () { theQueue.append(Function); } Function::~Function () : () { theQueue.remove(Function); } Function* default() { return(theQueue.top()); } }; Figure 4.41 illustrates the scheduling mechanism implemented in stack. Each function template, which is an instance of FifoFunction, maintains its instances using a linked list. The structure of each node in the linked list is defined by class FunctionQueue. Whenever a new instance of the function template is created, that is, a invocation request is received, the function template appends the new instance to the end of the linked list. Among the member functions of FifoFunction, we are most interested in its de fault function. The default function simply returns the pointer to the function which stack object function template push <) Top | executing I instance append function template pop() Z L function template top() Figure 4.41: FIFO Scheduling of Functions using Linked List 189 is on the top of the linked list. As a result, function templates which are instances of FifoFunction manipulate their instances using the FIFO algorithm. class FunctionQueue { // FIFO queue of function instances Function* ptrToFunction; // pointer to function FunctionQueue* next; // pointer to next node public: append(Function* fptr) { if (next == NIL) { ptrToFunction = fptr; next = new FunctionQueue(); } else next->append(fptr); Function* top() { return(ptrToFunction); } Boolean remove(Function* fptr) { FunctionQueue* ptr; if(ptrToFunction == fptr) { ptr = next; ptrToFunction = next->ptrToFunction; next = next->next; delete ptr; return(True) ; } else if (next !== NIL) { return(next->delete(fptr)); } else { return (False) ; } } FunctionQueue() { /* constructor */ ptrToFunction = NIL; next = NIL; } } ; Above is the definition of FunctionQueue. Implementation of FunctionQueue is straightforward. FunctionQueue provides three fundamental queue operations: append (), top (), and remove (). The append () operation creates a new node at 190 the end of the linked list. The top () operation simply returns the function pointer stored in the node at the top of the linked list. The remove () operation deletes a node from the linked list. The node to be removed is specified by the parameter. If some other scheduling algorithms are required, we can simply define an appro priate metaclass of functions. The example below shows metaclass LifoFunction which provides the LIFO scheduling using a stack instead of a queue. metaclass LifoFunction of Function { functionStack theStack; public: Function::Function() : () { theStack.push(this); } Function::-Function() : () { theStack.pop(); } Function* default() { return(theStack.top()) } } ; Whenever a new instance is created, the instance will be put on the top of the stack. Since the default function returns the function pointer at the top of the stack, the lat est request will be processed next. As a result, functions are scheduled in a LIFO manner. 191 4 .7 .4 . A synchronous Function Calls In C++, operation invocations are always synchronous, that is, whenever an object invokes an operation of another object, the invoking object will be suspended until the invoked object finishes the execution of the operation. In Galois, operation invocations can be either synchronous or asynchronous. When an instance of an operation invokes another operation synchronously, the invoking operation instance stays active while it is waiting for the completion of the syn chronously invoked operation. Thus, instances of other operations of the object cannot start. As a result, the object, as a whole, is suspended. Figure 4.42 shows an example of such synchronous function invocation. Assume an instance of op eration A of some object synchronously invokes operation P of another object. Then the instance of the operation A remains active while it is waiting for the com- invoking object invoked object Figure 4.42: Synchronous Function Invocation Ilium . ready state , executing . n ready state executing f ___ ^ executing state . ■ , busy waiting j i synchronous invocation 1 executing state operation P 192 destructor constructor Executing Terminated Invoked Ready wait() readyO Waiting Figure 4.43: Refined Function State Transition Diagram pletion of the instance of the operation P. Thus instances of other operations, such as operation B and C, cannot start until the instance of operation A completes. On the other hand, when an instance of an operation of an object invokes another operation asynchronously, the instance of the operation will be immediately put into the waiting state. As a result, while the instance is waiting for the completion of the asynchronously invoked function, instances of other operations can start. Figure 4.43 illustrates the state transition diagram of function instances which is a refine ment of Figure 4.36. When an executing instance makes asynchronous function in vocation, it will be put into the waiting state until the invoked operation is finished. When the invoked operation is finished, the waiting instance is put into the ready state and will be executed in accordance with the scheduling algorithm of the func tion template. As I mentioned earlier, the class Function defines operations the transition of the state of function instances. The below is the refinement of Function. Here, in ad dition to three operations: the constructor, the destructor, and the run () operation which we saw before, wait () and readyO operations are defined. The wait () 193 operation is executed when an executing function instance is put into the waiting state, while the ready () operation is executed when waiting function instances are put back into the ready state. class Function { functionState state; public: Function(); // constructor : invoked -> ready ~Function(); // destructor : executing -> terminated run(); // ready -> executing wait(); // executing -> waiting readyO; // waiting -> ready Function* default(); }; Figure 4.44 illustrates the difference against Figure 4.42 when an asynchronous function invocation is made. Here an instance of the operation A asynchronously invokes the operation P, but other conditions are same as Figure 4.42. The instance of the operation A is immediately put into the waiting state as it asynchronously in vokes the operation P. Then instances of other operations, such as operations B and C, can start. When the execution of the instance of operation P completes, the in stance of the operation A is put into the ready state. Then the ready instance of the operation A will be executed in accordance with the scheduling rule of its function template. In this case, the instance of the operation A cannot resume immediately, because an instance of the operation C is still running, when the instance of the op eration P completes. When the instance of the operation C finishes, the instance of the operation A will resume. 1 9 4 invoking object invoked object ready executing I I---------- 1 ready executing ^11 t t ■sin h u m m m i i m m tin executing waiting state ready executin, m i i i B t i i i u i m i i i e readv operation C operation B — I operation A asynchronous invocation executing state operation P Figure 4.44: Asynchronous Function Invocation The example below defines class spooler which includes an asynchronous func tion invocation, spooler consists of three operations: submit <), state (), and remove (). submit () will put the requested job into the printer queue, state () will display the list of jobs in the printer queue with job ID, and remove () kills a specified job in the printer queue. class spooler { printer thePrinter; // ... public: FifoFunction void submit() { 11 ... thePrinter.prints(); 11... } void state () { 11... } void remove(int jobID) { // ... 195 } ; Asynchronous function call is specified by inserting & between the function name and its argument list, such as: thePrinter.prints(); Since printing jobs take considerably long time, submit() invokes print() asyn chronously so that the spooler object can perform other tasks while it is waiting for the completion of the printing job. When submit() invokes thePrinter .prints () the execution of submit () is suspended until the execution of print () completes. But since submit () invokes print () asynchronously, the spooler object is still allowed to execute other operations. We may execute remove () and/or state () operations while we are waiting for the completion of the submit () operation. Furthermore, we can issue another request to execute submit () operation to the spooler object. Figure 4.45 shows a sample timing diagram of a spooler object. Assume three consecutive requests to print some documents are issued: (1) When the spooler object receives a request to submit job A, the spooler object starts execution of the submit () operation. (2) The spooler object may receive a request to submit another job B. Then the execution of such a request will be put into the ready state, because the spooler object is currently busy to execute the first submit operation for job A. 196 time print Job A thePrinter print print delete submit Job A submit Job B spooler object delete remove Job B print submit Job C H I— I (10) (6) (7) Figure 4.45: Timing Diagram of A Spooler Object (3) When the first submit () operation invokes the print operation asyn chronously, the submit () operation is put into the waiting state until thePrinter finishes the print () operation. Then the second submit () operation which was in the ready state is executed. (4) When the second submit < ) operation invokes the p t m t () operation asynchronously, the second submit () operation is put into the waiting state until thePrinter finishes the requested print () operation. In this case the print () operation itself is also delayed because the print () request by the first submit () operation is still in progress. (5) Meanwhile, the spooler object is allowed to execute other operations. In this case, the spooler object receives a request to remove the queued job B. The remove () operation is immediately executed and the job B which is currently in the waiting state is removed. 197 (6) The spooler object may receive a request to submit another job C. Then the submit () operation is put into the ready state because the remove () operation is in progress even if the first submit () operation is in the waiting state. (7) When the remove {) operation finishes, the third submit () operation is executed. (8) When the third submit () operation invokes the print () operation asynchronously, the third submit () operation is put into the waiting state until thePrinter finishes the requested print () operation. In this case the print () operation itself is again delayed because the print < ) request by the first submit () operation is still in progress. (9) When thePrinter finishes printing job A, the first submit () operation is reactivated. At the same time, the print () request by the third submit () operation is reactivated. (10) When thePrinter finishes printing job C, the third submit () operation is reactivated, and proceeds. When we take asynchronous function invocation into consideration, the class FifoFunction which I defined before need to be rewritten as follows. The FifoFunction has two different queues for ready and waiting instances respec tively. metaclass FifoFunction of Function { FunctionQueue readyQueue, waitQueue; public: Function::Function() : () { readyQueue.append(Function); 198 } Function::run() { readyQueue.remove(Function); run (); } Function::wait(){ waitQueue.append(Function); ready(); // remove from ready Q // execute it // put into waiting Q Function: :readyO { waitQueue.remove(Function); readyQueue.append(Function); readyO ; // remove from waiting Q // put into ready Q Function* default() { return(readyQueue.top()) } Figure 4.46 illustrates the scheduling mechanism implemented in FifoFunction: (i) When a function which is an instance of FifoFunction is invoked, a new func tion instance, denoted as a box, is created. At the same time, the instance is put into readyQueue which maintains a sequence of function instances in a FIFO fashion. These operations are performed by the constructor, (ii) The function instance which will be executed next is determined by the default function. In this case, the func tion instance which lies on the top of readyQueue will be executed first. The func tion instance to be executed is removed from readyQueue and executed by the run () operation, (iii) When the executing function instance completes, the instance is deleted by the destructor, (iv) During execution, the instance might be delayed by an asynchronous function invocation. Then, the function instance is put into waitQueue by wait () operation, (v) When the cause of delay disappears, the 199 Invoked new instance constructor □ f Ready readyQueue Executing Terminated (v) executing instance (ii) I — I (iii) run() I | deleted destructor readyO (iv) wait() waitQueue Waiting Figure 4.46: Execution of Fifo Functions function instance is removed from waitQueue and put into readyQueue by the readyO operation. 4.7.5. Background operations In C++, member functions of an object are invoked only when the object receives requests to execute the functions. In Galois, objects are allowed to execute their op erations without receiving any request. Such operations are called background op erations. Background operations of an object are executed periodically when the object is idle. In other words, an instance of a background function template is cre ated periodically. class personalPostOffice { mailBox myMailBox; void arrivalMessage(); // put arrival messages public: void sendMail(); // send a new mail void readMailO; // read mails background void checkMailO; // check mail arrival 200 }; mailBox::checkMail() { if(myMailBox.empty() == False) arrivalMessage() ; } The example above shows class personaiPostof f ice which provides three public Operations: sendMail (), readMail (), and checkMail (). sendMail () allows U S to send a new mail, readMail () allows us to read mails delivered to our mail box myMailBox, and checkMail () checks to see if our mail box is empty or not Here checkM ail () is defined as a background operation, while others not. checkMail () is executed periodically when the p ersonaiP ostof f ic e object is idle, while other operations are executed only when we invoke them. checkMail () checks to see if our mail box is empty or not, and if the mail box is not empty, it will put a message by invoking arrivalM essage (). It is also possible to pre-determine the time when a background operation is exe cuted by associating a precondition with the operation. c la s s p erso n a iP o sto ffice { mailBox myMailBox; v o id arrivalM essage() ; / / put a rriv a l messages hour previousCheck; p u b l i c : v oid sendMail (); / / send a new mail v oid readM ail(); / / read m ails background v o id checkM ail() p re co n d itio n {currentTim e( ) . hour != previousCheck} }; mailBox: : checkM ail() { if(myM ailBox. empty() == F alse) { arrivalM essage(); previousCheck = currentTime( ) .hour; 201 } } In the example above, checkMail () is associated with a precondition so that it is executed every one hour. The next section will discuss the preconditions and post conditions associated with operations. 4.8. Rule-based Features In addition to the scheduling mechanism embedded within the class of functions, Galois provides an alternate way to control the execution of function instances. 4.8.1. Preconditions and Postconditions Member functions can be associated with preconditions and postconditions. Preconditions will assure that the associated member function is executed only when the preconditions are met. The execution of a member function, which is as sociated with preconditions that are not met, will be delayed. In other words, func tion instances whose preconditions are not met are put into the waiting state. When the preconditions are satisfied, the instances will be put into the ready state, and will be executed with respect to the regular scheduling mechanism. When a background operation is associated with a precondition, the operation is invoked only when the precondition is met. Similarly, postconditions will assure that the postconditions are met after the execu tion of the associated member function. If the associated postconditions are not met 202 after the execution of the member function, the termination of the function will be delayed, that is, the function does not return the control to the invoking object. In other words, function instances whose postconditions are not met will be put into the waiting state. class CreditCard { int accountNumber; float balance; float creditLine; public: void purchase(float) precondition { balance<= =creditLine }; void payment(float) precondition{ balance>0 }; CreditCard(int, float); // constructor }; void CreditCard::purchase(float amount) { balance += amount); } void CreditCard::payment(float amount) { balance -= amount; } CreditCard::CreditCard(int an, float cl) { // constructor accountNumber = an; creditLine = cl; } The definition of class CreditCard can be seen above. The class CreditCard will be used to maintain a credit card account of individuals. Class CreditCard has three member functions: purchase (), payment (), and the constructor CreditCard (). The function purchase () is associated with a precondition balance<=creditLine 203 which states that purchase 0 can be successfully invoked only when balance is equal to or less than creditLine. The precondition prohibits card holders, whose credit card balance is over the credit line, from making additional purchase. Similarly, the function payment () is associated with a precondition balance>0 which states that payment () can be successfully invoked only when balance is positive. The precondition assures that card holders who have a credit balance do not make additional payment. 4.8.2. Preprocesses and Postprocesses Member functions can be associated with preprocesses and postprocesses. Preprocesses and postprocesses explicitly specify predecessor and successor rela tionships among member functions. When a preprocess is associated with a member function, the preprocess is exe cuted before the member function starts, whenever the member function is invoked. After the completion of the preprocess, the member function will start. Similarly when a postprocess is associated with a member function, the postprocess is exe cuted whenever the member function finishes. Furthermore, if operations executed by preprocesses and postprocesses are associated with their own preprocesses and postprocesses respectively, these preprocesses and postprocesses are also executed 204 recursively. As a result, preconditions establish backward chaining of operations, while postconditions establish/? rward chaining of operations. class PermitSales { // . . . public: CheckCitation(int usrld); IssuePermit(int usrld) preprocess{CheckCitation(usrld);} postprocess(CollectFee(usrld);}; CollectFee(int usrld); }; Class PermitSales will implement a system to be used in a parking office. PermitSales has three operations: CheckCitation () , IssuePermit (), and CollectFee (). Notice that IssuePermit () is associated with a preprocess and a postprocess. Whenever IssuePermit () is invoked, CheckCitation () is executed before IssuePermit () starts, and CollectFee () is executed after the execution of IssuePermit (). This will guarantee that all the citations are cleared before a new parking permit is issued, and the parking fee will be charged to the permit folder's account. When a precondition and/or a postcondition are used together with a preprocess and/or a postprocess, the precondition is used to trigger the preprocess and the postcondition is used to trigger the postprocess. This allows preprocesses and postprocesses to execute only when certain conditions are met. One of typical ex amples is error recovery. It is sometimes necessary to be able to avoid the delay caused by preconditions and postconditions. Preprocesses and postprocesses will serve as exception handlers. 205 Below shows a sample skeleton of a function with a precondition and a preprocess. returnType function(argumentList) precondition{ condition } preprocess{ process; }; The execution of the function above is equivalent to the following infinite loop: returnType returnValue ; whiled) { if( ! condition ) { process; } else { returnValue = function(argumentList); b r e a k ; } } The preprocess is repeatedly executed until the precondition is met. When the pre condition is satisfied, the function itself is executed and the loop is terminated. Below is a refinement of the class CreditCard which I defined before. class CreditCard { int accountNumber; float balance; float creditLine; void overlimitMessage(); void creditBalanceMessage() ; void financialChargeMessage(); public: void purchase(float) precondition{balance<=creditLine} preprocess{ overlimitMessage(); break; }; void payment(float) precondition{balance>0} 206 preprocess{ creditBalanceMessage(); break; } postcondition{balance<=0} postprocess{ financialChargeMessage(); break; } ; CreditCard(int, float); } ; void CreditCard;:overlimitMessage() { printf("You account is over your credit limit.\n"); printf("You are not allowed to make new purchase.\n"); } void CreditCard::creditBalanceMessage() { printf("You account has a credit balance.\n"); printf("Please do not pay.\n"); } void CreditCard::financialChargeMessage() { printf("You did not pay in full.\n”); printf("Unpaid balance is subject to financial charge.\n"); } void CreditCard::purchase(float amount) { balance += amount; } void CreditCard::payment(float amount) { balance -= amount; } CreditCard::CreditCard(int an, float cl) { accountNumber = an; creditLine = cl; } Function purchase () is associated with a preprocess overlimitMessage (). If purchase () is invoked when balance is greater than creditLine, overlimitMessage () will be executed without any delay. overlimitMessage () will put a warning message saying that the card holder is not allowed to make new purchase. The preprocess of purchase () includes a break statement. This is nec- 207 essary in order to avoid the execution of the purchase () function after executing the overlimitMessage <). The execution flow of purchase () is equivalent to the following: while (1) { if( ! balance <= creditLine ) { overlimitMessage(); // error message break; // exit from the loop } else { purchase(...); break; } } The break statement after overlimitMessage () enable us to exit from the loop without executing overlimitMessage () repeatedly. Sim ilarly, function payment () is associated with a preprocess creditBalanceMessage () and a postprocess f inancialChargeMessage (). If balance is not positive when payment () is invoked, creditBalanceMessage () will be executed. It will put a warning message saying that the payment is not nec essary. If balance is Still positive after the execution of payment () , f inancialChargeMessage () will be executed which also puts a message saying that the unpaid balance is subject to the financial charge. 4 .8 .3 . Classical C oncurrency Problem s In this section, I will present solutions in Galois to classical concurrency problems. This section is provided to demonstrate that the Galois' parallel programming ca pabilities are powerful enough to elegantly solve the classical problems. 208 Below is a solution to the bounded-buffer problem in Galois. The bounded buffer, defined by class buffer, provides two operations: put () and get (). The put () operation will store a new integer into the buffer, while the get () operation will remove an integer in the buffer and return it. Integers will be stored and removed in a FIFO order. class buffer { int *buf ; int n; // int in; // int out; // public: void put(int) precondition{ n < max }; int get() precondition{ n > 0 }; buffer(int max); // constructor }; buffer::buffer(int max) { // constructor buf = (char*) malloc(max*sizeof(int)); n = 0 ; in = 0 ; out = 0; } void buffer::put(int c) { buf[in] = c; // put a new integer into the buffer in = (in + 1) % max; // increment to the next empty slot n++; // increment the number of integers } int buffer::get() { int c; c = buf[out]; out = (out +1) % max; // increment to next integer n— ; // decrement the number of integers return c; // return an integer in the buffer } When the buffer is full, requests to the put () operation are queued, because a pre condition: 209 n < max is associated with the put () operation, n indicates the number of integers in the j buffer, while max indicates the capacity of the buffer. They are initialized to zero by the constructor. When the precondition becomes True, one of the queued requests is chosen non-deterministically, and is executed. On the other hand, requests to the get () operation will be delayed if the buffer is empty, because a precondition: n > 0 is associated with the get < ) operation. When the buffer becomes non-empty, one of the queued requests is chosen non-deterministically, and is executed. One of the most significant advantages of using Galois against other parallel lan guages is in its scheduling capability. In the solution given below, multiple requests to execute put () and get () operations are processed in the order they arrive at the buffer. The FIFO scheduling mechanism can be easily obtained by simply declaring the put () and get () operations as instances of Fif oFunction. class FifoBuffer { int *buf; int n; // number of integers in buffer int in; // index to next empty slot in the buffer int out; // index of next integer in the buffer public: FifoFunction void put(int c) precondition{ n < max }; FifoFunction int get() precondition! n > 0 }; FifoBuffer(int max); // constructor 210 FifoBuffer::FifoBuffer(int max) { buf = (int*) malloc(max*sizeof(int)); n = 0; in = 0; out = 0; void FifoBuffer::put(int c) { buf[in] = c; // put a new integer into the buffer in = (in + 1) % max; // increment to the next empty slot n++; // increment the number of integers } int FifoBuffer::get() { int c; c = buf[out]; out = (out + 1) % max; // increment to the next integer n— ; // decrement the number of integers return c; // return an integer in the buffer Below is a solution to the producer-consumer problem is Galois. In the solution, I use the bounded buffer defined above. The send () operation of the producer pro cess, defined by class producer, sends a character obtained from the standard in put to the bounded buffer, while the receive () operation of the consumer process, defined by class consumer, obtains a character from the bounded buffer and puts it to the standard output. Both send () and receive () are background operations. The producer process and the consumer process repeatedly send and receive charac ters through the bounded buffer. The producer process asynchronously invokes the put () operation of the bounded buffer. Therefore, other activities of the producer process will not be delayed, even if the put () operation is queued by the bounded buffer when the buffer is full. Similarly, the consumer process asynchronously invokes the get () operation of 211 the bounded buffer. Therefore, other activities of the consumer process will not be delayed, even if the g et () operation is queued by the bounded buffer when the buffer is empty. class producer { // producer process buffer *buf; public: background void send() { int c ; c = getchar(); buf->put&(c) // asynchronous call } producer(buffer *b) { // constructor buf = b; } } ; class consumer { // consumer process buffer *buf ; public: background void receive() { int c; c = buf->get&(); // asynchronous call putchar(c); } consumer(buffer *b) { // constructor buf = b; } }; main() { buffer buf ; // create a bounded buffer producer aProducer(&buf); // create a producer consumer aConsumer(&buf); // create a consumer n ... } 212 Chapter__________ 5 Cooperative Working 5.1. Cooperative Working Software development is a multi-agent activity. Software development projects in volve multiple number of software engineers who are working together to accom plish a single goal. Furthermore, software development projects are heterogeneous. Each software development project involves various kinds of software engineers. Those who, required to accomplish a single software development project, include not only programmers, but also managers, system analysts, designers, quality as surance groups, and so on. As a result, resources in software development envi ronments as well as software processes are shared among these multiple and heterogeneous software engineers. In order to coordinate multi-agent and heterogeneous software development, pro cess programs must carry the following capabilities: 213 (1) Coordinate Shared Processes There might be multiple programmers working in a single instantiated pro cess. There need coordination among these co-working programmers. Furthermore, a single process template may be instantiated two or more times in a single project, or even in multiple projects. For instance, a pro cess template for debugging may be instantiated for different bugs. Therefore, it is necessary to be able to coordinate these multiple instances of a single process template. (2) Coordinate Shared Software Resources It commonly happens that a single resource is shared by several program mers and/or processes. Two programmers might work on different bugs which reside in a single module. This will result in the sharing of the mod ule by these programmers. But not all resources admit simultaneous access to them. For instance, simultaneous update of a single source files by two or more programmers will result in chaos. It is essential that process pro grams are capable of providing mechanisms for coordinating the simultane ous access to resources so that they can be properly shared by multiple pro grammers. In this chapter I will develop several process programs which are capable of coor dinating shared processes as well as resources. The rest of this chapter is divided into two sections. The first section is devoted to discussing process programs which coordinate multiple processes. And the second section is devoted to dis cussing process programs which coordinate shared resources. 214 5 .2 . M a n a g in g P ro c e s s e s 5 .2 .1 . M anaging M ultiple Users In this section, I will give a sample process program which defines a software pro cess that can be used by two or more programmers. The process defines a login/logout mechanism like the Unix shell. Before accessing commands of the process, users are required to execute login () command. Only one user is al lowed to log into the process at a time, and the operations in the process are avail able only to the logged-in users. After finishing their activities, the users will exe cute logout () command, so that other users can log into the process. The skeleton of class Process which defines such processes is shown below. class Process { UserRecord userList; // list of registered users Boolean loggedin; // user logged in ? string suPWD; // password of superuser Boolean suMode // super user mode ? void message(string) ; // // .. resources for the process // public: void login(string userlD, string password) precondition{ loggedin == False } preprocess( message("Process in use"); break; }; // // user operations guarded by "loggedin" // will be defined here // void logout() precondition{ loggedin == True } preprocess{ message("Invalid Command"); break; }; void su(string userlD, string password) 215 } ; precondition{ loggedin == False }; preprocess{ message("Process in use"); break; }; void newUser(string userlD, string password) precondition{ loggedin == True && suMode == True } preprocess( message("permission denied"); break;}; void removeUser(string userlD, string password) precondition} loggedin == True && suMode == True } preprocess( message("permission denied"); break;}; Process(string password) { // constructor suPWD = password; // password for superuser loggedin = False; // originally no user logged in suMode = False; } -Process () // destructor precondition{ loggedin == False }; The class definition above illustrates the typical structure of process templates. Each process template declares a set of resources which will be manipulated within the process, and a set of operations to manipulate the declared resources. Operations are associated with preconditions and preprocessed so that they could be invoked only when certain conditions are met. In addition to the resources, each process template declares several data structures which will be used to coordinate the activi ties performed within the process. In the example above, the process template in cludes four members: userList, loggedin, suPWD, and suMode. Each process instance keeps the list of users who are allowed to log into the process along with their user ID and password. The list of users is stored in the member userList. userList is defined by class userRecord which will be discussed later. userList is used by the login () operation in order to determine the user issuing a login request is properly registered or not. The definition of the login () 216 operation is given below. The login () operation takes two arguments: userlD and password which need to be supplied by the user who wants to login. Then the login () operation determines if the user is properly registered or not by invoking find () operation of userList. If the user is properly registered, his login request is granted, otherwise it is denied. void Process::login(string userlD, string password) { if( userList.find(userlD, password) ) // registered ? loggedin = True; else message("You are not registered"); } The login () operation is associated with a precondition: loggedin == False indicating that the login () operation can be invoked only when the member loggedin is equal to False. The member loggedin, which is originally set to False by the constructor Process () , is set to True by the login () operation, and is set to False by the logout () operation, which is defined below. Therefore, once a user is logged into a process instance, other users are not allowed to log into the same process instance until the logout () operation is executed. void Process::logout() { loggedin = False; suMode = False; ) Furthermore, the login () operation is also associated with a preprocess: preprocess( message("Process in use"); break; } 217 Consequently, if the login () operation is invoked when the associated precondi tion is not met, a message saying that the process is currently used by another user will be displayed. In order to maintain the list of registered users, the process template provides two operations: newUser () and removeUser (). These operations are available only to the superusers of the process. When each process is created, a password for the superuser is specified, and is stored in the member s u p w d . Those who know this password can be a superuser. In order to log into a process instance as a superuser, we must execute su () opera tion. The definition of the su () operation is given below, su () asks the password for the superuser. If the password supplied agrees with s u p w d , then the request is granted, otherwise the request is denied. Also notice that su () command is associ ated with the same precondition as the login () operation. Therefore, only one regular or super user can log into each process instance at a time. The su () opera tion also sets suMode to True indicating that the current user is a superuser. void Process::su (string userlD, string password) { if(password == suPWD ) { loggedin = True; suMode = True; } e l s e Message("Superuser request was denied"); } The newUser o and removeUser () operations are associated with the same pre conditions: 218 loggedin == True & & suMode == True The members: loggedin and suMode are originally set to False by the construc tor, and are set to True by the su () operation. Therefore, these operations are available only to the superusers. The implementation of newUser () and removeUser () is straightforward. newUser o adds a new user to the registered user list userList by invoking the append () operation, while removeUser () deletes a user specified by the argu ments from the user list by invoking the remove () operation on userList. void Process::newUser(string userlD, string password) { userList.append(userlD, password); } void Process::removeUser(string userlD, string password) { userList.remove(userlD, password); } Below is the definition of the class UserRecord which determines the behavior and the structure of userList. The class UserRecord defines a linked list of registered users. Each node in the linked list keeps the user ID and his or her password, as well as the pointer to the next node. class UserRecord { string userlD; // user ID string password; // password fro the user UserRecord *next; // pointer to the next node public: void append(string u, string p) ; // append a new user void remove(string u, string p); // remove a user Boolean find(string u, string p); // find a user in the list 219 UserRecord() { // constructor next = NIL; } }; The class UserRecord provides four operations: append (), remove () , find () , and the constructor UserRecord (). The definition of these operations is given be low. The append () operation first traverses the linked list, on which the append () op eration is invoked, until the terminal node is encountered by invoking itself recur sively. Once the terminal node is reached, it adds a new user node at the end of the linked list. void UserRecord::append(string u, string p) { if(next) // non terminal node ? next->append(u, p); else { userlD = u; password = p; next = new UserRecord; } } The remove () operation removes the user node specified by the arguments. The remove () operation first searches the user node specified by the two arguments: u and p indicating the user ID and the password respectively. Like the append () op eration, the searching is performed by invoking the remove () operation recur sively. If the user node is found in the linked list, the node is removed and the linked list is shrunk. void UserRecord::remove(string u, string p) { UserRecord *node; // node to be removed 220 if(next) // non terminal node ? if (userlD == u && password == p) { node = next ; userlD = next->userID; password = next->passWord; next = next->next; delete node; } else next->remove(u, p); } } The find () operation searches the user node specified by the arguments, and re turns True if the user node is found in the list, otherwise it returns False. Boolean UserRecord::find(string u, string p) { if(next) // non terminal node ? if(userlD == u && password == p) return(True); else return(next->find(u, p) ; else return(False) ; } 5.2,2, Managing Multiple Processes In the previous section, I introduced a mechanism which guarantees that only one user resides in each process instance at a time, like the Unix shell. The mechanism was used to coordinate activities performed by multiple users who reside in a single process instance. On the contrary, the mechanism, which I will present in this sec tion, is capable of monitoring the activities performed on multiple instances of a single process template. The mechanism is used to coordinate activities performed on different process instances. The mechanism to monitor the activities performed on multiple instances of a pro cess template is implemented on the process template itself. Process templates are 221 manager Tom 1 f process template ptrToProcess next userlD status process instance idle process nstanc unused active unused defctijc prSpess; nstanc process instance Jack NIL Jack NIL su Figure 5.1: Keeping Track of Process Instances objects which can perform such monitoring activities. Class processManager de fines the behavior of the process template as an object. Process templates which are instances of processManager are able to keep track of the history of their in stances, such as (i) how many instances are created; (ii) when each instance was started; (iii) who is currently logged in each instance; (iv) when each instance was terminated, and so on. The definition of ProcessManager is given below. Each instance of ProcessManager, which is a process template, keeps track of its process instances using a linked list as illustrated in Figure 5.1. Each node in the linked list holds the pointer to the process instance, the pointer to the next node in the linked list, the name of the user of the process instance, and the state of each process instance. A new node will be created in the linked list whenever a new instance of the process template is created. The ProcessManager class has a private member processList which is an instance of class ProcessRecord. The class ProcessRecord defines 222 Instance ID created deleted status current user 102993 Dec 20, 89 n/a active Tom 204483 Jan 10, 90 n/a idle unused 110234 Jan 20, 90 Feb 10, 90 terminated unused 956423 Jan 25, 90 n/a superuser mode Jack Figure 5.2: Process List the structure of the linked list described above. The definition of ProcessRecord will be given later. xnetaclass ProcessManager of Process { ProcessRecord processList; public: void listProcess {); // list all process instances Process::Process(); // refinement of constructor Process::-Process(); // refinement of destructor Process::login(string string); // refinement of login() Process::su(string, string); // refinement of su() Process::logout(); // refinement of logout() }; The class ProcessManager has its own operation: listProcess (). The operation listProcess () will produce a list of all the instances of the process template on which the listProcess () operation is invoked. Figure 5.2 gives a sample output of the listProcess () operation. The listProcess () operation simply invokes the print () operation of processList. void ProcessManager::listProcess() { processList->print() ; ) In addition to defining its own operation, ProcessManager refines several opera tions of its instances so that it can keep track of the state of each instance. The con- 223 stractor Process () is refined so that the new node is created in the member processList upon the creation of a new instance of the process template. ProcessManager::Process::Process(string password) : (password) { processList.append(Process) ; // add a new node } Similarly, Process:: -Process () refines the destructor of the process template. Whenever a process instance is deleted, the date of termination is recorded in the corresponding node. And the state of the process will be changed to "terminated". The corresponding node is searched by invoking find () operation on processList. The find() operation uses the address of the process instance for the search key. Notice that each node is not deleted even if the corresponding process instance is deleted. This will make possible to keep the permanent records of process instances. ProcessManager::Process::~Process() { ProcessRecord* node; node = processList.find(Process); // find the node node->setState("terminated");// change to terminated state node->setFinishDate(); // record termination date } ProcessManager also refines login (), su (), and logout () operations, because the execution of these operations changes the state of each process instance, and the changes need to be recorded in the corresponding node in processList. login o operation is refined, as below, so that whenever a user logs into a process instance, the user name is recorded, and the state of the process will be changed to "active 224 ProcessManager::Process::login(string u, string p) : (u, p) { ProcessRecord* node; node = processList. find (Process) ; // find the node node->setUser(u); // record the user name node->setState("active"); // change to active state Similarly, su () operation is refined so that whenever a superuser logs into a pro cess instance, the name of the superuser is recorded, and the state of the process will be changed to "superuser mode": ProcessManager:rProcess::su(string u, string p) ; (u, p) { ProcessRecord* node; node = processList. find (Process) ; // find the node node->setUser(u); // record the user name node->setState("superuser mode"); // change to superuser mode } logout () operation is also refined so that whenever a user logs out from a process instance, the user name is removed, and the state of the process instance is changed to "idle": ProcessManager::Process::logout() : () { ProcessRecord* node; node = processList. find (Process) ; // find the node node->setUser("n/a"); // remove the user name node->setState("idle") ; // change to idle state The definition of the class ProcessRecord which determines the behavior and the structure of processList is given below. Like the UserRecord class which I de fined in the previous section, the ProcessRecord class establishes a linked list. Whenever a new process instance is created, a corresponding node in the list is 225 created. Each node in the list points to the corresponding process instance, and keeps several information regarding the process instance. class ProcessRecord { Process* ptrToProcess; // pointer to process string userlD; // user of process date start; // start date of process date end; // finish date of process string status; // status of process ProcessRecord *next; // pointer to the next node public; void append(Process* p); void print(); ProcessRecord* find(Process* p); void setState(string); void setUser(string) ; void setFinishDate() ; ProcessRecord() ; // constructor The class ProcessRecord has six members: ptrToProcess, userlD, start, end, status, and next. ptrToProcess is the pointer to the corresponding process in stance for which the node is created. userlD keeps the ID of the user who is cur rently working in the process instance, start and end will keep the time when the process instance is created and deleted respectively, status will keep the current status of the process instance, such as "started”, "terminated", and so forth, next keeps the pointer to the succeeding node in the linked list. The class ProcessRecord has seven member operations: append () , print (), find () , setState () , setUser () , setFinishDate () , and the constructor 226 ProcessRecord(). The constructor ProcessRecord () simply creates an empty ProcessRecord::ProcessRecord() { // constructor ptrToProcess = NIL; next = NIL; } The append () operation creates a new node at the end of the linked list. The pointer to the process instance, which is passed by the argument, is recorded in ptrToProcess and the current date is assigned to start indicating that the process is created at that time. The status is set to "idle" which indicates that the process is created but no user is logged into it yet. ProcessRecord::append(Process* p) { if(!next) { // terminal node ? ptrToProcess = p; userlD = "unused"; start = currentDate (); end = 0; status = "idle”; next = new ProcessRecord(); } e l s e next->append(p); } The print () operation generates a list of process instances along with information stored in the each node. ProcessRecord::print() { if(next) { printf("Instance ID : %s\n", ptrToProcess); printf("created : %s\n", start); printf("deleted : ") ; if(end == 0) printf("n/a\n") ; else printf("%s\n", end); 227 printf("status : %s\n", status); printf("current user : %s\n", userlD); next->print(); } } The find () operation looks for the node in the linked list which points to the pro cess instance specified by the argument. If such a node is found, the address of the node is returned, otherwise nil is returned. ProcessRecord *UserRecord::find(Process *p) { if(next) // non terminal node ? if(ptrToProcess == p) return(this); else return(next->find(p)); else return(NIL); } The setstate () and setUser () will be invoked to change the status and useriD of each node. setFinishDate () will be invoked to record the date when each process instance is deleted. ProcessRecord;:setstate(string s) { status = s; } ProcessRecord::setUser(string u) { userlD = u; ) ProcessRecord::setFinishDate() { end = currentDate(); } Although I implemented the mechanism for monitoring the activities performed on multiple instances of a process template using Galois' metaclass, the readers might 228 I /process^ — I in stance^ process instance. process ( process^ instanceJ process instance processes for processes for banking system project library system project Figure 5.3: Flat Coordination of Processes using Static Members think that a similar mechanism could be implemented using static members in C++. But this is not true. The main difference is that metaclasses allow us to coordinate instances in a hierarchical fashion, while static members allow us to coordinate in stances only in a flat structure. A single process template might be used in multiple projects. Typical debugging process templates might be used in various projects, say a banking system project and a library system project. In this case, it is desirable to be able to coordinate pro cesses for each project independently. But, static members are shared by all the in stance a class. In other words, the static members cannot distinguish process in stances for the banking system project and those for the library system project. As a result, we are not allowed to coordinate process instances for the banking system project and those for the library system project independently. As Figure 5.3 illus trates, all the instances are coordinated by the single process template. 229 process instance template for banking template for library process instance processes for processes for banking system project library system project Figure 5.4: Hierarchical Coordination of Processes using A Metaclass On the other hand, a metaclass of a process template allows us to produce multiple replicas of the process template which are capable of coordinating their instances. We can produce different process templates for individual projects. One for the banking system project, while another for the library system project, as illustrated in Figure 5.4. As a result, process instances of the banking system project and those of the library system project can be monitored individually by the two process templates. Furthermore, we may give the metaclass itself capability of coordinating their instances, that is, the replicas of the process template. 5.3. Modeling Shared Resources It commonly happens that a single resource is shared by several processes. A single programmer might be involved in two or more software development project, and a single source program might be used in two or more systems. It is necessary to 230 Programmer Category Project A Project B Project C Sam Full-time 50% 20% 30% Tom Full-time 30% 0% 20% John Half-time 0% 50% 0% Figure 5.5: Sharing of programmers provide a natural notation representing a single resource being shared by two or more processes. 5 .3 .1 . Representing Shared Resources Consider, as an example, the sharing of a single programmer by two or more pro jects in some company illustrated in Figure 5.5. Three programmers Sam, Tom and John are working on three project A, B, and C. Sam is a full-time programmer, and working 50% on the project A, and 20% and 30% on the project B and C respec tively. Tom is also a full-time programmer, and is working 30% for the project A and 20% for the project C. He still has 50% free time. John is a half-time pro grammer, and is working 50% on the project B. Before going into the implementation details in Galois, consider how C++ fails to represent shared resources. Consider the following C++ classes worker and Pro jectWorker. class Worker { public: string workerName; int maxLoad; 231 int workLoad; /* constructor */ Worker(string name, int mLoad, int wLoad){ workerName = name; maxLoad = mLoad; workLoad = wLoad; } } ; class ProjectWorker : Worker{ public: string projectName; int projectLoad; /* constructor */ ProjectWorker(string wName, int mLoad, int wLoad, string pName, int pLoad) : (wName, mLoad, wLoad) /* passed to worker */{ projectName = pName; projectLoad = pLoad; } }; Workers in the company are represented by the class worker. The worker class has three members: workerName, maxLoad, and workLoad. workerName holds the programmer's name. maxLoad will indicate the maximum working load of each programmer. It can take the value between 0 and 100. For instance, maxLoad will be 100 for a full-time programmer, while it will be 50 for a half-time programmer. workLoad indicates the current working load of each programmer. The Worker class has also the constructor: worker (). The constructor takes three arguments which denote the Worker's name, the maximum load, and the current working load respectively. Then the following instantiation sequence will define objects which denote the three programmers above. Sam is initialized so that his 232 workerName is "Sam" and his maxLoad is 100. Tom and John are similarly initial ized. Worker Sam = Worker("Sam", 100, 100); // full-time worker Worker Tom = Worker("Tom", 100, 50); // full-time worker Worker John = Worker("John", 50, 50); // half-time worker The class Pro jectWorker is a subclass of the class worker which represents workers involved in some projects. The Pro jectWorker class has two local mem bers: projectName and projectLoad. projectName holds the name of the project in which the programmer is currently involved. projectLoad indicates the current working load of the programmer on the project denoted by projectName. The Pro jectWorker class has also a constructor Pro jectWorker () . The con structor takes six arguments: wName, mLoad, wLoad, pName, pLoad which specify the programmer’ s name, the maximum working load of the programmer , the cur rent working load of the programmer, the project's name, and the working load of the programmer on the project respectively. pName and pLoad are directly assigned to the corresponding local members. The constructor also invokes the constructor of its superclass worker to initialize members inherited from the superclass, in ad dition to initializing local members. The following instantiation sequence of Pro jectWorker will define the project de fined above. SamOnA is initialized so that his local members: projectName and projectLoad are "Project A" and 50 respectively, while the inherited members: workerName, maxLoad, and workLoad are "Sam", 100, and 100 respectively. 233 ProjectWorker SamOnA = ProjectWorker("Sam",100,100, "Project A",50); ProjectWorker SamOnB = ProjectWorker("Sam",100,100, "Project B”,20); ProjectWorker SamOnC = ProjectWorker("Sam",100,100, "Project C",30); ProjectWorker TomOnA = ProjectWorker("Tom",100,50, "Project A”,30); ProjectWorker TomOnC = ProjectWorker("Tom",100,50, "Project C",20); ProjectWorker JohnOnB = ProjectWorker("John",50,50, "Project B",50); But notice that this representation has a big problem which must be solved, as illus trated in Figure 5.6. First, we cannot represent the fact that some objects actually represent a single person, because all the objects instantiated above are different. For instance, Sam, SamOnA, SamOnB, and SamOnC are four different objects but rep resent a single person whose name is Sam. The representation above indicates that these three persons represented by SamOnA, SamOnB, and SamOnC have the same name, but we cannot conclude that these three persons are actually an identical per son. Second, each Pro jectWorker object has its own copy of members inherited from the superclass worker, as well as local members in the Pro jectWorker class. Thus a change to an inherited member in one object will not be reflected on other objects, even though the inherited members usually represent common characteris tics of all the objects of that class. For instance, the maxLoad on Sam, SamOnA, SamOnB, and SamOnC should always keep the same value. But, even if the maxLoad member of Sam is changed to 50%, the modification will not be reflected on 234 SamOnA SamOnB SamOnC / s projectName Project A / projectLoad 50% / workerName !§ jf§ tltl§ / maxLoad 100 % / workLoad 100 % / / y Project B / 20% / i 100 % i L O O % / / / Project C / 30% / Sam / 100 % / 1G O % f Sam / y Sam / workerName 100 % / maxLoad 100 % / workLoad Figure 5.6: Representation of Resource Sharing using Subclasses in C++ SamOnA, SamOnB, and SamOnC. Since these objects represent a single person, the modification to Sam should be reflected on the other related objects at the same time. Third, notice that workLoad need to be dynamically changing. Whenever a pro grammer is allocated to a project, or released from a project, the value of workLoad should be incremented or decremented by an appropriate value. Or, when pro jectLoad is modified on some object, workLoad of all the related objects should be recomputed. workLoad is defined to be the sum of the related Pro jectWorker objects. For instance, workLoad of Sam as well as SamOnA, SamOnB, and SamOnC Should be Computed as the sum Of pro jectLoad Of SamOnA, SamOnB, and SamOnC. And, whenever pro jectLoad is modified on some object, workLoad of all the related objects should be recomputed. But such coordination cannot be achieved. Galois can represent this situation both faithfully and elegantly using a derived class. Figure 5.7 illustrates how Galois represents the Pro jectWorker example in troduced above. Here, SamOnA, SamOnB, and SamOnC are defined as children of 235 SamOnB Project B 20% SamOnA I child of / ............. / l l l i i l l ✓ ............. / Project A Project C 100% / child of child of 30% / / / SamOnC Sam Figure 5.7: Representation of Resource Sharing in Galois Sam. As a result, SamOnA, SamOnB, and SamOnC do not have their own copy of members workerName, maxLoad, and workLoad of Sam, while in the C++ example above they keep their own copy of the members as depicted in Figure 5.6. Rather, they share the object Sam. Sam is considered as a part of SamOnA, SamOnB, and samOnc. Furthermore, all the public members and operations of Sam are directly ac cessible through SamOnA, SamOnB, and SamOnc. Requests for executing these op erations and/or accessing public members to these objects will be forwarded to Sam, and will be carried out there. Users of derived objects need not distinguish whether the necessary information is on the children or the parent. Below is the definition of the Programmer and Pro jectProgrammer classes in Galois which are refinement of the Worker and Pro jectWorker classes seen in the C++ example above. Here, the Pro jectProgrammer class is defined as a derived 236 class of the Programmer class, while the Pro jectWorker class was defined as a subclass of the Worker class. class Programmer { friend class ProjectProgrammer; int workLoad; public: string programmerName; int maxLoad; Programmer(string myName, int myMaxLoad) { // constructor programmerName = myName; maxLoad = myMaxLoad; workLoad = 0; } } ; Programmer ProjectProgrammer { public: string projectName; int projectLoad; ProjectProgrammer (string pName, int load) {// constructor projectName = pName; projectLoad = load; workLoad += load; // workLoad in programmer class } -ProjectProgrammer () { // destructor workLoad -= projectLoad; // workLoad in Programmer } } ; The program segment below will create Programmer objects Sam, Tom, and John to be shared among several projects. The definition of Sam, Tom, and John indicates that they are objects of the Programmer class. Programmer Sam = Programmer("Sam", 100); Programmer Tom = Programmer("Tom", 100); Programmer John = Programmer("John", 50); 237 Objects of derived classes are created by derivations rather than instantiation. The program segment below will generate objects of the Pro jectProgrammer class which share the objects O f the Programmer class created above. SamOnA, SamOnB, and SamOnc are derived from the object Sam, rather than instantiating their class Pro jectProgrammer. This is necessary in order to establish the special relationship between the children and the parent which indicates that the children and the parent actually represent the same person. If SamOnA is created by simply instantiating its class, it cannot know its parent. We establish a child-of relationship between SamOnA, SamOnB, SamOnC and Sam by Creating SamOnA, SamOnB, and SamOnC from Sam. As a result, these three objects share the object Sam. ProjectProgrammer SamOnA = Sam.ProjectProgrammer("Project A", 50); ProjectProgrammer SamOnB = Sam.ProjectProgrammer("Project B",20); ProjectProgrammer SamOnC = Sam.ProjectProgrammer("Project C",30); ProjectProgrammer TomOnA = Tom.ProjectProgrammer("Project A",30); ProjectProgrammer TomOnC = Tom.ProjectProgrammer("Project C",20); ProjectProgrammer JohnOnB = John.ProjectProgrammer("Project B",50); The Programmer class has constructor Programmer () , and the Pro jectProgrammer class has constructor Pro jectProgrammer () and destructor ~Pro jectProgrammer (). Notice that the constructor Programmer () initially sets the member workLoad to 0. And the value of workLoad is incremented and decre mented by'the constructor and the destructor of the Pro jectProgrammer class re 2 3 8 spectively. Whenever an object of the Pro jectProgrammer class is derived from a Programmer object, the constructor of the Pro jectProgrammer class increments workLoad of the Programmer object. And whenever a Pro jectProgrammer object is deleted, the destructor "Pro jectProgrammer () decrements workLoad of the Programmer object. Remember that a derived class is allowed to access public members of its parent class, but not private members. But in this case, since the Programmer class desig nates the Pro jectProgrammer class as its friend class, the Pro jectProgrammer class is allowed to directly access every member of the Programmer class. Therefore, the constructor and the destructor of the Pro jectWorker class are al lowed to increment or decrement the value of workLoad which is a private member of the Programmer class. Furthermore, since workLoad is private, workLoad is protected from any other malicious modification. Therefore, workLoad can always keep the correct working load of each programmer. In the example above, when Sam object is created, workLoad of Sam is initially 0%, as illustrated in Figure 5.8 (a). This indicates that Sam is not involved in any pro ject. When SamOnA is created, workLoad of Sam is incremented to 50% by the con structor Pro jectWorker o , as illustrated in Figure 5.8 (b). Similarly, when SamOnB is created, workLoad of Sam is incremented by 20% the constructor Pro jectProgrammer (). As a result, workLoad of Sam gets 70%, as illustrated in Figure 5.8 (c). Inversely, if SamOnA is deleted, workLoad of Sam will be decre- 2 3 9 j SamOnB Sam y / Sam / 100% / 0% / (a) Initial construction Sam SamOnA Project A 50% child of y y Sam / 100% / 50% y (b) SamOnA is derived from Sam Project B child of SamOnA y y Project A / 50% y child of Sam 100% Sam (c) SamOnB is derived from Sam Figure 5.8: Coordinating Shared Resource in Galois mented by 50%. Similarly, if SamOnB is deleted, workLoad of Sam will be decre mented by 20%. The readers might think that similar effect can be achieved by use of explicit links among related objects. But this is not true. For instance, the Pro jectWorker class may have a member ptrToWorker which is a pointer to a Worker object. Each Pro jectWorker object will be linked to an appropriate worker object by this pointer. The link will be established by the constructor. class Worker! friend class Pro jectWorker ; int workLoad; public: string workerName; int maxLoad; /* constructor */ 240 Worker(string name, int mLoad){ workerName = name; maxLoad = mLoad; workLoad =0; // initially NO job is assigned } } class ProjectWorker { public: string projectName; int projectLoad; Worker* ptrToWorker; // link to a worker object /* constructor */ anotherProjectWorker(string pName,int pLoad,Worker *ptr){ ptrToWorker = ptr; projectName = pName; projectLoad = pLoad; ptrToWorker->wLoad +=pLoad; } /* destructor */ anotherProjectWorker() { ptrToWorker->wLoad -=pLoad; } This approach establishes a very weak link among related objects, but a tighter rela tionship is desired. ProjectWorker SamOnA = ProjectWorker("Project A", 50, &Sam); ProjectWorker SamOnB = ProjectWorker("Project B", 20, &Sam); ProjectWorker SamOnC = ProjectWorker("Project C", 30, &Sam); ProjectWorker TomOnA = ProjectWorker("Project A", 30, &Tom); ProjectWorker TomOnC = ProjectWorker("Project C", 20, &Tom); ProjectWorker JohnOnB = ProjectWorker("Project B", 50, SJohn); In this approach, no information is duplicated. But accessing the shared information is very tedious. Users always need to know the location where the information is stored. For instance, users must understand that workerName is stored in a worker object, while projectName is stored in a Pro jectWorker object, and so forth. 241 Furthermore, in order to access workerName of Sam through SamOnA, we must write: SamOnA.ptrToWorker->workerName I Whenever users need to access members in Sam, they always need to recognize the I existence of the pointer object ptrToWorker. Also notice that the static member cannot solve this problem. This is because a static member is shared by all the objects of the class. A static member of a class in C++, which corresponds to a class variable in Smalltalk [40], is shared by all the objects of the class. class Worker{ public: static string workerName; static int maxLoad; static int workLoad; /* constructor */ Worker(string name, int load){ workerName = name; maxLoad = load; } In this case, if we define workerName, maxLoad, and workLoad to be static as above, these members are shared by all the objects, that is, not only by SamOnA, SamOnB, SamOnC, but also TomOnA and JohnOnB. workerName and maxLoad on SamOnA should be ’ 'SamM and 100% respectively, while those on JohnOnB should be "John” and 50% respectively. // shared by all instances // shared by all instances // shared by all instances 242 5 .3 .2 . C oordinating Shared Resources In the previous section, I introduced the fundamental way of modeling shared re sources. Derived classes allow us to effectively share resources. But, there are still several problems need to be solved. Go back to the example seen in Figure 5.5. Remember that Figure 5.5 shows the sharing of three programmers among three projects. The first question is how to represent the membership of programmers to their pro jects. Sam and Tom are working on the project A, while John does not. I already i tried to represent the membership relationship in a very weak form. Objects which ! represent programmers are named so that we can infer the project on which the pro grammers are working. TomOnA and SamOnA indicate that both Tom and Sam are working on the project A, while JohnOnB indicates that John is working on the project B. Furthermore, these objects include a member which refers to the project name on which they are working. For instance, the projectName member of the object SamOnA is "project A", while the member projectName of the object JohnOnB is "project b", and so forth. One of the problems of this approach is that the establishment and coordination of the membership relationship are totally left to process programmers, and no support can be obtained from the process pro gramming environment and the process programming language processor. The second issue is how to coordinate the programmers working for the same pro ject. The mechanism which I introduced in the previous section provided the way of 243 coordinating programmers from the resource manager's point of view. Derived classes allow us to coordinate all the children of a single resources. For instance, we can compute the total working load of individual programmer who is working for two or more projects. But this mechanism does not allow us to compute either the total working load of all the programmers of a single project, or the number of programmers working in the project. We need to coordinate programmers from the project manager’ s point of view, as well as the resource manager's point of view. To attack these problems, several different approaches are conceivable within the scope of C++. We may define a new class in order to define a set of objects. Fundamentally, a class in C++ is a record type and is an aggregation of objects of other classes. Thus, C++ inherently allows composite objects to be defined. For instance, Pro jectA class might be defined as a pair of SamOnA and TomOnA in order to represent that SamOnA and TomOnA are working for project A. But the problem here is that all the members of a class must be completely determined in advance, 1 I and the membership cannot be changed dynamically. I class ProjectA { ProjectWorker SamOnA; ProjectWorker TomOnA; n ... class ProjectB { ProjectWorker SamOnB; ProjectWorker JohnOnB; II ... Another approach is the use of subclasses. We may define subclasses of ProjectWorker:ProjectAworker, ProjectBworker, and ProjectCworker 244 projectWorker class projectAworker class SamOnA projectBworker class TomOnA SamOnB projectCworker class JohnOnB, SamOnC TomOnC Figure 5.9: Representing Group of Objects using Subclasses which indicate the sets of programmers working for project A, project B, and pro ject C respectively. Then, SamOnA and TomOnA will be defined as objects of ProjectAworker class, SamOnB and JohnOnB will be defined as an object of Pro jectBworker class, and so on. Figure 5.9 illustrates the grouping of objects by the use of subclasses. ProjectAworker SamOnA, TomOnA; ProjectBworker SamOnB, JohnOnB; ProjectCworker SamOnC, TomOnC; This approach has a great advantage against the naming convention approach, be cause these relationship between objects and their classes can be subject to the type checking capability of the process programming language processor. Programmers on different projects are not interchangeable. Access from the project A should be performed on ProjectAworker objects, such as SamOnA, TomOnA, but not JohnOnB. The typing system will assure only the appropriate objects are accessed from the projects. Furthermore, the membership relationship between objects and 245 their classes is dynamic. We need not completely determine the members of projects in advance. But this approach still lacks the capability of coordinating the program mers of an individual project. In Galois, this can be represented more elegantly using a metaclass. Instead of defining three different subclasses of the Pro jectProgrammer class, I define a metaclass Pro j e ctP rograiranerGr oup of Pro jectProgrammer. Each object of Pro jectProgrammerGroup will represent the collection of programmers working for a single project. metaclass ProjectProgrammerGroup of ProjectProgrammer { string projectName; int totalWorkingLoad; int numberOfProgrammers; public: int totalLoad(); int numberOfMembers(); / / ... ProjectProgrammer::ProjectProgrammer(string, int ); ProjectProgrammer::-ProjectProgrammer(); For instance, I may define Pro jectAprogrammer, Pro jectBprogrammer, and P ro j ectCprogrammer to be objects of P ro j ec t P r ogrammerGroup. Pro jectAprogrammer, Pro jectBprogrammer, and Pro jectCprogrammer repre sent the collections of all the programmers in project A, project B, and project C re spectively. ProjectProgrammerGroup ProjectAprogrammer; ProjectProgrammerGroup ProjectBprogrammer; ProjectProgrammerGroup ProjectCprogrammer; 246 As a class, Pro jectAprogrammer, Pro jectBprogrammer, and Pro jectCprogrammer are equivalent to Pro jectProgrammer. Therefore, SamOnA, SamOnB, and SamOnc can be derived from Sam, and TomOnA and TomOnC can be derived from Tom, and so on. Notice that each programmer is declared as an in stance of the corresponding metaclass object rather than the Pro jectProgrammer class. But the internal structure and the behavior of the programmers are still de termined by Pro jectProgrammer class, because the sample class of the Pro jectProgrammerGroup class is Pro jectProgrammer. ProjectAprogrammer SamOnA = Sam.ProjectAprogrammer(...); ProjectAprogrammer TomOnA = Tom. ProjectAprogrammer(...); ProjectBprogrammer SamOnB = Sam.ProjectBprogrammer(...); ProjectBprogrammer JohnOnB = John.ProjectBprogrammer(...); ProjectCprogrammer SamOnC = Sam.ProjectCprogrammer(...); ProjectCprogrammer TomOnC = Tom.ProjectCprogrammer(...); j As a result, the membership relation of a programmer and his/her project is estab lished as the instance-of relationship of a class and its instances. SamOnA and TomOnA are instances Of the Pro jectAprogrammer object, while SamOnB and JohnOnB are instances of the Pro jectBprogrammer object, and so forth. Remember that SamOnA, SamOnB and SamOnc are defined as children of Sam so that Sam can be shared by two or more projects. Therefore, SamOnA, SamOnB, and SamOnc are still children of Sam, while TomOnA and TomOnC are still children of Tom, and JohnOnB is still a child of John. Therefore, the declaration above establishes 1 i i i 247 projectAworker instance of instance of TomOnA Tom SamOnA y child of Sam child JohnOnB John SamOnB / child of instance of instance of projectBworker Figure 5.10: Multiple Relationships Among Programmers two kinds of relationship among objects: instance-of and child-of, as illustrated in Figure 5.10. In order to coordinate programmers of each project, the Pro jectProgrammerGroup class has operations which compute the total working load of all the programmers. The Pro jectProgrammerGroup includes two members totalWorkingLoad and numberof Programmers in order to record the total working load of all the pro grammers in each project and the number of programmers of each project respec tively. The operations totaiLoadO and numberOfMembers () simply return the value of these two members. ProjectProgrammerGroup::totalLoad() { return totalWorkingLoad; } ProjectProgrammerGroup::numberOfMembers() { return numberOfProgrammers; } 248 The value of these members should be updated whenever a new instance of the Pro jectProgrammerGroup class is created or an existing instance is deleted. The Pro jectProgrammerGroup refines the constructor and the destructor of its sample Class Pro jectProgrammer. ProjectProgrammerGroup::ProjectProgrammer::ProjectProgrammer (string pName, int load) : (pName, load) { numberOfProgrammers++; totalWorkingLoad += load; } ProjectProgrammerGroup::ProjectProgrammer::~ProjectProgrammer(){ numberOfProgrammers— ; totalWorkingLoad -= ProjectProgrammer->projectLoad; } 5 .3 .3 . Accessing S h ared R esources As I mentioned in the previous section, Galois allows us to represent shared re sources using derived objects. In the OPM environment, all the resources are origi nally stored in an object database which is maintained by the resource manager. Processes are not allowed to access resources directly. Processes will issue a re quest to the resource manager when they need to access some resources. Then each process will receive a child of the resource from the resource manager. Through the child, each process is allowed to access the resource, as illustrated in Figure 5.11. The children will provide all the necessary operations to access the resource, in addition to the public operations available on the resource. Once a process receives a child from the resource manager, the process is allowed to directly access the 249 Workstation P Workstation Q object base Resource child child Figure 5.11: Synchronous Access Control child. This guarantees the distributed access to resources. When two or more pro cesses need to access the same resource, they will receive different children of the single resource from the resource manager, but a single child cannot be shared by two or more processes. Fundamentally, each process can access a resource through a child of the resource independently of other processes which keep children of the same resource. But some access to the resource must be carried out synchronously. For example, si multaneous update of a single resource by two or more processes will conflict with each other, thus only one process should be allowed to update the resource at a time. i l When children of a resource receive requests which need synchronization, the chil- | ] dren will process these requests synchronously by communicating to each other j through the resource, their parent. The resource will work as a communication ! center among its children. \ 250 child child Reserve Shared Resource Reserve Edit Edit Check Check Release Release process A process C child Reserve Edit Check Release process B Figure 5.12: Mutual Exclusive Access to a Shared Resource One of such synchronization technique is mutual exclusive control. The mutual ex clusive access of resources guarantees that only one process is allowed to access a resource at a time. The remaining part of this section will be devoted to the discus sion of resource r which provides the mutual exclusive access control. Each child of the resource R provides four operations: Reserve (), Edit o , check (), and Release (), as illustrated in Figure 5.12. A user process of a child of the resource will first execute the Reserve () operation which will guarantee the mutual exclusive access to the resource. Then it will repeatedly execute the Edit () and Check () operation. The Edit () operation allows the user process to modify the resource, and the check () operation performs the syntax check of the resource. After finishing its job, the process will execute the Release () operation which will allow another process to get an access right to the resource. Successive execution of the Reserve () operation by other processes will be denied or delayed until the first 251 process Release the resource. When two or more processes execute the Reserve () operation at a time, only one request is accepted, and the other requests will be denied or delayed. The resource r as well as its children belong to their own classes. The class of the resource r defines operations of the resource r as a communication center, while the class of the children defines the front-end operations for the resource manipula tion. The resource r belongs to the sharedResource class, while its children be long to the sharedinterf ace class. As a result, all the children provide the same set of operations. Below, you will see the definition of the sharedinterface class which defines the behavior of the children of the resource R. The class sharedinterface is defined as a derived class of the SharedResource class which will be defined later. SharedResource Sharedinterface { Boolean locked; void Error() { printf<"You must reserve the resource\n"); printf("before executing this operation\n”); } ; void Alert() { printf<"The resource is already reserved for you\n"); } ; public: void Reserve () Precondition{locked == False} Preprocess{ Alert(); break; }; void Edit () Precondition{locked == True} Preprocess{ Error(); break; }; void Check() Precondition{locked == True} Preprocess{ Error(); break; }; void Release() 252 Precondition{locked == True} Preprocess{ Error(); break; }; Sharedinterface!); // constructor .. -Sharedinterface!); // destructor .. }; The Sharedinterface class provides four operations: Reserve () , Edit () , compile (), and Release (). These operations are available on all the children of the resource r. These operations are associated with preconditions and prepro cesses. The Edit () operation, for example, is associated with the precondition: locked == True indicating that the Edit () operation can be invoked only when the member locked is True. The Edit () operation is also associated with a preprocess Error (). When locked is False, the invocation of the Edit () operation will display an er ror message by calling Error!). The value of the m ember locked is initialized by the constructor Sharedinterface (), and is controlled by Reserve () and Release () Operations. The definition of these operations is given below. The member locked is origi nally set to False by the constructor, and is set to True by the Reserve () opera tion. Thus, Edit o and compile () as well as Release () operations can be in voked only after the Reserve!) operation is completed. On the contrary, Reserve () operation is allowed to be executed only when the child has not exe cuted Reserve () yet. 253 Sharedinterface::Sharedinterface() { // constructor locked = False; } void Sharedinterface::Reserve() { i f (Lock () == True) // operation in the parent class locked = True; else printf("Request denied\n"); } void Sharedinterface::Release() { locked = False; UnLockO; // operation in the parent class } Sharedinterface::-Sharedinterface() { // destructor if (locked) UnLock(); } The execution of the Reserve () and Release o operations require the synchro nization among all the children of the resource R to assure that only one child could succeed in executing the Reserve () operation. Otherwise, two or more processes are allowed to execute the Edit () operation simultaneously. The synchronization mechanism on the resource r is illustrated in Figure 5.13. In order to achieve the mutual exclusiveness, the Reserve () and Release () op erations invoke Lock () and UnLock () operations on the resource r. Since the re source r is a sequential process, it executes only one operation at a time. Thus, only one request to execute the Lock <) operation is granted at a time, even if two or more requests to execute the Lock () operation arrive at the resource r at the same time. The resource r accepts only the first request to lock itself, and successive re quests are denied until the UnLock () operation is executed. The Lock () operation returns True when the request is granted, while it returns False when the request 254 a child of resource R resource R ..... , f locked | Edit() | 1 True (..CheckO" | | ReleaseQ | LockQ UnLockO J ReserveQ | waiting locked | EditQ | 1 F a l s e I j CheckQ | • j ReleaseQ | a child of resource R Figure 5.13: Synchronization Mechanism in Resource r is denied. As a result, only one process can successfully execute its Reserve () op eration. The destructor ~sharedinterface () will also execute the UnLock () operation if the resource is not unlocked when the child is deleted. This will prevent the re source from being locked by children which do not exist anymore. The definition of the SharedResource class which is the class of the resource r is given below: class SharedResource { friend class Sharedinterface; Boolean busy; Boolean Lock() { if(busy) return(False) ; else { busy = True; 255 return(True) ; } } void UnLock () { busy = False; } public: SharedResource () { busy == False } // constructor }; The sharedResource has Boolean member busy, and provides two operations: Lock () and UnLock (). The member busy is originally set to False by the con structor, indicating that initially the resource is not locked by any process. The Lock () operation sets the member busy to True and returns True if the member busy is False, otherwise it returns False. As a result, Lock < ) operation returns False when the resource is already locked by some other process, and it returns True only when the resource is not locked by any other processes. Also notice that Lock () and UnLock () operations are defined to be private, and sharedResource designates sharedinterface to be its friend class. Therefore, only the children of the resource r are allowed to execute the Lock () and UnLock () operations of the resource r. This will protect these operations from being executed by unrelated objects accidentally. The whole scenario of executing the Reserve () and Release () operations is as follows. 256 (i) When the Reserve () operation is invoked on some child of the resource r, the Reserve () operation will first invoke the Lock () operation of the re source R. (ii) Then the boolean member busy will get True. This indicates that the re source r is reserved for the exclusive use through the child which executes the Reserve () operation. Successive requests to Lock () by Reserve () opera tions from other children will fail until the boolean member busy gets False. (iii) After executing the Lock o operation, the Reserve () operation assigns True to the member locked. Then other operations Edit (), check (), and Release () become available to the users on the child. At this moment, Edit ( ), check (>, and Release () operations on other children are still unavailable to the users. As illustrated in Figure 5.13, children of the resource R have their own copy of the member locked. Therefore, even if locked of one child gets True, locked of other children will remain False, locked on each child can be True only when the child successfully completes the Reserve () operation. Especially, the Release () operation can be executed only by the child which have successfully executed the Reserve () operation. (iv) User processes of the resource r will execute the Release {) operation in order to release the resource to other user processes when they finish their job. The Release () operation will first assign False to the local member locked. This makes other operations Edit (), check (), and Release () unavailable to the users on the child. Then the Release () operation invokes the UnLock () operation of the resource r. This assigns False to the boolean member busy. This will allow other processes to reserve the resource r. 257 Here, the class sharedinterface is defined as a derived class of the class SharedResource. But notice that the class sharedinterface cannot be defined as a subclass Of the class SharedResource. If the class Sharedinterface is defined as a subclass of SharedResource, Lock () and UnLock () operation will be avail able on all the children of the resource r. But, in order to synchronize all the chil dren, the Lock () and UnLock () operation must be available on the resource R, not on the children. This implies that the class sharedinterface can be a derived class of the SharedResource, but not its subclass. As far as the implementation of Edit () and check () operations is concerned, two fundamentally different ways are conceivable. The first way is to directly and newly implement these operations in the SharedResource class, while the second way is to reuse the implementation in some other classes. Here I will discuss the second approach. Assume class Base, which defines single-user resources, is given. The Base class provides Edit () and check < ) operations, and includes members to be accessed by the Edit () and Check < ) operations. Although the Base class provides the Edit () and check () operations, it does not provide any synchronization mechanism. Therefore, simultaneous execution of the Edit () operation by two or more users will result in chaos. class Base { // data // e.g. source program, project data, etc. public: 258 inherited from Base class local operations and members Figure 5.14: Implementation of Resource r Reusing An Existing Class void Edit(); void Check(); / / . . . }; Here, I will present how to reuse the Base class, which provides fundamental Edit () and Check () operations although no synchronization is maintained, when we define the SharedResource class which provides mutual exclusive access con trol. The fundamental idea is given in the Figure 5.14. sharedResource and sharedinterf ace are defined as subclasses of Base so that they can inherit the implementation of Edito and Check () operations. At the same time, the SharedResource class and the sharedinterface class inherit the data to be ac cessed by the Edit () and check () operations from the Base class. Whenever a 2 5 9 a child of resource R resource R Data to be modified ‘ i Edit i S i n x m i n u H i i M e ; Check : busy 1 True 1 Lock i lrue i UnLock Data to be modified Check write Data to be modified Check executing completed a child of resource R Reserve ~| waiting I Release I J child locks the resource, inherited members are copied from the resource to the child. This is possible because both SharedResource and Sharedlnterf ace are subclasses of Base. The sharedinterface class makes the inherited operations Edit () and check () public, while SharedResource does not. As a result, the copied members can be accessed with these operations on the children. Similarly, the Release () operation writes back the inherited members from the child to the resource. This will update the resource based upon the modification made on the child. Local members and operations in SharedResource and Sharedinterface assure the mutual exclusive access to the resource. You will see the definition Of the SharedResource class below. SharedResource is a subclass of Base. As a result, it inherits Edit () and check () operations from Base. But since SharedResource does not make these inherited operations public, these operations are not available to the users. In addition to these inherited mem bers, SharedResource adds several local members and operations which I already discussed. class SharedResource : Base { friend class Sharedinterface Boolean busy; Boolean Lock(); void UnLock() ; public: SharedResource() ; // constructor Sharedinterface is also a subclass of Base. As a result, it inherits Edit () and check () operations from Base. Here, inherited operations are available to the users, because sharedinterface makes these inherited operations public. In addi 260 tion to these inherited members, SharedResource adds several local members and operations which I already discussed, sharedinterface adds preconditions and preprocesses to the inherited operations in order to assure the mutual exclusive ac cess. SharedResource Sharedinterface : public Base { Boolean locked; void Error () { printf("You must reserve the resource\n"); printf("before executing this operation\n"); } ; void Alert() { printf("The resource is already reserved for you\n"); } ; public: void Reserve() Precondition{locked == False} Preprocess{ Alert(); break; }; void Edit () Precondition{locked == True} Preprocesst Error!) ; break; }; void Check () Precondition{locked == True} Preprocess{ Error(); break; }; void Release() Precondition{locked == True} Preprocess{ Error!) ; break; }; Sharedinterface() ; // constructor .. -Sharedinterface() ; // destructor .. In addition to communicate to the resource, the Reserve < ) and Release () opera tions perform copying data between the resource and children. Whenever a child successfully locks the resource, the inherited members are copied from the resource to the child. The copied members are only accessible by the inherited operations. Sharedinterface::Reserve() { if(Lock() == True) { // operation in the parent class locked = True; (Base)*this = (Base)*SharedResource; 261 } else printf("Request denied\n"); } Finally, whenever a child unlocks the resource, the inherited members are copied back from the child to the resource. As a result, modification made on the child will be copied into the resource. Sharedinterface::Release() { (Base)*SharedResource = (Base)*this; locked = False; UnLockO; // operation in the parent class } 5.3.4. Scheduling Shared Resources In the previous section, I presented the way of implementing mutual exclusive ac cess control of resources in Galois. When two or more processes try to reserve a single resource, only the first process succeeds in reserving the resource, and any other processes will fail. The failed processes need to keep executing the Reserve () operation until it succeeds. One way to release processes from the repetitive invocation of the Reserve () op eration is to provide a queue of requests to the Lock () operation, and process the requests in the queue based upon some scheduling algorithm, such as FIFO (First In First Out) or SJF (Shortest Job First) and so forth. In the following sections, I will present several resource scheduling mechanisms based upon classical schedul ing algorithms, including FIFO and Priority Scheduling. 262 5.3.4.1. FIFO scheduling The first scheduling algorithm which I will implement here is the FIFO scheduling algorithm. Multiple requests to execute the Reserve () operation will be processed in the order of the requests are received. As an example, consider the following resource q. The resource q belongs to the FifoResource class, while its children belong to the Fif ointerf ace class. The definition of the FifoResource class which is the class of the resource q is given below: class FifoResource { friend class Fifolnterface; Boolean busy; FifoFunction void Lock() precondition{ busy == False }; void UnLock() precondition{ busy == True }; public: FifoResource() { busy == False }; // constructor }; The FifoResource provides two operations: Lock () and Unlock (). Notice that the Lock () operation does not return any value, while the Lock () operation in the SharedResource class returns a Boolean value indicating if the request to lock the resource is granted or not. Instead, the Lock o operation of FifoResource is as sociated with a precondition: busy == False 263 The member busy is originally set to False by the constructor, and is set to True by the Lock () operation: FifoFunction void FifoResource::Lock() { busy = True; } void FifoResource::UnLock() { busy = False; } This will guarantee that only the first request to execute the Lock () operation is granted, and successive requests to the Lock () operation will be delayed, not de nied, until the unLock () operation is executed by the first process. The unLock () operation sets the member busy to False so that the requests to execute the Lock () operation by other processes can be granted. The Lock() operation is defined to be an instance of the function class FifoFunction which I defined in Chapter 4. Lock () operation will maintain a FIFO queue of its instances which are waiting for being executed, and successive requests to invoke Lock () operation will be processed in the FIFO order. The definition of the Fifointerf ace class which defines the behavior of the chil dren of the resource q is given below. The class Fifointerface is defined as a derived class of the FifoResource class. FifoResource Fifointerface { Boolean locked; void Error () { printf("You must reserve the resource\n"); printf("before executing this operation\n"); 264 }; void Alert() { printf("The resource is already reserved for you\n"); } ; public: void Reserve () Precondition{locked == False} Preprocess{ Alert(); break; }; void Edit () Precondition{locked == True} Preprocess{ Error(); break; }; void Check () Precondition{locked == True} Preprocess{ Error() ; break; }; void Release() Precondition{locked == True} Preprocess{ Error(); break; }; Fifolnterface() ; // constructor ~Fifolnterface(); // destructor } ; The Fifointerface class is almost same as the Sharedinterface class except for the implementation of the Reserve () operation. Fifointerface::Fifolnterface() { // constructor locked = False; } void Fifointerface::Reserve() { Locks(); // asynchronous call locked = True; } void Fifointerface::Release() { locked = False; UnLock{); // operation in the parent class } Fifointerface::~Fifolnterface() { // destructor if(locked) UnLock(); } The Reserve () operation need not check to see if the request to lock the resource q is granted or not, because the Lock < ) operation does not return a value any more. 265 Workstation P Workstation Q r ------------ I Manager I I Process instance priority =1 object base Project Leader Process instance priority =2 Resource f Junior ^ ^ instance _ _ __y ^ s instance f Senior I Programmer priority=4 priority =3 Programmer ^ Process J ^ Process J Workstation S Workstation T Figure 5.15: Scheduling based upon Priority Instead, the Reserve () operation invokes the Lock () operation asynchronously, because the Lock () operation maintains a queue of requests and requests to execute the LockO operation might be delayed. The asynchronous invocation to the Lock () operation allows the process to carry out other operations while it is waiting for the resource q. 5 .3 .4 .2 . P rio rity Scheduling In this section, I will present resource p which implements priority scheduling. The resource p implements the mutual exclusive access control like the resource r. But, each child of the resource p is associated with a priority upon its creation. Usually, the priority of a child is determined by the characteristic of the user process of the resource p. For instance, as Figure 5.15 illustrates, a child of the resource p in a manager process may get the highest priority 1, while another child in a junior pro grammer process may get the lowest priority 4. Requests to reserve the resource p will be granted based upon the priority associated with the children. 266 Below is the definition of class P rio rity in te rfa c e which determines the behav ior of children of the resource p. P r i o r i t y i n t e r f a c e is almost same as F ifo in te rfa c e which I defined in the previous section except that the children of P r io r ity in te r fa c e are capable of keeping their priority assigned upon their cre ation. PriorityR esource P rio rity in te rfa c e { Boolean locked; in t p r io rity ; / / p r io r ity void Error () { printf("You must reserve the resou rce\n "); p rin tf(" b efore executing th is operation \n "); }; void A lert () { printf("The resource is already reserved for you\n"); }; public: void R eserv e() Precondition{locked == False}; Preprocess{ A le r t(); break; }; void E dit () Precondition{locked == True} Preprocess{ E rro r)); break; }; void Check() Precondition{locked == True} Preprocess{ E rror(); break; }; void R e lea se () Precondition{locked == True} Preprocess{ Error() ; break; }; P r io r ity in te r fa c e (in t) ; / / constructor .. -P r io r ity ln te r fa c e () ; / / destructor .. } ; The constructor will be given the priority of the child upon creation. The priority will be kept in the member p rio rity . The member locked is initially set to False by the constructor, and is controlled by other operations like in the F ifo in terfa ce 267 class. The Reserve () operation invokes the Lock () operating of the resource p by passing the priority. Priorityinterface::Priorityinterface(int p) { // constructor priority = p; locked = False; } Priorityinterface::Reserve() { Lock&(priority); // invoke operation in parent class locked = True; } Priorityinterface::Release() { locked = False; UnLock(); // invoke operation in parent class } Prioritylnterface::-Prioritylnterface() { // destructor if(locked) UnLock(); } The behavior of the resource p is determined by class PriorityResource which is the class Of the resource P. PriorityResource is almost same as FifoResource except for the following two exceptions. First, the Lock () operation is a child of PriorityFunction instead of FifoFunction, and second the Lock o operation takes an argument which determines the priority of the function child to be created. Lock () maintains function instances based upon the priority assigned to each in stance. Other parts of PriorityResource are same as FifoResource. Class PriorityResource { friend class Priorityinterface; Boolean busy; PriorityFunction void Lock(int level) precondition{ busy == False }; void UnLockO; public: 268 PriorityResource () { busy = False; }; // constructor }; PriorityFunction void. PriorityResource::Lock(int level) ; (level){ busy = True; } void PriorityResource::UnLock() { busy = False; } Before defining the class PriorityFunction, we need to refine the Function class so that each function instance can keep its priority. The subclass pFunc of the class Function defines such refinement. The constructor pFunc < ) of pFunc takes a single argument p which specifies the priority of the instance to be created. The value of p is stored in private member priority. class pFunc : public Function { int priority; // priority public: void priority() { return(priority) ; }; PriorityFunc(int p) : () { // constructor priority = p; } } The definition of metaclass PriorityFunction can be seen below. PriorityFunction is same as FifoFunction except for: (i) the sample class of PriorityFunction is pFunc rather than Function; (ii) readyQueue and waitQueue are defined to be instances of PriorityQueue, which will be defined later, rather than Fif oQueue; (iii) the refining constructor of pFunc takes an argu ment p which indicates the priority of the instance to be created. The argument is di rectly passed to the constructor of pFunc so that the instance can get an appropriate priority. 269 metaclass PriorityFunction of pFunc { PriorityQueue readyQueue, waitQueue; public: pFunc::pFunc(int p) // constructor of pFunc : (p) /* passed to pFunc */ { readyQueue.append(pFunc); } pFunc::run() { readyQueue.remove(pFunc); run(); } // remove from ready Q // execute it pFunc::wait(){ waitQueue.append(pFunc) ready(); } // put into waiting Q pFunc::ready(){ waitQueue.remove(pFunc); readyQueue.append(pFunc) ready(); // remove from waiting Q // put into ready Q pFunc* default() { return(readyQueue.top(] } }; Finally, the definition of class PriorityQueue can be seen below. class PriorityQueue { // priority queue of function children pFunc* ptrToFunction; // pointer to function PriorityQueue* next; // pointer to next node public: append(pFunc* fptr); // append new function instance pFunc* top(); // highest priority function instance remove(pFunc* fptr); // remove function instance PriorityQueue() ; // constructor PriorityQueue maintains pointers to function instances using a linked list, as il lustrated in Figure 5.16. All the children pointed by the linked list are sorted with respect to the priority of each function instance. The implementation of 270 function instance priority function instance ptrToFunction next priority function instance priority node in queue node in queue node in queue NIL node in queue Figure 5.16: Priority Queue of Function Children PriorityQueue is straightforward. PriorityQueue provides four operations: append (), top (), remove (), and the constructor PriorityQueue (). The defini tion of these functions are given below. PriorityQueue::PriorityQueue() { /* constructor */ ptrToFunction = NIL; next = NIL; } The constructor PriorityQueue () simply creates an empty list. PriorityQueue::append(pFunc* fptr) { PriorityQueue *newNode if(next == NIL) { // if terminal node ptrToFunction = fptr; next = new PriorityQueue(); } else { if(fptr->priority() < ptrToFunction->priority()) { newNode = new PriorityQueue(); newNode->ptrToFunction = ptrToFunction; newNode->next = next; next = newNode; ptrToFunction = fptr; } else { 271 next->append(fptr); } } } append () will create a new node for the given function instance designated by the argument, and place the node into the liked list based upon the priority of the given function instance. pFunc* PriorityQueue::top() { return(ptrToFunction); } top () simply returns the pointer to the function instance stored in the first node. That is, top () will return the pointer to the function instance which has the highest priority. Boolean PriorityQueue::remove(pFunc* fptr) { PriorityQueue *nodeRemoved; if(ptrToFunction == fptr) { // node found ? nodeRemoved = next; ptrToFunction = next->ptrToFunction; next = next->next; delete nodeRemoved; return(True) ; } else if(next != NIL) { return(next->delete(fp)); } else { return(False); f } remove () will first locate the node which points to the function instance designated by the argument, and then remove the node. If the node to be removed is found, remove () returns True, otherwise, it returns False. 272 5 .3 .4 .3 . Scheduling w ith B ackground O perations It is sometimes happens that a process is allowed to access a resource only for a finite amount of time. In such a case, it is required to keep watching activities of the process which the resource is reserved for, and prevent the process from occupying the resource too long. The resource t which is defined below counts the period of time while the resource is continuously reserved for a single process, and it prevents the process from oc cupying the resource longer than the predefined period of time. As usual, the re source t is defined with two classes: TSResource, the class of the resource t itself, and TSinterface, the class of children of the resource t through which the re source t is accessed. The definition of TSResource which is the class of the resource t can be seen be low. TSResource has four private members : locked, f inishDate, warning and expired, locked indicates if the resource is allocated to some process for its ex clusive use. f inishDate indicates the expiration date of the current allocation of the resource, warning indicates that the expiration date of the current allocation is ap proaching, while expired indicates that the allocation has been expired. class TSResource { friend class TSinterface; Boolean locked; // allocated ? int finishDate; // expiration date of allocation Boolean warning; // allocation will be expired soon Boolean expired; // allocation has been expired 273 PriorityFunction void Lock(int timeAllowance) : (timeAllowance) precondition{ locked == False}{ locked = True; finishDate = currentDate() + timeAllowance; warning = False; expired = False; } ; void UnLock () { finishDate = 0; locked = False; }; background void sentinel() precondition{ finishDate != 0 }; public: TSResource() { // constructor Currentlnstance = NIL; finishDate = 0; } } The class TSResource has four operations. The Lock < ) operation is defined to be an instance of priorityFunction which I defined in the previous section. Upon creation, each function instance of Lock () passes the expected time allowance to the constructor of the function class Lock < ) as its priority. As a result, requests to invoke Lock () with shorter expected time allowance have higher priority. Thus, the Lock () operation is processed in the Shortest-Job-First (SJF) basis. The Lock () operation assigns True to locked to indicate that the resource is allo cated to the process which executes the Lock o operation. It also computes the value of f inishDate which indicates the expiration date of the allocation. The Lock () operation is also guarded with a precondition: locked == False 274 so that the Lock () operation can be successfully executed only when the resource is not allocated to any other processes. The key concept, which plays an important role in representing the resource t, is a background operation sentinel (). You will see the definition of sentinel () be low. TSResource::sentinel() { if( finishDate < currentDate() + 3 ) warning = True; // will be expired soon !! else if(finishDate < currentDate() ) expired = True; // expired !! } sentinel () is repeatedly executed when the resource is idle because it is a back ground operation, while regular non-background operations are executed only when requested, sentinel () will repeatedly check to see if the process which cur rently reserves the resource does not occupy the resource beyond the period of time which is allowed to it. When the expiration date is approaching, first sentinel () assigns True to warning. And once the expiration date has come, sentinel () assigns True to expired. These value will be used in the children of the resource T. Below is the definition of class TSinterface which determines the behavior and structure of children of the resource t. Like Fifointerface, the TSInterface provides Reserve (), Edit () , Check (), and Release () Operations. TSResource TSinterface { 275 Boolean locked; void Error() { printf("You must reserve the resource\n"); printf("before executing this operation\n"); } void Alert() { printf("The resource is already reserved for you\n"); } public: void Reserve(int) Precondition{locked == False} Preprocess( Alert(); break; }; void Edit () Precondition{locked == True && expired == False } Preprocess{ Error(); break; }; void Check () Precondition{locked == True && expired == False } Preprocess{ Error() ; break; }; void Release() Precondition{locked == True} Preprocess{ ErrorQ; break; }; background void warningMessage() precondition( locked == True && warning == True }; TSinterface() { // constructor locked = False; } -TSinterface() { // destructor if (locked) UnLock(); } } ; The Reserve () operation takes one argument. The users of the resource t need to specify the expected length of time required to finish their job when they reserve the resource. The value of the argument is simply passed to the Lock () operation of the parent class TSResource. void TSinterface::Reserve(int timeAllowance) { Locks(timeAllowance) ; // asynchronous call locked = True; } void TSinterface::Release() { locked = False; 276 UnLock<); // operation in the parent class } Like Fifointerface class, the TSinterface class has a member locked. The I member locked is used to make the Edit () and check () operations available to the users only after they successfully reserve the resource. The member locked is originally set to False by the constructor. It is set to True by the Reserve () op eration, and is again set to False by the Release () operation. The important issue here is that the E d i t ( ) and check () operations are associated with the precondition: locked == True && expired == False J 1 expired is a member of the parent class TSResource. As I explained earlier, expired is set to True when the allocation is expired. Therefore, if the allocation is expired, Edit () and check () operations are not available to the users. Only the Release () operation is available. Therefore the user is forced to release the re source. Another important issue is the background operation warningMessage (). The warningMessage () operation is associated with the precondition: locked == True && warning == True ! i warning is a member of the parent class TSResource, and is set to True when the expiration date of the allocation is approaching. Therefore, the warningMessage () operation is executed only when the resource is reserved for the user working on 277 the child, and the expiration date of the allocation to the user is approaching. You will see the definition of the warningMessage () operation below. background void TSinterface::warningMessage() { if (warning == True && expired === False) printf("Allowance will be expired soon\n"); else if (expired == True) { printf("Allowance has been expired\n"); printf("Release the resource immediately\n"); } } The warningMessage () operation repeatedly displays a warning message while warning is True and expired is False, that is, the allocation is going to expire but not expired yet. But once the allocation is expired, that is expired is set to True, the warningMessage () operation repeatedly displays an alert message. 5.3.5. Monitoring the Access to Shared Resources In the preceding sections, I have discussed several ways of modeling and schedul- j ing shared resources using derived classes. In this section I will introduce a way of j monitoring activities performed on shared resources by two or more processes. At the beginning of this chapter, I introduced the way of coordinating multiple in stances of a single process template. There, each process template itself was in i < charge of monitoring its instances. But here, we need to coordinate multiple chil- I I dren of a single resource. I will extend the Fifointerface and FifoResource class so that resources can coordinate the activities on multiple children of the resources, while it still able to 278 maintain the FIFO scheduling algorithm. Below, you will see the definition of class MonitoredResource and class Monitoredlnterf ace. The MonitoredResource class is a template of resources which are capable of monitoring their children, while Monitoredlnterf ace class determines the behavior and the structure of the children of the resources which are instances of the MonitoredResource class. The MonitoredResource Class is a subclass Of the FifoResource class which I defined before. As a result, the MonitoredResource class inherits the FIFO scheduling capability of the FifoResource class. class MonitoredResource : FifoResource { ResUser userList; int userCounter; 11 ... int newUser(); void setState(int, string); void quit(int) ; public: void listUser(); // list status of users MonitoredResource(); // constructor -MonitoredResource() ; // destructor } ; The MonitoredResource class has two local members: userList and userCounter. userList is a linked list defined by class ResUser which will be discussed later. Each node in userList has one-to-one correspondence with the children of the resource. Whenever a new child of the resource is created, a new node is added to the linked list. Each node keeps the state of the corresponding child. userCounter is used to count the total number of children ever created. userCounter is initialized to zero by the constructor. 279 MonitoredResource::MonitoredResource() : () { userCounter = 0; } The MonitoredResource class has three private operations which need to be in voked by the children in order to inform the state of each child to the resource. newUser () needs to be invoked whenever a new child is created. newUser () in crements userCounter by one, and adds a new node to the linked list userList. newUser () returns the user ID of the new child which will be required to commu nicate with the resource later. Here, the value of userCounter is used as the user ID of the new child. But other value might be used for the user ID. For instance, address of each child will be a good alternative. int MonitoredResource::newUser() { ResUser *node; int newUserlD; newUserlD = ++userCounter; node = userList.append(newUserlD); return(newUserlD); } setstate () is invoked by the children in order to inform their state to the resource. The setstate () operation takes two arguments: useriD and newstate. The setstate () operation first searches for the node which corresponds to the child denoted by the first argument useriD, and updates the state information in the node based upon the second argument newstate. MonitoredResource::setstate(int useriD, string newState) { ResUser *node; node = userList.find(useriD); node->setState(newstate); } 280 quit () is invoked by the destructor of the children whenever a child is deleted. The quit () operation first searches for the node which corresponds to the child to be deleted, and records the time when the child is deleted. And it also updates the state information to "terminated". MonitoredResource::quit(int useriD) { ResUser *node; node = userList.find(useriD); node->setFinishTime(); node->setState("terminated"); In addition to these three private operations, MonitoredResource provides a public operation listuser < ) which generates a list of users of the resource along with various information stored in the linked list. MonitoredResource::listUser() { userList.print(); } Below is the definition of the Monitoredlnterf ace class which is the template of children of resources Of the MonitoredResource class. Monitoredlnterf ace is a derived class of the MonitoredResource class, and, at the same time, is a subclass of the Fifointerface class. As a result, it inherits public operations, such as Reserve (), Edit (), Check (), and Release () from the Fifointerface class. MonitoredResource Monitoredlnterface : Fifointerface { int userID; public: void Reserve(); void Release() ; Monitoredlnterface() ; // constructor ~MonitoredInterface() ; // destructor }; 281 The Monitoredlnterf ace class has a local member useriD which keeps the user ID of each child. The constructor Monitoredlnterf ace () simply invokes the newUser o operation of the parent class MonitoredResource to obtain the user ID, and assigns it to useriD. Monitoredlnterf ace:‘ .Monitoredlnterf ace <) : () { useriD = newUser(); } The Monitoredlnterf ace class refines two operations: Reserve () and Release () operations, which are inherited from the superclass Fifointerface, so that they inform the state change of the child to the resource. This is necessary because the execution of Reserve () and Release () operation will change the state of the child. The execution of the Reserve < ) operation will change the state from "idle" to "reserved", while the execution of the Release () operation will change the State from "reserved" to "idle". The Reserve () operation first executes setstate () operation to change its state to "waiting" because the lock request may be delayed. Then it sends a lock request to the resource by executing the inherited Reserve () operation. When the lock re quest is successfully completed, it again invokes the set Sat et () operation to change the state to "reserved". Monitoredlnterface::Reserve() { setstate(useriD, "waiting"); Fifolnterface::Reserve() ; // lock the resource setstate(useriD, "reserved"); } 282 The Release () operation is refined similarly. It first unlocks the resource by exe cuting the inherited Release <) operation, and it executes the setstate () opera tion to change the state to "idle". Monitoredlnterface::Release() { UnLock(); // unlock the resource setstate(useriD, "idle"); } Notice that even though the implementation of Reserve () and Release () opera tion is refined, their preconditions and preprocesses are not refined. Therefore, in herited preconditions and preprocesses are still active in the MonitoredResource class. The destructor is also refined so that it invokes the quit () operation. As we saw before, the quit () operation records the time when the child is deleted and updates the state information to "terminated". Monitoredlnterface::-Monitoredlnterface() : () { quit(useriD); } Below is the definition of the class ResUser which determines the behavior and the structure of each node in userList. ResUser is almost same as the class ProcessRecord which I defined earlier. Each node in a linked list includes five members: useriD keeps the user ID of the corresponding child of the resource, start and end date which indicate the start and finish time of the child respectively, 283 status which indicates the current state of the child, and next which is the pointer to the next node in the linked list. class ResUser{ int userlD; date start; // date end; / / string status; ResUser *next; public: ResUser *append(int); ResUser *find(int); v o i d setState(string); v oi d setFinishTime(); v o i d print(); ResUser(); // constructor } ; The constructor ResUser () creates an empty list. It simply assigns nil to next. ResUser::ResUser() { // constructor next = NIL; // create an empty list } Each node in userList is created by append () operation. The append () operation is invoked whenever a new child of the resource is created. The append () opera tion first traces the linked list until the terminal node is encountered by invoking it self recursively. Once the terminal node is encountered, it stores the user ID of the new child which is passed by its argument, and records the current time as the cre ation date of the new child. Originally the state of the new child is set to "idle". ResUser::append(int newUserlD) { i f ((next) { // terminal node ? userID = newUserlD; start = currentDate(); end = 0; // user ID start time of child finish time of child // state of child // pointer to the next node 284 status = "idle"; next = new ResRec.ord () ; } else next->append(p); The find () operation looks for a node designated by the argument in the linked list. If such a node is found, the address of the node is returned. ResUser::find(int id) { if(!next) // terminal node ? return(NIL); else if(userlD == id) return(this); else return(next->find(id)); The print () operation generates a list of nodes in the linked list along with the in formation stored in the nodes. ResUser::print() { if(next) { printf("Child ID : %s\n", ptrToChild); printf("start time : %s\n", start); printf("finish time : "); if(end == 0) printf("not finished\n") ; else printf("%s\n", end); printf("status : %s\n", status); next->print(); } } The setstate () operation simply changes status of each node based upon the argument. ResUser::setState(string s) { status = s; 285 } The setFinishDate () operation records the current date as the termination date of the corresponding child. ResUser::setFinishTime() { end = currentDate (); } 286 Chapter 6 Resource Management 6.1. Modeling Version Control In this section resources with several versions will be discussed. This section is divided into four subsections. The first subsection is devoted to the description of a version control mechanism. In the second subsection, I will describe the implemen tation using Galois of the version control mechanism described in the first subsec tion. Notice that the version control mechanism which will be described is just an example. Galois itself does not has any built-in version control mechanism. Rather, Galois allows us to design and implement a version control mechanism which will mostly fit us. The third subsection will describe the way of accessing versions. The resources provide a mechanism to accommodate the access from multiple users. Finally, in the last subsection, the way of selecting versions will be discussed. 6.1.1. A Version Control Mechanism The version control mechanism which I am going to present here assumes that ver sions form a tree. Versions of each resource are defined inductively as follows: 287 W is a revision of V Figure 6.1: A Sample Version Tree • Each resource starts with a single version. • Versions are allowed to have any number of versions, called revisions. When a version has a revision, the version is called the parent version of the revi sion. • Revisions, in their turn, are allowed to have any number of their revisions. Figure 6.1 shows a sample version tree. The resource starts with a single version a. The top level version a has three revisions: b, c, and d. The first revision b, in its turn, has a single revision: e; the second revision c, in its turn, has two revisions: f, and g; and the third revision d has two revisions: h and I, and so forth. A sequence of successive revisions forms a branch. As illustrated in Figure 6.2, the version tree in Figure 6.1 has five branches: main, alpha, beta, gamma, and delta. The main branch consists of three versions named a, b, and e, while the alpha branch consists of four versions named a, c, f and j, and so forth. Notice that a single version may be included in two ore more branches. Therefore, each branch is 288 1 3 ] delta branch main branch m alpha branch r a gamma branch beta branch Figure 6.2: Branches in a Version Tree closed with respect to the parent-of relationship. As a result, every predecessor of every version in a branch is also included in the branch. Users are allowed to access all the predecessors of a given version without leaving the branch in which the ver sion resides. Each branch has a unique name among all the branches of a single resource. Versions in a single branch also have unique names. Each version is identified by a pair of the version name and the name of a branch in which the version resides. If a version belongs to two or more branches, two or more combinations of the version name of the branch names refer to the same version. For instance, in Figure 6.2, the version named c in the alpha branch and the version named c in the beta branch are identical. Versions are assumed to be immutable. That is, versions can be created and de stroyed. But once a version is created, it cannot be modified. Modification of an 289 (a) Generating A Version Sequence (b) Creating A New Branch Figure 6.3: Generation of a Version Tree existing version will result in creating a new revision. The Figure 6.3 shows the way of creating new versions. When a version which has no revision is modified, simply a new revision is created as shown in Figure 6.3 (a). As a result, such modification sequence will result in a sequence of versions. On the other hand, when a version which has one or more revisions is modified, a new branch is gen erated, as shown in Figure 6.3 (b). Such modification sequence will result in a tree of versions. When two programmers modify a single version at the same time, two different re visions of the version will be generated. Fortunately, simultaneous update of a sin gle version does not result in the loss of any of modification because versions are immutable. Different modifications will result in the generation of different revi sions. But often such simultaneous modification need to be incorporated into a sin gle version. For instance, assume there are two programmers fixing two bugs A and B in a single source file. If they work simultaneously, it will result in two dif ferent versions: one includes the fix of the bug A, and the other includes the fix of the bug B, as illustrated in Figure 6.4 (a). But such bug fixes need to be merged 290 | original^- original | A&B | (a) Diverging Bug Fix (b) Incorporated Bug Fix (c) Incorporated Bug Fix Figure 6.4: Conflicting Version Generation into a single version, like Figure 6.4 (b) or (c). In order to avoid this problem, only one programmers should be allowed to modify a version at a time. Furthermore, in such cases, the programmers always need to keep track of the evolution of versions to identify the version on which the programmer should work. In the example above, the second programmer need to modify the version which was generated by the first programmer, rather than the original version. If more programmers are involved, the task of keeping track of the version evolution will be getting more and more complex. In order to avoid these problems, the version control mechanism, which I present here, takes the following approach. The version control mechanism considers each resource is a collection of branches, or variations, which are capable of keeping track of their modification history. Each branch is a mutable unit of modification. When a programmer modifies a resource, the programmer selects a branch, rather than a version. And the programmer is required to lock the branch. This will guar- 291 antee only one programmer is allowed to modify the branch at a time. Then, the programmer is allowed to modify the latest version of the branch. The program mer's modification will result in the creation of the new latest version of the branch. This will free programmers from keeping track of the evolution of versions. If a programmer need to modify an intermediate version in a branch, the program mer first creates a new branch separating from the intermediate version, so that the intermediate version in the original branch becomes the latest version in the new branch. Then, the programmer can keep working in the new branch. Read-only access is not restricted to the latest version in each branch. Users are al lowed to read any intermediate versions in a branch without creating a new branch. Similarly, read-only access to a version is allowed without locking the branch in which the version resides. 6.1.2. Implementing the Version Control Mechanism The implementation, which I am going to present here, of the version control mech anism described above involves several classes. The tree structure of versions is determined by two classes: branch and version. The class branch defines the structure of each branch, while the class version defines the structure of each ver sion. Figure 6.5 illustrates how branches and versions in Figure 6.2 are represented using the version and branch classes. All branches in a resource are maintained using a linked list. And each branch points to the latest version in the branch. 292 i 1 version I ML I parent m name 3 version version _ | parent B I name parent 1 C I name version version 1 . » | parent 1 1 i | name J parent T name version ] parent name latest next mam name latest next 1 alPha 1 name version Is *" " j parent 1 G " j name version I f 1 parent m name version I parent I M 1 name latest next j beta ~ | name branch branch version 1 parent | iD " | name i 1 version 1 J | parent [ H 1 name s version _ parent L~1 name parent m name 1 h r ^ -► i < h lN I L I l a t e s t next latest next | gamma | | delta | name name branch branch branch linked list of branches Figure 6.5: Hierarchical versions Versions in a branch are also maintained as a linked list. Each version points to its parent version. Here, I will define the version control mechanism by refining an existing class which does not provide any version control mechanism. Given a class, say B ase class, we can add the version control mechanism to it. Assume below is the kernel of the B a se class. c l a s s B a se { // 293 // private data for Base class. // eg. source code, project data etc. // public: void Edit(); // Edit operation void Browse(); // Browse the content Base(); // constructor // ... You will see the definition of the version class which determines the structure of each version below. The version class is defined as a subclass of the Base class. This will allow us to add new members and operations which are required to add the version control mechanism to the existing class Base. Each version has its name, its owner's name, its creation date, and a comment which gives a brief explanation of the version. Objects of the version class form a linked list. Each version object includes member parent which points to the par ent version in the linked list. class version : Base { string name; string owner; string comment; date created; version* parent; // ... public: version* find(string); // find a version void list(); // list all versions version* append (string, string,, string) ; // append a new version at the end of the version list version(string, string, string); // constructor II ... } ; // this class defines each version // name of the version // name of the owner // brief explanation of this version // creation date of the version // pointer to the parent version 294 The version class has a constructor: version o . The constructor takes three ar guments which specify the name of the version to be created, the owner's name of the version, and a brief annotation for the version respectively. The constructor also assigns the current date to the member created which indicates the creation date of the version. The constructor initially assigns nil to parent. Therefore, the con structor creates a new version list consisting of a single version. version::version(string n, string o, string c) { name = n; // version name owner = o; // owner name comment = c; // brief explanation created = currentDate(); // creation date parent = NIL; } The linked list can be extended by append () operation. The append () operation simply creates a new version and designates the version on which the append () operation is invoked as the parent version of the newly created version. version* version::append(string n, string o, string c) { version* ptr; ptr = new version(n, o, c); ptr->parent = this; // // DELTA mechanism may be defined here 11 return (ptr) ; } The version class provides an operation to find a version with a specific name in each version list. The find () operation simply traces the version list until it finds the version with the designated name by recursively invoking itself, and returns the 295 pointer to the matched version. If no match is found, the find () operation returns NIL. version* version::find(string n) { version* ptr; if(n == name) return (this) ; else if(parent) return(parent->find(n)); else return(NIL) : The version class also provides list () operation which generates a list of the versions in the version list, on which the list () operation is invoked, along with a variety of information. The list () operation is also a recursive function. The list () operation first checks to see if the version on which the list () operation is invoked has its parent version. If so, it recursively invokes the list () operation on the parent object. As a result, all the versions in the version list will be listed in chronological order. void version::list() { if(parent) parent->list(); // first list parent versions printf("version name : %s\n", name); printf("owner name : %s\n”, owner); created.print(); // print creation date printf("Comment : %s\n", comment); Below is the definition of the branch class which determines the structure and be havior of branches. Each branch has members: name, created, and comment indi cating its name, the creation date, and a brief explanation of the branch. Each 296 branch also includes a member l a t e s t which points to the latest version in the branch. class branch { friend class privateVersion; string name; // name of this branch date created; // creation date of this branch string comment; // brief explanation of this branch version* latest; // pointer to latest version in branch version* next; // pointer to the next branch Boolean busy; /* operations for synchronous access */ FifoFunction void Lock() precondition{ busy == False }; void UnLockO precondition { busy == True }; n ... public; /* operations for versions in the branch */ version* findVersion<); // find latest version version* findVersion(string) ; // find a version version* newVersion(string, string, string); // create a new version at the end of the branch /* Operations for the branch list */ branch* append(string, string, version*); // create a new branch branch* find(string); // find a branch in the branch list void list(); // list all branches in the branch list /* constructor */ branch(string, string, version*); 11 ... The branch class has a constructor branch () which takes three arguments. The first argument specifies the name of the branch to be created, the second argument gives a brief explanation of the branch, and the third argument indicates the version from which the branch is started. Objects of the branch class form linked lists. Each branch object includes the member next which points to the successor 297 branch object in the branch list. The constructor initially assigns n i l to next. As a result, the constructor creates a branch list consisting of a single branch. 1 branch::branch(string n, string c , version* v) { name = n; comment = c; created = currentDate() ; next = N I L ; latest = v; busy = False; // originally unlocked Branch lists can be extended by the append () operation. The append () operation adds a new node at the end of the linked list of branches. The append () operation takes three arguments. The first argument specifies the name of the branch to be created. The second argument gives a brief explanation of the branch. And the third argument indicates the version from which the branch is separated. branch *branch::append(string n, string c, version *v) { if(next) // next->append(n, c , v) ; else next = new branch(n, c , v); } The branch class also provides find () operation which checks to see if a branch with the designated name, specified by the argument, exists in the branch list on which the find () operation is invoked. If such a branch exists, the find () opera tion returns the address of the branch. branch *branch::find(string n) { if(name == n) return(this) ; else if (next) // non terminal node ? return(next->find(n) ) ; else 298 j } return(NIL); The branch class provides Lock () and UnLock () operations so that multiple pro cesses can properly share branches. Those operations are totally same as the opera tions with same name in the FifoResource class which I defined in the previous chapter. The Lock () operation is associated with a precondition: busy == False The member busy is originally set to False by the constructor branch (), and is set to True by the Lock () operation itself. As a result, only one request to execute the Lock () operation is granted at a time. Successive requests to execute the Lock () operation will be delayed until the member busy is set to False by the UnLock () operation. The Lock () operation is an instance of the function metaclass FifoFunction. As a result, consecutive requests to execute the Lock () operation will be processed in FIFO basis. FifoFunction void branch::Lock() { busy = True; } void branch::UnLock() { busy = False; } The branch class provides several operations for manipulating versions in each branch. The newVersion () operation creates a new version at the end of the branch, and the address of the newly created version is stored in the member latest. As a result, the member latest always points to the latest version in the branch. 299 version *branch::newVersion(string n, string o, string c) { return(latest = latest->append(n, o, c)) ; } The branch class provides two f indVersion () operations. The first f indVersion () operation takes no argument. It returns the pointer to the latest version in the branch. On the other hand, the second f indVersion () operation takes one argument which designates the name of the version to be searched. The second f indVersion () operation simply invokes the find () operation of its ver sion list, and returns the result. branch::findVersion() { // find latest version return(latest) ; } branch::findVersion(string n) { // find designated version return(latest->find(n) ); } The branch class also provides operation list () which generates a list of the branches of the branch list. void branch::list() { printf("Branch name : %s\n", name); created.print(); // print creation date printf("Comment : %s\n", comment); if(latest) latest->list(); else printf("No version exists in this branch\n"); if(next) next->list(); } 300 The definition below will show the skeleton of the class rwv which determines the structure of resource with versions. Each resource has its own name and keeps the name of the owner. class RWV { // Resource With Versions friend class privateVersion; string name; // name of the resource string owner; // name of the resource owner branch main; // main branch version root; // root version public: branch *newBranch(string, string, version*); branch *findBranch(string) ; void list(); // list all the branches and versions RWV(string, string); // constructor Each resource starts with a single branch called main, which includes a single ver sion called root. The constructor of the rwv class initializes the resource name and the owner, the main branch, and the root version. RWV::RWV(string n /* resource */, string o /* owner */ ) : main("main", "main branch", &root), root("root", o, "root version") { name = n; owner = o; } The rwv class provides operations to manipulate branches in each resource. The newBranch () operation creates a new branch starting from a designated version. The newBranch () Operation takes three arguments; name, comment, branched which indicate the name of the new branch, a brief explanation of the new branch, and the address of the version from which the new branch is generated respectively. 301 branch name version name owner creation date annotation main - Sam 12/20/89 main branch 1 Tom 12/25/89 Beta version 2 Jack 1/10/90 Bug p-15 w as fixed math extension - Bob 2/5/90 Supporting Math Co-processor 1 Bob 2/15/90 Math co-processor supported 2 Bob 2/19/90 Bug m-1 w as fixed Figure 6.6: Version List branch *RWV::newBranch(string name /* new branch name */, string comment /* comment */, version *branched /* branched from */ ){ branch *ptr; ptr = main.append(name, comment, branched); ptr->Lock(); return(ptr); The f indBranch () operation checks to see if a branch with a designated name ex ists in the resource on which the f indBranch () operation is executed. If such a branch exists, the f indBranch () operation returns the address of the branch. branch* RWV::findBranch(string name) { return(main.find(name)); } The operation l i s t () will generate a list all branches and versions in the resource with a variety of information, as illustrated in Figure 6.6, by invoking the l i s t () operation of the main branch. void RWV::list() { 302 main.list(); } 6.1.3. Accessing Versions As I mentioned in the previous chapter, in OPM, when a process needs to access a resource, the process will first obtain a child of the resource from the resource man- i ager, and the process will manipulate the resource through the operations available on the Child. This is also true for resource with versions. Before accessing any re source with versions, processes must obtain a child of the resource. When a process needs to modify a resource with versions, the process will subject to the following procedure: (1) First, the process will obtain a child of the resource from the resource man ager. If there exist two or more processes which need to access a single re source, they must obtain different children individually, as illustrated in Figure 6.7 (a). After that, each process will be able to access the resource through their own children of the resource individually. (2) Second, the process will Reserve the branch to be modified as illustrated in Figure 6.7 (b). Then a copy of the latest version of the reserved branch is created in the child. The Reserve () operation locks the reserved branch to guarantee the mutual exclusive access to the branch. Successive execu tion of the Reserve () operation to the same branch will be delayed until the first process will execute the Release () operation. (3) The process will make necessary modification to the copy of the latest ver sion of the branch. 303 a child on a child on workstation Q (a) Reserving A Branch a child on workstation P Save workstation P Reserve Lock append Reserve copy waiting waiting a child on workstation Q (b) Creating First New Revision a child on a child on workstation P Release Lock Reserve copy a child on workstation Q workstation P Save append waiting a child on workstation Q (c) Creating Second New Revision (d) Releasing the Revision Figure 6.7: Creating a New Revision (4) After finishing the modification, the process will execute Save () opera tion. The save < ) operation will create a new revision which includes the modification performed on the child as illustrated in Figure 6.7 (c). The newly created revision becomes the latest version of the reserved branch. (5) The process may execute save < ) operation again after new modification is performed. The Save operation will create a new revision of the previously created revision as illustrated in Figure 6.7 (c). (6) Finally, the process will Release the branch so that other user processes can reserve the branch as illustrated in Figure 6.7 (d). The next user pro cess of the branch will start the modification on the last revision which was created by the previous user. (7) If the modification needs to be abandoned, Release () operation will be executed without executing the Save < ) operation. The definition below illustrates the skeleton of the privateVersion class which is a derived class of the rwv class. The class privateVersion defines the user inter face of the children of the resources defined by the rwv class. Also notice that the privateVersion class is a subclass of the Base class. As a result, the privateVersion class inherits all the members and operations from the Base class. RWV privateVersion : public Base { Boolean locked; Boolean copied; version* currentVersion; branch* currentBranch; public: /* RESERVE operations */ void Reserve () // Reserve the main branch precondition{ locked == False && copied == False }; void Reserve(string /* branch */) // Reserve the designated branch precondition{ locked == False && copied == False}; /* GET operations for READ-ONLY purpose */ void Get() // Get a copy of the latest version in main branch precondition} locked == False && copied == False }; void Get(string /* branch */) // Get a copy of the latest version //in the designated branch precondition{ locked == False && copied == False}; 305 void Get(string /* branch */, string /* version */) // Get a copy of the designated version // in the designated branch precondition{ locked == False && copied == False}; void makeBranch(string, string, string, string) // make a new branch and lock it precondition} locked == False && copied == False }; void Edit() // inherited from Base class precondition} locked == True }; void Browse () // inherited from Base class precondition} copied == True }; void Save(string) precondition} locked == True }; void Save(string /* branch */, string /* version */); precondition} locked == True }; void Release() precondition} copied == True }; privateVersion() ; // constructor ~privateVersion(); // destructor // ... }; Operations of the privateVersion class are associated with preconditions. For instance, Edit () and Save () operations are associated with the precondition: locked == True so that they can be invoked only when the member locked is True. On the other hand, the read-only operation Browse () is associated with the precondition: copied == True so that the Browse () operation is available only when the member copied is True. Both the members: locked and copied are originally set to False by the con structor. Therefore, originally no operations are available. 306 locked == False copied == False :SaVe Browse ReserveQ GetQ ReleaseQ Edit Saye; Save Browse Browse locked == True locked == False copied == True copied == True Figure 6.8: State Transition of Resources privateVersion::privateVersion() { // constructor locked = False; copied = False; } The member locked is set to True by the Reserve () operation, while the member copied is set to True by the Reserve () and Get () operations. Consequently, as illustrated in Figure 6.8, the Edit () and save () operations are available only after the Reserve () operation is completed, while the Browse () operation is available only after either the Reserve () or Get () operation is completed. Both the member locked and copied are set back to False by the Release () operation. Therefore, once the Release () operation is executed, the Edit (), Save (), as well as Browse () operations become unavailable. The privateVersion class provides two Reserved operations. The first Reserve () operation takes no argument. It locks the main branch, and gets a copy of the latest version in the main branch. Since both the version and 307 privateVersion classes are subclasses of the Base class, they inherit same mem bers from the Base class. The Reserve () operation copies only the inherited mem bers from the Version class to the privateVersion class. Finally, the Reserve () operation sets the members locked and copied to True as I mentioned above. void privateVersion::Reserve() { currentBranch = findBranch("main"); currentBranch->Lock&() ; // lock main branch currentVersion = currentBranch->find(); // find latest version (Base)*this = (Base)*(currentVersion); // copy latest version locked = True; copied = True; } The second Reserve () operation takes one argument which indicates the branch to be locked. It locks the specified branch and gets a copy of the latest version in the branch. The Reserve () operation first checks to see if the specified branch exists by invoking the f indBranch () operation of the rwv class. The f indBranch () op eration is not defined in the privateVersion class, but it is available in the privateVersion class because the privateVersion class is a derived class of the rwv class which provides the f indBranch () operation. If the designated branch exists, it locks the branch, and gets a copy of the latest version of the branch. Otherwise, an error message will be generated. void privateVersion::Reserve(string b /* branch name */) { currentBranch = findBranch(b); // find a branch if(currentBranch) { // if found currentVersion = currentBranch->find(); currentBranch->Lock&(); // lock the designated branch (Base) *this = (Base) *(currentVersion); // copy latest version locked = True; 308 O p e ra tio n s Functionality R eserveQ R e se rv e th e m ain bran ch , an d g et a co p y of th e latest version in th e m ain b ran ch R eserv e(strin g ) R e se rv e th e d e sig n a te d b ran ch , a n d g e t a co p y of th e latest v ersio n in th e d e sig n a te d b ran ch Get() G et a copy of th e latest version in th e m ain b ran ch G et(string) G et a co p y of th e latest version in th e d e sig n a te d b ran ch G et(string, string) G et a co p y of th e d e sig n a te d v ersio n in th e d e sig n a te d b ranch Figure 6.9: Reserve () and Get < ) operations copied = True; } else // if not found error(); } The privateVersion class provides three Get () operations. Like the Reserve () operations, these three Get () operations takes different number of arguments. Figure 6.9 lists all the Reserve () and Get () operations. The first Get () operation takes no argument. It gets a copy of the latest version in the main branch. The Get () operation sets the member copied to True, but the member locked is not changed. void privateVersion::Get() { currentBranch = findBranch("main"); currentVersion = currentBranch->find(); // find latest version (Base)*this = (Base)*(currentVersion); // copy latest version copied = True; } 309 The second Get () operation takes one argument, and gets a copy of the latest ver sion in the designated branch by the argument, while the third Get () operation takes two argument, and gets a copy of the designated version in the designated branch by the arguments. void privateVersion::Get(string b /* branch name */) { currentBranch = findBranch(b); // find a branch if(currentBranch) { //if found currentVersion = currentBranch->find() ; (Base) *this = (Base) *(currentVersion); // copy latest version copied = True; } else // if not found error(); } void privateVersion::Get(string b /* branch name */, string v /* version name */ ) { currentBranch = findBranch(b); // find a branch if(currentBranch) { // if found currentVersion = currentBranch->findVersion(v) if(currentVersion) { (Base) *this = (Base) *(currentVersion); copied = True; } else error(); } else error(); } The makeBranch () operation creates a new branch from a designated version in a designated branch. The makeBranch () operation takes four arguments. The first two arguments specify the names of the branch and the version from which a new branch is generated. The remaining two arguments specify the name of the new branch and a brief explanation of the new branch respectively. The makeBranch () operation first looks for the branch with the given name by invoking the f indBranch () operation of the parent class. If the specified branch is found, the 310 makeBranch () operation then looks for the version with the given name in the found branch by invoking the f in d v ersio n () operation. If the specified version is found, a new branch will be created by executing the newBranch () operation of the parent class. Remember that the newly created branch is already locked by the newBranch () operation. And it will get a copy of the found version. If not found, an error message will be displayed. void privateVersion::makeBranch(string b /* branch name */, string v /* version name */, string nb /* new branch name */, string c /* comment */ ) { currentBranch = findBranch(b); // find designated branch if(currentBranch) { I t if found currentVersion = currentBranch->findVersion(v) if(currentVersion) { currentBranch = newBranch(nb, c, currentVersion); (Base) *this = (Base) *(currentVersion); locked = True; copied = True; } else error(); } else error() ; } The save () operation creates a new version based upon the modification made in the copy. The save () operation takes three arguments. It simply creates a new version at the end of the reserved branch. The arguments specify the name, the owner, and the annotation of the version to be created. void privateVersion::Save(string n /* version name */, string o /* owner */, string c /* annotation */ ) { currentVersion = currentBranch->newVersion(n, o, c); (Base) *currentVersion = (Base) *this; } 311 The Release( ) operation unlocks the locked branch, if exists, so that other pro cesses can reserve the branch, and sets the members locked and copied to False. privateVersion::Release() { if(locked) { currentVersion->UnLock(); locked = False; } copied = False; } The destructor -privateVersion () executes the unLock () operation if the child still holds some version. This will prevent the versions of the resource from being locked by children which do not exist anymore. privateVersion-privateVersion() { // destructor if(locked) currentBranch-MJnLock () ; 6.1.4. Selecting V ersions Remember that the privateVersion class provides several Reserve () and Get () I operations so that the desired version can be easily retrieved. These operations are overloaded, and allow us to select the following versions based upon the arguments supplied to the operations, as summarized in Figure 6.9: (i) the latest version in the j main branch; (ii) the latest version in the designated branch; (iii) the designated ver- « sion in the designated branch. Especially, the Reserve () and Get () operations re trieve the latest version in the main branch as their default when no argument is I supplied. But some user might need to access a specific version in a specific branch 312 frequently. Then, it is desirable that the user is allowed to override the default, and designate the specific version in the specific branch as a new default. Below you will see a way of overriding the default version to be used by Get () and Reserve () operations using a subclass. Assume programmers are in the process of alpha-testing, and are fixing bugs on a branch called alpha rather than the main branch. A new class alphaVersion is defined as a subclass of the vers ionlnter face class. Notice that alphaVersion is also a derived class of the rwv class, like the versioninterface class. Therefore, It is possible to derive alphaVersion objects from rwv objects, as we derived versioninterface ob jects from r w v objects. The alphaVersion class makes the superclass versioninterface tobe public. As a result, all the public operations in the versioninterface class are directly available in the alphaVersion class, except for the Reserve () and Get () operations which are refined in the alphaVersion class. RWV alphaVersion : public versioninterface { public: void Reserved ; // override Reserved of superclass void Get (); // override Get() of superclass } void alphaVersion::Reserve() { versioninterface::Reserve("alpha"); } void alphaVersion::Get() { versioninterface::Get("alpha"); } The Reserve () operation simply invokes the Reserve (string) operation in the superclass, which takes one argument, by passing the argument: "alpha". As a re 313 suit, the Reserve o operation locks the latest version in the alpha branch rather than the main branch. Similarly, the Get () operation invokes the Get (string) op eration in the superclass by passing the same argument. As a result it retrieves the latest version in the alpha branch for the read-only purpose. Therefore, users of the alphaVersion class can always retrieve the latest version in the alpha branch with out specifying any arguments. Furthermore, different users may choose different default for their own purpose. For instance, once the testing by the programmers is finished, the software will be released to the quality assurance group for further testing. And it is decided that the version called QA in the alpha branch is sent to the quality assurance group. Then the version to be sent to the quality assurance group is selected by the following subclass QAVersion: RWV QAVersion : public versioninterface { public: void Get (); // override Get() of superclass } void QAVersion::Get() { versioninterface::Get("alpha", "QA"); } The class QAVersion is a subclass of the class versioninterface. As a result, the versioninterface class has two subclasses: alphaVersion and QAVersion, and both of them are derived classes of the rw v class, as illustrated in Figure 6.10. Different subclasses of the rwv class allow us to select different default versions. 3 1 4 latest version ^ in main branch > versioninterface derived class of subclass of subclass of RWV class derived class of alphaVersion QAVersion latest version^ in alpha branch j for QA group for programmers Figure 6.10: Selecting Versions This time, the Get < ) operation invokes the Get (string, string) operation in the superclass, which takes two arguments, by passing the arguments: "alpha" and "qa". As a result it retrieves the QA version in the alpha branch for the read-only purpose. Therefore, the quality assurance group can always retrieve the designated version easily. 6.2. Modeling Software Manufacture The purpose of this section is to introduce a way of modeling software manufactur ing processes [14] using Galois. There are several issues need to be considered in managing manufacturing processes. In this section, I will focus on the following 315 two issues: (i) how to represent and identify manufacturing relationships among objects; and (ii) how to represent manufacturing operations, and how to coordinate the execution of manufacturing operations. 6.2.1. Representing Manufacturing Relationships There are two kinds of manufacturing relationships need to be modeled. The first type of manufacturing relationships includes those among classes, and the second type of manufacturing relationships includes those among individual objects. As an example, consider compilation operations. Source programs are divided into several groups, for instance: C source programs, Pascal source programs, Fortran source programs, and so on. These source programs can be considered to belong to their own classes, say, class csource, class Pascaisource, and class FortranSource respectively. Similarly object programs are divided into several groups: C object programs, Pascal object programs, Fortran object programs, and so on. These object programs also belong to their own classes, say, class cob ject, class Pascaiob ject, and class FortranObject respectively. Compilation opera tions establish compiled-from relationships between classes. For instance, as Figure 6.11 illustrates, instances of Pascaiob ject are obtained from instances of Pascaisource but not from instances of FortranSource, and instances of FortranObject are obtained from instances of FortranSource, and so on. Thus, we need to be able to represent these relationships between classes using some lan guage constructs. 3 1 6 PascalSource class class FortranObject class CSource class PascalObject class FortranSourci class CObject Figure 6.11: Manufacturing Relationship among Classes Compilation operations establish manufacturing relationships between individual source programs and object programs, as well as their classes. A single class may contain two or more instances. Therefore, even a relationship between classes is given, it does not state any relationships among instances of these classes. As Figure 6.12 illustrates, two or more source program s may belong to Pascaisource, and two or more object programs may belong to Pascaiob ject. The question here is how we establish the compiled-of relationship between source programs and their object programs. For instance, hisObject is created from hisSource, while myOb ject is created from mySource. Before going into details of the strategy of modeling software manufacturing pro cesses using Galois, I will briefly show how C++ fails to represent the two kinds of manufacturing relationships discussed above. In order to represent the first kind of software manufacturing relationships, we need to establish relationships between classes. But unfortunately, in C++, we are not allowed to define a new relationship between classes. Only the relationship among classes allowed in C++ is the sub class relationship. But if we use the subclass relationship to represent the software 3 1 7 class PascalSource class PascalObject mySource hisSource hisObject myObject f instance of ^ compiled from Figure 6.12: Manufacturing Relationship among Objects manufacturing relationship, we encounter the redundancy problem which we saw earlier when we tried to represent shared resources using subclasses in Chapter 5. One typical approach to model the second kind of relationships between individual objects is to provide explicit pointers between objects. We may provide an explicit pointer in an object program so that it points to the corresponding source program, as you will see in the program segment below. But, in this case, we will encounter the same problems caused by the use of explicit pointers as I discussed in Chapter 5. class PascalSource { string name; H .. . } class PascalObject { string name; 11 ... sourceProgram* obtainedFrom; } In Galois, manufacturing relationships are modeled using derived classes. Derived classes will establish a relationship between classes, as well as objects of these classes at the same time. When an object is obtained by a manufacturing process 3 1 8 derived class instance of instance of child of mySource myObject class obiectProgram class sourceProgram Figure 6.13: Modeling A Compilation Process from another object, the newly created object is modeled as a child of the source object. The class of the newly created object will be defined as a derived class of the class of the source object. Figure 6.13 illustrates an example of modeling a manufacturing process which compiles a source program and generates an object program. Class ob jectProgram is defined to be a derived class of class sourceProgram. Given a sourceProgram i object mySource, its object program myObject is defined as an ob jectProgram object. myObject will be obtained by deriving mySource. By deriving mySource, it becomes clear that myObject is created from mySource by an appropriate manufac turing process. The derivation creating myObject from mySource is given below. i i sourceProgram mySource; // definition of source program objectProgram myObject; // definition of object program // ------ myObject = mySource.objectProgram(...); The example above gave us a fundamental idea of representing software manufac turing processes using Galois' derived classes. But here, there is a question which must be answered. Manufacturing processes usually generate multiple objects from 31 9 a combination of other multiple objects. The question which arises here is how to represent manufacturing processes involving multiple objects using derived classes. In Galois, a manufacturing process which involves multiple objects is represented as a derivation of objects which include multiple members. When a manufacturing process involves two or more inputs, all the inputs will be grouped together as a single object. The object includes all the inputs as its members. When a manufactur ing process produces two or more outputs at the same time, all the outputs are grouped together as a single object, and the output object, as an aggregate, is repre sented as a child of the input object which is also an aggregate. This indicates that the members of the output object are obtained by the manufacturing operation from the members of the input object. The manufacturing operation is implemented in the ; t output object, rather than in individual members of the output object. For instance, a link operation usually takes two or more object files as well as li brary files to produce an executable file. Figure 6.14 illustrates how a link operation i is modeled. All the necessary input files are grouped together as a single object of class Sysxob j. The input object includes two object programs: x. o and y .o, and j i one library module xterm. lib as its members. Notice that although the input ob ject, which is an instance of the class Sysxobj, is a primitive object, its members x. o and y . o are derived from corresponding source program objects. The output I files are also grouped together as a single object of class Sysxexe. The output ob ject includes the executable program x. exe and the symbol table x. tbi as its mem bers. sysxexe is represented as a derived class of Sysxobj. This indicates that a 320 derived class sourceProgram class class of class SysXobj SysXexe instance of instance of instance of child y.o y.c child of child of Figure 6.14: Modeling A Link Operation Sysxexe object is obtained by a manufacturing process from a SysXobj object. Below you will see a skeleton of these classes. class SysXobj { string name; string owner; date created; // creation date public: objectProgram x; objectProgram y; library lib; //. . . SysXobj(sourceProgram SxSource, sourceProgram sySource) { x = xSource.objectProgram(); // derived object y = ySource.objectProgram(); // derived object } ; SysXobj SysXexe { string name; string owner; date created; public: executableProgram symbolTable / / ... The class Sysxobj represents a collection of three objects which are required to generate a desired executable program. Although Sysxobj is a primitive class, its // creation date exe; tbl; 321 members x and y are derived objects obtained from their parent objects. The con structor Sysxobj () defines the derivation of these members. The class sysxexe represents a collection of objects which will be generated by the link operation. Sysxexe is represented as a derived class of Sysxobj . This indi cates that the members: exe and tbi of a Sysxexe object are derived from the members: x, y, and lib of the corresponding sysxobj object. 6.2.2. Coordinating Software Manufacture The discussion in the previous section has clarified that two kinds of software man ufacturing relationships can be effectively modeled by means of derived classes. The remaining issues which I need to cover is how to represent the software manu facturing operations, and how to coordinate their execution. In Galois, manufacturing operations are defined as operations in derived classes. Consider the compile derivation example in the previous section again. Below you will see a skeleton of the sourceProgram and ob jectProgram classes. The objectProgram class provides an operation compile () to compile a source pro gram and to obtain the corresponding object program. When the compile () opera tion is executed on an objectProgram object, the corresponding sourceProgram object is compiled and a new object code is generated. class sourceProgram { friend class objectProgram; string name; string owner; AsciiCode sourceCode; // source code 322 date created; // creation date date sourceModified; // last modification date p u b l i c : edit(); // ... } sourceProgram objectProgram { string name; string owner; binaryCode objectCode; // object code date created; // creation date date objectModified; // last modification date p u b l i c : compile() p r e c o n d i t i o n{ compiled() == Fal se } p r e p r o c e s s { break; }; Boolean compiledO; objectProgram(); // constructor ... } objectProgram::compile() { objectCode = pas(sourceCode); // invoke a pascal compiler objectModified = currentDate(); } Boolean objectProgram: :compiledO { r e t u r n(sourceModified < objectModified); } Notice that, in the example above, the com pile () operation is associated with a I I precondition: j { t compiledO == False j i which indicates that the compile () operation becomes active only when the corre- I sponding sourceProgram object is modified after the last execution of the compile () operation. This will guarantee that the compile () operation is executed only when it is necessary. If compiledO is True when the compile () operation is invoked, the associated preprocess is executed. But in this case, the preprocess 323 includes only a single break statement. Thus, the execution of the compile () op eration is simply avoided. Also notice that since objectProgram is a derived class of sourceProgram, all the public operations available on a sourceProgram object are also available on the corresponding objectProgram object. As a result, users are freed from tracking up and down the manufacturing sequence during software development. For instance, if a bug is found in an objectProgram object, the user can directly invoke the edit () operation of the corresponding sourceProgram object on the objectProgram object, rather than first identify the corresponding sourceProgram object and second invoke the edit () operation on the sourceProgram object. As this example shows, the manufacturing operation is usually implemented in the derived class rather than the parent class. This is because a single object may be in volved in two or more manufacturing processes in order to produce different ob jects. In such cases, it is convenient to be able to model these manufacturing pro- j cesses by different derived classes rather than providing multiple manufacturing op erations in a single parent class. Below, you will see how multiple manufacturing processes can be effectively modeled using multiple derived classes. Consider the compilation example again. The objectProgram class provides its own compilation operation required to gen erate a specific object program. But, a single source program might be used to gen erate two or more different object programs using different compilation operations. I ] 324 In such cases, each compilation operation will be implemented in different classes which are all derived classes of the sourceProgram class, but they provide differ ent compilation operations. In the example below three different derived classes objectProgram,anotherObjectProgram, and yetAnotherObjectProgram, which implement different compilation operations, are defined in order to obtain specific object programs. sourceProgram mySource; objectProgram myObject; // use version 1 compiler anotherObjectProgram hisObject; // use version 2 compiler yetAnotherObjectProgram herObject; // use debug option // ... myObject = mySource.objectProgram(...); // release version // ... hisObject = mySource.anotherObjectProgram(...);// beta version // ... herObject = mySource.yetAnotherObjectProgram(...);// QA version Three object programs: myObject, hisObject, and herObject are defined as in stances of the three different derived classes objectProgram, anotherOb jectProgram, and yetAnotherOb jectProgram respectively. These three objects are derived from the same sourceProgram object mySource. As a re sult, myObject, hisObject, and herObject will be obtained by different compila tion operations from the single source program mySource. Manufacturing operations which produce multiple outputs from multiple inputs can be defined in a similar fashion. Below you will see a refinement of Sysxobj and SysXexe classes which include the manufacturing operations. class SysXobj { public: objectProgram x; 325 objectProgram y; library lib; Boolean compiledO { return(x.compiled() && y.compiled()); } void compile() { // compile all members x.compile(); y.compile(); } SysXobj(sourceProgram SxSource, sourceProgram &ySource) x = xSource.objectProgram () ; y = ySource.objectProgram(); }; } ; SysXobj SysXexe { string name; string owner; date created; date lastModified; public: executableProgram exe; symbolTable tbl; void link() precondition{compiled() == True } preprocess{compile();}; } ; void SysXexe::link() { pasLink(x, y, lib, &exe, &tbl); // invoke a pascal linker lastModified = currentDate(); { The SysXobj class has compile () operation which compiles all the related source programs. The Sysxexe class has link o operation which generates the executable program and the symbol table from the object programs in the corresponding Sysxobj object. The link () operation is associated with a precondition: compiledO == True which guarantees that the link < ) operation is invoked only after all the related source programs have been properly compiled. If one or more source programs need to be compiled, the compile < ) operation will be executed before the link () operation starts. I have introduced the notion of derived classes in order to represent software manu facturing operations, because the object creation mechanism, i.e. instantiation, of conventional object oriented languages, including Smalltalk [40] and C++ [93], is not appropriate for representing software manufacturing operations. In the instan tiation model, concrete objects are created from an abstract description of objects, that is classes. On the other hand, software manufacturing operations transforma tionally produce concrete objects, which belong to their own classes, from other existing concrete objects, which also belong to their own classes. Software objects are not produced from their classes. Furthermore, software manufacturing consists of a series of transformational op erations. The final product will be created by successive execution of quite a few manufacturing operations. But unfortunately, conventional object-oriented pro gramming languages do not allow successive execution of instantiation. In C++, a class is just a template, but not an object. Therefore, classes cannot be produced by instantiating other objects. And objects, which are not classes, are not instantiable. In Smalltalk, although classes are instances of their metaclasses, all metaclasses are instances of the metaclass called metaclass. As a result, we cannot extend the 327 instance-of relationship beyond metaclass. Furthermore, classes are uniquely as sociated with their metaclasses, and cannot be produced by instantiating the meta classes. As a result, in both languages, C++ and Smalltalk, we are not allowed to produce objects by a series of instantiation. 328 Chapter 7 Conclusions In this dissertation, I have presented the software process modeling strategy object process modeling and the object-oriented process programming language Galois. Process programs written in Galois define working environments in which pro grammers are allowed to actually work in order to accomplish a task. The process program encodes the knowledge of software development experts which is essential to accomplish the task into a machine readable form. The programmers will be able to get assistance from the working environment which has the equivalent knowl edge as the software development experts. I have identified two major capabilities of process programs written in Galois. One of the major capabilities of software processes in Galois is the support for coopera tive work among a team of developers. A process program will collect all the neces sary resources to accomplish the desired task and define a set of operations to be performed on the collected resources. The execution of these operations will be monitored and scheduled by the software process. 329 Another major capability of software processes in Galois is resource management, Galois will define data structures which represent resources, such as computer files, computer hardwares, and human resources, to be involved in software pro cesses. Resources described in Galois provide operations, including those for ver sion and configuration management and resource allocation, so that the resources can be shared properly among multiple processes. As a programming language, Galois is a superset of C++, and has the following four major features. First, classes in Galois are typed. Classes are instances of their metaclasses which define the behavior of the classes as objects, and can be pro duced by instantiating the metaclasses. As a result, classes are first class objects which are capable of coordinating their multiple instances. Second, Galois provides a new object-creation mechanism called derivation which is a generalization of the traditional instantiation model. In the derivation model, although every object be longs to its own class, it is not created from the class. Derivation allows objects to be created from some other existing objects by transformational operations. Third, operations in Galois are also typed. Galois provides a concurrent environment in which multiple objects are performing their tasks simultaneously. But each object is essentially a sequential process which executes one of its operations at a time. The type of an operation determines the scheduling algorithm of multiple invocation of the operation. Fourth, in Galois, operations can be associated with a precondition and/or a postcondition so that the operations are carried out only when the condi tions are met. Galois also allows us to specify preprocesses and postprocesses of 330 each operation which determine the order of execution of the operations. Preprocesses are used to achieve backward chaining of operations, while postpro cesses are used to achieve forward chaining of operations. j Using Galois, I have implemented major important features in OPM, including pro- i cess coordination, software manufacturing, resource sharing, and version and con figuration control. Metaclasses were used to coordinate and monitor the multiple instances of a single process model. Derivation was used to model software manu facturing and resources shared by multiple processes and programmers. Typed op erations were used to implement the scheduling of shared resources. And precondi tions and postconditions were used to navigate human activities in software pro cesses, as well as, to achieve chaining of operations in configuration control. These attempts have proved that the implementation of these mechanisms in Galois is far more efficient and effective than the implementation of the same mechanisms in conventional programming languages. This leads to the conclusion that object pro cess modeling and Galois are essential for effective and efficient software process modeling. 331 References [1] Reference Manual fo r the Ada Programming Language. ANSI/MIL-STD- | 1815 A -1983. United States Department of Defense, 1983. i [2] Proceedings of the 5th International Software Process W orkshop. Kennebunkport, Maine, ACM, IEEE, October, 1989. [3] Agha, G. An Overview of Actor Languages. ACM SIGPLAN N otices, vol.21, no. 10, pages 58-67, October, 1986. ■ [4] Aho, A. V., J. E. Hopcroft and J. D. Ullman. The Design and Analysis o f Computer Algorithms. Addison-Wesley, Reading, Massachusetts, 1974. [5] Andrews, T. and C. Harris. Combining Language and Database Advances in an Object-Oriented Development Environment, in Proceedings of the ACM Symposium on Object-Oriented Programming Systems, Languages and A pplications, pages 430-440, Orland, Florida, ACM, October, 1987. published as ACM SIGPLAN Notices, vol.22, no. 12, December, 1987. [6] Avrunin, G. S., L. K. Dillon, J. C. Wileden and W. E. Riddle. Constrained Expressions: Adding Analysis Capabilities to Design Methods for Concurrent Software Systems. IEEE Transactions on Software Engineering, IEEE, vol.SE-12, no.2, pages 278-292, February, 1985. [7] Babich, W. A. Software Configuration Management. Addison-W esley, Reading, Massachusetts, 1986. [8] Balzer, R., T. E. Cheatham Jr. and C. Green. Software Technology in the 1990's: Using a New Paradigm. Computer, IEEE, vol. 16, no. 11, pages 39- 45, November, 1983. [9] Benington, H. D. Production of Large Computer Programs, in Proceedings of the Symposium on Advanced Programming Methods fo r Digital Computer, pages 15-27, Washington, D.C., Office of Naval Research, June, 1956. published as ONR Symposium Report ACR-15, also available in Annals of the History of Computing, vol.5, No.4, pages 350-361, October, 1983. 332 [10] Bobrow, D. G. and G. Kiczales. The Common Lisp Object System Metaobject Kernel: A Status Report, in Proceedings of the ACM Conference on Lisp and Functional Programming, pages 309-315, Snowbird, Utah, ACM SIGPLAN/SIGACT/SIGART, July, 1988. [11] Boehm, B. A Spiral Model of Software Development and Enhancement, in Proceedings of the International Workshop on the Software Process and Software Environm ent, pages 22-42, Cote de Caza, Trabuco Canyon, C alifornia, ACM SIGSOFT, M arch, 1985. published as Software Engineering Notes, vol. 11, no.4, August, 1985. [12] Booch, G. Object-Oriented Development. IEEE Transactions on Software Engineering, IEEE, vol.SE-12, no.2, pages 211-221, February, 1986. [13] Booch, G. Software Engineering with Ada. Benjamin/Cummings, Menlo Park, California, 1986. [14] Borison, E. A Model of Software Manufacture, in Proceedings of the International Workshop on Advanced Programming Environments, pages 197-220, Trondheim, Norway, IFIP WG 2.4, June, 1986. [15] Boming, A. The programming Language Aspects of ThingLab, a Constraint- Oriented Simulation Laboratory. ACM Transactions on Programming \ Languages and Systems, ACM, vol.3, no.4, pages 353-387, October, 1981. [16] Boming, A. Classes versus Prototypes in Object-Oriented Languages, in Proceedings of the ACM/IEEE Fall Joint Computer Conference, pages 36-40, Dallas, Texas, November, 1986. [17] Boming, A. and D. H. H. Ingalls. A Type Declaration and Inference System j for Smalltalk, in Proceedings of the Ninth Annual ACM Symposium on Principles o f Programming Languages, pages 133-141, Albuquerque, New Mexico, ACM, January, 1982. [18] Boudier, G., F. Gallo, R. Minot and I. Thomas. An Overview of PCTE and PCTE+. in Proceedings of the AC M SIG SO FT/SIG PLAN Softw are j Engineering Symposium on Practical Software Development Environment, pages 248-257, Boston, M assachusetts, ACM SIGSOFT/SIGPLAN, November, 1988. published as ACM SIGSOFT Software Engineering Notes, vol. 13, no.5, November, 1988. [19] Brinch-Hansen, P. Operation System Principles. Prentice-Hall, Englewood Cliffs, New Jersey, 1973. 333 [20] Canning, P. S., W. R. Cook, W. L. Hill and W. G. Olthoff. Interfaces for Strongly-Typed Object-Oriented Programming, in Proceedings of the Object- Oriented Programming Systems, Languages and Applications, pages 457- I 467, New Orleans, Louisiana, ACM, October, 1989. published as ACM SIGPLAN Notices, vol.24, no. 10, October, 1989. [21] Chen, P. P. The Entity-Relationship Model-Toward a Unified View of Data. ACM Transactions on Database Systems, vol.l, no.l, pages 9-36, March, 1976. [22] Chen, Y. F. and C. V. Ramamoorthy. The C Information Abstractor, in Proceedings of tht IEEE 10th International Annual Computer Software and Application Conference (COMPSAC), pages 291-298, Chicago, IEEE, October, 1986. [23] Coad, P. and E. Yourdon. O bject-O riented Analysis. Prentice Hall, Englewood Cliffs, New Jersey, 1990. I [24] Cohen, E. S., D. A. Soni, R. Gluecker, H. W. M., R. W. Schwanke and M. E. Wagner. Version Management in Gypsy, in Proceedings of the AC M SIGSOFT!SIGPLAN Software Engineering Symposium on Practical Software Development Environment, pages 201-215, Boston, Massachusetts, ACM SIGSOFT/SIGPLAN, November, 1988. published as ACM SIGSOFT Software Engineering Notes, vol. 13, no.5, November, 1988. [25] Cointe, P. Metaclasses are First Class : the ObjVlisp Model, in Proceedings of the Object-Oriented Programming Systems, Languages and Applications, pages 156-167, Orlando, Florida, ACM, October, 1987. published as ACM SIGPLAN Notices, vol.22, no. 12, December, 1987. j I [26] Compbell, R. H. and P. A. Kirslis. The SAGA Project: A system for | Software Development, in Proceedings o f the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical Software Development ! E n viro n m en ts, pages 73-80, ACM, May, 1984. published as ACM SIGPLAN Notices, vol. 19, no.5, May, 1984. [27] Cox, B. J. Object-Oriented Programming : An Evolutionary Approach. Addison-Wesley, Reading, Massachusetts, 1986. j [28] Curtis, B., H. Krasner, V. Shen and N. Iscoe. On Building Software Process Models Under the Lamppost, in Proceedings of the 9th International Conference on Software Engineering, pages 96-103, Monterey, California, ACM-IEEE, March, 1987. [29] Dahl, O. and K. Nygaard. SIMULA - An ALGOL-based Simulation Language. Communications o f the ACM, vol.9, no.9, pages 671-678, September, 1966. [30] Dittrich, K. R., W. Gotthard and P. C. Lockermann. DAMOKLES - A Database System for Software Engineering Environment,, in Proceedings of the International Workshop on Advanced Programming Environments, pages 353-371, Trondheim, Norway, IFIP WG 2.4, June, 1986. I [31] Dowson, M. (ed), Proceedings of the 3rd International Software Process Workshop. Breckenridge, Colorado, ACM-IEEE, November, 1986. [32] Dresher, G. L. The ObjectLisp User M anual. LM I, C am bridge, Massachusetts, 1985. [33] Feldman, S. I. MAKE - A Program for Maintaining Computer Programns. Software-Practice and Experience, vol.9, pages 255-265, 1979. [34] Gallo, F., R. Minot and I. Thomas. The Object Management System of PCTE as a Software Engineering Database M anagement System, in Proceedings o f the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical Software Development Environments, pages 12-15, ACM, December, 1986. published as ACM SIGPLAN Notices, vol.22, n o .l, January, 1987. [35] Gehani, N. H. Working in Concurrent C. UNIX Review, vol.7, no.5, pages 60-70, May, 1989. [36] Gehani, N. H. and W. D. Roome. Concurrent C. Software-Practice and E xperience, John Wiley & Sons, Ltd., vol. 16, no.9, pages 821-844, September, 1986. [37] Gehani, N. H. and W. D. Roome. C oncurrent C++: Concurrent Programming with Class(es). Software-Practice and Experience, John Wiley & Sons, Ltd., vol.18, no.12, pages 1157-1177, December, 1988. [38] Gehani, N. H. and W. D. Roome. Rendezvous Facilities: Concurrent C and Ada Language. IEEE Transactions on Software Engineering, IEEE, vol. 14, no. 11, pages 1546-1553, November, 1988. [39] Gidding, R. V. Accom m odating U ncertainty in Software Design. Communications o f the ACM, ACM, vol.27, no.5, pages 428-434, May, 1984. [40] Goldberg, A. and D. Robson. Smalltalk-80 : The Language and its Implementation. Addison-Wesley, Reading, Massachusetts, 1983. [41] Heimbigner, D. P 4:A Logic Language for Process Programming, in Proceedings of the 5th International Software Process Workshop, Kennebunkport, Maine, IEEE-ACM, October, 1989. 335 i I [42] H eim bigner, D. and S. Krane. A Graph Tram sform M odel for Configuration, in Proceedings of the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical Software Development Environment, pages 216-225, Boston, M assachusetts, ACM SIGSOFT/SIGPLAN, November, 1988. published as ACM SIGSOFT Software Engineering Notes, vol. 13, no.5, November, 1988. [43] Heimbigner, D., S. Sutton Jr. and L. J. Osterweil. APPL/A: A Language fo r Managing Relations Among Software Objects and Processes. Technical Report CU-CS-374-87, Department of Computer Science, University of Colorado, Boulder, Colorado, 1987. [44] Hoare, C. A. R. An Axiom atic Basis to Com puter Programming. Communications o f the ACM, ACM, vol.12, no.10, pages 576-580, 583, October, 1969. [45] Hoare, C. A. R. Monitors: An Operating System Structuring Concept. Communications o f the ACM, vol.17, no. 10, pages 549-557, October, 1974. [46] Hoare, C. A. R. Communicating Sequential Processes. Communications o f the ACM, vol.21, no.8, pages 666-677, August, 1978. i [47] Hudson, S. E. and R. King. Object-Oriented Database Support for Software ! Environments, in Proceedings of the AC M SIGM OD International Conference on Management o f Data, pages 491-503, San Francisco, California, ACM, May, 1987. published as ACM SIGMOD Record, vol. 16, no.3, December, 1987. [48] Huff, K. E. and V. R. Lesser. A Plan-based Intelligent Assitant That Supports the Software Development Process, in Proceedings of the AC M \ SIGSOFT/SIGPLAN Software Engineering Symposium on Practical ' Software Development Environments, pages 97-106, Boston, Massachusetts, ! ACM SIGSOFT/SIGPLAN, November, 1988. published as Software Engineering Notes, vol. 13, no.5, November, 1988. [49] Jacob, R. J. K. A State Transition Diagram Language for Visual Programming. Computer, IEEE, vol.18, no.8, pages 51-59, August, 1985. ! ( [50] Johnson, R. E. Type-Checking Smalltalk, in Proceedings of the A C M • Symposium on Object-Oriented Programming Systems, Languages and Applications, pages 315-321, Portland, Oregon, ACM, September, 1986. published as ACM SIGPLAN Notices, vol.21, no. 11, November, 1986. [51] Kaiser, G. E. and P. H. Feiler. An Architecture for Intelligent Assistance in Software Development, in Proceedings of the 9th International Conference on Software Engineering, pages 180-188, Monterey, California, ACM-IEEE, March, 1987. 336 [52] Kaiser, G. E. and A. N. Habermann. An Environment for System Version Control, in Proceedings of the COMPCON Spring 83, pages 415-420, IEEE, March, 1983. [53] Kemighan, B. W. and D. M. Ritchie. The C Programming Language. Prectice Hall, Englewood Cliffs, New Jersey, 1978. [54] Kemighan, B. W. and D. M. Ritchie. The C Programming Language Second ! Edition. Prentice Hall, Englewood Cliffs, New Jersey, 1988. i I j [55] Lampson, B. W. and E. E. Schmidt. Organizing Software in a Distributed i Environment, in Proceedings of the ACM SIGPLAN 83 Symposium on Programming Language Issues in Software Systems, pages 1-13, ACM, 1983. published as ACM SIGPLAN Notices, vol. 18, no.6, June, 1983. [56] Leblang, D. B. and J. Chase Robert P. Com puter-Aided Software Engineering in a Distributed Workstation Environment, in Proceedings of the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical S o ftw a re D evelo p m en t E n viro n m en ts, pages 104-112, ACM SIGSOFT/SIGPLAN, 1984. published as ACM SIGPLAN Notices, vol. 19, no.5, May, 1984. [57] Leblang, D. B., R. P. Chase Jr. and G. D. McLean Jr. The DOMAIN Software Engineering Environment for Large Scale Software Development Efforts, in Proceedings of the IEEE Conference on Workstations, pages 266- 280, San Jose, California, IEEE, November, 1985. [58] Lehman, M. M. A Further Model of Coherent Programming Processes, in Proceedings of the Software Process Workshop, pages 27-33, Surrey, UK, IEEE, February, 1984. [59] Lehman, M. M. Process Models, Process Programs, Programming Support, in Proceedings of the 9th International Conference on Software Engineering, pages 14-16, Monterey, California, ACM-IEEE, March, 1987. [60] Lieberman, H. Using Prototypical Objects to Implement Shared Behavior in Object Oriented Systems, in Proceedings of the ACM Symposium on Object- Oriented Programming Systems, Languages and Applications, pages 214- 223, Portland, Oregon, ACM, September, 1986. published as ACM SIGPLAN Notices, vol.21, no. 11, November, 1986. [61] Linton, M. A. Implementing Relational Views of Programs, in Proceedings o f the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical Software Development Environments, pages 132-140, ACM, May, 1984. published as ACM SIGPLAN Notices, vol.19, no.5, May, 1984. 337 [62] Liskov, B., A. Snyder, R. Atkinson and C. Schaffert. Abstraction Mechanism in CLU. Communications o f the ACM, vol.20, no.8, pages 564- 576, August, 1977. [63] Liu, L.-C. and E. Horowitz. A Formal M odel for Software Project Management. IEEE Transactions on Software Engineering, IEEE, vol. 15, no. 10, pages 1280-1293, October, 1989. [64] M adhavji, N. H., V. Gruhn, W. Deiters and W. Schafer. Prism = Methodology + Process-oriented Environment, in Proceedings of the 12th International Conference on Software Engineering, Nice, France, IEEE, ACM, March, 1990. [65] McCracken, D. D. and M. A. Jackson. Life Cycle Concept Considered Harmful. Software Engineering Notes, ACM SIGSOFT, vol.7, no.2, pages 29-32, April, 1982. [66] Meyer, B. Object-oriented Software Construction. Prentice Hall, New York, 1988. [67] Moon, D. A. Object-Oriented Programming with Flavors, in Proceedings of the Object-Oriented Programming Systems, Languages and Applications, pages 1-8, Portland, Oregon, ACM, September, 1986. published as ACM SIGPLAN Notices, vol.21, no. 11, November, 1986. [68] M oriconi, M. and D. F. Hare. Visualizing Program Designs Through 1 PegaSys. Computer, IEEE, vol. 18, no.8, pages 72-85, August, 1985. [69] Murray, J. Source Control Using VM/SP and CMS. Software Engineering Notes, ACM SIGSOFT, vol. 13, no.2, pages 51-54, April, 1988. [70] Narayanaswamy, K. and K. V. Bapa Rao. An Incremental Mechanism for Schema Evolution in Engineering Domain, in Proceedings of the D ata Engineering, pages 294-301, IEEE, 1988. [71] Narayanaswamy, K., W. Scacchi and D. Mcleod. Information Management Support fo r Evolving Software Systems. Technical Report USC TR 85-324, University of Southern California, Computer Science Department, March, 1985. [72] Nestor, J. R. Toward a Persistent Object Base, in Proceedings o f the International Workshop on Advanced Programming Environments, pages 372-394, Trondheim, Norway, IFIP WG 2.4, June, 1986. 338 [73] Notkin, D. The Relationship Between Software Development Environments and the Software Process, in Proceedings of the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical Software Development E n v ir o n m e n t, pages 107-109, B oston, M assachusetts, ACM SIGSOFT/SIGPLAN, November, 1988. published as ACM SIGSOFT Software Engineering Notes, vol. 13, no.5, November, 1988. [74] Obst, W. Delta Technique and String-to-String Correction, in Proceedings of the 1st European Software Engineering Conference, pages 64-68, Strasbourg, France, September, 1987. [75] Osterbye, K. Abstract Data Types with Shared Operations. ACM SIGPLAN Notices, vol.23, no.6, pages 91-96, June, 1988. [76] Osterweil, L. J. Software Process Interpretation and Software Environments. Technical Report CU-CS-324-86, University of Colorado Boulder, 1986. [77] Osterweil, L. J. Software Processes are Software Too. in Proceedings of the 9th International Conference on Software Engineering, pages 2-13, Montrey, California, ACM-IEEE, March, 1987. [78] Osterweil, L. J. Experiences with Process Programming, in Proceedings of the 5th International Software Process Workshop, Kennebunkport, Maine, IEEE-ACM, October, 1989. [79] Pamas, D. L. Designing Software for Ease of Extension and Contraction. IEEE Transactions on Software Engineering, IEEE, vol.SE-5, no.2, pages 128-137, March, 1979. [80] Perry, D. E. Software Interconnection Models, in Proceedings of the 9th International Conference on Software Engineering, pages 61-69, Monterey, California, ACM-IEEE, March, 1987. [81] Perry, D. E. and G. E. Kaiser. Models of Software Developm ent Environments, in Proceedings of the 10th International Conference on Software Engineering, pages 60-68, IEEE, 1988. [82] Potts, C. (ed), Proceedings of the Software Process Workshop. Surrey, UK, IEEE, February, 1984. [83] Raeder, G. A Survey of Current Graphical Programming Techniques. Computer, IEEE, vol.18, no.8, pages 11-25, August, 1985. [84] Ramamoorthy, C. V., Y. Usuda, W. T. Tsai and A. Prakash. Genesis: An Integrated Environment for Supporting Development and Evolution of Software, in Proceedings of the IEEE 9th International Computer and Applications Conference, pages 472-479, IEEE, October, 1985. 339 [85] Rochkind, M. J. The Source Code Control System. IEEE Transactions on Software Engineering, vol.SE-1, no.4, pages 364-370, December, 1975. [86] Royce, W. W. Managing the Development of Large Software Systems: Concepts and Techniques, in Proceedings of the IEEE WESCON, pages 1-9, IEEE, August, 1970. [87] Schaffert, C., T. Cooper, B. Bullis, M. Kilian and C. W ilport. An Introduction to Trellis/Owl. in Proceedings of the O b ject-O rien ted Programming Systems, Languages and Applications, pages 9-16, Portland, Oregon, ACM, September, 1986. published as ACM SIGPLAN Notices, vol.21, no. 11, November, 1986. [88] Snyder, A. CommonObjects : An Overview. ACM SIGPLAN Notices, vol.21, no. 10, pages 19-28, October, 1986. [89] Snyder, A. Encapsulation and Inheritance in Object-Oriented Programming Languages, in Proceedings of the Object-Oriented Programming Systems, Languages and Applications, pages 38-45, Portland, Oregon, ACM, September, 1986. published as ACM SIGPLAN Notices, vol.21, no.11, November, 1986. [90] Snyder, A. Inheritance and the Development of Encapsulated Software Sty stems. In Research Directions in Object-Oriented Programming. Shriver and Wegner ed. pages 165-188, MIT Press, 1987. [91] Stein, L. A. Delegation Is Inheritance, in Proceedings of the Object-Oriented Programming Systems, Languages and Applications, pages 138-146, Orlando, Florida, ACM, October, 1987. published as ACM SIGPLAN Notices, vol.22, no. 12, December, 1987. [92] Stonebraker, M., E. Wong and P. Kreps. The Design and Implementation of INGRES. TODBS, vol.l, no.3, pages 189-222, September, 1976. [93] Stroustrup, B. The C++ Programming Language. Addison-Wesley, Reading, Massachusetts, 1986. [94] Stroustrup, B. An Overview of C++. ACM SIGPLAN Notices, ACM, vol.21, no. 10, pages 7-18, October, 1986. [95] Sugiyama, Y. and E. Horowitz. Managing the components o f a Large-Scale Software System. Technical Report CRI-87-56, Com puter Science Department, University of Southern California, October, 1987. [96] Sugiyama, Y. and E. Horowitz. OPM: An Object Process Modeling Environment, in Proceedings of the 5th International Software Process Workshop, Kennebunkport, Maine, October, 1989. 340 [97] Suzuki, N. Inferring Types in Smalltalk, in Proceedings of the Eighth Annual ACM Symposium on Principles o f Programming Languages, pages 187-199, 1981. [98] Taylor, R. N„ F. C. Belz, L. A. Clarke, L. J. Osterweil, R. W. Selby, J. C. W ileden, A. L. W olf and M. Young. Foundations for the Arcadia Environment Architecture, in Proceedings of the ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical Software Development E n v i r o n m e n t , pages 1-13, B oston, M assach u setts, ACM SIGSOFT/SIGPLAN, November, 1988. published as ACM SIGSOFT Software Engineering Notes, vol.13, no.5, November, 1988. [99] Terwilliger, R. B. and R. H. Campbell. PLEASE: A Language for Increm ental Software Development, in Proceedings of the Fourth International Workshop on Software Specification and Design, pages 249- 256, April, 1987. [100] Terwilliger, R. B., M. J. Maybee and L. J. Osterweil. An Example of Formal Specification as an Aid to Design and Development, in Proceedings of the Fifth International Workshop on Software Specification and Design, pages 266-272, Pittsburgh, Pennsylvania, IEEE-ACM , May, 1989. published as ACM SIGSOFT Software Engineering Notes, vol. 14, no.3, May, 1989. [101] Tichy, W. F. Design, Implementation and Evaluation of a Revision Control System, in Proceedings of the 6th International Conference on Software Engineering, pages 58-67, Tokyo, Japan, ACM-IEEE, September, 1982. [102] Tichy, W. F. Tools for Software Configuration M anagement, in Proceedings of the First International Workshop on Software Version and Configuration Control, Grassau, FRG, Tuebner-Verlag, January, 1988. [103] Tully, C. (ed), Proceedings of the 4th International Software Process Workshop. M oretonhampstead, Devon, UK, ACM -IEEE, May, 1988. published as ACM SIGSOFT Software Engineering Notes, vol. 14, no.4, June, 1989. [104] Wegner, P. Workshop on Object-Oriented Programming ECOOP 1987, Paris, June 18, 1987. ACM SIGPLAN Notices, vol.23, no.l, pages 16-37, January, 1988. [105] Wiest, J. and F. Levy. A Management Guide to PERT/CPM. Prentice Hall, New York, 1977. [106] Wileden, J. C. and M. Dowson (ed), Proceedings of the International Workshop on the Software Process and Software Environments. Cote de Caza, Trabuco Canyon, California, ACM, August, 1985. published as ACM SIGSOFT Software Engineering Notes, vol. 11, no.4, August, 1986. [107] Williams, L. G. Software Process Modeling: A Behavioral Approach, in Proceedings of the 10th International Conference on Software Engineering, pages 174-186, IEEE, 1988. [108] Zdonik, S. B. Version Management in an Object-Oriented Database, in Proceedings o f the International Workshop on Advanced Programming Environments, pages 405-422, Trondheim, Norway, IFIP WG 2.4, June, 1986. 342
Abstract (if available)
Linked assets
University of Southern California Dissertations and Theses
Conceptually similar
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
PDF
00001.tif
Asset Metadata
Core Title
00001.tif
Tag
OAI-PMH Harvest
Permanent Link (DOI)
https://doi.org/10.25549/usctheses-oUC11257163
Unique identifier
UC11257163
Legacy Identifier
DP22808